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