blob: fee60d01affc390617af0c8714340272967fd511 [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;
Long Ling741bf5f2019-04-15 14:08:49 -0700410 public static final int PRIORITY_LOW_BRIGHTNESS = 3;
411 public static final int PRIORITY_LOW_POWER_MODE = 4;
Michael Wrighta3dab232019-02-22 16:54:21 +0000412
413 // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
414 // appropriate, as well as priorityToString.
415
416 public static final int MIN_PRIORITY = PRIORITY_USER_SETTING;
417 public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
418
419 /**
420 * A value signifying an invalid width or height in a vote.
421 */
422 public static final int INVALID_SIZE = -1;
423
424 /**
425 * The requested width of the display in pixels, or INVALID_SIZE;
426 */
427 public final int width;
428 /**
429 * The requested height of the display in pixels, or INVALID_SIZE;
430 */
431 public final int height;
432
433 /**
434 * The lowest desired refresh rate.
435 */
436 public final float minRefreshRate;
437 /**
438 * The highest desired refresh rate.
439 */
440 public final float maxRefreshRate;
441
442 public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) {
443 return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate);
444 }
445
446 public static Vote forSize(int width, int height) {
447 return new Vote(width, height, 0f, Float.POSITIVE_INFINITY);
448 }
449
450 private Vote(int width, int height,
451 float minRefreshRate, float maxRefreshRate) {
452 this.width = width;
453 this.height = height;
454 this.minRefreshRate = minRefreshRate;
455 this.maxRefreshRate = maxRefreshRate;
456 }
457
458 public static String priorityToString(int priority) {
459 switch (priority) {
460 case PRIORITY_USER_SETTING:
461 return "PRIORITY_USER_SETTING";
462 case PRIORITY_APP_REQUEST_REFRESH_RATE:
463 return "PRIORITY_APP_REQUEST_REFRESH_RATE";
464 case PRIORITY_APP_REQUEST_SIZE:
465 return "PRIORITY_APP_REQUEST_SIZE";
466 case PRIORITY_LOW_POWER_MODE:
467 return "PRIORITY_LOW_POWER_MODE";
468 default:
469 return Integer.toString(priority);
470 }
471 }
472
473 @Override
474 public String toString() {
475 return "Vote{"
476 + "width=" + width
477 + ", height=" + height
478 + ", minRefreshRate=" + minRefreshRate
479 + ", maxRefreshRate=" + maxRefreshRate
480 + "}";
481 }
482 }
483
484 private final class SettingsObserver extends ContentObserver {
485 private final Uri mRefreshRateSetting =
486 Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
487 private final Uri mLowPowerModeSetting =
488 Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
Long Ling741bf5f2019-04-15 14:08:49 -0700489 private final Uri mBrightnessSetting =
490 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
Michael Wrighta3dab232019-02-22 16:54:21 +0000491
492 private final Context mContext;
493 private final float mDefaultPeakRefreshRate;
Long Ling741bf5f2019-04-15 14:08:49 -0700494 private final int mBrightnessThreshold;
Michael Wrighta3dab232019-02-22 16:54:21 +0000495
496 SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
497 super(handler);
498 mContext = context;
499 mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
500 R.integer.config_defaultPeakRefreshRate);
Long Ling741bf5f2019-04-15 14:08:49 -0700501 mBrightnessThreshold = context.getResources().getInteger(
502 R.integer.config_brightnessThresholdOfPeakRefreshRate);
Michael Wrighta3dab232019-02-22 16:54:21 +0000503 }
504
505 public void observe() {
506 final ContentResolver cr = mContext.getContentResolver();
507 cr.registerContentObserver(mRefreshRateSetting, false /*notifyDescendants*/, this,
508 UserHandle.USER_SYSTEM);
509 cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
510 UserHandle.USER_SYSTEM);
Long Ling741bf5f2019-04-15 14:08:49 -0700511 if (mBrightnessThreshold >= 0) {
512 cr.registerContentObserver(mBrightnessSetting, false /*notifyDescendants*/, this,
513 UserHandle.USER_SYSTEM);
514 }
Michael Wrighta3dab232019-02-22 16:54:21 +0000515 synchronized (mLock) {
516 updateRefreshRateSettingLocked();
517 updateLowPowerModeSettingLocked();
Long Ling741bf5f2019-04-15 14:08:49 -0700518 updateBrightnessSettingLocked();
Michael Wrighta3dab232019-02-22 16:54:21 +0000519 }
520 }
521
522 @Override
523 public void onChange(boolean selfChange, Uri uri, int userId) {
524 synchronized (mLock) {
525 if (mRefreshRateSetting.equals(uri)) {
526 updateRefreshRateSettingLocked();
527 } else if (mLowPowerModeSetting.equals(uri)) {
528 updateLowPowerModeSettingLocked();
Long Ling741bf5f2019-04-15 14:08:49 -0700529 } else if (mBrightnessThreshold >=0 && mBrightnessSetting.equals(uri)) {
530 updateBrightnessSettingLocked();
Michael Wrighta3dab232019-02-22 16:54:21 +0000531 }
532 }
533 }
534
535 private void updateLowPowerModeSettingLocked() {
536 boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
537 Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
538 final Vote vote;
539 if (inLowPowerMode) {
540 vote = Vote.forRefreshRates(0f, 60f);
541 } else {
542 vote = null;
543 }
544 updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
545 }
546
547 private void updateRefreshRateSettingLocked() {
548 float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
Adrian Salido41cc1862019-04-25 19:34:37 -0700549 Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
Michael Wrighta3dab232019-02-22 16:54:21 +0000550 Vote vote = Vote.forRefreshRates(0f, peakRefreshRate);
551 updateVoteLocked(Vote.PRIORITY_USER_SETTING, vote);
552 }
553
Long Ling741bf5f2019-04-15 14:08:49 -0700554 private void updateBrightnessSettingLocked() {
555 int brightness = Settings.System.getInt(mContext.getContentResolver(),
556 Settings.System.SCREEN_BRIGHTNESS, -1);
557
558 if (brightness < 0) {
559 return;
560 }
561
562 final Vote vote;
563 if (brightness <= mBrightnessThreshold) {
564 vote = Vote.forRefreshRates(0f, 60f);
565 } else {
566 vote = null;
567 }
568 updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
569 }
570
Michael Wrighta3dab232019-02-22 16:54:21 +0000571 public void dumpLocked(PrintWriter pw) {
572 pw.println(" SettingsObserver");
573 pw.println(" mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
574 }
575 }
576
577 final class AppRequestObserver {
578 private SparseArray<Display.Mode> mAppRequestedModeByDisplay;
579
580 AppRequestObserver() {
581 mAppRequestedModeByDisplay = new SparseArray<>();
582 }
583
584 public void setAppRequestedMode(int displayId, int modeId) {
585 synchronized (mLock) {
586 setAppRequestedModeLocked(displayId, modeId);
587 }
588 }
589
590 private void setAppRequestedModeLocked(int displayId, int modeId) {
591 final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
592 if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
593 return;
594 }
595
596 final Vote refreshRateVote;
597 final Vote sizeVote;
598 if (requestedMode != null) {
599 mAppRequestedModeByDisplay.put(displayId, requestedMode);
600 float refreshRate = requestedMode.getRefreshRate();
601 refreshRateVote = Vote.forRefreshRates(refreshRate, refreshRate);
602 sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
603 requestedMode.getPhysicalHeight());
604 } else {
605 mAppRequestedModeByDisplay.remove(displayId);
606 refreshRateVote = null;
607 sizeVote = null;
608 }
609 updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
610 updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
611 return;
612 }
613
614 private Display.Mode findModeByIdLocked(int displayId, int modeId) {
615 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
616 if (modes == null) {
617 return null;
618 }
619 for (Display.Mode mode : modes) {
620 if (mode.getModeId() == modeId) {
621 return mode;
622 }
623 }
624 return null;
625 }
626
627 public void dumpLocked(PrintWriter pw) {
628 pw.println(" AppRequestObserver");
629 pw.println(" mAppRequestedModeByDisplay:");
630 for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
631 final int id = mAppRequestedModeByDisplay.keyAt(i);
632 final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
633 pw.println(" " + id + " -> " + mode);
634 }
635 }
636 }
637
638 private final class DisplayObserver implements DisplayManager.DisplayListener {
639 // Note that we can never call into DisplayManager or any of the non-POD classes it
640 // returns, while holding mLock since it may call into DMS, which might be simultaneously
641 // calling into us already holding its own lock.
642 private final Context mContext;
643 private final Handler mHandler;
644
645 DisplayObserver(Context context, Handler handler) {
646 mContext = context;
647 mHandler = handler;
648 }
649
650 public void observe() {
651 DisplayManager dm = mContext.getSystemService(DisplayManager.class);
652 dm.registerDisplayListener(this, mHandler);
653
654 // Populate existing displays
655 SparseArray<Display.Mode[]> modes = new SparseArray<>();
656 SparseArray<Display.Mode> defaultModes = new SparseArray<>();
657 DisplayInfo info = new DisplayInfo();
658 Display[] displays = dm.getDisplays();
659 for (Display d : displays) {
660 final int displayId = d.getDisplayId();
661 d.getDisplayInfo(info);
662 modes.put(displayId, info.supportedModes);
663 defaultModes.put(displayId, info.getDefaultMode());
664 }
665 synchronized (mLock) {
666 final int size = modes.size();
667 for (int i = 0; i < size; i++) {
668 mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i));
669 mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i));
670 }
671 }
672 }
673
674 @Override
675 public void onDisplayAdded(int displayId) {
676 updateDisplayModes(displayId);
677 }
678
679 @Override
680 public void onDisplayRemoved(int displayId) {
681 synchronized (mLock) {
682 mSupportedModesByDisplay.remove(displayId);
683 mDefaultModeByDisplay.remove(displayId);
684 }
685 }
686
687 @Override
688 public void onDisplayChanged(int displayId) {
689 updateDisplayModes(displayId);
690 }
691
692 private void updateDisplayModes(int displayId) {
693 Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
Michael Wright91c28382019-03-11 12:54:22 +0000694 if (d == null) {
695 // We can occasionally get a display added or changed event for a display that was
696 // subsequently removed, which means this returns null. Check this case and bail
697 // out early; if it gets re-attached we'll eventually get another call back for it.
698 return;
699 }
Michael Wrighta3dab232019-02-22 16:54:21 +0000700 DisplayInfo info = new DisplayInfo();
701 d.getDisplayInfo(info);
702 boolean changed = false;
703 synchronized (mLock) {
704 if (!Arrays.equals(mSupportedModesByDisplay.get(displayId), info.supportedModes)) {
705 mSupportedModesByDisplay.put(displayId, info.supportedModes);
706 changed = true;
707 }
708 if (!Objects.equals(mDefaultModeByDisplay.get(displayId), info.getDefaultMode())) {
709 changed = true;
710 mDefaultModeByDisplay.put(displayId, info.getDefaultMode());
711 }
712 if (changed) {
713 notifyAllowedModesChangedLocked();
714 }
715 }
716 }
717 }
718}