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;
42import java.util.Collections;
43import 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 */
51final 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                        // If the state change was from or to VR, then we need to tell the light
475                        // so that it can apply appropriate VR brightness settings. This should
476                        // happen prior to changing the brightness but also if there is no
477                        // brightness change at all.
478                        if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
479                                currentState != state) {
480                            setVrMode(state == Display.STATE_VR);
481                        }
482
483
484                        // Apply brightness changes given that we are in a non-suspended state.
485                        if (brightnessChanged) {
486                            setDisplayBrightness(brightness);
487                        }
488
489                        // Enter the final desired state, possibly suspended.
490                        if (state != currentState) {
491                            setDisplayState(state);
492                        }
493                    }
494
495                    private void setVrMode(boolean isVrEnabled) {
496                        if (DEBUG) {
497                            Slog.d(TAG, "setVrMode("
498                                    + "id=" + displayId
499                                    + ", state=" + Display.stateToString(state) + ")");
500                        }
501                        mBacklight.setVrMode(isVrEnabled);
502                    }
503
504                    private void setDisplayState(int state) {
505                        if (DEBUG) {
506                            Slog.d(TAG, "setDisplayState("
507                                    + "id=" + displayId
508                                    + ", state=" + Display.stateToString(state) + ")");
509                        }
510
511                        Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
512                                + "id=" + displayId
513                                + ", state=" + Display.stateToString(state) + ")");
514                        try {
515                            final int mode = getPowerModeForState(state);
516                            SurfaceControl.setDisplayPowerMode(token, mode);
517                        } finally {
518                            Trace.traceEnd(Trace.TRACE_TAG_POWER);
519                        }
520                    }
521
522                    private void setDisplayBrightness(int brightness) {
523                        if (DEBUG) {
524                            Slog.d(TAG, "setDisplayBrightness("
525                                    + "id=" + displayId + ", brightness=" + brightness + ")");
526                        }
527
528                        Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
529                                + "id=" + displayId + ", brightness=" + brightness + ")");
530                        try {
531                            mBacklight.setBrightness(brightness);
532                        } finally {
533                            Trace.traceEnd(Trace.TRACE_TAG_POWER);
534                        }
535                    }
536                };
537            }
538            return null;
539        }
540
541        @Override
542        public void requestDisplayModesInTransactionLocked(
543                int colorMode, int modeId) {
544            if (requestModeInTransactionLocked(modeId) ||
545                    requestColorModeInTransactionLocked(colorMode)) {
546                updateDeviceInfoLocked();
547            }
548        }
549
550        public boolean requestModeInTransactionLocked(int modeId) {
551            if (modeId == 0) {
552                modeId = mDefaultModeId;
553            } else if (mSupportedModes.indexOfKey(modeId) < 0) {
554                Slog.w(TAG, "Requested mode " + modeId + " is not supported by this display,"
555                        + " reverting to default display mode.");
556                modeId = mDefaultModeId;
557            }
558
559            int physIndex = findDisplayInfoIndexLocked(modeId);
560            if (physIndex < 0) {
561                Slog.w(TAG, "Requested mode ID " + modeId + " not available,"
562                        + " trying with default mode ID");
563                modeId = mDefaultModeId;
564                physIndex = findDisplayInfoIndexLocked(modeId);
565            }
566            if (mActivePhysIndex == physIndex) {
567                return false;
568            }
569            SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
570            mActivePhysIndex = physIndex;
571            mActiveModeId = modeId;
572            mActiveModeInvalid = false;
573            return true;
574        }
575
576        public boolean requestColorModeInTransactionLocked(int colorMode) {
577            if (mActiveColorMode == colorMode) {
578                return false;
579            }
580            if (!mSupportedColorModes.contains(colorMode)) {
581                Slog.w(TAG, "Unable to find color mode " + colorMode
582                        + ", ignoring request.");
583                return false;
584            }
585            SurfaceControl.setActiveColorMode(getDisplayTokenLocked(), colorMode);
586            mActiveColorMode = colorMode;
587            mActiveColorModeInvalid = false;
588            return true;
589        }
590
591        @Override
592        public void dumpLocked(PrintWriter pw) {
593            super.dumpLocked(pw);
594            pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
595            pw.println("mActivePhysIndex=" + mActivePhysIndex);
596            pw.println("mActiveModeId=" + mActiveModeId);
597            pw.println("mActiveColorMode=" + mActiveColorMode);
598            pw.println("mState=" + Display.stateToString(mState));
599            pw.println("mBrightness=" + mBrightness);
600            pw.println("mBacklight=" + mBacklight);
601            pw.println("mDisplayInfos=");
602            for (int i = 0; i < mDisplayInfos.length; i++) {
603                pw.println("  " + mDisplayInfos[i]);
604            }
605            pw.println("mSupportedModes=");
606            for (int i = 0; i < mSupportedModes.size(); i++) {
607                pw.println("  " + mSupportedModes.valueAt(i));
608            }
609            pw.print("mSupportedColorModes=[");
610            for (int i = 0; i < mSupportedColorModes.size(); i++) {
611                if (i != 0) {
612                    pw.print(", ");
613                }
614                pw.print(mSupportedColorModes.get(i));
615            }
616            pw.println("]");
617        }
618
619        private int findDisplayInfoIndexLocked(int modeId) {
620            DisplayModeRecord record = mSupportedModes.get(modeId);
621            if (record != null) {
622                for (int i = 0; i < mDisplayInfos.length; i++) {
623                    SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[i];
624                    if (record.hasMatchingMode(info)){
625                        return i;
626                    }
627                }
628            }
629            return -1;
630        }
631
632        private void updateDeviceInfoLocked() {
633            mInfo = null;
634            sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
635        }
636    }
637
638    /**
639     * Keeps track of a display configuration.
640     */
641    private static final class DisplayModeRecord {
642        public final Display.Mode mMode;
643
644        public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys) {
645            mMode = createMode(phys.width, phys.height, phys.refreshRate);
646        }
647
648        /**
649         * Returns whether the mode generated by the given PhysicalDisplayInfo matches the mode
650         * contained by the record modulo mode ID.
651         *
652         * Note that this doesn't necessarily mean the the PhysicalDisplayInfos are identical, just
653         * that they generate identical modes.
654         */
655        public boolean hasMatchingMode(SurfaceControl.PhysicalDisplayInfo info) {
656            int modeRefreshRate = Float.floatToIntBits(mMode.getRefreshRate());
657            int displayInfoRefreshRate = Float.floatToIntBits(info.refreshRate);
658            return mMode.getPhysicalWidth() == info.width
659                    && mMode.getPhysicalHeight() == info.height
660                    && modeRefreshRate == displayInfoRefreshRate;
661        }
662
663        public String toString() {
664            return "DisplayModeRecord{mMode=" + mMode + "}";
665        }
666    }
667
668    private final class HotplugDisplayEventReceiver extends DisplayEventReceiver {
669        public HotplugDisplayEventReceiver(Looper looper) {
670            super(looper);
671        }
672
673        @Override
674        public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
675            synchronized (getSyncRoot()) {
676                if (connected) {
677                    tryConnectDisplayLocked(builtInDisplayId);
678                } else {
679                    tryDisconnectDisplayLocked(builtInDisplayId);
680                }
681            }
682        }
683    }
684}
685