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