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