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;
18 
19 
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.database.ContentObserver;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.os.PowerManager;
27 import android.os.UserHandle;
28 import android.provider.Settings;
29 import android.util.MathUtils;
30 
31 import java.util.LinkedList;
32 import java.util.Queue;
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 
44     private static final String TAG = "BrightnessSynchronizer";
45     private static final Uri BRIGHTNESS_URI =
46             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
47     private static final Uri BRIGHTNESS_FLOAT_URI =
48             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT);
49 
50     // The tolerance within which we consider brightness values approximately equal to eachother.
51     // This value is approximately 1/3 of the smallest possible brightness value.
52     public static final float EPSILON = 0.001f;
53 
54     private final Context mContext;
55 
56     private final Queue<Object> mWriteHistory = new LinkedList<>();
57 
58     private final Handler mHandler = new Handler() {
59         @Override
60         public void handleMessage(Message msg) {
61             switch (msg.what) {
62                 case MSG_UPDATE_FLOAT:
63                     updateBrightnessFloatFromInt(msg.arg1);
64                     break;
65                 case MSG_UPDATE_INT:
66                     updateBrightnessIntFromFloat(Float.intBitsToFloat(msg.arg1));
67                     break;
68                 default:
69                     super.handleMessage(msg);
70             }
71 
72         }
73     };
74 
75 
BrightnessSynchronizer(Context context)76     public BrightnessSynchronizer(Context context) {
77         final BrightnessSyncObserver mBrightnessSyncObserver;
78         mContext = context;
79         mBrightnessSyncObserver = new BrightnessSyncObserver(mHandler);
80         mBrightnessSyncObserver.startObserving();
81     }
82 
83     /**
84      * Converts between the int brightness system and the float brightness system.
85      */
brightnessIntToFloat(Context context, int brightnessInt)86     public static float brightnessIntToFloat(Context context, int brightnessInt) {
87         final PowerManager pm = context.getSystemService(PowerManager.class);
88         final float pmMinBrightness = pm.getBrightnessConstraint(
89                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
90         final float pmMaxBrightness = pm.getBrightnessConstraint(
91                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
92         final int minBrightnessInt = Math.round(brightnessFloatToIntRange(pmMinBrightness,
93                 PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
94                 PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
95         final int maxBrightnessInt = Math.round(brightnessFloatToIntRange(pmMaxBrightness,
96                 PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
97                 PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
98 
99         return brightnessIntToFloat(brightnessInt, minBrightnessInt, maxBrightnessInt,
100                 pmMinBrightness, pmMaxBrightness);
101     }
102 
103     /**
104      * Converts between the int brightness system and the float brightness system.
105      */
brightnessIntToFloat(int brightnessInt, int minInt, int maxInt, float minFloat, float maxFloat)106     public static float brightnessIntToFloat(int brightnessInt, int minInt, int maxInt,
107             float minFloat, float maxFloat) {
108         if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
109             return PowerManager.BRIGHTNESS_OFF_FLOAT;
110         } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
111             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
112         } else {
113             return MathUtils.constrainedMap(minFloat, maxFloat, (float) minInt, (float) maxInt,
114                     brightnessInt);
115         }
116     }
117 
118     /**
119      * Converts between the float brightness system and the int brightness system.
120      */
brightnessFloatToInt(Context context, float brightnessFloat)121     public static int brightnessFloatToInt(Context context, float brightnessFloat) {
122         return Math.round(brightnessFloatToIntRange(context, brightnessFloat));
123     }
124 
125     /**
126      * Converts between the float brightness system and the int brightness system, but returns
127      * the converted value as a float within the int-system's range. This method helps with
128      * conversions from one system to the other without losing the floating-point precision.
129      */
brightnessFloatToIntRange(Context context, float brightnessFloat)130     public static float brightnessFloatToIntRange(Context context, float brightnessFloat) {
131         final PowerManager pm = context.getSystemService(PowerManager.class);
132         final float minFloat = pm.getBrightnessConstraint(
133                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
134         final float maxFloat = pm.getBrightnessConstraint(
135                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
136         final float minInt = brightnessFloatToIntRange(minFloat,
137                 PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
138                 PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
139         final float maxInt = brightnessFloatToIntRange(maxFloat,
140                 PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
141                 PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
142         return brightnessFloatToIntRange(brightnessFloat, minFloat, maxFloat, minInt, maxInt);
143     }
144 
145     /**
146      * Translates specified value from the float brightness system to the int brightness system,
147      * given the min/max of each range.  Accounts for special values such as OFF and invalid values.
148      * Value returned as a float privimite (to preserve precision), but is a value within the
149      * int-system range.
150      */
brightnessFloatToIntRange(float brightnessFloat, float minFloat, float maxFloat, float minInt, float maxInt)151     private static float brightnessFloatToIntRange(float brightnessFloat, float minFloat,
152             float maxFloat, float minInt, float maxInt) {
153         if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
154             return PowerManager.BRIGHTNESS_OFF;
155         } else if (Float.isNaN(brightnessFloat)) {
156             return PowerManager.BRIGHTNESS_INVALID;
157         } else {
158             return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
159         }
160     }
161 
getScreenBrightnessFloat(Context context)162     private static float getScreenBrightnessFloat(Context context) {
163         return Settings.System.getFloatForUser(context.getContentResolver(),
164                 Settings.System.SCREEN_BRIGHTNESS_FLOAT, Float.NaN, UserHandle.USER_CURRENT);
165     }
166 
getScreenBrightnessInt(Context context)167     private static int getScreenBrightnessInt(Context context) {
168         return Settings.System.getIntForUser(context.getContentResolver(),
169                 Settings.System.SCREEN_BRIGHTNESS, 0, UserHandle.USER_CURRENT);
170     }
171 
172     private float mPreferredSettingValue;
173 
174     /**
175      * Updates the float setting based on a passed in int value. This is called whenever the int
176      * setting changes. mWriteHistory keeps a record of the values that been written to the settings
177      * from either this method or updateBrightnessIntFromFloat. This is to ensure that the value
178      * being set is due to an external value being set, rather than the updateBrightness* methods.
179      * The intention of this is to avoid race conditions when the setting is being changed
180      * frequently and to ensure we are not reacting to settings changes from this file.
181      * @param value Brightness value as int to store in the float setting.
182      */
updateBrightnessFloatFromInt(int value)183     private void updateBrightnessFloatFromInt(int value) {
184         Object topOfQueue = mWriteHistory.peek();
185         if (topOfQueue != null && topOfQueue.equals(value)) {
186             mWriteHistory.poll();
187         } else {
188             if (brightnessFloatToInt(mContext, mPreferredSettingValue) == value) {
189                 return;
190             }
191             float newBrightnessFloat = brightnessIntToFloat(mContext, value);
192             mWriteHistory.offer(newBrightnessFloat);
193             mPreferredSettingValue = newBrightnessFloat;
194             Settings.System.putFloatForUser(mContext.getContentResolver(),
195                     Settings.System.SCREEN_BRIGHTNESS_FLOAT, newBrightnessFloat,
196                     UserHandle.USER_CURRENT);
197         }
198     }
199 
200     /**
201      * Updates the int setting based on a passed in float value. This is called whenever the float
202      * setting changes. mWriteHistory keeps a record of the values that been written to the settings
203      * from either this method or updateBrightnessFloatFromInt. This is to ensure that the value
204      * being set is due to an external value being set, rather than the updateBrightness* methods.
205      * The intention of this is to avoid race conditions when the setting is being changed
206      * frequently and to ensure we are not reacting to settings changes from this file.
207      * @param value Brightness setting as float to store in int setting.
208      */
updateBrightnessIntFromFloat(float value)209     private void updateBrightnessIntFromFloat(float value) {
210         int newBrightnessInt = brightnessFloatToInt(mContext, value);
211         Object topOfQueue = mWriteHistory.peek();
212         if (topOfQueue != null && topOfQueue.equals(value)) {
213             mWriteHistory.poll();
214         } else {
215             mWriteHistory.offer(newBrightnessInt);
216             mPreferredSettingValue = value;
217             Settings.System.putIntForUser(mContext.getContentResolver(),
218                     Settings.System.SCREEN_BRIGHTNESS, newBrightnessInt, UserHandle.USER_CURRENT);
219         }
220     }
221 
222     /**
223      * Tests whether two brightness float values are within a small enough tolerance
224      * of each other.
225      * @param a first float to compare
226      * @param b second float to compare
227      * @return whether the two values are within a small enough tolerance value
228      */
floatEquals(float a, float b)229     public static boolean floatEquals(float a, float b) {
230         if (a == b) {
231             return true;
232         } else if (Float.isNaN(a) && Float.isNaN(b)) {
233             return true;
234         } else if (Math.abs(a - b) < EPSILON) {
235             return true;
236         } else {
237             return false;
238         }
239     }
240 
241     private class BrightnessSyncObserver extends ContentObserver {
242         /**
243          * Creates a content observer.
244          * @param handler The handler to run {@link #onChange} on, or null if none.
245          */
BrightnessSyncObserver(Handler handler)246         BrightnessSyncObserver(Handler handler) {
247             super(handler);
248         }
249 
250         @Override
onChange(boolean selfChange)251         public void onChange(boolean selfChange) {
252             onChange(selfChange, null);
253         }
254 
255         @Override
onChange(boolean selfChange, Uri uri)256         public void onChange(boolean selfChange, Uri uri) {
257             if (selfChange) {
258                 return;
259             }
260             if (BRIGHTNESS_URI.equals(uri)) {
261                 int currentBrightness = getScreenBrightnessInt(mContext);
262                 mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
263             } else if (BRIGHTNESS_FLOAT_URI.equals(uri)) {
264                 float currentFloat = getScreenBrightnessFloat(mContext);
265                 int toSend = Float.floatToIntBits(currentFloat);
266                 mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget();
267             }
268         }
269 
startObserving()270         public void startObserving() {
271             final ContentResolver cr = mContext.getContentResolver();
272             cr.unregisterContentObserver(this);
273             cr.registerContentObserver(BRIGHTNESS_URI, false, this, UserHandle.USER_ALL);
274             cr.registerContentObserver(BRIGHTNESS_FLOAT_URI, false, this, UserHandle.USER_ALL);
275         }
276 
stopObserving()277         public void stopObserving() {
278             final ContentResolver cr = mContext.getContentResolver();
279             cr.unregisterContentObserver(this);
280         }
281     }
282 }
283