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