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.internal.display;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.database.ContentObserver;
22 import android.hardware.display.DisplayManager;
23 import android.hardware.display.DisplayManager.DisplayListener;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.PowerManager;
29 import android.os.SystemClock;
30 import android.os.UserHandle;
31 import android.provider.Settings;
32 import android.util.MathUtils;
33 import android.util.Slog;
34 import android.view.Display;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.io.PrintWriter;
39 
40 /**
41  * BrightnessSynchronizer helps convert between the int (old) system and float
42  * (new) system for storing the brightness. It has methods to convert between the two and also
43  * observes for when one of the settings is changed and syncs this with the other.
44  */
45 public class BrightnessSynchronizer {
46     private static final String TAG = "BrightnessSynchronizer";
47 
48     private static final boolean DEBUG = false;
49     private static final Uri BRIGHTNESS_URI =
50             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
51 
52     private static final long WAIT_FOR_RESPONSE_MILLIS = 200;
53 
54     private static final int MSG_RUN_UPDATE = 1;
55 
56     // The tolerance within which we consider brightness values approximately equal to eachother.
57     // This value is approximately 1/3 of the smallest possible brightness value.
58     public static final float EPSILON = 0.001f;
59 
60     private static int sBrightnessUpdateCount = 1;
61 
62     private final Context mContext;
63     private final BrightnessSyncObserver mBrightnessSyncObserver;
64     private final Clock mClock;
65     private final Handler mHandler;
66 
67     private DisplayManager mDisplayManager;
68     private int mLatestIntBrightness;
69     private float mLatestFloatBrightness;
70     private BrightnessUpdate mCurrentUpdate;
71     private BrightnessUpdate mPendingUpdate;
72 
BrightnessSynchronizer(Context context)73     public BrightnessSynchronizer(Context context) {
74         this(context, Looper.getMainLooper(), SystemClock::uptimeMillis);
75     }
76 
77     @VisibleForTesting
BrightnessSynchronizer(Context context, Looper looper, Clock clock)78     public BrightnessSynchronizer(Context context, Looper looper, Clock clock) {
79         mContext = context;
80         mClock = clock;
81         mBrightnessSyncObserver = new BrightnessSyncObserver();
82         mHandler = new BrightnessSynchronizerHandler(looper);
83     }
84 
85     /**
86      * Starts brightnessSyncObserver to ensure that the float and int brightness values stay
87      * in sync.
88      * This also ensures that values are synchronized at system start up too.
89      * So we force an update to the int value, since float is the source of truth. Fallback to int
90      * value, if float is invalid. If both are invalid, use default float value from config.
91      */
startSynchronizing()92     public void startSynchronizing() {
93         if (mDisplayManager == null) {
94             mDisplayManager = mContext.getSystemService(DisplayManager.class);
95         }
96         if (mBrightnessSyncObserver.isObserving()) {
97             Slog.wtf(TAG, "Brightness sync observer requesting synchronization a second time.");
98             return;
99         }
100         mLatestFloatBrightness = getScreenBrightnessFloat();
101         mLatestIntBrightness = getScreenBrightnessInt();
102         Slog.i(TAG, "Initial brightness readings: " + mLatestIntBrightness + "(int), "
103                 + mLatestFloatBrightness + "(float)");
104 
105         if (!Float.isNaN(mLatestFloatBrightness)) {
106             mPendingUpdate = new BrightnessUpdate(BrightnessUpdate.TYPE_FLOAT,
107                     mLatestFloatBrightness);
108         } else if (mLatestIntBrightness != PowerManager.BRIGHTNESS_INVALID) {
109             mPendingUpdate = new BrightnessUpdate(BrightnessUpdate.TYPE_INT,
110                     mLatestIntBrightness);
111         } else {
112             final float defaultBrightness = mContext.getResources().getFloat(
113                     com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat);
114             mPendingUpdate = new BrightnessUpdate(BrightnessUpdate.TYPE_FLOAT, defaultBrightness);
115             Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
116         }
117 
118         mBrightnessSyncObserver.startObserving(mHandler);
119         mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
120     }
121 
122     /**
123      * Prints data on dumpsys.
124      */
dump(PrintWriter pw)125     public void dump(PrintWriter pw) {
126         pw.println("BrightnessSynchronizer");
127         pw.println("  mLatestIntBrightness=" + mLatestIntBrightness);
128         pw.println("  mLatestFloatBrightness=" + mLatestFloatBrightness);
129         pw.println("  mCurrentUpdate=" + mCurrentUpdate);
130         pw.println("  mPendingUpdate=" + mPendingUpdate);
131     }
132 
133     /**
134      * Converts between the int brightness system and the float brightness system.
135      */
brightnessIntToFloat(int brightnessInt)136     public static float brightnessIntToFloat(int brightnessInt) {
137         if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
138             return PowerManager.BRIGHTNESS_OFF_FLOAT;
139         } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
140             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
141         } else {
142             final float minFloat = PowerManager.BRIGHTNESS_MIN;
143             final float maxFloat = PowerManager.BRIGHTNESS_MAX;
144             final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
145             final float maxInt = PowerManager.BRIGHTNESS_ON;
146             return MathUtils.constrainedMap(minFloat, maxFloat, minInt, maxInt, brightnessInt);
147         }
148     }
149 
150     /**
151      * Converts between the float brightness system and the int brightness system.
152      */
brightnessFloatToInt(float brightnessFloat)153     public static int brightnessFloatToInt(float brightnessFloat) {
154         return Math.round(brightnessFloatToIntRange(brightnessFloat));
155     }
156 
157     /**
158      * Translates specified value from the float brightness system to the int brightness system,
159      * given the min/max of each range. Accounts for special values such as OFF and invalid values.
160      * Value returned as a float primitive (to preserve precision), but is a value within the
161      * int-system range.
162      */
brightnessFloatToIntRange(float brightnessFloat)163     public static float brightnessFloatToIntRange(float brightnessFloat) {
164         if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
165             return PowerManager.BRIGHTNESS_OFF;
166         } else if (Float.isNaN(brightnessFloat)) {
167             return PowerManager.BRIGHTNESS_INVALID;
168         } else {
169             final float minFloat = PowerManager.BRIGHTNESS_MIN;
170             final float maxFloat = PowerManager.BRIGHTNESS_MAX;
171             final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
172             final float maxInt = PowerManager.BRIGHTNESS_ON;
173             return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
174         }
175     }
176 
177     /**
178      * Consumes a brightness change event for the float-based brightness.
179      *
180      * @param brightness Float brightness.
181      */
handleBrightnessChangeFloat(float brightness)182     private void handleBrightnessChangeFloat(float brightness) {
183         mLatestFloatBrightness = brightness;
184         handleBrightnessChange(BrightnessUpdate.TYPE_FLOAT, brightness);
185     }
186 
187     /**
188      * Consumes a brightness change event for the int-based brightness.
189      *
190      * @param brightness Int brightness.
191      */
handleBrightnessChangeInt(int brightness)192     private void handleBrightnessChangeInt(int brightness) {
193         mLatestIntBrightness = brightness;
194         handleBrightnessChange(BrightnessUpdate.TYPE_INT, brightness);
195     }
196 
197     /**
198      * Consumes a brightness change event.
199      *
200      * @param type Type of the brightness change (int/float)
201      * @param brightness brightness.
202      */
handleBrightnessChange(int type, float brightness)203     private void handleBrightnessChange(int type, float brightness) {
204         boolean swallowUpdate = mCurrentUpdate != null
205                 && mCurrentUpdate.swallowUpdate(type, brightness);
206         BrightnessUpdate prevUpdate = null;
207         if (!swallowUpdate) {
208             prevUpdate = mPendingUpdate;
209             mPendingUpdate = new BrightnessUpdate(type, brightness);
210         }
211         runUpdate();
212 
213         // If we created a new update and it is still pending after the update, add a log.
214         if (!swallowUpdate && mPendingUpdate != null) {
215             Slog.i(TAG, "New PendingUpdate: " + mPendingUpdate + ", prev=" + prevUpdate);
216         }
217     }
218 
219     /**
220      * Runs updates for current and pending BrightnessUpdates.
221      */
runUpdate()222     private void runUpdate() {
223         if (DEBUG) {
224             Slog.d(TAG, "Running update mCurrent="  + mCurrentUpdate
225                     + ", mPending=" + mPendingUpdate);
226         }
227 
228         // do-while instead of while to allow mCurrentUpdate to get set if there's a pending update.
229         do {
230             if (mCurrentUpdate != null) {
231                 mCurrentUpdate.update();
232                 if (mCurrentUpdate.isRunning()) {
233                     break; // current update is still running, nothing to do.
234                 } else if (mCurrentUpdate.isCompleted()) {
235                     if (mCurrentUpdate.madeUpdates()) {
236                         Slog.i(TAG, "Completed Update: " + mCurrentUpdate);
237                     }
238                     mCurrentUpdate = null;
239                 }
240             }
241             // No current update any more, lets start the next update if there is one.
242             if (mCurrentUpdate == null && mPendingUpdate != null) {
243                 mCurrentUpdate = mPendingUpdate;
244                 mPendingUpdate = null;
245             }
246         } while (mCurrentUpdate != null);
247     }
248 
249     /**
250      * Gets the stored screen brightness float value from the display brightness setting.
251      * @return brightness
252      */
getScreenBrightnessFloat()253     private float getScreenBrightnessFloat() {
254         return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY);
255     }
256 
257     /**
258      * Gets the stored screen brightness int from the system settings.
259      * @return brightness
260      */
getScreenBrightnessInt()261     private int getScreenBrightnessInt() {
262         return Settings.System.getIntForUser(mContext.getContentResolver(),
263                 Settings.System.SCREEN_BRIGHTNESS, PowerManager.BRIGHTNESS_INVALID,
264                 UserHandle.USER_CURRENT);
265     }
266 
267     /**
268      * Tests whether two brightness float values are within a small enough tolerance
269      * of each other.
270      * @param a first float to compare
271      * @param b second float to compare
272      * @return whether the two values are within a small enough tolerance value
273      */
floatEquals(float a, float b)274     public static boolean floatEquals(float a, float b) {
275         if (a == b) {
276             return true;
277         } else if (Float.isNaN(a) && Float.isNaN(b)) {
278             return true;
279         } else if (Math.abs(a - b) < EPSILON) {
280             return true;
281         } else {
282             return false;
283         }
284     }
285 
286     /**
287      * Encapsulates a brightness change event and contains logic for synchronizing the appropriate
288      * settings for the specified brightness change.
289      */
290     @VisibleForTesting
291     public class BrightnessUpdate {
292         static final int TYPE_INT = 0x1;
293         static final int TYPE_FLOAT = 0x2;
294 
295         private static final int STATE_NOT_STARTED = 1;
296         private static final int STATE_RUNNING = 2;
297         private static final int STATE_COMPLETED = 3;
298 
299         private final int mSourceType;
300         private final float mBrightness;
301 
302         private long mTimeUpdated;
303         private int mState;
304         private int mUpdatedTypes;
305         private int mConfirmedTypes;
306         private int mId;
307 
BrightnessUpdate(int sourceType, float brightness)308         BrightnessUpdate(int sourceType, float brightness) {
309             mId = sBrightnessUpdateCount++;
310             mSourceType = sourceType;
311             mBrightness = brightness;
312             mTimeUpdated = 0;
313             mUpdatedTypes = 0x0;
314             mConfirmedTypes = 0x0;
315             mState = STATE_NOT_STARTED;
316         }
317 
318         @Override
toString()319         public String toString() {
320             return "{[" + mId + "] " + toStringLabel(mSourceType, mBrightness)
321                     + ", mUpdatedTypes=" + mUpdatedTypes + ", mConfirmedTypes=" + mConfirmedTypes
322                     + ", mTimeUpdated=" + mTimeUpdated + "}";
323         }
324 
325         /**
326          * Runs the synchronization process, moving forward through the internal state machine.
327          */
update()328         void update() {
329             if (mState == STATE_NOT_STARTED) {
330                 mState = STATE_RUNNING;
331 
332                 // check if we need to update int
333                 int brightnessInt = getBrightnessAsInt();
334                 if (mLatestIntBrightness != brightnessInt) {
335                     Settings.System.putIntForUser(mContext.getContentResolver(),
336                             Settings.System.SCREEN_BRIGHTNESS, brightnessInt,
337                             UserHandle.USER_CURRENT);
338                     mLatestIntBrightness = brightnessInt;
339                     mUpdatedTypes |= TYPE_INT;
340                 }
341 
342                 // check if we need to update float
343                 float brightnessFloat = getBrightnessAsFloat();
344                 if (!floatEquals(mLatestFloatBrightness, brightnessFloat)) {
345                     mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, brightnessFloat);
346                     mLatestFloatBrightness = brightnessFloat;
347                     mUpdatedTypes |= TYPE_FLOAT;
348                 }
349 
350                 // If we made updates, lets wait for responses.
351                 if (mUpdatedTypes != 0x0) {
352                     // Give some time for our updates to return a confirmation response. If they
353                     // don't return by that time, MSG_RUN_UPDATE will get sent and we will stop
354                     // listening for responses and mark this update as complete.
355                     if (DEBUG) {
356                         Slog.d(TAG, "Sending MSG_RUN_UPDATE for "
357                                 + toStringLabel(mSourceType, mBrightness));
358                     }
359                     Slog.i(TAG, "[" + mId + "] New Update "
360                             + toStringLabel(mSourceType, mBrightness) + " set brightness values: "
361                             + toStringLabel(mUpdatedTypes & TYPE_FLOAT, brightnessFloat) + " "
362                             + toStringLabel(mUpdatedTypes & TYPE_INT, brightnessInt));
363 
364                     mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE,
365                             mClock.uptimeMillis() + WAIT_FOR_RESPONSE_MILLIS);
366                 }
367                 mTimeUpdated = mClock.uptimeMillis();
368             }
369 
370             if (mState == STATE_RUNNING) {
371                 // If we're not waiting on any more confirmations or the time has expired, move to
372                 // completed state.
373                 if (mConfirmedTypes == mUpdatedTypes
374                         || (mTimeUpdated + WAIT_FOR_RESPONSE_MILLIS) < mClock.uptimeMillis()) {
375                     mState = STATE_COMPLETED;
376                 }
377             }
378         }
379 
380         /**
381          * Attempts to consume the specified brightness change if it is determined that the change
382          * is a notification of a change previously made by this class.
383          *
384          * @param type The type of change (int|float)
385          * @param brightness The brightness value.
386          * @return True if the change was caused by this class, thus swallowed.
387          */
swallowUpdate(int type, float brightness)388         boolean swallowUpdate(int type, float brightness) {
389             if ((mUpdatedTypes & type) != type || (mConfirmedTypes & type) != 0x0) {
390                 // It's either a type we didn't update, or one we've already confirmed.
391                 return false;
392             }
393 
394             final boolean floatUpdateConfirmed =
395                     type == TYPE_FLOAT && floatEquals(getBrightnessAsFloat(), brightness);
396             final boolean intUpdateConfirmed =
397                     type == TYPE_INT && getBrightnessAsInt() == (int) brightness;
398 
399             if (floatUpdateConfirmed || intUpdateConfirmed) {
400                 mConfirmedTypes |= type;
401                 Slog.i(TAG, "Swallowing update of " + toStringLabel(type, brightness)
402                         + " by update: " + this);
403                 return true;
404             }
405             return false;
406         }
407 
isRunning()408         boolean isRunning() {
409             return mState == STATE_RUNNING;
410         }
411 
isCompleted()412         boolean isCompleted() {
413             return mState == STATE_COMPLETED;
414         }
415 
madeUpdates()416         boolean madeUpdates() {
417             return mUpdatedTypes != 0x0;
418         }
419 
getBrightnessAsInt()420         private int getBrightnessAsInt() {
421             if (mSourceType == TYPE_INT) {
422                 return (int) mBrightness;
423             }
424             return brightnessFloatToInt(mBrightness);
425         }
426 
getBrightnessAsFloat()427         private float getBrightnessAsFloat() {
428             if (mSourceType == TYPE_FLOAT) {
429                 return mBrightness;
430             }
431             return brightnessIntToFloat((int) mBrightness);
432         }
433 
toStringLabel(int type, float brightness)434         private String toStringLabel(int type, float brightness) {
435             return (type == TYPE_INT) ? ((int) brightness) + "(i)"
436                     : ((type == TYPE_FLOAT) ? brightness + "(f)"
437                     : "");
438         }
439     }
440 
441     /** Functional interface for providing time. */
442     @VisibleForTesting
443     public interface Clock {
444         /** @return system uptime in milliseconds. */
uptimeMillis()445         long uptimeMillis();
446     }
447 
448     class BrightnessSynchronizerHandler extends Handler {
BrightnessSynchronizerHandler(Looper looper)449         BrightnessSynchronizerHandler(Looper looper) {
450             super(looper);
451         }
452 
453         @Override
handleMessage(Message msg)454         public void handleMessage(Message msg) {
455             switch (msg.what) {
456                 case MSG_RUN_UPDATE:
457                     if (DEBUG) {
458                         Slog.d(TAG, "MSG_RUN_UPDATE");
459                     }
460                     runUpdate();
461                     break;
462                 default:
463                     super.handleMessage(msg);
464             }
465 
466         }
467     };
468 
469     private class BrightnessSyncObserver {
470         private boolean mIsObserving;
471 
472         private final DisplayListener mListener = new DisplayListener() {
473             @Override
474             public void onDisplayAdded(int displayId) {}
475 
476             @Override
477             public void onDisplayRemoved(int displayId) {}
478 
479             @Override
480             public void onDisplayChanged(int displayId) {
481                 handleBrightnessChangeFloat(getScreenBrightnessFloat());
482             }
483         };
484 
createBrightnessContentObserver(Handler handler)485         private ContentObserver createBrightnessContentObserver(Handler handler) {
486             return new ContentObserver(handler) {
487                 @Override
488                 public void onChange(boolean selfChange, Uri uri) {
489                     if (selfChange) {
490                         return;
491                     }
492                     if (BRIGHTNESS_URI.equals(uri)) {
493                         handleBrightnessChangeInt(getScreenBrightnessInt());
494                     }
495                 }
496             };
497         }
498 
499         boolean isObserving() {
500             return mIsObserving;
501         }
502 
503         void startObserving(Handler handler) {
504             final ContentResolver cr = mContext.getContentResolver();
505             cr.registerContentObserver(BRIGHTNESS_URI, false,
506                     createBrightnessContentObserver(handler), UserHandle.USER_ALL);
507             mDisplayManager.registerDisplayListener(mListener, handler,
508                     DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
509             mIsObserving = true;
510         }
511     }
512 }
513