blob: 0940a2e4b3cdc796057c18aa09d9ee0512256e45 [file] [log] [blame]
Michael Wrighta3dab232019-02-22 16:54:21 +00001/*
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
17package com.android.server.display;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.database.ContentObserver;
24import android.hardware.display.DisplayManager;
25import android.net.Uri;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.util.Slog;
32import android.util.SparseArray;
33import android.view.Display;
34import android.view.DisplayInfo;
35
36import com.android.internal.R;
37
38import java.io.PrintWriter;
39import java.util.ArrayList;
40import java.util.Arrays;
41import 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 */
47public 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
Michael Wrighta3dab232019-02-22 16:54:21 +000057 // 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
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 */
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
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
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
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
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
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 */
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 */
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
321 private void updateVoteLocked(int priority, Vote vote) {
322 updateVoteLocked(GLOBAL_ID, priority, vote);
323 }
324
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
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
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 */
385 void onAllowedDisplayModesChanged();
386 }
387
388 private static final class DisplayModeDirectorHandler extends Handler {
389 DisplayModeDirectorHandler(Looper looper) {
390 super(looper, null, true /*async*/);
391 }
392
393 @Override
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
441 public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) {
442 return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate);
443 }
444
445 public static Vote forSize(int width, int height) {
446 return new Vote(width, height, 0f, Float.POSITIVE_INFINITY);
447 }
448
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
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
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
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
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
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
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
534 private void updateRefreshRateSettingLocked() {
535 float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
Adrian Salido41cc1862019-04-25 19:34:37 -0700536 Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
Michael Wrighta3dab232019-02-22 16:54:21 +0000537 Vote vote = Vote.forRefreshRates(0f, peakRefreshRate);
538 updateVoteLocked(Vote.PRIORITY_USER_SETTING, vote);
539 }
540
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
550 AppRequestObserver() {
551 mAppRequestedModeByDisplay = new SparseArray<>();
552 }
553
554 public void setAppRequestedMode(int displayId, int modeId) {
555 synchronized (mLock) {
556 setAppRequestedModeLocked(displayId, modeId);
557 }
558 }
559
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
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
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
615 DisplayObserver(Context context, Handler handler) {
616 mContext = context;
617 mHandler = handler;
618 }
619
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
645 public void onDisplayAdded(int displayId) {
646 updateDisplayModes(displayId);
647 }
648
649 @Override
650 public void onDisplayRemoved(int displayId) {
651 synchronized (mLock) {
652 mSupportedModesByDisplay.remove(displayId);
653 mDefaultModeByDisplay.remove(displayId);
654 }
655 }
656
657 @Override
658 public void onDisplayChanged(int displayId) {
659 updateDisplayModes(displayId);
660 }
661
662 private void updateDisplayModes(int displayId) {
663 Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
Michael Wright91c28382019-03-11 12:54:22 +0000664 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 }
Michael Wrighta3dab232019-02-22 16:54:21 +0000670 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}