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