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.UserHandle;
30 import android.provider.Settings;
31 import android.util.MathUtils;
32 import android.view.Display;
33 
34 /**
35  * BrightnessSynchronizer helps convert between the int (old) system and float
36  * (new) system for storing the brightness. It has methods to convert between the two and also
37  * observes for when one of the settings is changed and syncs this with the other.
38  */
39 public class BrightnessSynchronizer {
40 
41     private static final int MSG_UPDATE_FLOAT = 1;
42     private static final int MSG_UPDATE_INT = 2;
43     private static final int MSG_UPDATE_BOTH = 3;
44 
45     private static final String TAG = "BrightnessSynchronizer";
46     private static final Uri BRIGHTNESS_URI =
47             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
48 
49     // The tolerance within which we consider brightness values approximately equal to eachother.
50     // This value is approximately 1/3 of the smallest possible brightness value.
51     public static final float EPSILON = 0.001f;
52 
53     private DisplayManager mDisplayManager;
54     private final Context mContext;
55 
56     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
57         @Override
58         public void handleMessage(Message msg) {
59             switch (msg.what) {
60                 case MSG_UPDATE_FLOAT:
61                     updateBrightnessFloatFromInt(msg.arg1);
62                     break;
63                 case MSG_UPDATE_INT:
64                     updateBrightnessIntFromFloat(Float.intBitsToFloat(msg.arg1));
65                     break;
66                 case MSG_UPDATE_BOTH:
67                     updateBoth(Float.intBitsToFloat(msg.arg1));
68                     break;
69                 default:
70                     super.handleMessage(msg);
71             }
72 
73         }
74     };
75 
76     private float mPreferredSettingValue;
77 
BrightnessSynchronizer(Context context)78     public BrightnessSynchronizer(Context context) {
79         mContext = context;
80     }
81 
82     /**
83      * Starts brightnessSyncObserver to ensure that the float and int brightness values stay
84      * in sync.
85      * This also ensures that values are synchronized at system start up too.
86      * So we force an update to the int value, since float is the source of truth. Fallback to int
87      * value, if float is invalid. If both are invalid, use default float value from config.
88      */
startSynchronizing()89     public void startSynchronizing() {
90         if (mDisplayManager == null) {
91             mDisplayManager = mContext.getSystemService(DisplayManager.class);
92         }
93 
94         final BrightnessSyncObserver brightnessSyncObserver;
95         brightnessSyncObserver = new BrightnessSyncObserver();
96         brightnessSyncObserver.startObserving();
97 
98         final float currentFloatBrightness = getScreenBrightnessFloat();
99         final int currentIntBrightness = getScreenBrightnessInt(mContext);
100 
101         if (!Float.isNaN(currentFloatBrightness)) {
102             updateBrightnessIntFromFloat(currentFloatBrightness);
103         } else if (currentIntBrightness != -1) {
104             updateBrightnessFloatFromInt(currentIntBrightness);
105         } else {
106             final float defaultBrightness = mContext.getResources().getFloat(
107                     com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat);
108             mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, defaultBrightness);
109 
110         }
111     }
112 
113     /**
114      * Converts between the int brightness system and the float brightness system.
115      */
brightnessIntToFloat(int brightnessInt)116     public static float brightnessIntToFloat(int brightnessInt) {
117         if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
118             return PowerManager.BRIGHTNESS_OFF_FLOAT;
119         } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
120             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
121         } else {
122             final float minFloat = PowerManager.BRIGHTNESS_MIN;
123             final float maxFloat = PowerManager.BRIGHTNESS_MAX;
124             final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
125             final float maxInt = PowerManager.BRIGHTNESS_ON;
126             return MathUtils.constrainedMap(minFloat, maxFloat, minInt, maxInt, brightnessInt);
127         }
128     }
129 
130     /**
131      * Converts between the float brightness system and the int brightness system.
132      */
brightnessFloatToInt(float brightnessFloat)133     public static int brightnessFloatToInt(float brightnessFloat) {
134         return Math.round(brightnessFloatToIntRange(brightnessFloat));
135     }
136 
137     /**
138      * Translates specified value from the float brightness system to the int brightness system,
139      * given the min/max of each range. Accounts for special values such as OFF and invalid values.
140      * Value returned as a float primitive (to preserve precision), but is a value within the
141      * int-system range.
142      */
brightnessFloatToIntRange(float brightnessFloat)143     public static float brightnessFloatToIntRange(float brightnessFloat) {
144         if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
145             return PowerManager.BRIGHTNESS_OFF;
146         } else if (Float.isNaN(brightnessFloat)) {
147             return PowerManager.BRIGHTNESS_INVALID;
148         } else {
149             final float minFloat = PowerManager.BRIGHTNESS_MIN;
150             final float maxFloat = PowerManager.BRIGHTNESS_MAX;
151             final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
152             final float maxInt = PowerManager.BRIGHTNESS_ON;
153             return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
154         }
155     }
156 
157     /**
158      * Gets the stored screen brightness float value from the display brightness setting.
159      * @return brightness
160      */
getScreenBrightnessFloat()161     private float getScreenBrightnessFloat() {
162         return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY);
163     }
164 
165     /**
166      * Gets the stored screen brightness int from the system settings.
167      * @param context for accessing settings
168      *
169      * @return brightness
170      */
getScreenBrightnessInt(Context context)171     private static int getScreenBrightnessInt(Context context) {
172         return Settings.System.getIntForUser(context.getContentResolver(),
173                 Settings.System.SCREEN_BRIGHTNESS, PowerManager.BRIGHTNESS_INVALID,
174                 UserHandle.USER_CURRENT);
175     }
176 
177     /**
178      * Updates the settings based on a passed in int value. This is called whenever the int
179      * setting changes. mPreferredSettingValue holds the most recently updated brightness value
180      * as a float that we would like the display to be set to.
181      *
182      * We then schedule an update to both the int and float settings, but, remove all the other
183      * messages to update all, to prevent us getting stuck in a loop.
184      *
185      * @param value Brightness value as int to store in the float setting.
186      */
updateBrightnessFloatFromInt(int value)187     private void updateBrightnessFloatFromInt(int value) {
188         if (brightnessFloatToInt(mPreferredSettingValue) == value) {
189             return;
190         }
191 
192         mPreferredSettingValue = brightnessIntToFloat(value);
193         final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue);
194         mHandler.removeMessages(MSG_UPDATE_BOTH);
195         mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget();
196     }
197 
198     /**
199      * Updates the settings based on a passed in float value. This is called whenever the float
200      * setting changes. mPreferredSettingValue holds the most recently updated brightness value
201      * as a float that we would like the display to be set to.
202      *
203      * We then schedule an update to both the int and float settings, but, remove all the other
204      * messages to update all, to prevent us getting stuck in a loop.
205      *
206      * @param value Brightness setting as float to store in int setting.
207      */
updateBrightnessIntFromFloat(float value)208     private void updateBrightnessIntFromFloat(float value) {
209         if (floatEquals(mPreferredSettingValue, value)) {
210             return;
211         }
212 
213         mPreferredSettingValue = value;
214         final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue);
215         mHandler.removeMessages(MSG_UPDATE_BOTH);
216         mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget();
217     }
218 
219 
220     /**
221      * Updates both setting values if they have changed
222      * mDisplayManager.setBrightness automatically checks for changes
223      * Settings.System.putIntForUser needs to be checked, to prevent an extra callback to this class
224      *
225      * @param newBrightnessFloat Brightness setting as float to store in both settings
226      */
updateBoth(float newBrightnessFloat)227     private void updateBoth(float newBrightnessFloat) {
228         int newBrightnessInt = brightnessFloatToInt(newBrightnessFloat);
229         mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat);
230         if (getScreenBrightnessInt(mContext) != newBrightnessInt) {
231             Settings.System.putIntForUser(mContext.getContentResolver(),
232                     Settings.System.SCREEN_BRIGHTNESS, newBrightnessInt, UserHandle.USER_CURRENT);
233         }
234 
235     }
236 
237     /**
238      * Tests whether two brightness float values are within a small enough tolerance
239      * of each other.
240      * @param a first float to compare
241      * @param b second float to compare
242      * @return whether the two values are within a small enough tolerance value
243      */
floatEquals(float a, float b)244     public static boolean floatEquals(float a, float b) {
245         if (a == b) {
246             return true;
247         } else if (Float.isNaN(a) && Float.isNaN(b)) {
248             return true;
249         } else if (Math.abs(a - b) < EPSILON) {
250             return true;
251         } else {
252             return false;
253         }
254     }
255 
256     private class BrightnessSyncObserver {
257         private final DisplayListener mListener = new DisplayListener() {
258             @Override
259             public void onDisplayAdded(int displayId) {}
260 
261             @Override
262             public void onDisplayRemoved(int displayId) {}
263 
264             @Override
265             public void onDisplayChanged(int displayId) {
266                 float currentFloat = getScreenBrightnessFloat();
267                 int toSend = Float.floatToIntBits(currentFloat);
268                 mHandler.removeMessages(MSG_UPDATE_INT);
269                 mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget();
270             }
271         };
272 
273         private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
274             @Override
275             public void onChange(boolean selfChange, Uri uri) {
276                 if (selfChange) {
277                     return;
278                 }
279                 if (BRIGHTNESS_URI.equals(uri)) {
280                     int currentBrightness = getScreenBrightnessInt(mContext);
281                     mHandler.removeMessages(MSG_UPDATE_FLOAT);
282                     mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
283                 }
284             }
285         };
286 
startObserving()287         public void startObserving() {
288             final ContentResolver cr = mContext.getContentResolver();
289             cr.unregisterContentObserver(mContentObserver);
290             cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
291                     UserHandle.USER_ALL);
292 
293             mDisplayManager.registerDisplayListener(mListener, mHandler,
294                     DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
295                         | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
296         }
297 
stopObserving()298         public void stopObserving() {
299             final ContentResolver cr = mContext.getContentResolver();
300             cr.unregisterContentObserver(mContentObserver);
301             mDisplayManager.unregisterDisplayListener(mListener);
302         }
303     }
304 }
305