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
17package com.android.server.display;
18
19import android.content.res.Resources;
20import com.android.server.LocalServices;
21import com.android.server.lights.Light;
22import com.android.server.lights.LightsManager;
23
24import android.content.Context;
25import android.os.Build;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Looper;
29import android.os.PowerManager;
30import android.os.SystemProperties;
31import android.os.Trace;
32import android.util.Slog;
33import android.util.SparseArray;
34import android.view.Display;
35import android.view.DisplayEventReceiver;
36import android.view.Surface;
37import android.view.SurfaceControl;
38
39import java.io.PrintWriter;
40import java.util.ArrayList;
41import java.util.Arrays;
42
43/**
44 * A display adapter for the local displays managed by Surface Flinger.
45 * <p>
46 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
47 * </p>
48 */
49final class LocalDisplayAdapter extends DisplayAdapter {
50    private static final String TAG = "LocalDisplayAdapter";
51    private static final boolean DEBUG = false;
52
53    private static final String UNIQUE_ID_PREFIX = "local:";
54
55    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
56
57    private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] {
58            SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN,
59            SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI,
60    };
61
62    private final SparseArray<LocalDisplayDevice> mDevices =
63            new SparseArray<LocalDisplayDevice>();
64    @SuppressWarnings("unused")  // Becomes active at instantiation time.
65    private HotplugDisplayEventReceiver mHotplugReceiver;
66
67    // Called with SyncRoot lock held.
68    public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
69            Context context, Handler handler, Listener listener) {
70        super(syncRoot, context, handler, listener, TAG);
71    }
72
73    @Override
74    public void registerLocked() {
75        super.registerLocked();
76
77        mHotplugReceiver = new HotplugDisplayEventReceiver(getHandler().getLooper());
78
79        for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) {
80            tryConnectDisplayLocked(builtInDisplayId);
81        }
82    }
83
84    private void tryConnectDisplayLocked(int builtInDisplayId) {
85        IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
86        if (displayToken != null) {
87            SurfaceControl.PhysicalDisplayInfo[] configs =
88                    SurfaceControl.getDisplayConfigs(displayToken);
89            if (configs == null) {
90                // There are no valid configs for this device, so we can't use it
91                Slog.w(TAG, "No valid configs found for display device " +
92                        builtInDisplayId);
93                return;
94            }
95            int activeConfig = SurfaceControl.getActiveConfig(displayToken);
96            if (activeConfig < 0) {
97                // There is no active config, and for now we don't have the
98                // policy to set one.
99                Slog.w(TAG, "No active config found for display device " +
100                        builtInDisplayId);
101                return;
102            }
103            LocalDisplayDevice device = mDevices.get(builtInDisplayId);
104            if (device == null) {
105                // Display was added.
106                device = new LocalDisplayDevice(displayToken, builtInDisplayId,
107                        configs, activeConfig);
108                mDevices.put(builtInDisplayId, device);
109                sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
110            } else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig)) {
111                // Display properties changed.
112                sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
113            }
114        } else {
115            // The display is no longer available. Ignore the attempt to add it.
116            // If it was connected but has already been disconnected, we'll get a
117            // disconnect event that will remove it from mDevices.
118        }
119    }
120
121    private void tryDisconnectDisplayLocked(int builtInDisplayId) {
122        LocalDisplayDevice device = mDevices.get(builtInDisplayId);
123        if (device != null) {
124            // Display was removed.
125            mDevices.remove(builtInDisplayId);
126            sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
127        }
128    }
129
130    static int getPowerModeForState(int state) {
131        switch (state) {
132            case Display.STATE_OFF:
133                return SurfaceControl.POWER_MODE_OFF;
134            case Display.STATE_DOZE:
135                return SurfaceControl.POWER_MODE_DOZE;
136            case Display.STATE_DOZE_SUSPEND:
137                return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
138            default:
139                return SurfaceControl.POWER_MODE_NORMAL;
140        }
141    }
142
143    private final class LocalDisplayDevice extends DisplayDevice {
144        private final int mBuiltInDisplayId;
145        private final Light mBacklight;
146        private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
147        private final SparseArray<Display.ColorTransform> mSupportedColorTransforms =
148                new SparseArray<>();
149
150        private DisplayDeviceInfo mInfo;
151        private boolean mHavePendingChanges;
152        private int mState = Display.STATE_UNKNOWN;
153        private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT;
154        private int mActivePhysIndex;
155        private int mDefaultModeId;
156        private int mActiveModeId;
157        private boolean mActiveModeInvalid;
158        private int mDefaultColorTransformId;
159        private int mActiveColorTransformId;
160        private boolean mActiveColorTransformInvalid;
161        private Display.HdrCapabilities mHdrCapabilities;
162
163        private  SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
164
165        public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
166                SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
167            super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId);
168            mBuiltInDisplayId = builtInDisplayId;
169            updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo);
170            if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
171                LightsManager lights = LocalServices.getService(LightsManager.class);
172                mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
173            } else {
174                mBacklight = null;
175            }
176            mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken);
177        }
178
179        public boolean updatePhysicalDisplayInfoLocked(
180                SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
181            mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length);
182            mActivePhysIndex = activeDisplayInfo;
183            ArrayList<Display.ColorTransform> colorTransforms = new ArrayList<>();
184
185            // Build an updated list of all existing color transforms.
186            boolean colorTransformsAdded = false;
187            Display.ColorTransform activeColorTransform = null;
188            for (int i = 0; i < physicalDisplayInfos.length; i++) {
189                SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
190                // First check to see if we've already added this color transform
191                boolean existingMode = false;
192                for (int j = 0; j < colorTransforms.size(); j++) {
193                    if (colorTransforms.get(j).getColorTransform() == info.colorTransform) {
194                        existingMode = true;
195                        break;
196                    }
197                }
198                if (existingMode) {
199                    continue;
200                }
201                Display.ColorTransform colorTransform = findColorTransform(info);
202                if (colorTransform == null) {
203                    colorTransform = createColorTransform(info.colorTransform);
204                    colorTransformsAdded = true;
205                }
206                colorTransforms.add(colorTransform);
207                if (i == activeDisplayInfo) {
208                    activeColorTransform = colorTransform;
209                }
210            }
211
212            // Build an updated list of all existing modes.
213            ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
214            boolean modesAdded = false;
215            for (int i = 0; i < physicalDisplayInfos.length; i++) {
216                SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
217                // First, check to see if we've already added a matching mode. Since not all
218                // configuration options are exposed via Display.Mode, it's possible that we have
219                // multiple PhysicalDisplayInfos that would generate the same Display.Mode.
220                boolean existingMode = false;
221                for (int j = 0; j < records.size(); j++) {
222                    if (records.get(j).hasMatchingMode(info)) {
223                        existingMode = true;
224                        break;
225                    }
226                }
227                if (existingMode) {
228                    continue;
229                }
230                // If we haven't already added a mode for this configuration to the new set of
231                // supported modes then check to see if we have one in the prior set of supported
232                // modes to reuse.
233                DisplayModeRecord record = findDisplayModeRecord(info);
234                if (record == null) {
235                    record = new DisplayModeRecord(info);
236                    modesAdded = true;
237                }
238                records.add(record);
239            }
240
241            // Get the currently active mode
242            DisplayModeRecord activeRecord = null;
243            for (int i = 0; i < records.size(); i++) {
244                DisplayModeRecord record = records.get(i);
245                if (record.hasMatchingMode(physicalDisplayInfos[activeDisplayInfo])){
246                    activeRecord = record;
247                    break;
248                }
249            }
250            // Check whether surface flinger spontaneously changed modes out from under us. Schedule
251            // traversals to ensure that the correct state is reapplied if necessary.
252            if (mActiveModeId != 0
253                    && mActiveModeId != activeRecord.mMode.getModeId()) {
254                mActiveModeInvalid = true;
255                sendTraversalRequestLocked();
256            }
257            // Check whether surface flinger spontaneously changed color transforms out from under
258            // us.
259            if (mActiveColorTransformId != 0
260                    && mActiveColorTransformId != activeColorTransform.getId()) {
261                mActiveColorTransformInvalid = true;
262                sendTraversalRequestLocked();
263            }
264
265            boolean colorTransformsChanged =
266                    colorTransforms.size() != mSupportedColorTransforms.size()
267                    || colorTransformsAdded;
268            boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
269            // If neither the records nor the supported color transforms have changed then we're
270            // done here.
271            if (!recordsChanged && !colorTransformsChanged) {
272                return false;
273            }
274            // Update the index of modes.
275            mHavePendingChanges = true;
276
277            mSupportedModes.clear();
278            for (DisplayModeRecord record : records) {
279                mSupportedModes.put(record.mMode.getModeId(), record);
280            }
281            mSupportedColorTransforms.clear();
282            for (Display.ColorTransform colorTransform : colorTransforms) {
283                mSupportedColorTransforms.put(colorTransform.getId(), colorTransform);
284            }
285
286            // Update the default mode and color transform if needed. This needs to be done in
287            // tandem so we always have a default state to fall back to.
288            if (findDisplayInfoIndexLocked(mDefaultColorTransformId, mDefaultModeId) < 0) {
289                if (mDefaultModeId != 0) {
290                    Slog.w(TAG, "Default display mode no longer available, using currently"
291                            + " active mode as default.");
292                }
293                mDefaultModeId = activeRecord.mMode.getModeId();
294                if (mDefaultColorTransformId != 0) {
295                    Slog.w(TAG, "Default color transform no longer available, using currently"
296                            + " active color transform as default");
297                }
298                mDefaultColorTransformId = activeColorTransform.getId();
299            }
300            // Determine whether the active mode is still there.
301            if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
302                if (mActiveModeId != 0) {
303                    Slog.w(TAG, "Active display mode no longer available, reverting to default"
304                            + " mode.");
305                }
306                mActiveModeId = mDefaultModeId;
307                mActiveModeInvalid = true;
308            }
309
310            // Determine whether the active color transform is still there.
311            if (mSupportedColorTransforms.indexOfKey(mActiveColorTransformId) < 0) {
312                if (mActiveColorTransformId != 0) {
313                    Slog.w(TAG, "Active color transform no longer available, reverting"
314                            + " to default transform.");
315                }
316                mActiveColorTransformId = mDefaultColorTransformId;
317                mActiveColorTransformInvalid = true;
318            }
319            // Schedule traversals so that we apply pending changes.
320            sendTraversalRequestLocked();
321            return true;
322        }
323
324        private DisplayModeRecord findDisplayModeRecord(SurfaceControl.PhysicalDisplayInfo info) {
325            for (int i = 0; i < mSupportedModes.size(); i++) {
326                DisplayModeRecord record = mSupportedModes.valueAt(i);
327                if (record.hasMatchingMode(info)) {
328                    return record;
329                }
330            }
331            return null;
332        }
333
334        private Display.ColorTransform findColorTransform(SurfaceControl.PhysicalDisplayInfo info) {
335            for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
336                Display.ColorTransform transform = mSupportedColorTransforms.valueAt(i);
337                if (transform.getColorTransform() == info.colorTransform) {
338                    return transform;
339                }
340            }
341            return null;
342        }
343
344        @Override
345        public void applyPendingDisplayDeviceInfoChangesLocked() {
346            if (mHavePendingChanges) {
347                mInfo = null;
348                mHavePendingChanges = false;
349            }
350        }
351
352        @Override
353        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
354            if (mInfo == null) {
355                SurfaceControl.PhysicalDisplayInfo phys = mDisplayInfos[mActivePhysIndex];
356                mInfo = new DisplayDeviceInfo();
357                mInfo.width = phys.width;
358                mInfo.height = phys.height;
359                mInfo.modeId = mActiveModeId;
360                mInfo.defaultModeId = mDefaultModeId;
361                mInfo.supportedModes = new Display.Mode[mSupportedModes.size()];
362                for (int i = 0; i < mSupportedModes.size(); i++) {
363                    DisplayModeRecord record = mSupportedModes.valueAt(i);
364                    mInfo.supportedModes[i] = record.mMode;
365                }
366                mInfo.colorTransformId = mActiveColorTransformId;
367                mInfo.defaultColorTransformId = mDefaultColorTransformId;
368                mInfo.supportedColorTransforms =
369                        new Display.ColorTransform[mSupportedColorTransforms.size()];
370                for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
371                    mInfo.supportedColorTransforms[i] = mSupportedColorTransforms.valueAt(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 requestColorTransformAndModeInTransactionLocked(
524                int colorTransformId, int modeId) {
525            if (modeId == 0) {
526                modeId = mDefaultModeId;
527            } else if (mSupportedModes.indexOfKey(modeId) < 0) {
528                Slog.w(TAG, "Requested mode " + modeId + " is not supported by this display,"
529                        + " reverting to default display mode.");
530                modeId = mDefaultModeId;
531            }
532
533            if (colorTransformId == 0) {
534                colorTransformId = mDefaultColorTransformId;
535            } else if (mSupportedColorTransforms.indexOfKey(colorTransformId) < 0) {
536                Slog.w(TAG, "Requested color transform " + colorTransformId + " is not supported"
537                        + " by this display, reverting to the default color transform");
538                colorTransformId = mDefaultColorTransformId;
539            }
540            int physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
541            if (physIndex < 0) {
542                Slog.w(TAG, "Requested color transform, mode ID pair (" + colorTransformId + ", "
543                        + modeId + ") not available, trying color transform with default mode ID");
544                modeId = mDefaultModeId;
545                physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
546                if (physIndex < 0) {
547                    Slog.w(TAG, "Requested color transform with default mode ID still not"
548                            + " available, falling back to default color transform with default"
549                            + " mode.");
550                    colorTransformId = mDefaultColorTransformId;
551                    physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
552                }
553            }
554            if (mActivePhysIndex == physIndex) {
555                return;
556            }
557            SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
558            mActivePhysIndex = physIndex;
559            mActiveModeId = modeId;
560            mActiveModeInvalid = false;
561            mActiveColorTransformId = colorTransformId;
562            mActiveColorTransformInvalid = false;
563            updateDeviceInfoLocked();
564        }
565
566        @Override
567        public void dumpLocked(PrintWriter pw) {
568            super.dumpLocked(pw);
569            pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
570            pw.println("mActivePhysIndex=" + mActivePhysIndex);
571            pw.println("mActiveModeId=" + mActiveModeId);
572            pw.println("mActiveColorTransformId=" + mActiveColorTransformId);
573            pw.println("mState=" + Display.stateToString(mState));
574            pw.println("mBrightness=" + mBrightness);
575            pw.println("mBacklight=" + mBacklight);
576            pw.println("mDisplayInfos=");
577            for (int i = 0; i < mDisplayInfos.length; i++) {
578                pw.println("  " + mDisplayInfos[i]);
579            }
580            pw.println("mSupportedModes=");
581            for (int i = 0; i < mSupportedModes.size(); i++) {
582                pw.println("  " + mSupportedModes.valueAt(i));
583            }
584            pw.println("mSupportedColorTransforms=[");
585            for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
586                if (i != 0) {
587                    pw.print(", ");
588                }
589                pw.print(mSupportedColorTransforms.valueAt(i));
590            }
591            pw.println("]");
592        }
593
594        private int findDisplayInfoIndexLocked(int colorTransformId, int modeId) {
595            DisplayModeRecord record = mSupportedModes.get(modeId);
596            Display.ColorTransform transform = mSupportedColorTransforms.get(colorTransformId);
597            if (record != null && transform != null) {
598                for (int i = 0; i < mDisplayInfos.length; i++) {
599                    SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[i];
600                    if (info.colorTransform == transform.getColorTransform()
601                            && record.hasMatchingMode(info)){
602                        return i;
603                    }
604                }
605            }
606            return -1;
607        }
608
609        private void updateDeviceInfoLocked() {
610            mInfo = null;
611            sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
612        }
613    }
614
615    /**
616     * Keeps track of a display configuration.
617     */
618    private static final class DisplayModeRecord {
619        public final Display.Mode mMode;
620
621        public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys) {
622            mMode = createMode(phys.width, phys.height, phys.refreshRate);
623        }
624
625        /**
626         * Returns whether the mode generated by the given PhysicalDisplayInfo matches the mode
627         * contained by the record modulo mode ID.
628         *
629         * Note that this doesn't necessarily mean the the PhysicalDisplayInfos are identical, just
630         * that they generate identical modes.
631         */
632        public boolean hasMatchingMode(SurfaceControl.PhysicalDisplayInfo info) {
633            int modeRefreshRate = Float.floatToIntBits(mMode.getRefreshRate());
634            int displayInfoRefreshRate = Float.floatToIntBits(info.refreshRate);
635            return mMode.getPhysicalWidth() == info.width
636                    && mMode.getPhysicalHeight() == info.height
637                    && modeRefreshRate == displayInfoRefreshRate;
638        }
639
640        public String toString() {
641            return "DisplayModeRecord{mMode=" + mMode + "}";
642        }
643    }
644
645    private final class HotplugDisplayEventReceiver extends DisplayEventReceiver {
646        public HotplugDisplayEventReceiver(Looper looper) {
647            super(looper);
648        }
649
650        @Override
651        public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
652            synchronized (getSyncRoot()) {
653                if (connected) {
654                    tryConnectDisplayLocked(builtInDisplayId);
655                } else {
656                    tryDisconnectDisplayLocked(builtInDisplayId);
657                }
658            }
659        }
660    }
661}
662