1 /*
2  * Copyright (C) 2013 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.systemui.settings;
18 
19 import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX;
20 import static com.android.settingslib.display.BrightnessUtils.convertGammaToLinear;
21 import static com.android.settingslib.display.BrightnessUtils.convertLinearToGamma;
22 
23 import android.animation.ValueAnimator;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.database.ContentObserver;
27 import android.hardware.display.DisplayManager;
28 import android.net.Uri;
29 import android.os.AsyncTask;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.PowerManager;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.provider.Settings;
39 import android.service.vr.IVrManager;
40 import android.service.vr.IVrStateCallbacks;
41 import android.util.Log;
42 
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
45 import com.android.settingslib.RestrictedLockUtilsInternal;
46 import com.android.systemui.Dependency;
47 
48 import java.util.ArrayList;
49 
50 public class BrightnessController implements ToggleSlider.Listener {
51     private static final String TAG = "StatusBar.BrightnessController";
52     private static final int SLIDER_ANIMATION_DURATION = 3000;
53 
54     private static final int MSG_UPDATE_SLIDER = 1;
55     private static final int MSG_SET_CHECKED = 2;
56     private static final int MSG_ATTACH_LISTENER = 3;
57     private static final int MSG_DETACH_LISTENER = 4;
58     private static final int MSG_VR_MODE_CHANGED = 5;
59 
60     private final int mMinimumBacklight;
61     private final int mMaximumBacklight;
62     private final int mDefaultBacklight;
63     private final int mMinimumBacklightForVr;
64     private final int mMaximumBacklightForVr;
65     private final int mDefaultBacklightForVr;
66 
67     private final Context mContext;
68     private final ToggleSlider mControl;
69     private final boolean mAutomaticAvailable;
70     private final DisplayManager mDisplayManager;
71     private final CurrentUserTracker mUserTracker;
72     private final IVrManager mVrManager;
73 
74     private final Handler mBackgroundHandler;
75     private final BrightnessObserver mBrightnessObserver;
76 
77     private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
78             new ArrayList<BrightnessStateChangeCallback>();
79 
80     private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
81     private volatile boolean mIsVrModeEnabled;
82     private boolean mListening;
83     private boolean mExternalChange;
84     private boolean mControlValueInitialized;
85 
86     private ValueAnimator mSliderAnimator;
87 
88     public interface BrightnessStateChangeCallback {
onBrightnessLevelChanged()89         public void onBrightnessLevelChanged();
90     }
91 
92     /** ContentObserver to watch brightness **/
93     private class BrightnessObserver extends ContentObserver {
94 
95         private final Uri BRIGHTNESS_MODE_URI =
96                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE);
97         private final Uri BRIGHTNESS_URI =
98                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
99         private final Uri BRIGHTNESS_FOR_VR_URI =
100                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR);
101 
BrightnessObserver(Handler handler)102         public BrightnessObserver(Handler handler) {
103             super(handler);
104         }
105 
106         @Override
onChange(boolean selfChange)107         public void onChange(boolean selfChange) {
108             onChange(selfChange, null);
109         }
110 
111         @Override
onChange(boolean selfChange, Uri uri)112         public void onChange(boolean selfChange, Uri uri) {
113             if (selfChange) return;
114 
115             if (BRIGHTNESS_MODE_URI.equals(uri)) {
116                 mBackgroundHandler.post(mUpdateModeRunnable);
117                 mBackgroundHandler.post(mUpdateSliderRunnable);
118             } else if (BRIGHTNESS_URI.equals(uri)) {
119                 mBackgroundHandler.post(mUpdateSliderRunnable);
120             } else if (BRIGHTNESS_FOR_VR_URI.equals(uri)) {
121                 mBackgroundHandler.post(mUpdateSliderRunnable);
122             } else {
123                 mBackgroundHandler.post(mUpdateModeRunnable);
124                 mBackgroundHandler.post(mUpdateSliderRunnable);
125             }
126             for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
127                 cb.onBrightnessLevelChanged();
128             }
129         }
130 
startObserving()131         public void startObserving() {
132             final ContentResolver cr = mContext.getContentResolver();
133             cr.unregisterContentObserver(this);
134             cr.registerContentObserver(
135                     BRIGHTNESS_MODE_URI,
136                     false, this, UserHandle.USER_ALL);
137             cr.registerContentObserver(
138                     BRIGHTNESS_URI,
139                     false, this, UserHandle.USER_ALL);
140             cr.registerContentObserver(
141                     BRIGHTNESS_FOR_VR_URI,
142                     false, this, UserHandle.USER_ALL);
143         }
144 
stopObserving()145         public void stopObserving() {
146             final ContentResolver cr = mContext.getContentResolver();
147             cr.unregisterContentObserver(this);
148         }
149 
150     }
151 
152     private final Runnable mStartListeningRunnable = new Runnable() {
153         @Override
154         public void run() {
155             mBrightnessObserver.startObserving();
156             mUserTracker.startTracking();
157 
158             // Update the slider and mode before attaching the listener so we don't
159             // receive the onChanged notifications for the initial values.
160             mUpdateModeRunnable.run();
161             mUpdateSliderRunnable.run();
162 
163             mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
164         }
165     };
166 
167     private final Runnable mStopListeningRunnable = new Runnable() {
168         @Override
169         public void run() {
170             mBrightnessObserver.stopObserving();
171             mUserTracker.stopTracking();
172 
173             mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
174         }
175     };
176 
177     /**
178      * Fetch the brightness mode from the system settings and update the icon. Should be called from
179      * background thread.
180      */
181     private final Runnable mUpdateModeRunnable = new Runnable() {
182         @Override
183         public void run() {
184             if (mAutomaticAvailable) {
185                 int automatic;
186                 automatic = Settings.System.getIntForUser(mContext.getContentResolver(),
187                         Settings.System.SCREEN_BRIGHTNESS_MODE,
188                         Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
189                         UserHandle.USER_CURRENT);
190                 mAutomatic = automatic != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
191             } else {
192                 mHandler.obtainMessage(MSG_SET_CHECKED, 0).sendToTarget();
193             }
194         }
195     };
196 
197     /**
198      * Fetch the brightness from the system settings and update the slider. Should be called from
199      * background thread.
200      */
201     private final Runnable mUpdateSliderRunnable = new Runnable() {
202         @Override
203         public void run() {
204             final int val;
205             final boolean inVrMode = mIsVrModeEnabled;
206             if (inVrMode) {
207                 val = Settings.System.getIntForUser(mContext.getContentResolver(),
208                         Settings.System.SCREEN_BRIGHTNESS_FOR_VR, mDefaultBacklightForVr,
209                         UserHandle.USER_CURRENT);
210             } else {
211                 val = Settings.System.getIntForUser(mContext.getContentResolver(),
212                         Settings.System.SCREEN_BRIGHTNESS, mDefaultBacklight,
213                         UserHandle.USER_CURRENT);
214             }
215             mHandler.obtainMessage(MSG_UPDATE_SLIDER, val, inVrMode ? 1 : 0).sendToTarget();
216         }
217     };
218 
219     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
220         @Override
221         public void onVrStateChanged(boolean enabled) {
222             mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
223                     .sendToTarget();
224         }
225     };
226 
227     private final Handler mHandler = new Handler() {
228         @Override
229         public void handleMessage(Message msg) {
230             mExternalChange = true;
231             try {
232                 switch (msg.what) {
233                     case MSG_UPDATE_SLIDER:
234                         updateSlider(msg.arg1, msg.arg2 != 0);
235                         break;
236                     case MSG_SET_CHECKED:
237                         mControl.setChecked(msg.arg1 != 0);
238                         break;
239                     case MSG_ATTACH_LISTENER:
240                         mControl.setOnChangedListener(BrightnessController.this);
241                         break;
242                     case MSG_DETACH_LISTENER:
243                         mControl.setOnChangedListener(null);
244                         break;
245                     case MSG_VR_MODE_CHANGED:
246                         updateVrMode(msg.arg1 != 0);
247                         break;
248                     default:
249                         super.handleMessage(msg);
250                 }
251             } finally {
252                 mExternalChange = false;
253             }
254         }
255     };
256 
BrightnessController(Context context, ToggleSlider control)257     public BrightnessController(Context context, ToggleSlider control) {
258         mContext = context;
259         mControl = control;
260         mControl.setMax(GAMMA_SPACE_MAX);
261         mBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
262         mUserTracker = new CurrentUserTracker(mContext) {
263             @Override
264             public void onUserSwitched(int newUserId) {
265                 mBackgroundHandler.post(mUpdateModeRunnable);
266                 mBackgroundHandler.post(mUpdateSliderRunnable);
267             }
268         };
269         mBrightnessObserver = new BrightnessObserver(mHandler);
270 
271         PowerManager pm = context.getSystemService(PowerManager.class);
272         mMinimumBacklight = pm.getMinimumScreenBrightnessSetting();
273         mMaximumBacklight = pm.getMaximumScreenBrightnessSetting();
274         mDefaultBacklight = pm.getDefaultScreenBrightnessSetting();
275         mMinimumBacklightForVr = pm.getMinimumScreenBrightnessForVrSetting();
276         mMaximumBacklightForVr = pm.getMaximumScreenBrightnessForVrSetting();
277         mDefaultBacklightForVr = pm.getDefaultScreenBrightnessForVrSetting();
278 
279         mAutomaticAvailable = context.getResources().getBoolean(
280                 com.android.internal.R.bool.config_automatic_brightness_available);
281         mDisplayManager = context.getSystemService(DisplayManager.class);
282         mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
283                 Context.VR_SERVICE));
284     }
285 
addStateChangedCallback(BrightnessStateChangeCallback cb)286     public void addStateChangedCallback(BrightnessStateChangeCallback cb) {
287         mChangeCallbacks.add(cb);
288     }
289 
removeStateChangedCallback(BrightnessStateChangeCallback cb)290     public boolean removeStateChangedCallback(BrightnessStateChangeCallback cb) {
291         return mChangeCallbacks.remove(cb);
292     }
293 
294     @Override
onInit(ToggleSlider control)295     public void onInit(ToggleSlider control) {
296         // Do nothing
297     }
298 
registerCallbacks()299     public void registerCallbacks() {
300         if (mListening) {
301             return;
302         }
303 
304         if (mVrManager != null) {
305             try {
306                 mVrManager.registerListener(mVrStateCallbacks);
307                 mIsVrModeEnabled = mVrManager.getVrModeState();
308             } catch (RemoteException e) {
309                 Log.e(TAG, "Failed to register VR mode state listener: ", e);
310             }
311         }
312 
313         mBackgroundHandler.post(mStartListeningRunnable);
314         mListening = true;
315     }
316 
317     /** Unregister all call backs, both to and from the controller */
unregisterCallbacks()318     public void unregisterCallbacks() {
319         if (!mListening) {
320             return;
321         }
322 
323         if (mVrManager != null) {
324             try {
325                 mVrManager.unregisterListener(mVrStateCallbacks);
326             } catch (RemoteException e) {
327                 Log.e(TAG, "Failed to unregister VR mode state listener: ", e);
328             }
329         }
330 
331         mBackgroundHandler.post(mStopListeningRunnable);
332         mListening = false;
333         mControlValueInitialized = false;
334     }
335 
336     @Override
onChanged(ToggleSlider toggleSlider, boolean tracking, boolean automatic, int value, boolean stopTracking)337     public void onChanged(ToggleSlider toggleSlider, boolean tracking, boolean automatic,
338             int value, boolean stopTracking) {
339         if (mExternalChange) return;
340 
341         if (mSliderAnimator != null) {
342             mSliderAnimator.cancel();
343         }
344 
345         final int min;
346         final int max;
347         final int metric;
348         final String setting;
349 
350         if (mIsVrModeEnabled) {
351             metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR;
352             min = mMinimumBacklightForVr;
353             max = mMaximumBacklightForVr;
354             setting = Settings.System.SCREEN_BRIGHTNESS_FOR_VR;
355         } else {
356             metric = mAutomatic
357                     ? MetricsEvent.ACTION_BRIGHTNESS_AUTO
358                     : MetricsEvent.ACTION_BRIGHTNESS;
359             min = mMinimumBacklight;
360             max = mMaximumBacklight;
361             setting = Settings.System.SCREEN_BRIGHTNESS;
362         }
363 
364         final int val = convertGammaToLinear(value, min, max);
365 
366         if (stopTracking) {
367             MetricsLogger.action(mContext, metric, val);
368         }
369 
370         setBrightness(val);
371         if (!tracking) {
372             AsyncTask.execute(new Runnable() {
373                     public void run() {
374                         Settings.System.putIntForUser(mContext.getContentResolver(),
375                                 setting, val, UserHandle.USER_CURRENT);
376                     }
377                 });
378         }
379 
380         for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
381             cb.onBrightnessLevelChanged();
382         }
383     }
384 
checkRestrictionAndSetEnabled()385     public void checkRestrictionAndSetEnabled() {
386         mBackgroundHandler.post(new Runnable() {
387             @Override
388             public void run() {
389                 ((ToggleSliderView)mControl).setEnforcedAdmin(
390                         RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
391                                 UserManager.DISALLOW_CONFIG_BRIGHTNESS,
392                                 mUserTracker.getCurrentUserId()));
393             }
394         });
395     }
396 
setMode(int mode)397     private void setMode(int mode) {
398         Settings.System.putIntForUser(mContext.getContentResolver(),
399                 Settings.System.SCREEN_BRIGHTNESS_MODE, mode,
400                 mUserTracker.getCurrentUserId());
401     }
402 
setBrightness(int brightness)403     private void setBrightness(int brightness) {
404         mDisplayManager.setTemporaryBrightness(brightness);
405     }
406 
updateVrMode(boolean isEnabled)407     private void updateVrMode(boolean isEnabled) {
408         if (mIsVrModeEnabled != isEnabled) {
409             mIsVrModeEnabled = isEnabled;
410             mBackgroundHandler.post(mUpdateSliderRunnable);
411         }
412     }
413 
updateSlider(int val, boolean inVrMode)414     private void updateSlider(int val, boolean inVrMode) {
415         final int min;
416         final int max;
417         if (inVrMode) {
418             min = mMinimumBacklightForVr;
419             max = mMaximumBacklightForVr;
420         } else {
421             min = mMinimumBacklight;
422             max = mMaximumBacklight;
423         }
424         if (val == convertGammaToLinear(mControl.getValue(), min, max)) {
425             // If we have more resolution on the slider than we do in the actual setting, then
426             // multiple slider positions will map to the same setting value. Thus, if we see a
427             // setting value here that maps to the current slider position, we don't bother to
428             // calculate the new slider position since it may differ and look like a brightness
429             // change to the user even though it isn't one.
430             return;
431         }
432         final int sliderVal = convertLinearToGamma(val, min, max);
433         animateSliderTo(sliderVal);
434     }
435 
animateSliderTo(int target)436     private void animateSliderTo(int target) {
437         if (!mControlValueInitialized) {
438             // Don't animate the first value since its default state isn't meaningful to users.
439             mControl.setValue(target);
440             mControlValueInitialized = true;
441         }
442         if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
443             mSliderAnimator.cancel();
444         }
445         mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
446         mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
447             mExternalChange = true;
448             mControl.setValue((int) animation.getAnimatedValue());
449             mExternalChange = false;
450         });
451         final long animationDuration = SLIDER_ANIMATION_DURATION * Math.abs(
452                 mControl.getValue() - target) / GAMMA_SPACE_MAX;
453         mSliderAnimator.setDuration(animationDuration);
454         mSliderAnimator.start();
455     }
456 
457 }
458