blob: 4681cfe5a427e72b8d524dcdf44e5141f1779245 [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;
Long Lingbb459a02019-08-08 16:05:44 -070041import android.provider.DeviceConfig;
Michael Wrighta3dab232019-02-22 16:54:21 +000042import android.provider.Settings;
Long Lingbc841b02019-07-03 16:43:15 -070043import android.text.TextUtils;
Long Lingbb459a02019-08-08 16:05:44 -070044import android.util.Pair;
Michael Wrighta3dab232019-02-22 16:54:21 +000045import android.util.Slog;
46import android.util.SparseArray;
47import android.view.Display;
48import android.view.DisplayInfo;
49
Long Lingbb459a02019-08-08 16:05:44 -070050import com.android.internal.os.BackgroundThread;
Michael Wrighta3dab232019-02-22 16:54:21 +000051import com.android.internal.R;
Long Lingbc841b02019-07-03 16:43:15 -070052import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
53import com.android.server.display.whitebalance.AmbientFilter;
Michael Wrighta3dab232019-02-22 16:54:21 +000054
55import java.io.PrintWriter;
56import java.util.ArrayList;
57import java.util.Arrays;
Long Lingbc841b02019-07-03 16:43:15 -070058import java.util.List;
Michael Wrighta3dab232019-02-22 16:54:21 +000059import java.util.Objects;
60
61/**
62 * The DisplayModeDirector is responsible for determining what modes are allowed to be
63 * automatically picked by the system based on system-wide and display-specific configuration.
64 */
65public class DisplayModeDirector {
66 private static final String TAG = "DisplayModeDirector";
67 private static final boolean DEBUG = false;
68
69 private static final int MSG_ALLOWED_MODES_CHANGED = 1;
Long Lingbb459a02019-08-08 16:05:44 -070070 private static final int MSG_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
71 private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3;
Michael Wrighta3dab232019-02-22 16:54:21 +000072
73 // Special ID used to indicate that given vote is to be applied globally, rather than to a
74 // specific display.
75 private static final int GLOBAL_ID = -1;
76
Michael Wrighta3dab232019-02-22 16:54:21 +000077 // The tolerance within which we consider something approximately equals.
78 private static final float EPSILON = 0.001f;
79
80 private final Object mLock = new Object();
81 private final Context mContext;
82
83 private final DisplayModeDirectorHandler mHandler;
84
85 // A map from the display ID to the collection of votes and their priority. The latter takes
86 // the form of another map from the priority to the vote itself so that each priority is
87 // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
88 private final SparseArray<SparseArray<Vote>> mVotesByDisplay;
89 // A map from the display ID to the supported modes on that display.
90 private final SparseArray<Display.Mode[]> mSupportedModesByDisplay;
91 // A map from the display ID to the default mode of that display.
92 private final SparseArray<Display.Mode> mDefaultModeByDisplay;
93
94 private final AppRequestObserver mAppRequestObserver;
95 private final SettingsObserver mSettingsObserver;
96 private final DisplayObserver mDisplayObserver;
Long Lingbc841b02019-07-03 16:43:15 -070097 private final BrightnessObserver mBrightnessObserver;
Michael Wrighta3dab232019-02-22 16:54:21 +000098
Long Lingbb459a02019-08-08 16:05:44 -070099 private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
Michael Wrighta3dab232019-02-22 16:54:21 +0000100 private Listener mListener;
101
102 public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
103 mContext = context;
104 mHandler = new DisplayModeDirectorHandler(handler.getLooper());
105 mVotesByDisplay = new SparseArray<>();
106 mSupportedModesByDisplay = new SparseArray<>();
107 mDefaultModeByDisplay = new SparseArray<>();
108 mAppRequestObserver = new AppRequestObserver();
109 mSettingsObserver = new SettingsObserver(context, handler);
110 mDisplayObserver = new DisplayObserver(context, handler);
Long Lingbc841b02019-07-03 16:43:15 -0700111 mBrightnessObserver = new BrightnessObserver(context, handler);
Long Lingbb459a02019-08-08 16:05:44 -0700112 mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
Michael Wrighta3dab232019-02-22 16:54:21 +0000113 }
114
115 /**
116 * Tells the DisplayModeDirector to update allowed votes and begin observing relevant system
117 * state.
118 *
119 * This has to be deferred because the object may be constructed before the rest of the system
120 * is ready.
121 */
Long Lingbc841b02019-07-03 16:43:15 -0700122 public void start(SensorManager sensorManager) {
Michael Wrighta3dab232019-02-22 16:54:21 +0000123 mSettingsObserver.observe();
124 mDisplayObserver.observe();
125 mSettingsObserver.observe();
Long Lingbc841b02019-07-03 16:43:15 -0700126 mBrightnessObserver.observe(sensorManager);
Michael Wrighta3dab232019-02-22 16:54:21 +0000127 synchronized (mLock) {
128 // We may have a listener already registered before the call to start, so go ahead and
129 // notify them to pick up our newly initialized state.
130 notifyAllowedModesChangedLocked();
131 }
Long Lingbc841b02019-07-03 16:43:15 -0700132
Michael Wrighta3dab232019-02-22 16:54:21 +0000133 }
134
135 /**
136 * Calculates the modes the system is allowed to freely switch between based on global and
137 * display-specific constraints.
138 *
139 * @param displayId The display to query for.
140 * @return The IDs of the modes the system is allowed to freely switch between.
141 */
142 @NonNull
143 public int[] getAllowedModes(int displayId) {
144 synchronized (mLock) {
145 SparseArray<Vote> votes = getVotesLocked(displayId);
146 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
147 Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
148 if (modes == null || defaultMode == null) {
149 Slog.e(TAG, "Asked about unknown display, returning empty allowed set! (id="
150 + displayId + ")");
151 return new int[0];
152 }
153 return getAllowedModesLocked(votes, modes, defaultMode);
154 }
155 }
156
157 @NonNull
158 private SparseArray<Vote> getVotesLocked(int displayId) {
159 SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
160 final SparseArray<Vote> votes;
161 if (displayVotes != null) {
162 votes = displayVotes.clone();
163 } else {
164 votes = new SparseArray<>();
165 }
166
167 SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
168 if (globalVotes != null) {
169 for (int i = 0; i < globalVotes.size(); i++) {
170 int priority = globalVotes.keyAt(i);
171 if (votes.indexOfKey(priority) < 0) {
172 votes.put(priority, globalVotes.valueAt(i));
173 }
174 }
175 }
176 return votes;
177 }
178
179 @NonNull
180 private int[] getAllowedModesLocked(@NonNull SparseArray<Vote> votes,
181 @NonNull Display.Mode[] modes, @NonNull Display.Mode defaultMode) {
182 int lowestConsideredPriority = Vote.MIN_PRIORITY;
183 while (lowestConsideredPriority <= Vote.MAX_PRIORITY) {
184 float minRefreshRate = 0f;
185 float maxRefreshRate = Float.POSITIVE_INFINITY;
186 int height = Vote.INVALID_SIZE;
187 int width = Vote.INVALID_SIZE;
188
189 for (int priority = Vote.MAX_PRIORITY;
190 priority >= lowestConsideredPriority;
191 priority--) {
192 Vote vote = votes.get(priority);
193 if (vote == null) {
194 continue;
195 }
196 // For refresh rates, just use the tightest bounds of all the votes
197 minRefreshRate = Math.max(minRefreshRate, vote.minRefreshRate);
198 maxRefreshRate = Math.min(maxRefreshRate, vote.maxRefreshRate);
199 // For display size, use only the first vote we come across (i.e. the highest
200 // priority vote that includes the width / height).
201 if (height == Vote.INVALID_SIZE && width == Vote.INVALID_SIZE
202 && vote.height > 0 && vote.width > 0) {
203 width = vote.width;
204 height = vote.height;
205 }
206 }
207
208 // If we don't have anything specifying the width / height of the display, just use the
209 // default width and height. We don't want these switching out from underneath us since
210 // it's a pretty disruptive behavior.
211 if (height == Vote.INVALID_SIZE || width == Vote.INVALID_SIZE) {
212 width = defaultMode.getPhysicalWidth();
213 height = defaultMode.getPhysicalHeight();
214 }
215
216 int[] availableModes =
217 filterModes(modes, width, height, minRefreshRate, maxRefreshRate);
218 if (availableModes.length > 0) {
219 if (DEBUG) {
220 Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
221 + " with lowest priority considered "
222 + Vote.priorityToString(lowestConsideredPriority)
223 + " and constraints: "
224 + "width=" + width
225 + ", height=" + height
226 + ", minRefreshRate=" + minRefreshRate
227 + ", maxRefreshRate=" + maxRefreshRate);
228 }
229 return availableModes;
230 }
231
232 if (DEBUG) {
233 Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
234 + Vote.priorityToString(lowestConsideredPriority)
235 + " and with the following constraints: "
236 + "width=" + width
237 + ", height=" + height
238 + ", minRefreshRate=" + minRefreshRate
239 + ", maxRefreshRate=" + maxRefreshRate);
240 }
241 // If we haven't found anything with the current set of votes, drop the current lowest
242 // priority vote.
243 lowestConsideredPriority++;
244 }
245
246 // If we still haven't found anything that matches our current set of votes, just fall back
247 // to the default mode.
248 return new int[] { defaultMode.getModeId() };
249 }
250
251 private int[] filterModes(Display.Mode[] supportedModes,
252 int width, int height, float minRefreshRate, float maxRefreshRate) {
253 ArrayList<Display.Mode> availableModes = new ArrayList<>();
254 for (Display.Mode mode : supportedModes) {
255 if (mode.getPhysicalWidth() != width || mode.getPhysicalHeight() != height) {
256 if (DEBUG) {
257 Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
258 + ": desiredWidth=" + width
259 + ": desiredHeight=" + height
260 + ": actualWidth=" + mode.getPhysicalWidth()
261 + ": actualHeight=" + mode.getPhysicalHeight());
262 }
263 continue;
264 }
265 final float refreshRate = mode.getRefreshRate();
266 // Some refresh rates are calculated based on frame timings, so they aren't *exactly*
267 // equal to expected refresh rate. Given that, we apply a bit of tolerance to this
268 // comparison.
269 if (refreshRate < (minRefreshRate - EPSILON)
270 || refreshRate > (maxRefreshRate + EPSILON)) {
271 if (DEBUG) {
272 Slog.w(TAG, "Discarding mode " + mode.getModeId()
273 + ", outside refresh rate bounds"
274 + ": minRefreshRate=" + minRefreshRate
275 + ", maxRefreshRate=" + maxRefreshRate
276 + ", modeRefreshRate=" + refreshRate);
277 }
278 continue;
279 }
280 availableModes.add(mode);
281 }
282 final int size = availableModes.size();
283 int[] availableModeIds = new int[size];
284 for (int i = 0; i < size; i++) {
285 availableModeIds[i] = availableModes.get(i).getModeId();
286 }
287 return availableModeIds;
288 }
289
290 /**
291 * Gets the observer responsible for application display mode requests.
292 */
293 @NonNull
294 public AppRequestObserver getAppRequestObserver() {
295 // We don't need to lock here because mAppRequestObserver is a final field, which is
296 // guaranteed to be visible on all threads after construction.
297 return mAppRequestObserver;
298 }
299
300 /**
301 * Sets the listener for changes to allowed display modes.
302 */
303 public void setListener(@Nullable Listener listener) {
304 synchronized (mLock) {
305 mListener = listener;
306 }
307 }
308
309 /**
310 * Print the object's state and debug information into the given stream.
311 *
312 * @param pw The stream to dump information to.
313 */
314 public void dump(PrintWriter pw) {
315 pw.println("DisplayModeDirector");
316 synchronized (mLock) {
317 pw.println(" mSupportedModesByDisplay:");
318 for (int i = 0; i < mSupportedModesByDisplay.size(); i++) {
319 final int id = mSupportedModesByDisplay.keyAt(i);
320 final Display.Mode[] modes = mSupportedModesByDisplay.valueAt(i);
321 pw.println(" " + id + " -> " + Arrays.toString(modes));
322 }
323 pw.println(" mDefaultModeByDisplay:");
324 for (int i = 0; i < mDefaultModeByDisplay.size(); i++) {
325 final int id = mDefaultModeByDisplay.keyAt(i);
326 final Display.Mode mode = mDefaultModeByDisplay.valueAt(i);
327 pw.println(" " + id + " -> " + mode);
328 }
329 pw.println(" mVotesByDisplay:");
330 for (int i = 0; i < mVotesByDisplay.size(); i++) {
331 pw.println(" " + mVotesByDisplay.keyAt(i) + ":");
332 SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
333 for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
334 Vote vote = votes.get(p);
335 if (vote == null) {
336 continue;
337 }
338 pw.println(" " + Vote.priorityToString(p) + " -> " + vote);
339 }
340 }
341 mSettingsObserver.dumpLocked(pw);
342 mAppRequestObserver.dumpLocked(pw);
Long Lingbc841b02019-07-03 16:43:15 -0700343 mBrightnessObserver.dumpLocked(pw);
Michael Wrighta3dab232019-02-22 16:54:21 +0000344 }
345 }
346
347 private void updateVoteLocked(int priority, Vote vote) {
348 updateVoteLocked(GLOBAL_ID, priority, vote);
349 }
350
351 private void updateVoteLocked(int displayId, int priority, Vote vote) {
352 if (DEBUG) {
353 Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
354 + ", priority=" + Vote.priorityToString(priority)
355 + ", vote=" + vote + ")");
356 }
357 if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
358 Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
359 + " priority=" + Vote.priorityToString(priority)
360 + ", vote=" + vote, new Throwable());
361 return;
362 }
363 final SparseArray<Vote> votes = getOrCreateVotesByDisplay(displayId);
364
365 Vote currentVote = votes.get(priority);
366 if (vote != null) {
367 votes.put(priority, vote);
368 } else {
369 votes.remove(priority);
370 }
371
372 if (votes.size() == 0) {
373 if (DEBUG) {
374 Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
375 }
376 mVotesByDisplay.remove(displayId);
377 }
378
379 notifyAllowedModesChangedLocked();
380 }
381
382 private void notifyAllowedModesChangedLocked() {
383 if (mListener != null && !mHandler.hasMessages(MSG_ALLOWED_MODES_CHANGED)) {
384 // We need to post this to a handler to avoid calling out while holding the lock
385 // since we know there are things that both listen for changes as well as provide
386 // information. If we did call out while holding the lock, then there's no guaranteed
387 // lock order and we run the real of risk deadlock.
388 Message msg = mHandler.obtainMessage(MSG_ALLOWED_MODES_CHANGED, mListener);
389 msg.sendToTarget();
390 }
391 }
392
393 private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
394 int index = mVotesByDisplay.indexOfKey(displayId);
395 if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
396 return mVotesByDisplay.get(displayId);
397 } else {
398 SparseArray<Vote> votes = new SparseArray<>();
399 mVotesByDisplay.put(displayId, votes);
400 return votes;
401 }
402 }
403
404 /**
405 * Listens for changes to display mode coordination.
406 */
407 public interface Listener {
408 /**
409 * Called when the allowed display modes may have changed.
410 */
411 void onAllowedDisplayModesChanged();
412 }
413
Long Lingbb459a02019-08-08 16:05:44 -0700414 private final class DisplayModeDirectorHandler extends Handler {
Michael Wrighta3dab232019-02-22 16:54:21 +0000415 DisplayModeDirectorHandler(Looper looper) {
416 super(looper, null, true /*async*/);
417 }
418
419 @Override
420 public void handleMessage(Message msg) {
421 switch (msg.what) {
422 case MSG_ALLOWED_MODES_CHANGED:
423 Listener listener = (Listener) msg.obj;
424 listener.onAllowedDisplayModesChanged();
425 break;
Long Lingbb459a02019-08-08 16:05:44 -0700426
427 case MSG_BRIGHTNESS_THRESHOLDS_CHANGED:
428 Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
429
430 if (thresholds != null) {
431 mBrightnessObserver.onDeviceConfigThresholdsChanged(
432 thresholds.first, thresholds.second);
433 } else {
434 mBrightnessObserver.onDeviceConfigThresholdsChanged(null, null);
435 }
436 break;
437
438 case MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED:
439 Float defaultPeakRefreshRate = (Float) msg.obj;
440 mSettingsObserver.onDeviceConfigDefaultPeakRefreshRateChanged(
441 defaultPeakRefreshRate);
442 break;
Michael Wrighta3dab232019-02-22 16:54:21 +0000443 }
444 }
445 }
446
447 private static final class Vote {
Michael Wrighta3dab232019-02-22 16:54:21 +0000448 // We split the app request into two priorities in case we can satisfy one desire without
449 // the other.
Ady Abrahamce743b72019-06-11 19:16:12 -0700450 public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 0;
451 public static final int PRIORITY_APP_REQUEST_SIZE = 1;
452 public static final int PRIORITY_USER_SETTING_REFRESH_RATE = 2;
Long Ling741bf5f2019-04-15 14:08:49 -0700453 public static final int PRIORITY_LOW_BRIGHTNESS = 3;
454 public static final int PRIORITY_LOW_POWER_MODE = 4;
Michael Wrighta3dab232019-02-22 16:54:21 +0000455
456 // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
457 // appropriate, as well as priorityToString.
458
Ady Abrahamce743b72019-06-11 19:16:12 -0700459 public static final int MIN_PRIORITY = PRIORITY_APP_REQUEST_REFRESH_RATE;
Michael Wrighta3dab232019-02-22 16:54:21 +0000460 public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
461
462 /**
463 * A value signifying an invalid width or height in a vote.
464 */
465 public static final int INVALID_SIZE = -1;
466
467 /**
468 * The requested width of the display in pixels, or INVALID_SIZE;
469 */
470 public final int width;
471 /**
472 * The requested height of the display in pixels, or INVALID_SIZE;
473 */
474 public final int height;
475
476 /**
477 * The lowest desired refresh rate.
478 */
479 public final float minRefreshRate;
480 /**
481 * The highest desired refresh rate.
482 */
483 public final float maxRefreshRate;
484
485 public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) {
486 return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate);
487 }
488
489 public static Vote forSize(int width, int height) {
490 return new Vote(width, height, 0f, Float.POSITIVE_INFINITY);
491 }
492
493 private Vote(int width, int height,
494 float minRefreshRate, float maxRefreshRate) {
495 this.width = width;
496 this.height = height;
497 this.minRefreshRate = minRefreshRate;
498 this.maxRefreshRate = maxRefreshRate;
499 }
500
501 public static String priorityToString(int priority) {
502 switch (priority) {
Michael Wrighta3dab232019-02-22 16:54:21 +0000503 case PRIORITY_APP_REQUEST_REFRESH_RATE:
504 return "PRIORITY_APP_REQUEST_REFRESH_RATE";
505 case PRIORITY_APP_REQUEST_SIZE:
506 return "PRIORITY_APP_REQUEST_SIZE";
Ady Abrahamce743b72019-06-11 19:16:12 -0700507 case PRIORITY_USER_SETTING_REFRESH_RATE:
508 return "PRIORITY_USER_SETTING_REFRESH_RATE";
Michael Wrighta3dab232019-02-22 16:54:21 +0000509 case PRIORITY_LOW_POWER_MODE:
510 return "PRIORITY_LOW_POWER_MODE";
511 default:
512 return Integer.toString(priority);
513 }
514 }
515
516 @Override
517 public String toString() {
518 return "Vote{"
519 + "width=" + width
520 + ", height=" + height
521 + ", minRefreshRate=" + minRefreshRate
522 + ", maxRefreshRate=" + maxRefreshRate
523 + "}";
524 }
525 }
526
527 private final class SettingsObserver extends ContentObserver {
528 private final Uri mRefreshRateSetting =
529 Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
530 private final Uri mLowPowerModeSetting =
531 Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
532
533 private final Context mContext;
Long Lingbb459a02019-08-08 16:05:44 -0700534 private float mDefaultPeakRefreshRate;
Michael Wrighta3dab232019-02-22 16:54:21 +0000535
536 SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
537 super(handler);
538 mContext = context;
539 mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
540 R.integer.config_defaultPeakRefreshRate);
541 }
542
543 public void observe() {
544 final ContentResolver cr = mContext.getContentResolver();
545 cr.registerContentObserver(mRefreshRateSetting, false /*notifyDescendants*/, this,
546 UserHandle.USER_SYSTEM);
547 cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
548 UserHandle.USER_SYSTEM);
Long Lingbb459a02019-08-08 16:05:44 -0700549
550 Float deviceConfigDefaultPeakRefresh =
551 mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
552 if (deviceConfigDefaultPeakRefresh != null) {
553 mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh;
554 }
555
Michael Wrighta3dab232019-02-22 16:54:21 +0000556 synchronized (mLock) {
557 updateRefreshRateSettingLocked();
558 updateLowPowerModeSettingLocked();
559 }
560 }
561
Long Lingbb459a02019-08-08 16:05:44 -0700562 public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) {
563 if (defaultPeakRefreshRate == null) {
564 defaultPeakRefreshRate = (float) mContext.getResources().getInteger(
565 R.integer.config_defaultPeakRefreshRate);
566 }
567
568 if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
569 synchronized (mLock) {
570 mDefaultPeakRefreshRate = defaultPeakRefreshRate;
571 updateRefreshRateSettingLocked();
572 }
573 }
574 }
575
Michael Wrighta3dab232019-02-22 16:54:21 +0000576 @Override
577 public void onChange(boolean selfChange, Uri uri, int userId) {
578 synchronized (mLock) {
579 if (mRefreshRateSetting.equals(uri)) {
580 updateRefreshRateSettingLocked();
581 } else if (mLowPowerModeSetting.equals(uri)) {
582 updateLowPowerModeSettingLocked();
583 }
584 }
585 }
586
587 private void updateLowPowerModeSettingLocked() {
588 boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
589 Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
590 final Vote vote;
591 if (inLowPowerMode) {
592 vote = Vote.forRefreshRates(0f, 60f);
593 } else {
594 vote = null;
595 }
596 updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
Long Lingbc841b02019-07-03 16:43:15 -0700597 mBrightnessObserver.onLowPowerModeEnabled(inLowPowerMode);
Michael Wrighta3dab232019-02-22 16:54:21 +0000598 }
599
600 private void updateRefreshRateSettingLocked() {
601 float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
Adrian Salido41cc1862019-04-25 19:34:37 -0700602 Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
Michael Wrighta3dab232019-02-22 16:54:21 +0000603 Vote vote = Vote.forRefreshRates(0f, peakRefreshRate);
Ady Abrahamce743b72019-06-11 19:16:12 -0700604 updateVoteLocked(Vote.PRIORITY_USER_SETTING_REFRESH_RATE, vote);
Long Lingbc841b02019-07-03 16:43:15 -0700605 mBrightnessObserver.onPeakRefreshRateEnabled(peakRefreshRate > 60f);
Long Ling741bf5f2019-04-15 14:08:49 -0700606 }
607
Michael Wrighta3dab232019-02-22 16:54:21 +0000608 public void dumpLocked(PrintWriter pw) {
609 pw.println(" SettingsObserver");
610 pw.println(" mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
611 }
612 }
613
614 final class AppRequestObserver {
615 private SparseArray<Display.Mode> mAppRequestedModeByDisplay;
616
617 AppRequestObserver() {
618 mAppRequestedModeByDisplay = new SparseArray<>();
619 }
620
621 public void setAppRequestedMode(int displayId, int modeId) {
622 synchronized (mLock) {
623 setAppRequestedModeLocked(displayId, modeId);
624 }
625 }
626
627 private void setAppRequestedModeLocked(int displayId, int modeId) {
628 final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
629 if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
630 return;
631 }
632
633 final Vote refreshRateVote;
634 final Vote sizeVote;
635 if (requestedMode != null) {
636 mAppRequestedModeByDisplay.put(displayId, requestedMode);
637 float refreshRate = requestedMode.getRefreshRate();
638 refreshRateVote = Vote.forRefreshRates(refreshRate, refreshRate);
639 sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
640 requestedMode.getPhysicalHeight());
641 } else {
642 mAppRequestedModeByDisplay.remove(displayId);
643 refreshRateVote = null;
644 sizeVote = null;
645 }
646 updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
647 updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
648 return;
649 }
650
651 private Display.Mode findModeByIdLocked(int displayId, int modeId) {
652 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
653 if (modes == null) {
654 return null;
655 }
656 for (Display.Mode mode : modes) {
657 if (mode.getModeId() == modeId) {
658 return mode;
659 }
660 }
661 return null;
662 }
663
664 public void dumpLocked(PrintWriter pw) {
665 pw.println(" AppRequestObserver");
666 pw.println(" mAppRequestedModeByDisplay:");
667 for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
668 final int id = mAppRequestedModeByDisplay.keyAt(i);
669 final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
670 pw.println(" " + id + " -> " + mode);
671 }
672 }
673 }
674
675 private final class DisplayObserver implements DisplayManager.DisplayListener {
676 // Note that we can never call into DisplayManager or any of the non-POD classes it
677 // returns, while holding mLock since it may call into DMS, which might be simultaneously
678 // calling into us already holding its own lock.
679 private final Context mContext;
680 private final Handler mHandler;
681
682 DisplayObserver(Context context, Handler handler) {
683 mContext = context;
684 mHandler = handler;
685 }
686
687 public void observe() {
688 DisplayManager dm = mContext.getSystemService(DisplayManager.class);
689 dm.registerDisplayListener(this, mHandler);
690
691 // Populate existing displays
692 SparseArray<Display.Mode[]> modes = new SparseArray<>();
693 SparseArray<Display.Mode> defaultModes = new SparseArray<>();
694 DisplayInfo info = new DisplayInfo();
695 Display[] displays = dm.getDisplays();
696 for (Display d : displays) {
697 final int displayId = d.getDisplayId();
698 d.getDisplayInfo(info);
699 modes.put(displayId, info.supportedModes);
700 defaultModes.put(displayId, info.getDefaultMode());
701 }
702 synchronized (mLock) {
703 final int size = modes.size();
704 for (int i = 0; i < size; i++) {
705 mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i));
706 mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i));
707 }
708 }
709 }
710
711 @Override
712 public void onDisplayAdded(int displayId) {
713 updateDisplayModes(displayId);
714 }
715
716 @Override
717 public void onDisplayRemoved(int displayId) {
718 synchronized (mLock) {
719 mSupportedModesByDisplay.remove(displayId);
720 mDefaultModeByDisplay.remove(displayId);
721 }
722 }
723
724 @Override
725 public void onDisplayChanged(int displayId) {
726 updateDisplayModes(displayId);
Long Lingf3afe7d2019-08-05 12:19:42 -0700727 mBrightnessObserver.onDisplayChanged(displayId);
Michael Wrighta3dab232019-02-22 16:54:21 +0000728 }
729
730 private void updateDisplayModes(int displayId) {
731 Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
Michael Wright91c28382019-03-11 12:54:22 +0000732 if (d == null) {
733 // We can occasionally get a display added or changed event for a display that was
734 // subsequently removed, which means this returns null. Check this case and bail
735 // out early; if it gets re-attached we'll eventually get another call back for it.
736 return;
737 }
Michael Wrighta3dab232019-02-22 16:54:21 +0000738 DisplayInfo info = new DisplayInfo();
739 d.getDisplayInfo(info);
740 boolean changed = false;
741 synchronized (mLock) {
742 if (!Arrays.equals(mSupportedModesByDisplay.get(displayId), info.supportedModes)) {
743 mSupportedModesByDisplay.put(displayId, info.supportedModes);
744 changed = true;
745 }
746 if (!Objects.equals(mDefaultModeByDisplay.get(displayId), info.getDefaultMode())) {
747 changed = true;
748 mDefaultModeByDisplay.put(displayId, info.getDefaultMode());
749 }
750 if (changed) {
751 notifyAllowedModesChangedLocked();
752 }
753 }
754 }
755 }
Long Lingbc841b02019-07-03 16:43:15 -0700756
757 /**
758 * This class manages brightness threshold for switching between 60 hz and higher refresh rate.
759 * See more information at the definition of
760 * {@link R.array#config_brightnessThresholdsOfPeakRefreshRate} and
761 * {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
762 */
763 private class BrightnessObserver extends ContentObserver {
764 private final Uri mDisplayBrightnessSetting =
765 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
766
767 private final static int LIGHT_SENSOR_RATE_MS = 250;
Long Lingbb459a02019-08-08 16:05:44 -0700768 private int[] mDisplayBrightnessThresholds;
769 private int[] mAmbientBrightnessThresholds;
Long Lingbc841b02019-07-03 16:43:15 -0700770 // valid threshold if any item from the array >= 0
771 private boolean mShouldObserveDisplayChange;
772 private boolean mShouldObserveAmbientChange;
773
774 private SensorManager mSensorManager;
775 private Sensor mLightSensor;
Long Lingc1ee0422019-07-26 14:23:53 -0700776 private LightSensorEventListener mLightSensorListener = new LightSensorEventListener();
Long Lingbc841b02019-07-03 16:43:15 -0700777 // Take it as low brightness before valid sensor data comes
778 private float mAmbientLux = -1.0f;
779 private AmbientFilter mAmbientFilter;
780
781 private final Context mContext;
Long Lingbb459a02019-08-08 16:05:44 -0700782
783 // Enable light sensor only when mShouldObserveAmbientChange is true, screen is on, peak
784 // refresh rate enabled and low power mode off. After initialization, these states will
785 // be updated from the same handler thread.
Long Lingbc841b02019-07-03 16:43:15 -0700786 private boolean mScreenOn = false;
787 private boolean mPeakRefreshRateEnabled = false;
788 private boolean mLowPowerModeEnabled = false;
789
790 BrightnessObserver(Context context, Handler handler) {
791 super(handler);
792 mContext = context;
793 mDisplayBrightnessThresholds = context.getResources().getIntArray(
794 R.array.config_brightnessThresholdsOfPeakRefreshRate);
795 mAmbientBrightnessThresholds = context.getResources().getIntArray(
796 R.array.config_ambientThresholdsOfPeakRefreshRate);
Long Lingbb459a02019-08-08 16:05:44 -0700797
Long Lingbc841b02019-07-03 16:43:15 -0700798 if (mDisplayBrightnessThresholds.length != mAmbientBrightnessThresholds.length) {
799 throw new RuntimeException("display brightness threshold array and ambient "
800 + "brightness threshold array have different length");
801 }
Long Lingbc841b02019-07-03 16:43:15 -0700802 }
803
804 public void observe(SensorManager sensorManager) {
Long Lingbb459a02019-08-08 16:05:44 -0700805 mSensorManager = sensorManager;
806 // DeviceConfig is accessible after system ready.
807 int[] brightnessThresholds = mDeviceConfigDisplaySettings.getBrightnessThresholds();
808 int[] ambientThresholds = mDeviceConfigDisplaySettings.getAmbientThresholds();
809
810 if (brightnessThresholds != null && ambientThresholds != null
811 && brightnessThresholds.length == ambientThresholds.length) {
812 mDisplayBrightnessThresholds = brightnessThresholds;
813 mAmbientBrightnessThresholds = ambientThresholds;
Long Lingbc841b02019-07-03 16:43:15 -0700814 }
Long Lingbb459a02019-08-08 16:05:44 -0700815 restartObserver();
816 mDeviceConfigDisplaySettings.startListening();
Long Lingbc841b02019-07-03 16:43:15 -0700817 }
818
819 public void onPeakRefreshRateEnabled(boolean b) {
Long Lingbb459a02019-08-08 16:05:44 -0700820 if (mPeakRefreshRateEnabled != b) {
Long Lingbc841b02019-07-03 16:43:15 -0700821 mPeakRefreshRateEnabled = b;
822 updateSensorStatus();
823 }
824 }
825
826 public void onLowPowerModeEnabled(boolean b) {
Long Lingbb459a02019-08-08 16:05:44 -0700827 if (mLowPowerModeEnabled != b) {
Long Lingbc841b02019-07-03 16:43:15 -0700828 mLowPowerModeEnabled = b;
829 updateSensorStatus();
830 }
831 }
832
Long Lingbb459a02019-08-08 16:05:44 -0700833 public void onDeviceConfigThresholdsChanged(int[] brightnessThresholds,
834 int[] ambientThresholds) {
835 if (brightnessThresholds != null && ambientThresholds != null
836 && brightnessThresholds.length == ambientThresholds.length) {
837 mDisplayBrightnessThresholds = brightnessThresholds;
838 mAmbientBrightnessThresholds = ambientThresholds;
839 } else {
840 // Invalid or empty. Use device default.
841 mDisplayBrightnessThresholds = mContext.getResources().getIntArray(
842 R.array.config_brightnessThresholdsOfPeakRefreshRate);
843 mAmbientBrightnessThresholds = mContext.getResources().getIntArray(
844 R.array.config_ambientThresholdsOfPeakRefreshRate);
Long Lingf3afe7d2019-08-05 12:19:42 -0700845 }
Long Lingbb459a02019-08-08 16:05:44 -0700846 restartObserver();
Long Lingf3afe7d2019-08-05 12:19:42 -0700847 }
848
Long Lingbc841b02019-07-03 16:43:15 -0700849 public void dumpLocked(PrintWriter pw) {
850 pw.println(" BrightnessObserver");
851
852 for (int d: mDisplayBrightnessThresholds) {
853 pw.println(" mDisplayBrightnessThreshold: " + d);
854 }
855
856 for (int d: mAmbientBrightnessThresholds) {
857 pw.println(" mAmbientBrightnessThreshold: " + d);
858 }
859 }
860
Long Lingbb459a02019-08-08 16:05:44 -0700861 public void onDisplayChanged(int displayId) {
862 if (displayId == Display.DEFAULT_DISPLAY) {
863 onScreenOn(isDefaultDisplayOn());
864 }
865 }
866
Long Lingbc841b02019-07-03 16:43:15 -0700867 @Override
868 public void onChange(boolean selfChange, Uri uri, int userId) {
869 synchronized (mLock) {
870 onBrightnessChangedLocked();
871 }
872 }
873
Long Lingbb459a02019-08-08 16:05:44 -0700874 private void restartObserver() {
875 mShouldObserveDisplayChange = checkShouldObserve(mDisplayBrightnessThresholds);
876 mShouldObserveAmbientChange = checkShouldObserve(mAmbientBrightnessThresholds);
877
878 final ContentResolver cr = mContext.getContentResolver();
879 if (mShouldObserveDisplayChange) {
880 // Content Service does not check if an listener has already been registered.
881 // To ensure only one listener is registered, force an unregistration first.
882 cr.unregisterContentObserver(this);
883 cr.registerContentObserver(mDisplayBrightnessSetting,
884 false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM);
885 } else {
886 cr.unregisterContentObserver(this);
887 }
888
889 if (mShouldObserveAmbientChange) {
890 Resources resources = mContext.getResources();
891 String lightSensorType = resources.getString(
892 com.android.internal.R.string.config_displayLightSensorType);
893
894 Sensor lightSensor = null;
895 if (!TextUtils.isEmpty(lightSensorType)) {
896 List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
897 for (int i = 0; i < sensors.size(); i++) {
898 Sensor sensor = sensors.get(i);
899 if (lightSensorType.equals(sensor.getStringType())) {
900 lightSensor = sensor;
901 break;
902 }
903 }
904 }
905
906 if (lightSensor == null) {
907 lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
908 }
909
910 if (lightSensor != null) {
911 final Resources res = mContext.getResources();
912
913 mAmbientFilter = DisplayWhiteBalanceFactory.createBrightnessFilter(res);
914 mLightSensor = lightSensor;
915
916 onScreenOn(isDefaultDisplayOn());
917 }
918 } else {
919 mAmbientFilter = null;
920 mLightSensor = null;
921 }
922
923 updateSensorStatus();
924 synchronized (mLock) {
925 onBrightnessChangedLocked();
926 }
927 }
928
Long Lingbc841b02019-07-03 16:43:15 -0700929 /**
930 * Checks to see if at least one value is positive, in which case it is necessary to listen
931 * to value changes.
932 */
933 private boolean checkShouldObserve(int[] a) {
934 for (int d: a) {
935 if (d >= 0) {
936 return true;
937 }
938 }
939
940 return false;
941 }
942
943 private void onBrightnessChangedLocked() {
944 int brightness = Settings.System.getInt(mContext.getContentResolver(),
945 Settings.System.SCREEN_BRIGHTNESS, -1);
946
947 Vote vote = null;
948 for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
949 int disp = mDisplayBrightnessThresholds[i];
950 int ambi = mAmbientBrightnessThresholds[i];
951
952 if (disp >= 0 && ambi >= 0) {
953 if (brightness <= disp && mAmbientLux <= ambi) {
954 vote = Vote.forRefreshRates(0f, 60f);
955 }
956 } else if (disp >= 0) {
957 if (brightness <= disp) {
958 vote = Vote.forRefreshRates(0f, 60f);
959 }
960 } else if (ambi >= 0) {
961 if (mAmbientLux <= ambi) {
962 vote = Vote.forRefreshRates(0f, 60f);
963 }
964 }
965
966 if (vote != null) {
967 break;
968 }
969 }
970
971 if (DEBUG) {
972 Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " + mAmbientLux +
973 (vote != null ? " 60hz only" : " no refresh rate limit"));
974 }
975 updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
976 }
977
978 private void onScreenOn(boolean on) {
Long Lingbc841b02019-07-03 16:43:15 -0700979 if (mScreenOn != on) {
980 mScreenOn = on;
981 updateSensorStatus();
982 }
983 }
984
985 private void updateSensorStatus() {
986 if (mSensorManager == null || mLightSensorListener == null) {
987 return;
988 }
989
Long Lingbb459a02019-08-08 16:05:44 -0700990 if (mShouldObserveAmbientChange && mScreenOn && !mLowPowerModeEnabled
991 && mPeakRefreshRateEnabled) {
Long Lingbc841b02019-07-03 16:43:15 -0700992 mSensorManager.registerListener(mLightSensorListener,
993 mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
994 } else {
Long Lingc1ee0422019-07-26 14:23:53 -0700995 mLightSensorListener.removeCallbacks();
Long Lingbc841b02019-07-03 16:43:15 -0700996 mSensorManager.unregisterListener(mLightSensorListener);
997 }
998 }
999
Long Lingf3afe7d2019-08-05 12:19:42 -07001000 private boolean isDefaultDisplayOn() {
1001 final Display display = mContext.getSystemService(DisplayManager.class)
1002 .getDisplay(Display.DEFAULT_DISPLAY);
1003 return display.getState() != Display.STATE_OFF
1004 && mContext.getSystemService(PowerManager.class).isInteractive();
1005 }
1006
Long Lingc1ee0422019-07-26 14:23:53 -07001007 private final class LightSensorEventListener implements SensorEventListener {
1008 final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
1009 private float mLastSensorData;
1010
Long Lingbc841b02019-07-03 16:43:15 -07001011 @Override
1012 public void onSensorChanged(SensorEvent event) {
Long Lingc1ee0422019-07-26 14:23:53 -07001013 mLastSensorData = event.values[0];
1014 if (DEBUG) {
1015 Slog.d(TAG, "On sensor changed: " + mLastSensorData);
1016 }
Long Lingbc841b02019-07-03 16:43:15 -07001017
Long Lingc1ee0422019-07-26 14:23:53 -07001018 boolean zoneChanged = isDifferentZone(mLastSensorData, mAmbientLux);
1019 if (zoneChanged && mLastSensorData < mAmbientLux) {
1020 // Easier to see flicker at lower brightness environment. Forget the history to
1021 // get immediate response.
1022 mAmbientFilter.clear();
1023 }
1024
1025 long now = SystemClock.uptimeMillis();
1026 mAmbientFilter.addValue(now, mLastSensorData);
1027
1028 mHandler.removeCallbacks(mInjectSensorEventRunnable);
1029 processSensorData(now);
1030
1031 if (zoneChanged && mLastSensorData > mAmbientLux) {
1032 // Sensor may not report new event if there is no brightness change.
1033 // Need to keep querying the temporal filter for the latest estimation,
1034 // until enter in higher lux zone or is interrupted by a new sensor event.
1035 mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
Long Lingbc841b02019-07-03 16:43:15 -07001036 }
1037 }
1038
1039 @Override
1040 public void onAccuracyChanged(Sensor sensor, int accuracy) {
1041 // Not used.
1042 }
Long Lingc1ee0422019-07-26 14:23:53 -07001043
1044 public void removeCallbacks() {
1045 mHandler.removeCallbacks(mInjectSensorEventRunnable);
1046 }
1047
1048 private void processSensorData(long now) {
1049 mAmbientLux = mAmbientFilter.getEstimate(now);
1050
1051 synchronized (mLock) {
1052 onBrightnessChangedLocked();
1053 }
1054 }
1055
1056 private boolean isDifferentZone(float lux1, float lux2) {
1057 for (int z = 0; z < mAmbientBrightnessThresholds.length; z++) {
1058 final float boundary = mAmbientBrightnessThresholds[z];
1059
1060 // Test each boundary. See if the current value and the new value are at
1061 // different sides.
1062 if ((lux1 <= boundary && lux2 > boundary)
1063 || (lux1 > boundary && lux2 <= boundary)) {
1064 return true;
1065 }
1066 }
1067
1068 return false;
1069 }
1070
1071 private Runnable mInjectSensorEventRunnable = new Runnable() {
1072 @Override
1073 public void run() {
1074 long now = SystemClock.uptimeMillis();
1075 // No need to really inject the last event into a temporal filter.
1076 processSensorData(now);
1077
1078 // Inject next event if there is a possible zone change.
1079 if (isDifferentZone(mLastSensorData, mAmbientLux)) {
1080 mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
1081 }
1082 }
1083 };
Long Lingbb459a02019-08-08 16:05:44 -07001084 }
Long Lingbc841b02019-07-03 16:43:15 -07001085 }
Long Lingbb459a02019-08-08 16:05:44 -07001086
1087 private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
1088
1089 public DeviceConfigDisplaySettings() {
1090 }
1091
1092 public void startListening() {
1093 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
1094 BackgroundThread.getExecutor(), this);
1095 }
1096
1097 /*
1098 * Return null if no such property or wrong format (not comma separated integers).
1099 */
1100 public int[] getBrightnessThresholds() {
1101 return getIntArrayProperty(
1102 DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_BRIGHTNESS_THRESHOLDS);
1103 }
1104
1105 /*
1106 * Return null if no such property or wrong format (not comma separated integers).
1107 */
1108 public int[] getAmbientThresholds() {
1109 return getIntArrayProperty(
1110 DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_AMBIENT_THRESHOLDS);
1111 }
1112
1113 /*
1114 * Return null if no such property
1115 */
1116 public Float getDefaultPeakRefreshRate() {
1117 float defaultPeakRefreshRate = DeviceConfig.getFloat(
1118 DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
1119 DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
1120
1121 if (defaultPeakRefreshRate == -1) {
1122 return null;
1123 }
1124 return defaultPeakRefreshRate;
1125 }
1126
1127 @Override
1128 public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
1129 int[] brightnessThresholds = getBrightnessThresholds();
1130 int[] ambientThresholds = getAmbientThresholds();
1131 Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
1132
1133 mHandler.obtainMessage(MSG_BRIGHTNESS_THRESHOLDS_CHANGED,
1134 new Pair<int[], int[]>(brightnessThresholds, ambientThresholds))
1135 .sendToTarget();
1136 mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
1137 defaultPeakRefreshRate).sendToTarget();
1138 }
1139
1140 private int[] getIntArrayProperty(String prop) {
1141 String strArray = DeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
1142 null);
1143
1144 if (strArray != null) {
1145 return parseIntArray(strArray);
1146 }
1147
1148 return null;
1149 }
1150
1151 private int[] parseIntArray(@NonNull String strArray) {
1152 String[] items = strArray.split(",");
1153 int[] array = new int[items.length];
1154
1155 try {
1156 for (int i = 0; i < array.length; i++) {
1157 array[i] = Integer.parseInt(items[i]);
1158 }
1159 } catch (NumberFormatException e) {
1160 Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
1161 array = null;
1162 }
1163
1164 return array;
1165 }
1166 }
1167
Michael Wrighta3dab232019-02-22 16:54:21 +00001168}