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