OSDN Git Service

Revert "Fix vulnerability in MemoryIntArray"
[android-x86/frameworks-base.git] / services / core / java / com / android / server / display / LocalDisplayAdapter.java
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.server.display;
18
19 import android.content.res.Resources;
20 import com.android.server.LocalServices;
21 import com.android.server.lights.Light;
22 import com.android.server.lights.LightsManager;
23
24 import android.content.Context;
25 import android.os.Build;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.PowerManager;
30 import android.os.SystemProperties;
31 import android.os.Trace;
32 import android.util.Slog;
33 import android.util.SparseArray;
34 import android.view.Display;
35 import android.view.DisplayEventReceiver;
36 import android.view.Surface;
37 import android.view.SurfaceControl;
38
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.List;
44
45 /**
46  * A display adapter for the local displays managed by Surface Flinger.
47  * <p>
48  * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
49  * </p>
50  */
51 final class LocalDisplayAdapter extends DisplayAdapter {
52     private static final String TAG = "LocalDisplayAdapter";
53     private static final boolean DEBUG = false;
54
55     private static final String UNIQUE_ID_PREFIX = "local:";
56
57     private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
58
59     private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] {
60             SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN,
61             SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI,
62     };
63
64     private final SparseArray<LocalDisplayDevice> mDevices =
65             new SparseArray<LocalDisplayDevice>();
66     @SuppressWarnings("unused")  // Becomes active at instantiation time.
67     private HotplugDisplayEventReceiver mHotplugReceiver;
68
69     // Called with SyncRoot lock held.
70     public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
71             Context context, Handler handler, Listener listener) {
72         super(syncRoot, context, handler, listener, TAG);
73     }
74
75     @Override
76     public void registerLocked() {
77         super.registerLocked();
78
79         mHotplugReceiver = new HotplugDisplayEventReceiver(getHandler().getLooper());
80
81         for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) {
82             tryConnectDisplayLocked(builtInDisplayId);
83         }
84     }
85
86     private void tryConnectDisplayLocked(int builtInDisplayId) {
87         IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
88         if (displayToken != null) {
89             SurfaceControl.PhysicalDisplayInfo[] configs =
90                     SurfaceControl.getDisplayConfigs(displayToken);
91             if (configs == null) {
92                 // There are no valid configs for this device, so we can't use it
93                 Slog.w(TAG, "No valid configs found for display device " +
94                         builtInDisplayId);
95                 return;
96             }
97             int activeConfig = SurfaceControl.getActiveConfig(displayToken);
98             if (activeConfig < 0) {
99                 // There is no active config, and for now we don't have the
100                 // policy to set one.
101                 Slog.w(TAG, "No active config found for display device " +
102                         builtInDisplayId);
103                 return;
104             }
105             int activeColorMode = SurfaceControl.getActiveColorMode(displayToken);
106             if (activeColorMode < 0) {
107                 // We failed to get the active color mode. We don't bail out here since on the next
108                 // configuration pass we'll go ahead and set it to whatever it was set to last (or
109                 // COLOR_MODE_NATIVE if this is the first configuration).
110                 Slog.w(TAG, "Unable to get active color mode for display device " +
111                         builtInDisplayId);
112                 activeColorMode = Display.COLOR_MODE_INVALID;
113             }
114             int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
115             LocalDisplayDevice device = mDevices.get(builtInDisplayId);
116             if (device == null) {
117                 // Display was added.
118                 device = new LocalDisplayDevice(displayToken, builtInDisplayId,
119                         configs, activeConfig, colorModes, activeColorMode);
120                 mDevices.put(builtInDisplayId, device);
121                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
122             } else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig,
123                         colorModes, activeColorMode)) {
124                 // Display properties changed.
125                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
126             }
127         } else {
128             // The display is no longer available. Ignore the attempt to add it.
129             // If it was connected but has already been disconnected, we'll get a
130             // disconnect event that will remove it from mDevices.
131         }
132     }
133
134     private void tryDisconnectDisplayLocked(int builtInDisplayId) {
135         LocalDisplayDevice device = mDevices.get(builtInDisplayId);
136         if (device != null) {
137             // Display was removed.
138             mDevices.remove(builtInDisplayId);
139             sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
140         }
141     }
142
143     static int getPowerModeForState(int state) {
144         switch (state) {
145             case Display.STATE_OFF:
146                 return SurfaceControl.POWER_MODE_OFF;
147             case Display.STATE_DOZE:
148                 return SurfaceControl.POWER_MODE_DOZE;
149             case Display.STATE_DOZE_SUSPEND:
150                 return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
151             default:
152                 return SurfaceControl.POWER_MODE_NORMAL;
153         }
154     }
155
156     private final class LocalDisplayDevice extends DisplayDevice {
157         private final int mBuiltInDisplayId;
158         private final Light mBacklight;
159         private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
160         private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
161
162         private DisplayDeviceInfo mInfo;
163         private boolean mHavePendingChanges;
164         private int mState = Display.STATE_UNKNOWN;
165         private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT;
166         private int mActivePhysIndex;
167         private int mDefaultModeId;
168         private int mActiveModeId;
169         private boolean mActiveModeInvalid;
170         private int mActiveColorMode;
171         private boolean mActiveColorModeInvalid;
172         private Display.HdrCapabilities mHdrCapabilities;
173
174         private  SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
175
176         public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
177                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
178                 int[] colorModes, int activeColorMode) {
179             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId);
180             mBuiltInDisplayId = builtInDisplayId;
181             updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo,
182                     colorModes, activeColorMode);
183             updateColorModesLocked(colorModes, activeColorMode);
184             if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
185                 LightsManager lights = LocalServices.getService(LightsManager.class);
186                 mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
187             } else {
188                 mBacklight = null;
189             }
190             mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken);
191         }
192
193         @Override
194         public boolean hasStableUniqueId() {
195             return true;
196         }
197
198         public boolean updatePhysicalDisplayInfoLocked(
199                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
200                 int[] colorModes, int activeColorMode) {
201             mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length);
202             mActivePhysIndex = activeDisplayInfo;
203             // Build an updated list of all existing modes.
204             ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
205             boolean modesAdded = false;
206             for (int i = 0; i < physicalDisplayInfos.length; i++) {
207                 SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
208                 // First, check to see if we've already added a matching mode. Since not all
209                 // configuration options are exposed via Display.Mode, it's possible that we have
210                 // multiple PhysicalDisplayInfos that would generate the same Display.Mode.
211                 boolean existingMode = false;
212                 for (int j = 0; j < records.size(); j++) {
213                     if (records.get(j).hasMatchingMode(info)) {
214                         existingMode = true;
215                         break;
216                     }
217                 }
218                 if (existingMode) {
219                     continue;
220                 }
221                 // If we haven't already added a mode for this configuration to the new set of
222                 // supported modes then check to see if we have one in the prior set of supported
223                 // modes to reuse.
224                 DisplayModeRecord record = findDisplayModeRecord(info);
225                 if (record == null) {
226                     record = new DisplayModeRecord(info);
227                     modesAdded = true;
228                 }
229                 records.add(record);
230             }
231
232             // Get the currently active mode
233             DisplayModeRecord activeRecord = null;
234             for (int i = 0; i < records.size(); i++) {
235                 DisplayModeRecord record = records.get(i);
236                 if (record.hasMatchingMode(physicalDisplayInfos[activeDisplayInfo])){
237                     activeRecord = record;
238                     break;
239                 }
240             }
241             // Check whether surface flinger spontaneously changed modes out from under us. Schedule
242             // traversals to ensure that the correct state is reapplied if necessary.
243             if (mActiveModeId != 0
244                     && mActiveModeId != activeRecord.mMode.getModeId()) {
245                 mActiveModeInvalid = true;
246                 sendTraversalRequestLocked();
247             }
248
249             boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
250             // If the records haven't changed then we're done here.
251             if (!recordsChanged) {
252                 return false;
253             }
254             // Update the index of modes.
255             mHavePendingChanges = true;
256
257             mSupportedModes.clear();
258             for (DisplayModeRecord record : records) {
259                 mSupportedModes.put(record.mMode.getModeId(), record);
260             }
261             // Update the default mode, if needed.
262             if (findDisplayInfoIndexLocked(mDefaultModeId) < 0) {
263                 if (mDefaultModeId != 0) {
264                     Slog.w(TAG, "Default display mode no longer available, using currently"
265                             + " active mode as default.");
266                 }
267                 mDefaultModeId = activeRecord.mMode.getModeId();
268             }
269             // Determine whether the active mode is still there.
270             if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
271                 if (mActiveModeId != 0) {
272                     Slog.w(TAG, "Active display mode no longer available, reverting to default"
273                             + " mode.");
274                 }
275                 mActiveModeId = mDefaultModeId;
276                 mActiveModeInvalid = true;
277             }
278
279             // Schedule traversals so that we apply pending changes.
280             sendTraversalRequestLocked();
281             return true;
282         }
283
284         private boolean updateColorModesLocked(int[] colorModes,
285                 int activeColorMode) {
286             List<Integer> pendingColorModes = new ArrayList<>();
287
288             // Build an updated list of all existing color modes.
289             boolean colorModesAdded = false;
290             for (int colorMode: colorModes) {
291                 if (!mSupportedColorModes.contains(colorMode)) {
292                     colorModesAdded = true;
293                 }
294                 pendingColorModes.add(colorMode);
295             }
296
297             boolean colorModesChanged =
298                     pendingColorModes.size() != mSupportedColorModes.size()
299                     || colorModesAdded;
300
301             // If the supported color modes haven't changed then we're done here.
302             if (!colorModesChanged) {
303                 return false;
304             }
305
306             mHavePendingChanges = true;
307
308             mSupportedColorModes.clear();
309             mSupportedColorModes.addAll(pendingColorModes);
310             Collections.sort(mSupportedColorModes);
311
312             // Determine whether the active color mode is still there.
313             if (!mSupportedColorModes.contains(mActiveColorMode)) {
314                 if (mActiveColorMode != 0) {
315                     Slog.w(TAG, "Active color mode no longer available, reverting"
316                             + " to default mode.");
317                     mActiveColorMode = Display.COLOR_MODE_DEFAULT;
318                     mActiveColorModeInvalid = true;
319                 } else {
320                     if (!mSupportedColorModes.isEmpty()) {
321                         // This should never happen.
322                         Slog.e(TAG, "Default and active color mode is no longer available!"
323                                 + " Reverting to first available mode.");
324                         mActiveColorMode = mSupportedColorModes.get(0);
325                         mActiveColorModeInvalid = true;
326                     } else {
327                         // This should really never happen.
328                         Slog.e(TAG, "No color modes available!");
329                     }
330                 }
331             }
332             return true;
333         }
334
335         private DisplayModeRecord findDisplayModeRecord(SurfaceControl.PhysicalDisplayInfo info) {
336             for (int i = 0; i < mSupportedModes.size(); i++) {
337                 DisplayModeRecord record = mSupportedModes.valueAt(i);
338                 if (record.hasMatchingMode(info)) {
339                     return record;
340                 }
341             }
342             return null;
343         }
344
345         @Override
346         public void applyPendingDisplayDeviceInfoChangesLocked() {
347             if (mHavePendingChanges) {
348                 mInfo = null;
349                 mHavePendingChanges = false;
350             }
351         }
352
353         @Override
354         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
355             if (mInfo == null) {
356                 SurfaceControl.PhysicalDisplayInfo phys = mDisplayInfos[mActivePhysIndex];
357                 mInfo = new DisplayDeviceInfo();
358                 mInfo.width = phys.width;
359                 mInfo.height = phys.height;
360                 mInfo.modeId = mActiveModeId;
361                 mInfo.defaultModeId = mDefaultModeId;
362                 mInfo.supportedModes = new Display.Mode[mSupportedModes.size()];
363                 for (int i = 0; i < mSupportedModes.size(); i++) {
364                     DisplayModeRecord record = mSupportedModes.valueAt(i);
365                     mInfo.supportedModes[i] = record.mMode;
366                 }
367                 mInfo.colorMode = mActiveColorMode;
368                 mInfo.supportedColorModes =
369                         new int[mSupportedColorModes.size()];
370                 for (int i = 0; i < mSupportedColorModes.size(); i++) {
371                     mInfo.supportedColorModes[i] = mSupportedColorModes.get(i);
372                 }
373                 mInfo.hdrCapabilities = mHdrCapabilities;
374                 mInfo.appVsyncOffsetNanos = phys.appVsyncOffsetNanos;
375                 mInfo.presentationDeadlineNanos = phys.presentationDeadlineNanos;
376                 mInfo.state = mState;
377                 mInfo.uniqueId = getUniqueId();
378
379                 // Assume that all built-in displays that have secure output (eg. HDCP) also
380                 // support compositing from gralloc protected buffers.
381                 if (phys.secure) {
382                     mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
383                             | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
384                 }
385
386                 final Resources res = getContext().getResources();
387                 if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
388                     mInfo.name = res.getString(
389                             com.android.internal.R.string.display_manager_built_in_display_name);
390                     mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
391                             | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
392                     if (res.getBoolean(com.android.internal.R.bool.config_mainBuiltInDisplayIsRound)
393                             || (Build.IS_EMULATOR
394                             && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
395                         mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
396                     }
397                     mInfo.type = Display.TYPE_BUILT_IN;
398                     mInfo.densityDpi = (int)(phys.density * 160 + 0.5f);
399                     mInfo.xDpi = phys.xDpi;
400                     mInfo.yDpi = phys.yDpi;
401                     mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
402                 } else {
403                     mInfo.type = Display.TYPE_HDMI;
404                     mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
405                     mInfo.name = getContext().getResources().getString(
406                             com.android.internal.R.string.display_manager_hdmi_display_name);
407                     mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
408                     mInfo.setAssumedDensityForExternalDisplay(phys.width, phys.height);
409
410                     // For demonstration purposes, allow rotation of the external display.
411                     // In the future we might allow the user to configure this directly.
412                     if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
413                         mInfo.rotation = Surface.ROTATION_270;
414                     }
415
416                     // For demonstration purposes, allow rotation of the external display
417                     // to follow the built-in display.
418                     if (SystemProperties.getBoolean("persist.demo.hdmirotates", false)) {
419                         mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
420                     }
421
422                     if (!res.getBoolean(
423                                 com.android.internal.R.bool.config_localDisplaysMirrorContent)) {
424                         mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
425                     }
426                 }
427             }
428             return mInfo;
429         }
430
431         @Override
432         public Runnable requestDisplayStateLocked(final int state, final int brightness) {
433             // Assume that the brightness is off if the display is being turned off.
434             assert state != Display.STATE_OFF || brightness == PowerManager.BRIGHTNESS_OFF;
435
436             final boolean stateChanged = (mState != state);
437             final boolean brightnessChanged = (mBrightness != brightness) && mBacklight != null;
438             if (stateChanged || brightnessChanged) {
439                 final int displayId = mBuiltInDisplayId;
440                 final IBinder token = getDisplayTokenLocked();
441                 final int oldState = mState;
442
443                 if (stateChanged) {
444                     mState = state;
445                     updateDeviceInfoLocked();
446                 }
447
448                 if (brightnessChanged) {
449                     mBrightness = brightness;
450                 }
451
452                 // Defer actually setting the display state until after we have exited
453                 // the critical section since it can take hundreds of milliseconds
454                 // to complete.
455                 return new Runnable() {
456                     @Override
457                     public void run() {
458                         // Exit a suspended state before making any changes.
459                         int currentState = oldState;
460                         if (Display.isSuspendedState(oldState)
461                                 || oldState == Display.STATE_UNKNOWN) {
462                             if (!Display.isSuspendedState(state)) {
463                                 setDisplayState(state);
464                                 currentState = state;
465                             } else if (state == Display.STATE_DOZE_SUSPEND
466                                     || oldState == Display.STATE_DOZE_SUSPEND) {
467                                 setDisplayState(Display.STATE_DOZE);
468                                 currentState = Display.STATE_DOZE;
469                             } else {
470                                 return; // old state and new state is off
471                             }
472                         }
473
474                         // Apply brightness changes given that we are in a non-suspended state.
475                         if (brightnessChanged) {
476                             setDisplayBrightness(brightness);
477                         }
478
479                         // Enter the final desired state, possibly suspended.
480                         if (state != currentState) {
481                             setDisplayState(state);
482                         }
483                     }
484
485                     private void setDisplayState(int state) {
486                         if (DEBUG) {
487                             Slog.d(TAG, "setDisplayState("
488                                     + "id=" + displayId
489                                     + ", state=" + Display.stateToString(state) + ")");
490                         }
491
492                         Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
493                                 + "id=" + displayId
494                                 + ", state=" + Display.stateToString(state) + ")");
495                         try {
496                             final int mode = getPowerModeForState(state);
497                             SurfaceControl.setDisplayPowerMode(token, mode);
498                         } finally {
499                             Trace.traceEnd(Trace.TRACE_TAG_POWER);
500                         }
501                     }
502
503                     private void setDisplayBrightness(int brightness) {
504                         if (DEBUG) {
505                             Slog.d(TAG, "setDisplayBrightness("
506                                     + "id=" + displayId + ", brightness=" + brightness + ")");
507                         }
508
509                         Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
510                                 + "id=" + displayId + ", brightness=" + brightness + ")");
511                         try {
512                             mBacklight.setBrightness(brightness);
513                         } finally {
514                             Trace.traceEnd(Trace.TRACE_TAG_POWER);
515                         }
516                     }
517                 };
518             }
519             return null;
520         }
521
522         @Override
523         public void requestDisplayModesInTransactionLocked(
524                 int colorMode, int modeId) {
525             if (requestModeInTransactionLocked(modeId) ||
526                     requestColorModeInTransactionLocked(colorMode)) {
527                 updateDeviceInfoLocked();
528             }
529         }
530
531         public boolean requestModeInTransactionLocked(int modeId) {
532             if (modeId == 0) {
533                 modeId = mDefaultModeId;
534             } else if (mSupportedModes.indexOfKey(modeId) < 0) {
535                 Slog.w(TAG, "Requested mode " + modeId + " is not supported by this display,"
536                         + " reverting to default display mode.");
537                 modeId = mDefaultModeId;
538             }
539
540             int physIndex = findDisplayInfoIndexLocked(modeId);
541             if (physIndex < 0) {
542                 Slog.w(TAG, "Requested mode ID " + modeId + " not available,"
543                         + " trying with default mode ID");
544                 modeId = mDefaultModeId;
545                 physIndex = findDisplayInfoIndexLocked(modeId);
546             }
547             if (mActivePhysIndex == physIndex) {
548                 return false;
549             }
550             SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
551             mActivePhysIndex = physIndex;
552             mActiveModeId = modeId;
553             mActiveModeInvalid = false;
554             return true;
555         }
556
557         public boolean requestColorModeInTransactionLocked(int colorMode) {
558             if (mActiveColorMode == colorMode) {
559                 return false;
560             }
561             if (!mSupportedColorModes.contains(colorMode)) {
562                 Slog.w(TAG, "Unable to find color mode " + colorMode
563                         + ", ignoring request.");
564                 return false;
565             }
566             SurfaceControl.setActiveColorMode(getDisplayTokenLocked(), colorMode);
567             mActiveColorMode = colorMode;
568             mActiveColorModeInvalid = false;
569             return true;
570         }
571
572         @Override
573         public void dumpLocked(PrintWriter pw) {
574             super.dumpLocked(pw);
575             pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
576             pw.println("mActivePhysIndex=" + mActivePhysIndex);
577             pw.println("mActiveModeId=" + mActiveModeId);
578             pw.println("mActiveColorMode=" + mActiveColorMode);
579             pw.println("mState=" + Display.stateToString(mState));
580             pw.println("mBrightness=" + mBrightness);
581             pw.println("mBacklight=" + mBacklight);
582             pw.println("mDisplayInfos=");
583             for (int i = 0; i < mDisplayInfos.length; i++) {
584                 pw.println("  " + mDisplayInfos[i]);
585             }
586             pw.println("mSupportedModes=");
587             for (int i = 0; i < mSupportedModes.size(); i++) {
588                 pw.println("  " + mSupportedModes.valueAt(i));
589             }
590             pw.print("mSupportedColorModes=[");
591             for (int i = 0; i < mSupportedColorModes.size(); i++) {
592                 if (i != 0) {
593                     pw.print(", ");
594                 }
595                 pw.print(mSupportedColorModes.get(i));
596             }
597             pw.println("]");
598         }
599
600         private int findDisplayInfoIndexLocked(int modeId) {
601             DisplayModeRecord record = mSupportedModes.get(modeId);
602             if (record != null) {
603                 for (int i = 0; i < mDisplayInfos.length; i++) {
604                     SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[i];
605                     if (record.hasMatchingMode(info)){
606                         return i;
607                     }
608                 }
609             }
610             return -1;
611         }
612
613         private void updateDeviceInfoLocked() {
614             mInfo = null;
615             sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
616         }
617     }
618
619     /**
620      * Keeps track of a display configuration.
621      */
622     private static final class DisplayModeRecord {
623         public final Display.Mode mMode;
624
625         public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys) {
626             mMode = createMode(phys.width, phys.height, phys.refreshRate);
627         }
628
629         /**
630          * Returns whether the mode generated by the given PhysicalDisplayInfo matches the mode
631          * contained by the record modulo mode ID.
632          *
633          * Note that this doesn't necessarily mean the the PhysicalDisplayInfos are identical, just
634          * that they generate identical modes.
635          */
636         public boolean hasMatchingMode(SurfaceControl.PhysicalDisplayInfo info) {
637             int modeRefreshRate = Float.floatToIntBits(mMode.getRefreshRate());
638             int displayInfoRefreshRate = Float.floatToIntBits(info.refreshRate);
639             return mMode.getPhysicalWidth() == info.width
640                     && mMode.getPhysicalHeight() == info.height
641                     && modeRefreshRate == displayInfoRefreshRate;
642         }
643
644         public String toString() {
645             return "DisplayModeRecord{mMode=" + mMode + "}";
646         }
647     }
648
649     private final class HotplugDisplayEventReceiver extends DisplayEventReceiver {
650         public HotplugDisplayEventReceiver(Looper looper) {
651             super(looper);
652         }
653
654         @Override
655         public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
656             synchronized (getSyncRoot()) {
657                 if (connected) {
658                     tryConnectDisplayLocked(builtInDisplayId);
659                 } else {
660                     tryDisconnectDisplayLocked(builtInDisplayId);
661                 }
662             }
663         }
664     }
665 }