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