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