• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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