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