blob: 764863616db18390dbdaf690bcbfb475930da085 [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;
Long Lingbc841b02019-07-03 16:43:15 -070021import android.content.BroadcastReceiver;
Michael Wrighta3dab232019-02-22 16:54:21 +000022import android.content.ContentResolver;
23import android.content.Context;
Long Lingbc841b02019-07-03 16:43:15 -070024import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.res.Resources;
Michael Wrighta3dab232019-02-22 16:54:21 +000027import android.database.ContentObserver;
28import android.hardware.display.DisplayManager;
Long Lingbc841b02019-07-03 16:43:15 -070029import android.hardware.Sensor;
30import android.hardware.SensorEvent;
31import android.hardware.SensorEventListener;
32import android.hardware.SensorManager;
33
Michael Wrighta3dab232019-02-22 16:54:21 +000034import android.net.Uri;
35import android.os.Handler;
36import android.os.Looper;
37import android.os.Message;
38import android.os.UserHandle;
Long Lingbc841b02019-07-03 16:43:15 -070039import android.os.PowerManager;
40import android.os.SystemClock;
Michael Wrighta3dab232019-02-22 16:54:21 +000041import android.provider.Settings;
Long Lingbc841b02019-07-03 16:43:15 -070042import android.text.TextUtils;
Michael Wrighta3dab232019-02-22 16:54:21 +000043import android.util.Slog;
44import android.util.SparseArray;
45import android.view.Display;
46import android.view.DisplayInfo;
47
48import com.android.internal.R;
Long Lingbc841b02019-07-03 16:43:15 -070049import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
50import com.android.server.display.whitebalance.AmbientFilter;
Michael Wrighta3dab232019-02-22 16:54:21 +000051
52import java.io.PrintWriter;
53import java.util.ArrayList;
54import java.util.Arrays;
Long Lingbc841b02019-07-03 16:43:15 -070055import java.util.List;
Michael Wrighta3dab232019-02-22 16:54:21 +000056import java.util.Objects;
57
58/**
59 * The DisplayModeDirector is responsible for determining what modes are allowed to be
60 * automatically picked by the system based on system-wide and display-specific configuration.
61 */
62public class DisplayModeDirector {
63 private static final String TAG = "DisplayModeDirector";
64 private static final boolean DEBUG = false;
65
66 private static final int MSG_ALLOWED_MODES_CHANGED = 1;
67
68 // Special ID used to indicate that given vote is to be applied globally, rather than to a
69 // specific display.
70 private static final int GLOBAL_ID = -1;
71
Michael Wrighta3dab232019-02-22 16:54:21 +000072 // The tolerance within which we consider something approximately equals.
73 private static final float EPSILON = 0.001f;
74
75 private final Object mLock = new Object();
76 private final Context mContext;
77
78 private final DisplayModeDirectorHandler mHandler;
79
80 // A map from the display ID to the collection of votes and their priority. The latter takes
81 // the form of another map from the priority to the vote itself so that each priority is
82 // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
83 private final SparseArray<SparseArray<Vote>> mVotesByDisplay;
84 // A map from the display ID to the supported modes on that display.
85 private final SparseArray<Display.Mode[]> mSupportedModesByDisplay;
86 // A map from the display ID to the default mode of that display.
87 private final SparseArray<Display.Mode> mDefaultModeByDisplay;
88
89 private final AppRequestObserver mAppRequestObserver;
90 private final SettingsObserver mSettingsObserver;
91 private final DisplayObserver mDisplayObserver;
Long Lingbc841b02019-07-03 16:43:15 -070092 private final BrightnessObserver mBrightnessObserver;
Michael Wrighta3dab232019-02-22 16:54:21 +000093
94 private Listener mListener;
95
96 public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
97 mContext = context;
98 mHandler = new DisplayModeDirectorHandler(handler.getLooper());
99 mVotesByDisplay = new SparseArray<>();
100 mSupportedModesByDisplay = new SparseArray<>();
101 mDefaultModeByDisplay = new SparseArray<>();
102 mAppRequestObserver = new AppRequestObserver();
103 mSettingsObserver = new SettingsObserver(context, handler);
104 mDisplayObserver = new DisplayObserver(context, handler);
Long Lingbc841b02019-07-03 16:43:15 -0700105 mBrightnessObserver = new BrightnessObserver(context, handler);
106
Michael Wrighta3dab232019-02-22 16:54:21 +0000107 }
108
109 /**
110 * Tells the DisplayModeDirector to update allowed votes and begin observing relevant system
111 * state.
112 *
113 * This has to be deferred because the object may be constructed before the rest of the system
114 * is ready.
115 */
Long Lingbc841b02019-07-03 16:43:15 -0700116 public void start(SensorManager sensorManager) {
Michael Wrighta3dab232019-02-22 16:54:21 +0000117 mSettingsObserver.observe();
118 mDisplayObserver.observe();
119 mSettingsObserver.observe();
Long Lingbc841b02019-07-03 16:43:15 -0700120 mBrightnessObserver.observe(sensorManager);
Michael Wrighta3dab232019-02-22 16:54:21 +0000121 synchronized (mLock) {
122 // We may have a listener already registered before the call to start, so go ahead and
123 // notify them to pick up our newly initialized state.
124 notifyAllowedModesChangedLocked();
125 }
Long Lingbc841b02019-07-03 16:43:15 -0700126
Michael Wrighta3dab232019-02-22 16:54:21 +0000127 }
128
129 /**
130 * Calculates the modes the system is allowed to freely switch between based on global and
131 * display-specific constraints.
132 *
133 * @param displayId The display to query for.
134 * @return The IDs of the modes the system is allowed to freely switch between.
135 */
136 @NonNull
137 public int[] getAllowedModes(int displayId) {
138 synchronized (mLock) {
139 SparseArray<Vote> votes = getVotesLocked(displayId);
140 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
141 Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
142 if (modes == null || defaultMode == null) {
143 Slog.e(TAG, "Asked about unknown display, returning empty allowed set! (id="
144 + displayId + ")");
145 return new int[0];
146 }
147 return getAllowedModesLocked(votes, modes, defaultMode);
148 }
149 }
150
151 @NonNull
152 private SparseArray<Vote> getVotesLocked(int displayId) {
153 SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
154 final SparseArray<Vote> votes;
155 if (displayVotes != null) {
156 votes = displayVotes.clone();
157 } else {
158 votes = new SparseArray<>();
159 }
160
161 SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
162 if (globalVotes != null) {
163 for (int i = 0; i < globalVotes.size(); i++) {
164 int priority = globalVotes.keyAt(i);
165 if (votes.indexOfKey(priority) < 0) {
166 votes.put(priority, globalVotes.valueAt(i));
167 }
168 }
169 }
170 return votes;
171 }
172
173 @NonNull
174 private int[] getAllowedModesLocked(@NonNull SparseArray<Vote> votes,
175 @NonNull Display.Mode[] modes, @NonNull Display.Mode defaultMode) {
176 int lowestConsideredPriority = Vote.MIN_PRIORITY;
177 while (lowestConsideredPriority <= Vote.MAX_PRIORITY) {
178 float minRefreshRate = 0f;
179 float maxRefreshRate = Float.POSITIVE_INFINITY;
180 int height = Vote.INVALID_SIZE;
181 int width = Vote.INVALID_SIZE;
182
183 for (int priority = Vote.MAX_PRIORITY;
184 priority >= lowestConsideredPriority;
185 priority--) {
186 Vote vote = votes.get(priority);
187 if (vote == null) {
188 continue;
189 }
190 // For refresh rates, just use the tightest bounds of all the votes
191 minRefreshRate = Math.max(minRefreshRate, vote.minRefreshRate);
192 maxRefreshRate = Math.min(maxRefreshRate, vote.maxRefreshRate);
193 // For display size, use only the first vote we come across (i.e. the highest
194 // priority vote that includes the width / height).
195 if (height == Vote.INVALID_SIZE && width == Vote.INVALID_SIZE
196 && vote.height > 0 && vote.width > 0) {
197 width = vote.width;
198 height = vote.height;
199 }
200 }
201
202 // If we don't have anything specifying the width / height of the display, just use the
203 // default width and height. We don't want these switching out from underneath us since
204 // it's a pretty disruptive behavior.
205 if (height == Vote.INVALID_SIZE || width == Vote.INVALID_SIZE) {
206 width = defaultMode.getPhysicalWidth();
207 height = defaultMode.getPhysicalHeight();
208 }
209
210 int[] availableModes =
211 filterModes(modes, width, height, minRefreshRate, maxRefreshRate);
212 if (availableModes.length > 0) {
213 if (DEBUG) {
214 Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
215 + " with lowest priority considered "
216 + Vote.priorityToString(lowestConsideredPriority)
217 + " and constraints: "
218 + "width=" + width
219 + ", height=" + height
220 + ", minRefreshRate=" + minRefreshRate
221 + ", maxRefreshRate=" + maxRefreshRate);
222 }
223 return availableModes;
224 }
225
226 if (DEBUG) {
227 Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
228 + Vote.priorityToString(lowestConsideredPriority)
229 + " and with the following constraints: "
230 + "width=" + width
231 + ", height=" + height
232 + ", minRefreshRate=" + minRefreshRate
233 + ", maxRefreshRate=" + maxRefreshRate);
234 }
235 // If we haven't found anything with the current set of votes, drop the current lowest
236 // priority vote.
237 lowestConsideredPriority++;
238 }
239
240 // If we still haven't found anything that matches our current set of votes, just fall back
241 // to the default mode.
242 return new int[] { defaultMode.getModeId() };
243 }
244
245 private int[] filterModes(Display.Mode[] supportedModes,
246 int width, int height, float minRefreshRate, float maxRefreshRate) {
247 ArrayList<Display.Mode> availableModes = new ArrayList<>();
248 for (Display.Mode mode : supportedModes) {
249 if (mode.getPhysicalWidth() != width || mode.getPhysicalHeight() != height) {
250 if (DEBUG) {
251 Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
252 + ": desiredWidth=" + width
253 + ": desiredHeight=" + height
254 + ": actualWidth=" + mode.getPhysicalWidth()
255 + ": actualHeight=" + mode.getPhysicalHeight());
256 }
257 continue;
258 }
259 final float refreshRate = mode.getRefreshRate();
260 // Some refresh rates are calculated based on frame timings, so they aren't *exactly*
261 // equal to expected refresh rate. Given that, we apply a bit of tolerance to this
262 // comparison.
263 if (refreshRate < (minRefreshRate - EPSILON)
264 || refreshRate > (maxRefreshRate + EPSILON)) {
265 if (DEBUG) {
266 Slog.w(TAG, "Discarding mode " + mode.getModeId()
267 + ", outside refresh rate bounds"
268 + ": minRefreshRate=" + minRefreshRate
269 + ", maxRefreshRate=" + maxRefreshRate
270 + ", modeRefreshRate=" + refreshRate);
271 }
272 continue;
273 }
274 availableModes.add(mode);
275 }
276 final int size = availableModes.size();
277 int[] availableModeIds = new int[size];
278 for (int i = 0; i < size; i++) {
279 availableModeIds[i] = availableModes.get(i).getModeId();
280 }
281 return availableModeIds;
282 }
283
284 /**
285 * Gets the observer responsible for application display mode requests.
286 */
287 @NonNull
288 public AppRequestObserver getAppRequestObserver() {
289 // We don't need to lock here because mAppRequestObserver is a final field, which is
290 // guaranteed to be visible on all threads after construction.
291 return mAppRequestObserver;
292 }
293
294 /**
295 * Sets the listener for changes to allowed display modes.
296 */
297 public void setListener(@Nullable Listener listener) {
298 synchronized (mLock) {
299 mListener = listener;
300 }
301 }
302
303 /**
304 * Print the object's state and debug information into the given stream.
305 *
306 * @param pw The stream to dump information to.
307 */
308 public void dump(PrintWriter pw) {
309 pw.println("DisplayModeDirector");
310 synchronized (mLock) {
311 pw.println(" mSupportedModesByDisplay:");
312 for (int i = 0; i < mSupportedModesByDisplay.size(); i++) {
313 final int id = mSupportedModesByDisplay.keyAt(i);
314 final Display.Mode[] modes = mSupportedModesByDisplay.valueAt(i);
315 pw.println(" " + id + " -> " + Arrays.toString(modes));
316 }
317 pw.println(" mDefaultModeByDisplay:");
318 for (int i = 0; i < mDefaultModeByDisplay.size(); i++) {
319 final int id = mDefaultModeByDisplay.keyAt(i);
320 final Display.Mode mode = mDefaultModeByDisplay.valueAt(i);
321 pw.println(" " + id + " -> " + mode);
322 }
323 pw.println(" mVotesByDisplay:");
324 for (int i = 0; i < mVotesByDisplay.size(); i++) {
325 pw.println(" " + mVotesByDisplay.keyAt(i) + ":");
326 SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
327 for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
328 Vote vote = votes.get(p);
329 if (vote == null) {
330 continue;
331 }
332 pw.println(" " + Vote.priorityToString(p) + " -> " + vote);
333 }
334 }
335 mSettingsObserver.dumpLocked(pw);
336 mAppRequestObserver.dumpLocked(pw);
Long Lingbc841b02019-07-03 16:43:15 -0700337 mBrightnessObserver.dumpLocked(pw);
Michael Wrighta3dab232019-02-22 16:54:21 +0000338 }
339 }
340
341 private void updateVoteLocked(int priority, Vote vote) {
342 updateVoteLocked(GLOBAL_ID, priority, vote);
343 }
344
345 private void updateVoteLocked(int displayId, int priority, Vote vote) {
346 if (DEBUG) {
347 Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
348 + ", priority=" + Vote.priorityToString(priority)
349 + ", vote=" + vote + ")");
350 }
351 if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
352 Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
353 + " priority=" + Vote.priorityToString(priority)
354 + ", vote=" + vote, new Throwable());
355 return;
356 }
357 final SparseArray<Vote> votes = getOrCreateVotesByDisplay(displayId);
358
359 Vote currentVote = votes.get(priority);
360 if (vote != null) {
361 votes.put(priority, vote);
362 } else {
363 votes.remove(priority);
364 }
365
366 if (votes.size() == 0) {
367 if (DEBUG) {
368 Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
369 }
370 mVotesByDisplay.remove(displayId);
371 }
372
373 notifyAllowedModesChangedLocked();
374 }
375
376 private void notifyAllowedModesChangedLocked() {
377 if (mListener != null && !mHandler.hasMessages(MSG_ALLOWED_MODES_CHANGED)) {
378 // We need to post this to a handler to avoid calling out while holding the lock
379 // since we know there are things that both listen for changes as well as provide
380 // information. If we did call out while holding the lock, then there's no guaranteed
381 // lock order and we run the real of risk deadlock.
382 Message msg = mHandler.obtainMessage(MSG_ALLOWED_MODES_CHANGED, mListener);
383 msg.sendToTarget();
384 }
385 }
386
387 private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
388 int index = mVotesByDisplay.indexOfKey(displayId);
389 if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
390 return mVotesByDisplay.get(displayId);
391 } else {
392 SparseArray<Vote> votes = new SparseArray<>();
393 mVotesByDisplay.put(displayId, votes);
394 return votes;
395 }
396 }
397
398 /**
399 * Listens for changes to display mode coordination.
400 */
401 public interface Listener {
402 /**
403 * Called when the allowed display modes may have changed.
404 */
405 void onAllowedDisplayModesChanged();
406 }
407
408 private static final class DisplayModeDirectorHandler extends Handler {
409 DisplayModeDirectorHandler(Looper looper) {
410 super(looper, null, true /*async*/);
411 }
412
413 @Override
414 public void handleMessage(Message msg) {
415 switch (msg.what) {
416 case MSG_ALLOWED_MODES_CHANGED:
417 Listener listener = (Listener) msg.obj;
418 listener.onAllowedDisplayModesChanged();
419 break;
420 }
421 }
422 }
423
424 private static final class Vote {
Michael Wrighta3dab232019-02-22 16:54:21 +0000425 // We split the app request into two priorities in case we can satisfy one desire without
426 // the other.
Ady Abrahamce743b72019-06-11 19:16:12 -0700427 public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 0;
428 public static final int PRIORITY_APP_REQUEST_SIZE = 1;
429 public static final int PRIORITY_USER_SETTING_REFRESH_RATE = 2;
Long Ling741bf5f2019-04-15 14:08:49 -0700430 public static final int PRIORITY_LOW_BRIGHTNESS = 3;
431 public static final int PRIORITY_LOW_POWER_MODE = 4;
Michael Wrighta3dab232019-02-22 16:54:21 +0000432
433 // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
434 // appropriate, as well as priorityToString.
435
Ady Abrahamce743b72019-06-11 19:16:12 -0700436 public static final int MIN_PRIORITY = PRIORITY_APP_REQUEST_REFRESH_RATE;
Michael Wrighta3dab232019-02-22 16:54:21 +0000437 public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
438
439 /**
440 * A value signifying an invalid width or height in a vote.
441 */
442 public static final int INVALID_SIZE = -1;
443
444 /**
445 * The requested width of the display in pixels, or INVALID_SIZE;
446 */
447 public final int width;
448 /**
449 * The requested height of the display in pixels, or INVALID_SIZE;
450 */
451 public final int height;
452
453 /**
454 * The lowest desired refresh rate.
455 */
456 public final float minRefreshRate;
457 /**
458 * The highest desired refresh rate.
459 */
460 public final float maxRefreshRate;
461
462 public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) {
463 return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate);
464 }
465
466 public static Vote forSize(int width, int height) {
467 return new Vote(width, height, 0f, Float.POSITIVE_INFINITY);
468 }
469
470 private Vote(int width, int height,
471 float minRefreshRate, float maxRefreshRate) {
472 this.width = width;
473 this.height = height;
474 this.minRefreshRate = minRefreshRate;
475 this.maxRefreshRate = maxRefreshRate;
476 }
477
478 public static String priorityToString(int priority) {
479 switch (priority) {
Michael Wrighta3dab232019-02-22 16:54:21 +0000480 case PRIORITY_APP_REQUEST_REFRESH_RATE:
481 return "PRIORITY_APP_REQUEST_REFRESH_RATE";
482 case PRIORITY_APP_REQUEST_SIZE:
483 return "PRIORITY_APP_REQUEST_SIZE";
Ady Abrahamce743b72019-06-11 19:16:12 -0700484 case PRIORITY_USER_SETTING_REFRESH_RATE:
485 return "PRIORITY_USER_SETTING_REFRESH_RATE";
Michael Wrighta3dab232019-02-22 16:54:21 +0000486 case PRIORITY_LOW_POWER_MODE:
487 return "PRIORITY_LOW_POWER_MODE";
488 default:
489 return Integer.toString(priority);
490 }
491 }
492
493 @Override
494 public String toString() {
495 return "Vote{"
496 + "width=" + width
497 + ", height=" + height
498 + ", minRefreshRate=" + minRefreshRate
499 + ", maxRefreshRate=" + maxRefreshRate
500 + "}";
501 }
502 }
503
504 private final class SettingsObserver extends ContentObserver {
505 private final Uri mRefreshRateSetting =
506 Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
507 private final Uri mLowPowerModeSetting =
508 Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
509
510 private final Context mContext;
511 private final float mDefaultPeakRefreshRate;
512
513 SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
514 super(handler);
515 mContext = context;
516 mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
517 R.integer.config_defaultPeakRefreshRate);
518 }
519
520 public void observe() {
521 final ContentResolver cr = mContext.getContentResolver();
522 cr.registerContentObserver(mRefreshRateSetting, false /*notifyDescendants*/, this,
523 UserHandle.USER_SYSTEM);
524 cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
525 UserHandle.USER_SYSTEM);
526 synchronized (mLock) {
527 updateRefreshRateSettingLocked();
528 updateLowPowerModeSettingLocked();
529 }
530 }
531
532 @Override
533 public void onChange(boolean selfChange, Uri uri, int userId) {
534 synchronized (mLock) {
535 if (mRefreshRateSetting.equals(uri)) {
536 updateRefreshRateSettingLocked();
537 } else if (mLowPowerModeSetting.equals(uri)) {
538 updateLowPowerModeSettingLocked();
539 }
540 }
541 }
542
543 private void updateLowPowerModeSettingLocked() {
544 boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
545 Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
546 final Vote vote;
547 if (inLowPowerMode) {
548 vote = Vote.forRefreshRates(0f, 60f);
549 } else {
550 vote = null;
551 }
552 updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
Long Lingbc841b02019-07-03 16:43:15 -0700553 mBrightnessObserver.onLowPowerModeEnabled(inLowPowerMode);
Michael Wrighta3dab232019-02-22 16:54:21 +0000554 }
555
556 private void updateRefreshRateSettingLocked() {
557 float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
Adrian Salido41cc1862019-04-25 19:34:37 -0700558 Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
Michael Wrighta3dab232019-02-22 16:54:21 +0000559 Vote vote = Vote.forRefreshRates(0f, peakRefreshRate);
Ady Abrahamce743b72019-06-11 19:16:12 -0700560 updateVoteLocked(Vote.PRIORITY_USER_SETTING_REFRESH_RATE, vote);
Long Lingbc841b02019-07-03 16:43:15 -0700561 mBrightnessObserver.onPeakRefreshRateEnabled(peakRefreshRate > 60f);
Long Ling741bf5f2019-04-15 14:08:49 -0700562 }
563
Michael Wrighta3dab232019-02-22 16:54:21 +0000564 public void dumpLocked(PrintWriter pw) {
565 pw.println(" SettingsObserver");
566 pw.println(" mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
567 }
568 }
569
570 final class AppRequestObserver {
571 private SparseArray<Display.Mode> mAppRequestedModeByDisplay;
572
573 AppRequestObserver() {
574 mAppRequestedModeByDisplay = new SparseArray<>();
575 }
576
577 public void setAppRequestedMode(int displayId, int modeId) {
578 synchronized (mLock) {
579 setAppRequestedModeLocked(displayId, modeId);
580 }
581 }
582
583 private void setAppRequestedModeLocked(int displayId, int modeId) {
584 final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
585 if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
586 return;
587 }
588
589 final Vote refreshRateVote;
590 final Vote sizeVote;
591 if (requestedMode != null) {
592 mAppRequestedModeByDisplay.put(displayId, requestedMode);
593 float refreshRate = requestedMode.getRefreshRate();
594 refreshRateVote = Vote.forRefreshRates(refreshRate, refreshRate);
595 sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
596 requestedMode.getPhysicalHeight());
597 } else {
598 mAppRequestedModeByDisplay.remove(displayId);
599 refreshRateVote = null;
600 sizeVote = null;
601 }
602 updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
603 updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
604 return;
605 }
606
607 private Display.Mode findModeByIdLocked(int displayId, int modeId) {
608 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
609 if (modes == null) {
610 return null;
611 }
612 for (Display.Mode mode : modes) {
613 if (mode.getModeId() == modeId) {
614 return mode;
615 }
616 }
617 return null;
618 }
619
620 public void dumpLocked(PrintWriter pw) {
621 pw.println(" AppRequestObserver");
622 pw.println(" mAppRequestedModeByDisplay:");
623 for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
624 final int id = mAppRequestedModeByDisplay.keyAt(i);
625 final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
626 pw.println(" " + id + " -> " + mode);
627 }
628 }
629 }
630
631 private final class DisplayObserver implements DisplayManager.DisplayListener {
632 // Note that we can never call into DisplayManager or any of the non-POD classes it
633 // returns, while holding mLock since it may call into DMS, which might be simultaneously
634 // calling into us already holding its own lock.
635 private final Context mContext;
636 private final Handler mHandler;
637
638 DisplayObserver(Context context, Handler handler) {
639 mContext = context;
640 mHandler = handler;
641 }
642
643 public void observe() {
644 DisplayManager dm = mContext.getSystemService(DisplayManager.class);
645 dm.registerDisplayListener(this, mHandler);
646
647 // Populate existing displays
648 SparseArray<Display.Mode[]> modes = new SparseArray<>();
649 SparseArray<Display.Mode> defaultModes = new SparseArray<>();
650 DisplayInfo info = new DisplayInfo();
651 Display[] displays = dm.getDisplays();
652 for (Display d : displays) {
653 final int displayId = d.getDisplayId();
654 d.getDisplayInfo(info);
655 modes.put(displayId, info.supportedModes);
656 defaultModes.put(displayId, info.getDefaultMode());
657 }
658 synchronized (mLock) {
659 final int size = modes.size();
660 for (int i = 0; i < size; i++) {
661 mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i));
662 mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i));
663 }
664 }
665 }
666
667 @Override
668 public void onDisplayAdded(int displayId) {
669 updateDisplayModes(displayId);
670 }
671
672 @Override
673 public void onDisplayRemoved(int displayId) {
674 synchronized (mLock) {
675 mSupportedModesByDisplay.remove(displayId);
676 mDefaultModeByDisplay.remove(displayId);
677 }
678 }
679
680 @Override
681 public void onDisplayChanged(int displayId) {
682 updateDisplayModes(displayId);
Long Lingf3afe7d2019-08-05 12:19:42 -0700683 mBrightnessObserver.onDisplayChanged(displayId);
Michael Wrighta3dab232019-02-22 16:54:21 +0000684 }
685
686 private void updateDisplayModes(int displayId) {
687 Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
Michael Wright91c28382019-03-11 12:54:22 +0000688 if (d == null) {
689 // We can occasionally get a display added or changed event for a display that was
690 // subsequently removed, which means this returns null. Check this case and bail
691 // out early; if it gets re-attached we'll eventually get another call back for it.
692 return;
693 }
Michael Wrighta3dab232019-02-22 16:54:21 +0000694 DisplayInfo info = new DisplayInfo();
695 d.getDisplayInfo(info);
696 boolean changed = false;
697 synchronized (mLock) {
698 if (!Arrays.equals(mSupportedModesByDisplay.get(displayId), info.supportedModes)) {
699 mSupportedModesByDisplay.put(displayId, info.supportedModes);
700 changed = true;
701 }
702 if (!Objects.equals(mDefaultModeByDisplay.get(displayId), info.getDefaultMode())) {
703 changed = true;
704 mDefaultModeByDisplay.put(displayId, info.getDefaultMode());
705 }
706 if (changed) {
707 notifyAllowedModesChangedLocked();
708 }
709 }
710 }
711 }
Long Lingbc841b02019-07-03 16:43:15 -0700712
713 /**
714 * This class manages brightness threshold for switching between 60 hz and higher refresh rate.
715 * See more information at the definition of
716 * {@link R.array#config_brightnessThresholdsOfPeakRefreshRate} and
717 * {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
718 */
719 private class BrightnessObserver extends ContentObserver {
720 private final Uri mDisplayBrightnessSetting =
721 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
722
723 private final static int LIGHT_SENSOR_RATE_MS = 250;
724 private final int[] mDisplayBrightnessThresholds;
725 private final int[] mAmbientBrightnessThresholds;
726 // valid threshold if any item from the array >= 0
727 private boolean mShouldObserveDisplayChange;
728 private boolean mShouldObserveAmbientChange;
729
730 private SensorManager mSensorManager;
731 private Sensor mLightSensor;
Long Lingc1ee0422019-07-26 14:23:53 -0700732 private LightSensorEventListener mLightSensorListener = new LightSensorEventListener();
Long Lingbc841b02019-07-03 16:43:15 -0700733 // Take it as low brightness before valid sensor data comes
734 private float mAmbientLux = -1.0f;
735 private AmbientFilter mAmbientFilter;
736
737 private final Context mContext;
Long Lingbc841b02019-07-03 16:43:15 -0700738 // Enable light sensor only when screen is on, peak refresh rate enabled and low power mode
739 // off. After initialization, these states will be updated from the same handler thread.
740 private boolean mScreenOn = false;
741 private boolean mPeakRefreshRateEnabled = false;
742 private boolean mLowPowerModeEnabled = false;
743
744 BrightnessObserver(Context context, Handler handler) {
745 super(handler);
746 mContext = context;
747 mDisplayBrightnessThresholds = context.getResources().getIntArray(
748 R.array.config_brightnessThresholdsOfPeakRefreshRate);
749 mAmbientBrightnessThresholds = context.getResources().getIntArray(
750 R.array.config_ambientThresholdsOfPeakRefreshRate);
751 if (mDisplayBrightnessThresholds.length != mAmbientBrightnessThresholds.length) {
752 throw new RuntimeException("display brightness threshold array and ambient "
753 + "brightness threshold array have different length");
754 }
755
756 mShouldObserveDisplayChange = checkShouldObserve(mDisplayBrightnessThresholds);
757 mShouldObserveAmbientChange = checkShouldObserve(mAmbientBrightnessThresholds);
758 }
759
760 public void observe(SensorManager sensorManager) {
761 if (mShouldObserveDisplayChange) {
762 final ContentResolver cr = mContext.getContentResolver();
763 cr.registerContentObserver(mDisplayBrightnessSetting,
764 false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM);
765 }
766
767 if (mShouldObserveAmbientChange) {
768 Resources resources = mContext.getResources();
769 String lightSensorType = resources.getString(
770 com.android.internal.R.string.config_displayLightSensorType);
771
772 Sensor lightSensor = null;
773 if (!TextUtils.isEmpty(lightSensorType)) {
774 List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
775 for (int i = 0; i < sensors.size(); i++) {
776 Sensor sensor = sensors.get(i);
777 if (lightSensorType.equals(sensor.getStringType())) {
778 lightSensor = sensor;
779 break;
780 }
781 }
782 }
783
784 if (lightSensor == null) {
785 lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
786 }
787
788 if (lightSensor != null) {
789 final Resources res = mContext.getResources();
790
791 mAmbientFilter = DisplayWhiteBalanceFactory.createBrightnessFilter(res);
792 mSensorManager = sensorManager;
793 mLightSensor = lightSensor;
794
Long Lingf3afe7d2019-08-05 12:19:42 -0700795 onScreenOn(isDefaultDisplayOn());
Long Lingbc841b02019-07-03 16:43:15 -0700796 }
797 }
798
799 if (mShouldObserveDisplayChange || mShouldObserveAmbientChange) {
800 synchronized (mLock) {
801 onBrightnessChangedLocked();
802 }
803 }
804 }
805
806 public void onPeakRefreshRateEnabled(boolean b) {
807 if (mShouldObserveAmbientChange && mPeakRefreshRateEnabled != b) {
808 mPeakRefreshRateEnabled = b;
809 updateSensorStatus();
810 }
811 }
812
813 public void onLowPowerModeEnabled(boolean b) {
814 if (mShouldObserveAmbientChange && mLowPowerModeEnabled != b) {
815 mLowPowerModeEnabled = b;
816 updateSensorStatus();
817 }
818 }
819
Long Lingf3afe7d2019-08-05 12:19:42 -0700820 public void onDisplayChanged(int displayId) {
821 if (displayId == Display.DEFAULT_DISPLAY) {
822 onScreenOn(isDefaultDisplayOn());
823 }
824 }
825
Long Lingbc841b02019-07-03 16:43:15 -0700826 public void dumpLocked(PrintWriter pw) {
827 pw.println(" BrightnessObserver");
828
829 for (int d: mDisplayBrightnessThresholds) {
830 pw.println(" mDisplayBrightnessThreshold: " + d);
831 }
832
833 for (int d: mAmbientBrightnessThresholds) {
834 pw.println(" mAmbientBrightnessThreshold: " + d);
835 }
836 }
837
838 @Override
839 public void onChange(boolean selfChange, Uri uri, int userId) {
840 synchronized (mLock) {
841 onBrightnessChangedLocked();
842 }
843 }
844
845 /**
846 * Checks to see if at least one value is positive, in which case it is necessary to listen
847 * to value changes.
848 */
849 private boolean checkShouldObserve(int[] a) {
850 for (int d: a) {
851 if (d >= 0) {
852 return true;
853 }
854 }
855
856 return false;
857 }
858
859 private void onBrightnessChangedLocked() {
860 int brightness = Settings.System.getInt(mContext.getContentResolver(),
861 Settings.System.SCREEN_BRIGHTNESS, -1);
862
863 Vote vote = null;
864 for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
865 int disp = mDisplayBrightnessThresholds[i];
866 int ambi = mAmbientBrightnessThresholds[i];
867
868 if (disp >= 0 && ambi >= 0) {
869 if (brightness <= disp && mAmbientLux <= ambi) {
870 vote = Vote.forRefreshRates(0f, 60f);
871 }
872 } else if (disp >= 0) {
873 if (brightness <= disp) {
874 vote = Vote.forRefreshRates(0f, 60f);
875 }
876 } else if (ambi >= 0) {
877 if (mAmbientLux <= ambi) {
878 vote = Vote.forRefreshRates(0f, 60f);
879 }
880 }
881
882 if (vote != null) {
883 break;
884 }
885 }
886
887 if (DEBUG) {
888 Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " + mAmbientLux +
889 (vote != null ? " 60hz only" : " no refresh rate limit"));
890 }
891 updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
892 }
893
894 private void onScreenOn(boolean on) {
Long Lingbc841b02019-07-03 16:43:15 -0700895 if (mScreenOn != on) {
896 mScreenOn = on;
897 updateSensorStatus();
898 }
899 }
900
901 private void updateSensorStatus() {
902 if (mSensorManager == null || mLightSensorListener == null) {
903 return;
904 }
905
906 if (mScreenOn && !mLowPowerModeEnabled && mPeakRefreshRateEnabled) {
907 mSensorManager.registerListener(mLightSensorListener,
908 mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
909 } else {
Long Lingc1ee0422019-07-26 14:23:53 -0700910 mLightSensorListener.removeCallbacks();
Long Lingbc841b02019-07-03 16:43:15 -0700911 mSensorManager.unregisterListener(mLightSensorListener);
912 }
913 }
914
Long Lingf3afe7d2019-08-05 12:19:42 -0700915 private boolean isDefaultDisplayOn() {
916 final Display display = mContext.getSystemService(DisplayManager.class)
917 .getDisplay(Display.DEFAULT_DISPLAY);
918 return display.getState() != Display.STATE_OFF
919 && mContext.getSystemService(PowerManager.class).isInteractive();
920 }
921
Long Lingc1ee0422019-07-26 14:23:53 -0700922 private final class LightSensorEventListener implements SensorEventListener {
923 final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
924 private float mLastSensorData;
925
Long Lingbc841b02019-07-03 16:43:15 -0700926 @Override
927 public void onSensorChanged(SensorEvent event) {
Long Lingc1ee0422019-07-26 14:23:53 -0700928 mLastSensorData = event.values[0];
929 if (DEBUG) {
930 Slog.d(TAG, "On sensor changed: " + mLastSensorData);
931 }
Long Lingbc841b02019-07-03 16:43:15 -0700932
Long Lingc1ee0422019-07-26 14:23:53 -0700933 boolean zoneChanged = isDifferentZone(mLastSensorData, mAmbientLux);
934 if (zoneChanged && mLastSensorData < mAmbientLux) {
935 // Easier to see flicker at lower brightness environment. Forget the history to
936 // get immediate response.
937 mAmbientFilter.clear();
938 }
939
940 long now = SystemClock.uptimeMillis();
941 mAmbientFilter.addValue(now, mLastSensorData);
942
943 mHandler.removeCallbacks(mInjectSensorEventRunnable);
944 processSensorData(now);
945
946 if (zoneChanged && mLastSensorData > mAmbientLux) {
947 // Sensor may not report new event if there is no brightness change.
948 // Need to keep querying the temporal filter for the latest estimation,
949 // until enter in higher lux zone or is interrupted by a new sensor event.
950 mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
Long Lingbc841b02019-07-03 16:43:15 -0700951 }
952 }
953
954 @Override
955 public void onAccuracyChanged(Sensor sensor, int accuracy) {
956 // Not used.
957 }
Long Lingc1ee0422019-07-26 14:23:53 -0700958
959 public void removeCallbacks() {
960 mHandler.removeCallbacks(mInjectSensorEventRunnable);
961 }
962
963 private void processSensorData(long now) {
964 mAmbientLux = mAmbientFilter.getEstimate(now);
965
966 synchronized (mLock) {
967 onBrightnessChangedLocked();
968 }
969 }
970
971 private boolean isDifferentZone(float lux1, float lux2) {
972 for (int z = 0; z < mAmbientBrightnessThresholds.length; z++) {
973 final float boundary = mAmbientBrightnessThresholds[z];
974
975 // Test each boundary. See if the current value and the new value are at
976 // different sides.
977 if ((lux1 <= boundary && lux2 > boundary)
978 || (lux1 > boundary && lux2 <= boundary)) {
979 return true;
980 }
981 }
982
983 return false;
984 }
985
986 private Runnable mInjectSensorEventRunnable = new Runnable() {
987 @Override
988 public void run() {
989 long now = SystemClock.uptimeMillis();
990 // No need to really inject the last event into a temporal filter.
991 processSensorData(now);
992
993 // Inject next event if there is a possible zone change.
994 if (isDifferentZone(mLastSensorData, mAmbientLux)) {
995 mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
996 }
997 }
998 };
Long Lingbc841b02019-07-03 16:43:15 -0700999 };
Long Lingbc841b02019-07-03 16:43:15 -07001000 }
Michael Wrighta3dab232019-02-22 16:54:21 +00001001}