• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.database.ContentObserver;
24 import android.hardware.display.DisplayManager;
25 import android.net.Uri;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.UserHandle;
30 import android.provider.Settings;
31 import android.util.Slog;
32 import android.util.SparseArray;
33 import android.view.Display;
34 import android.view.DisplayInfo;
35 
36 import com.android.internal.R;
37 
38 import java.io.PrintWriter;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Objects;
42 
43 /**
44  * The DisplayModeDirector is responsible for determining what modes are allowed to be
45  * automatically picked by the system based on system-wide and display-specific configuration.
46  */
47 public class DisplayModeDirector {
48     private static final String TAG = "DisplayModeDirector";
49     private static final boolean DEBUG = false;
50 
51     private static final int MSG_ALLOWED_MODES_CHANGED = 1;
52 
53     // Special ID used to indicate that given vote is to be applied globally, rather than to a
54     // specific display.
55     private static final int GLOBAL_ID = -1;
56 
57     // The tolerance within which we consider something approximately equals.
58     private static final float EPSILON = 0.001f;
59 
60     private final Object mLock = new Object();
61     private final Context mContext;
62 
63     private final DisplayModeDirectorHandler mHandler;
64 
65     // A map from the display ID to the collection of votes and their priority. The latter takes
66     // the form of another map from the priority to the vote itself so that each priority is
67     // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
68     private final SparseArray<SparseArray<Vote>> mVotesByDisplay;
69     // A map from the display ID to the supported modes on that display.
70     private final SparseArray<Display.Mode[]> mSupportedModesByDisplay;
71     // A map from the display ID to the default mode of that display.
72     private final SparseArray<Display.Mode> mDefaultModeByDisplay;
73 
74     private final AppRequestObserver mAppRequestObserver;
75     private final SettingsObserver mSettingsObserver;
76     private final DisplayObserver mDisplayObserver;
77 
78 
79     private Listener mListener;
80 
DisplayModeDirector(@onNull Context context, @NonNull Handler handler)81     public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
82         mContext = context;
83         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
84         mVotesByDisplay = new SparseArray<>();
85         mSupportedModesByDisplay = new SparseArray<>();
86         mDefaultModeByDisplay =  new SparseArray<>();
87         mAppRequestObserver = new AppRequestObserver();
88         mSettingsObserver = new SettingsObserver(context, handler);
89         mDisplayObserver = new DisplayObserver(context, handler);
90     }
91 
92     /**
93      * Tells the DisplayModeDirector to update allowed votes and begin observing relevant system
94      * state.
95      *
96      * This has to be deferred because the object may be constructed before the rest of the system
97      * is ready.
98      */
start()99     public void start() {
100         mSettingsObserver.observe();
101         mDisplayObserver.observe();
102         mSettingsObserver.observe();
103         synchronized (mLock) {
104             // We may have a listener already registered before the call to start, so go ahead and
105             // notify them to pick up our newly initialized state.
106             notifyAllowedModesChangedLocked();
107         }
108     }
109 
110     /**
111      * Calculates the modes the system is allowed to freely switch between based on global and
112      * display-specific constraints.
113      *
114      * @param displayId The display to query for.
115      * @return The IDs of the modes the system is allowed to freely switch between.
116      */
117     @NonNull
getAllowedModes(int displayId)118     public int[] getAllowedModes(int displayId) {
119         synchronized (mLock) {
120             SparseArray<Vote> votes = getVotesLocked(displayId);
121             Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
122             Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
123             if (modes == null || defaultMode == null) {
124                 Slog.e(TAG, "Asked about unknown display, returning empty allowed set! (id="
125                         + displayId + ")");
126                 return new int[0];
127             }
128             return getAllowedModesLocked(votes, modes, defaultMode);
129         }
130     }
131 
132     @NonNull
getVotesLocked(int displayId)133     private SparseArray<Vote> getVotesLocked(int displayId) {
134         SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
135         final SparseArray<Vote> votes;
136         if (displayVotes != null) {
137             votes = displayVotes.clone();
138         } else {
139             votes = new SparseArray<>();
140         }
141 
142         SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
143         if (globalVotes != null) {
144             for (int i = 0; i < globalVotes.size(); i++) {
145                 int priority = globalVotes.keyAt(i);
146                 if (votes.indexOfKey(priority) < 0) {
147                     votes.put(priority, globalVotes.valueAt(i));
148                 }
149             }
150         }
151         return votes;
152     }
153 
154     @NonNull
getAllowedModesLocked(@onNull SparseArray<Vote> votes, @NonNull Display.Mode[] modes, @NonNull Display.Mode defaultMode)155     private int[] getAllowedModesLocked(@NonNull SparseArray<Vote> votes,
156             @NonNull Display.Mode[] modes, @NonNull Display.Mode defaultMode) {
157         int lowestConsideredPriority = Vote.MIN_PRIORITY;
158         while (lowestConsideredPriority <= Vote.MAX_PRIORITY) {
159             float minRefreshRate = 0f;
160             float maxRefreshRate = Float.POSITIVE_INFINITY;
161             int height = Vote.INVALID_SIZE;
162             int width = Vote.INVALID_SIZE;
163 
164             for (int priority = Vote.MAX_PRIORITY;
165                     priority >= lowestConsideredPriority;
166                     priority--) {
167                 Vote vote = votes.get(priority);
168                 if (vote == null) {
169                     continue;
170                 }
171                 // For refresh rates, just use the tightest bounds of all the votes
172                 minRefreshRate = Math.max(minRefreshRate, vote.minRefreshRate);
173                 maxRefreshRate = Math.min(maxRefreshRate, vote.maxRefreshRate);
174                 // For display size, use only the first vote we come across (i.e. the highest
175                 // priority vote that includes the width / height).
176                 if (height == Vote.INVALID_SIZE && width == Vote.INVALID_SIZE
177                         && vote.height > 0 && vote.width > 0) {
178                     width = vote.width;
179                     height = vote.height;
180                 }
181             }
182 
183             // If we don't have anything specifying the width / height of the display, just use the
184             // default width and height. We don't want these switching out from underneath us since
185             // it's a pretty disruptive behavior.
186             if (height == Vote.INVALID_SIZE || width == Vote.INVALID_SIZE) {
187                 width = defaultMode.getPhysicalWidth();
188                 height = defaultMode.getPhysicalHeight();
189             }
190 
191             int[] availableModes =
192                     filterModes(modes, width, height, minRefreshRate, maxRefreshRate);
193             if (availableModes.length > 0) {
194                 if (DEBUG) {
195                     Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
196                             + " with lowest priority considered "
197                             + Vote.priorityToString(lowestConsideredPriority)
198                             + " and constraints: "
199                             + "width=" + width
200                             + ", height=" + height
201                             + ", minRefreshRate=" + minRefreshRate
202                             + ", maxRefreshRate=" + maxRefreshRate);
203                 }
204                 return availableModes;
205             }
206 
207             if (DEBUG) {
208                 Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
209                         + Vote.priorityToString(lowestConsideredPriority)
210                         + " and with the following constraints: "
211                         + "width=" + width
212                         + ", height=" + height
213                         + ", minRefreshRate=" + minRefreshRate
214                         + ", maxRefreshRate=" + maxRefreshRate);
215             }
216             // If we haven't found anything with the current set of votes, drop the current lowest
217             // priority vote.
218             lowestConsideredPriority++;
219         }
220 
221         // If we still haven't found anything that matches our current set of votes, just fall back
222         // to the default mode.
223         return new int[] { defaultMode.getModeId() };
224     }
225 
filterModes(Display.Mode[] supportedModes, int width, int height, float minRefreshRate, float maxRefreshRate)226     private int[] filterModes(Display.Mode[] supportedModes,
227             int width, int height, float minRefreshRate, float maxRefreshRate) {
228         ArrayList<Display.Mode> availableModes = new ArrayList<>();
229         for (Display.Mode mode : supportedModes) {
230             if (mode.getPhysicalWidth() != width || mode.getPhysicalHeight() != height) {
231                 if (DEBUG) {
232                     Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
233                             + ": desiredWidth=" + width
234                             + ": desiredHeight=" + height
235                             + ": actualWidth=" + mode.getPhysicalWidth()
236                             + ": actualHeight=" + mode.getPhysicalHeight());
237                 }
238                 continue;
239             }
240             final float refreshRate = mode.getRefreshRate();
241             // Some refresh rates are calculated based on frame timings, so they aren't *exactly*
242             // equal to expected refresh rate. Given that, we apply a bit of tolerance to this
243             // comparison.
244             if (refreshRate < (minRefreshRate - EPSILON)
245                     || refreshRate > (maxRefreshRate + EPSILON)) {
246                 if (DEBUG) {
247                     Slog.w(TAG, "Discarding mode " + mode.getModeId()
248                             + ", outside refresh rate bounds"
249                             + ": minRefreshRate=" + minRefreshRate
250                             + ", maxRefreshRate=" + maxRefreshRate
251                             + ", modeRefreshRate=" + refreshRate);
252                 }
253                 continue;
254             }
255             availableModes.add(mode);
256         }
257         final int size = availableModes.size();
258         int[] availableModeIds = new int[size];
259         for (int i = 0; i < size; i++) {
260             availableModeIds[i] = availableModes.get(i).getModeId();
261         }
262         return availableModeIds;
263     }
264 
265     /**
266      * Gets the observer responsible for application display mode requests.
267      */
268     @NonNull
getAppRequestObserver()269     public AppRequestObserver getAppRequestObserver() {
270         // We don't need to lock here because mAppRequestObserver is a final field, which is
271         // guaranteed to be visible on all threads after construction.
272         return mAppRequestObserver;
273     }
274 
275     /**
276      * Sets the listener for changes to allowed display modes.
277      */
setListener(@ullable Listener listener)278     public void setListener(@Nullable Listener listener) {
279         synchronized (mLock) {
280             mListener = listener;
281         }
282     }
283 
284     /**
285      * Print the object's state and debug information into the given stream.
286      *
287      * @param pw The stream to dump information to.
288      */
dump(PrintWriter pw)289     public void dump(PrintWriter pw) {
290         pw.println("DisplayModeDirector");
291         synchronized (mLock) {
292             pw.println("  mSupportedModesByDisplay:");
293             for (int i = 0; i < mSupportedModesByDisplay.size(); i++) {
294                 final int id = mSupportedModesByDisplay.keyAt(i);
295                 final Display.Mode[] modes = mSupportedModesByDisplay.valueAt(i);
296                 pw.println("    " + id + " -> " + Arrays.toString(modes));
297             }
298             pw.println("  mDefaultModeByDisplay:");
299             for (int i = 0; i < mDefaultModeByDisplay.size(); i++) {
300                 final int id = mDefaultModeByDisplay.keyAt(i);
301                 final Display.Mode mode = mDefaultModeByDisplay.valueAt(i);
302                 pw.println("    " + id + " -> " + mode);
303             }
304             pw.println("  mVotesByDisplay:");
305             for (int i = 0; i < mVotesByDisplay.size(); i++) {
306                 pw.println("    " + mVotesByDisplay.keyAt(i) + ":");
307                 SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
308                 for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
309                     Vote vote = votes.get(p);
310                     if (vote == null) {
311                         continue;
312                     }
313                     pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
314                 }
315             }
316             mSettingsObserver.dumpLocked(pw);
317             mAppRequestObserver.dumpLocked(pw);
318         }
319     }
320 
updateVoteLocked(int priority, Vote vote)321     private void updateVoteLocked(int priority, Vote vote) {
322         updateVoteLocked(GLOBAL_ID, priority, vote);
323     }
324 
updateVoteLocked(int displayId, int priority, Vote vote)325     private void updateVoteLocked(int displayId, int priority, Vote vote) {
326         if (DEBUG) {
327             Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
328                     + ", priority=" + Vote.priorityToString(priority)
329                     + ", vote=" + vote + ")");
330         }
331         if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
332             Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
333                     + " priority=" + Vote.priorityToString(priority)
334                     + ", vote=" + vote, new Throwable());
335             return;
336         }
337         final SparseArray<Vote> votes = getOrCreateVotesByDisplay(displayId);
338 
339         Vote currentVote = votes.get(priority);
340         if (vote != null) {
341             votes.put(priority, vote);
342         } else {
343             votes.remove(priority);
344         }
345 
346         if (votes.size() == 0) {
347             if (DEBUG) {
348                 Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
349             }
350             mVotesByDisplay.remove(displayId);
351         }
352 
353         notifyAllowedModesChangedLocked();
354     }
355 
notifyAllowedModesChangedLocked()356     private void notifyAllowedModesChangedLocked() {
357         if (mListener != null && !mHandler.hasMessages(MSG_ALLOWED_MODES_CHANGED)) {
358             // We need to post this to a handler to avoid calling out while holding the lock
359             // since we know there are things that both listen for changes as well as provide
360             // information. If we did call out while holding the lock, then there's no guaranteed
361             // lock order and we run the real of risk deadlock.
362             Message msg = mHandler.obtainMessage(MSG_ALLOWED_MODES_CHANGED, mListener);
363             msg.sendToTarget();
364         }
365     }
366 
getOrCreateVotesByDisplay(int displayId)367     private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
368         int index = mVotesByDisplay.indexOfKey(displayId);
369         if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
370             return mVotesByDisplay.get(displayId);
371         } else {
372             SparseArray<Vote> votes = new SparseArray<>();
373             mVotesByDisplay.put(displayId, votes);
374             return votes;
375         }
376     }
377 
378     /**
379      * Listens for changes to display mode coordination.
380      */
381     public interface Listener {
382         /**
383          * Called when the allowed display modes may have changed.
384          */
onAllowedDisplayModesChanged()385         void onAllowedDisplayModesChanged();
386     }
387 
388     private static final class DisplayModeDirectorHandler extends Handler {
DisplayModeDirectorHandler(Looper looper)389         DisplayModeDirectorHandler(Looper looper) {
390             super(looper, null, true /*async*/);
391         }
392 
393         @Override
handleMessage(Message msg)394         public void handleMessage(Message msg) {
395             switch (msg.what) {
396                 case MSG_ALLOWED_MODES_CHANGED:
397                     Listener listener = (Listener) msg.obj;
398                     listener.onAllowedDisplayModesChanged();
399                     break;
400             }
401         }
402     }
403 
404     private static final class Vote {
405         public static final int PRIORITY_USER_SETTING = 0;
406         // We split the app request into two priorities in case we can satisfy one desire without
407         // the other.
408         public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 1;
409         public static final int PRIORITY_APP_REQUEST_SIZE = 2;
410         public static final int PRIORITY_LOW_POWER_MODE = 3;
411 
412         // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
413         // appropriate, as well as priorityToString.
414 
415         public static final int MIN_PRIORITY = PRIORITY_USER_SETTING;
416         public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
417 
418         /**
419          * A value signifying an invalid width or height in a vote.
420          */
421         public static final int INVALID_SIZE = -1;
422 
423         /**
424          * The requested width of the display in pixels, or INVALID_SIZE;
425          */
426         public final int width;
427         /**
428          * The requested height of the display in pixels, or INVALID_SIZE;
429          */
430         public final int height;
431 
432         /**
433          * The lowest desired refresh rate.
434          */
435         public final float minRefreshRate;
436         /**
437          * The highest desired refresh rate.
438          */
439         public final float maxRefreshRate;
440 
forRefreshRates(float minRefreshRate, float maxRefreshRate)441         public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) {
442             return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate);
443         }
444 
forSize(int width, int height)445         public static Vote forSize(int width, int height) {
446             return new Vote(width, height, 0f, Float.POSITIVE_INFINITY);
447         }
448 
Vote(int width, int height, float minRefreshRate, float maxRefreshRate)449         private Vote(int width, int height,
450                 float minRefreshRate, float maxRefreshRate) {
451             this.width = width;
452             this.height = height;
453             this.minRefreshRate = minRefreshRate;
454             this.maxRefreshRate = maxRefreshRate;
455         }
456 
priorityToString(int priority)457         public static String priorityToString(int priority) {
458             switch (priority) {
459                 case PRIORITY_USER_SETTING:
460                     return "PRIORITY_USER_SETTING";
461                 case PRIORITY_APP_REQUEST_REFRESH_RATE:
462                     return "PRIORITY_APP_REQUEST_REFRESH_RATE";
463                 case PRIORITY_APP_REQUEST_SIZE:
464                     return "PRIORITY_APP_REQUEST_SIZE";
465                 case PRIORITY_LOW_POWER_MODE:
466                     return "PRIORITY_LOW_POWER_MODE";
467                 default:
468                     return Integer.toString(priority);
469             }
470         }
471 
472         @Override
toString()473         public String toString() {
474             return "Vote{"
475                 + "width=" + width
476                 + ", height=" + height
477                 + ", minRefreshRate=" + minRefreshRate
478                 + ", maxRefreshRate=" + maxRefreshRate
479                 + "}";
480         }
481     }
482 
483     private final class SettingsObserver extends ContentObserver {
484         private final Uri mRefreshRateSetting =
485                 Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
486         private final Uri mLowPowerModeSetting =
487                 Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
488 
489         private final Context mContext;
490         private final float mDefaultPeakRefreshRate;
491 
SettingsObserver(@onNull Context context, @NonNull Handler handler)492         SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
493             super(handler);
494             mContext = context;
495             mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
496                     R.integer.config_defaultPeakRefreshRate);
497         }
498 
observe()499         public void observe() {
500             final ContentResolver cr = mContext.getContentResolver();
501             cr.registerContentObserver(mRefreshRateSetting, false /*notifyDescendants*/, this,
502                     UserHandle.USER_SYSTEM);
503             cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
504                     UserHandle.USER_SYSTEM);
505             synchronized (mLock) {
506                 updateRefreshRateSettingLocked();
507                 updateLowPowerModeSettingLocked();
508             }
509         }
510 
511         @Override
onChange(boolean selfChange, Uri uri, int userId)512         public void onChange(boolean selfChange, Uri uri, int userId) {
513             synchronized (mLock) {
514                 if (mRefreshRateSetting.equals(uri)) {
515                     updateRefreshRateSettingLocked();
516                 } else if (mLowPowerModeSetting.equals(uri)) {
517                     updateLowPowerModeSettingLocked();
518                 }
519             }
520         }
521 
updateLowPowerModeSettingLocked()522         private void updateLowPowerModeSettingLocked() {
523             boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
524                     Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
525             final Vote vote;
526             if (inLowPowerMode) {
527                 vote = Vote.forRefreshRates(0f, 60f);
528             } else {
529                 vote = null;
530             }
531             updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
532         }
533 
updateRefreshRateSettingLocked()534         private void updateRefreshRateSettingLocked() {
535             float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
536                     Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
537             Vote vote = Vote.forRefreshRates(0f, peakRefreshRate);
538             updateVoteLocked(Vote.PRIORITY_USER_SETTING, vote);
539         }
540 
dumpLocked(PrintWriter pw)541         public void dumpLocked(PrintWriter pw) {
542             pw.println("  SettingsObserver");
543             pw.println("    mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
544         }
545     }
546 
547     final class AppRequestObserver {
548         private SparseArray<Display.Mode> mAppRequestedModeByDisplay;
549 
AppRequestObserver()550         AppRequestObserver() {
551             mAppRequestedModeByDisplay = new SparseArray<>();
552         }
553 
setAppRequestedMode(int displayId, int modeId)554         public void setAppRequestedMode(int displayId, int modeId) {
555             synchronized (mLock) {
556                 setAppRequestedModeLocked(displayId, modeId);
557             }
558         }
559 
setAppRequestedModeLocked(int displayId, int modeId)560         private void setAppRequestedModeLocked(int displayId, int modeId) {
561             final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
562             if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
563                 return;
564             }
565 
566             final Vote refreshRateVote;
567             final Vote sizeVote;
568             if (requestedMode != null) {
569                 mAppRequestedModeByDisplay.put(displayId, requestedMode);
570                 float refreshRate = requestedMode.getRefreshRate();
571                 refreshRateVote = Vote.forRefreshRates(refreshRate, refreshRate);
572                 sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
573                         requestedMode.getPhysicalHeight());
574             } else {
575                 mAppRequestedModeByDisplay.remove(displayId);
576                 refreshRateVote = null;
577                 sizeVote = null;
578             }
579             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
580             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
581             return;
582         }
583 
findModeByIdLocked(int displayId, int modeId)584         private Display.Mode findModeByIdLocked(int displayId, int modeId) {
585             Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
586             if (modes == null) {
587                 return null;
588             }
589             for (Display.Mode mode : modes) {
590                 if (mode.getModeId() == modeId) {
591                     return mode;
592                 }
593             }
594             return null;
595         }
596 
dumpLocked(PrintWriter pw)597         public void dumpLocked(PrintWriter pw) {
598             pw.println("  AppRequestObserver");
599             pw.println("    mAppRequestedModeByDisplay:");
600             for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
601                 final int id = mAppRequestedModeByDisplay.keyAt(i);
602                 final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
603                 pw.println("    " + id + " -> " + mode);
604             }
605         }
606     }
607 
608     private final class DisplayObserver implements DisplayManager.DisplayListener {
609         // Note that we can never call into DisplayManager or any of the non-POD classes it
610         // returns, while holding mLock since it may call into DMS, which might be simultaneously
611         // calling into us already holding its own lock.
612         private final Context mContext;
613         private final Handler mHandler;
614 
DisplayObserver(Context context, Handler handler)615         DisplayObserver(Context context, Handler handler) {
616             mContext = context;
617             mHandler = handler;
618         }
619 
observe()620         public void observe() {
621             DisplayManager dm = mContext.getSystemService(DisplayManager.class);
622             dm.registerDisplayListener(this, mHandler);
623 
624             // Populate existing displays
625             SparseArray<Display.Mode[]> modes = new SparseArray<>();
626             SparseArray<Display.Mode> defaultModes = new SparseArray<>();
627             DisplayInfo info = new DisplayInfo();
628             Display[] displays = dm.getDisplays();
629             for (Display d : displays) {
630                 final int displayId = d.getDisplayId();
631                 d.getDisplayInfo(info);
632                 modes.put(displayId, info.supportedModes);
633                 defaultModes.put(displayId, info.getDefaultMode());
634             }
635             synchronized (mLock) {
636                 final int size = modes.size();
637                 for (int i = 0; i < size; i++) {
638                     mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i));
639                     mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i));
640                 }
641             }
642         }
643 
644         @Override
onDisplayAdded(int displayId)645         public void onDisplayAdded(int displayId) {
646             updateDisplayModes(displayId);
647         }
648 
649         @Override
onDisplayRemoved(int displayId)650         public void onDisplayRemoved(int displayId) {
651             synchronized (mLock) {
652                 mSupportedModesByDisplay.remove(displayId);
653                 mDefaultModeByDisplay.remove(displayId);
654             }
655         }
656 
657         @Override
onDisplayChanged(int displayId)658         public void onDisplayChanged(int displayId) {
659             updateDisplayModes(displayId);
660         }
661 
updateDisplayModes(int displayId)662         private void updateDisplayModes(int displayId) {
663             Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
664             if (d == null) {
665                 // We can occasionally get a display added or changed event for a display that was
666                 // subsequently removed, which means this returns null. Check this case and bail
667                 // out early; if it gets re-attached we'll eventually get another call back for it.
668                 return;
669             }
670             DisplayInfo info = new DisplayInfo();
671             d.getDisplayInfo(info);
672             boolean changed = false;
673             synchronized (mLock) {
674                 if (!Arrays.equals(mSupportedModesByDisplay.get(displayId), info.supportedModes)) {
675                     mSupportedModesByDisplay.put(displayId, info.supportedModes);
676                     changed = true;
677                 }
678                 if (!Objects.equals(mDefaultModeByDisplay.get(displayId), info.getDefaultMode())) {
679                     changed = true;
680                     mDefaultModeByDisplay.put(displayId, info.getDefaultMode());
681                 }
682                 if (changed) {
683                     notifyAllowedModesChangedLocked();
684                 }
685             }
686         }
687     }
688 }
689