1 /*
2  * Copyright (C) 2020 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.brightness;
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.annotation.NonNull;
25 import android.content.Context;
26 import android.database.ContentObserver;
27 import android.hardware.display.BrightnessInfo;
28 import android.hardware.display.DisplayManager;
29 import android.net.Uri;
30 import android.os.AsyncTask;
31 import android.os.Handler;
32 import android.os.HandlerExecutor;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.PowerManager;
36 import android.os.RemoteException;
37 import android.os.UserHandle;
38 import android.os.UserManager;
39 import android.provider.Settings;
40 import android.service.vr.IVrManager;
41 import android.service.vr.IVrStateCallbacks;
42 import android.util.Log;
43 import android.util.MathUtils;
44 
45 import androidx.annotation.Nullable;
46 
47 import com.android.internal.display.BrightnessSynchronizer;
48 import com.android.internal.logging.MetricsLogger;
49 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
50 import com.android.settingslib.RestrictedLockUtils;
51 import com.android.settingslib.RestrictedLockUtilsInternal;
52 import com.android.systemui.dagger.qualifiers.Background;
53 import com.android.systemui.dagger.qualifiers.Main;
54 import com.android.systemui.settings.DisplayTracker;
55 import com.android.systemui.settings.UserTracker;
56 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
57 import com.android.systemui.util.settings.SecureSettings;
58 
59 import java.util.concurrent.Executor;
60 
61 import dagger.assisted.Assisted;
62 import dagger.assisted.AssistedFactory;
63 import dagger.assisted.AssistedInject;
64 
65 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
66     private static final String TAG = "CentralSurfaces.BrightnessController";
67     private static final int SLIDER_ANIMATION_DURATION = 3000;
68 
69     private static final int MSG_UPDATE_SLIDER = 1;
70     private static final int MSG_ATTACH_LISTENER = 2;
71     private static final int MSG_DETACH_LISTENER = 3;
72     private static final int MSG_VR_MODE_CHANGED = 4;
73 
74     private static final Uri BRIGHTNESS_MODE_URI =
75             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE);
76 
77     private final int mDisplayId;
78     private final Context mContext;
79     private final ToggleSlider mControl;
80     private final DisplayManager mDisplayManager;
81     private final UserTracker mUserTracker;
82     private final DisplayTracker mDisplayTracker;
83     @Nullable
84     private final IVrManager mVrManager;
85 
86     private final SecureSettings mSecureSettings;
87 
88     private final Executor mMainExecutor;
89     private final Handler mBackgroundHandler;
90     private final BrightnessObserver mBrightnessObserver;
91 
92     private final DisplayTracker.Callback mBrightnessListener = new DisplayTracker.Callback() {
93         @Override
94         public void onDisplayChanged(int displayId) {
95             mBackgroundHandler.post(mUpdateSliderRunnable);
96         }
97     };
98 
99     private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
100     private volatile boolean mIsVrModeEnabled;
101     private boolean mListening;
102     private boolean mExternalChange;
103     private boolean mControlValueInitialized;
104     private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN;
105     private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX;
106 
107     private ValueAnimator mSliderAnimator;
108 
109     @Override
setMirror(BrightnessMirrorController controller)110     public void setMirror(BrightnessMirrorController controller) {
111         mControl.setMirrorControllerAndMirror(controller);
112     }
113 
114     /** ContentObserver to watch brightness */
115     private class BrightnessObserver extends ContentObserver {
116 
117         private boolean mObserving = false;
118 
BrightnessObserver(Handler handler)119         BrightnessObserver(Handler handler) {
120             super(handler);
121         }
122 
123         @Override
onChange(boolean selfChange, Uri uri)124         public void onChange(boolean selfChange, Uri uri) {
125             if (selfChange) return;
126 
127             if (BRIGHTNESS_MODE_URI.equals(uri)) {
128                 mBackgroundHandler.post(mUpdateModeRunnable);
129                 mBackgroundHandler.post(mUpdateSliderRunnable);
130             } else {
131                 mBackgroundHandler.post(mUpdateModeRunnable);
132                 mBackgroundHandler.post(mUpdateSliderRunnable);
133             }
134         }
135 
startObserving()136         public void startObserving() {
137             if (!mObserving) {
138                 mObserving = true;
139                 mSecureSettings.registerContentObserverForUser(
140                         BRIGHTNESS_MODE_URI,
141                         false, this, UserHandle.USER_ALL);
142             }
143         }
144 
stopObserving()145         public void stopObserving() {
146             mSecureSettings.unregisterContentObserver(this);
147             mObserving = false;
148         }
149 
150     }
151 
152     private final Runnable mStartListeningRunnable = new Runnable() {
153         @Override
154         public void run() {
155             if (mListening) {
156                 return;
157             }
158             mListening = true;
159 
160             if (mVrManager != null) {
161                 try {
162                     mVrManager.registerListener(mVrStateCallbacks);
163                     mIsVrModeEnabled = mVrManager.getVrModeState();
164                 } catch (RemoteException e) {
165                     Log.e(TAG, "Failed to register VR mode state listener: ", e);
166                 }
167             }
168 
169             mBrightnessObserver.startObserving();
170             mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
171                     new HandlerExecutor(mMainHandler));
172             mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
173 
174             // Update the slider and mode before attaching the listener so we don't
175             // receive the onChanged notifications for the initial values.
176             mUpdateModeRunnable.run();
177             mUpdateSliderRunnable.run();
178 
179             mMainHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
180         }
181     };
182 
183     private final Runnable mStopListeningRunnable = new Runnable() {
184         @Override
185         public void run() {
186             if (!mListening) {
187                 return;
188             }
189             mListening = false;
190 
191             if (mVrManager != null) {
192                 try {
193                     mVrManager.unregisterListener(mVrStateCallbacks);
194                 } catch (RemoteException e) {
195                     Log.e(TAG, "Failed to unregister VR mode state listener: ", e);
196                 }
197             }
198 
199             mBrightnessObserver.stopObserving();
200             mDisplayTracker.removeCallback(mBrightnessListener);
201             mUserTracker.removeCallback(mUserChangedCallback);
202 
203             mMainHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
204         }
205     };
206 
207     /**
208      * Fetch the brightness mode from the system settings and update the icon. Should be called from
209      * background thread.
210      */
211     private final Runnable mUpdateModeRunnable = new Runnable() {
212         @Override
213         public void run() {
214             int automatic;
215             automatic = Settings.System.getIntForUser(mContext.getContentResolver(),
216                     Settings.System.SCREEN_BRIGHTNESS_MODE,
217                     Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
218                     mUserTracker.getUserId());
219             mAutomatic = automatic != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
220         }
221     };
222 
223     /**
224      * Fetch the brightness from the system settings and update the slider. Should be called from
225      * background thread.
226      */
227     private final Runnable mUpdateSliderRunnable = new Runnable() {
228         @Override
229         public void run() {
230             final boolean inVrMode = mIsVrModeEnabled;
231             final BrightnessInfo info = mContext.getDisplay().getBrightnessInfo();
232             if (info == null) {
233                 return;
234             }
235             mBrightnessMax = info.brightnessMaximum;
236             mBrightnessMin = info.brightnessMinimum;
237             // Value is passed as intbits, since this is what the message takes.
238             final int valueAsIntBits = Float.floatToIntBits(info.brightness);
239             mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
240                     inVrMode ? 1 : 0).sendToTarget();
241         }
242     };
243 
244     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
245         @Override
246         public void onVrStateChanged(boolean enabled) {
247             mMainHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
248                     .sendToTarget();
249         }
250     };
251 
252     private final Handler.Callback mHandlerCallback = new Handler.Callback() {
253         @Override
254         public boolean handleMessage(Message msg) {
255             mExternalChange = true;
256             try {
257                 switch (msg.what) {
258                     case MSG_UPDATE_SLIDER:
259                         updateSlider(Float.intBitsToFloat(msg.arg1), msg.arg2 != 0);
260                         break;
261                     case MSG_ATTACH_LISTENER:
262                         mControl.setOnChangedListener(BrightnessController.this);
263                         break;
264                     case MSG_DETACH_LISTENER:
265                         mControl.setOnChangedListener(null);
266                         break;
267                     case MSG_VR_MODE_CHANGED:
268                         updateVrMode(msg.arg1 != 0);
269                         break;
270                     default:
271                         return false;
272 
273                 }
274             } finally {
275                 mExternalChange = false;
276             }
277             return true;
278         }
279     };
280 
281     private final Handler mMainHandler;
282 
283     private final UserTracker.Callback mUserChangedCallback =
284             new UserTracker.Callback() {
285                 @Override
286                 public void onUserChanged(int newUser, @NonNull Context userContext) {
287                     mBackgroundHandler.post(mUpdateModeRunnable);
288                     mBackgroundHandler.post(mUpdateSliderRunnable);
289                 }
290             };
291 
292     @AssistedInject
BrightnessController( Context context, @Assisted ToggleSlider control, UserTracker userTracker, DisplayTracker displayTracker, DisplayManager displayManager, SecureSettings secureSettings, @Nullable IVrManager iVrManager, @Main Executor mainExecutor, @Main Looper mainLooper, @Background Handler bgHandler)293     public BrightnessController(
294             Context context,
295             @Assisted ToggleSlider control,
296             UserTracker userTracker,
297             DisplayTracker displayTracker,
298             DisplayManager displayManager,
299             SecureSettings secureSettings,
300             @Nullable IVrManager iVrManager,
301             @Main Executor mainExecutor,
302             @Main Looper mainLooper,
303             @Background Handler bgHandler) {
304         mContext = context;
305         mControl = control;
306         mControl.setMax(GAMMA_SPACE_MAX);
307         mMainExecutor = mainExecutor;
308         mBackgroundHandler = bgHandler;
309         mUserTracker = userTracker;
310         mDisplayTracker = displayTracker;
311         mSecureSettings = secureSettings;
312         mDisplayId = mContext.getDisplayId();
313         mDisplayManager = displayManager;
314         mVrManager = iVrManager;
315 
316         mMainHandler = new Handler(mainLooper, mHandlerCallback);
317         mBrightnessObserver = new BrightnessObserver(mMainHandler);
318     }
319 
registerCallbacks()320     public void registerCallbacks() {
321         mBackgroundHandler.removeCallbacks(mStartListeningRunnable);
322         mBackgroundHandler.post(mStartListeningRunnable);
323     }
324 
325     /** Unregister all call backs, both to and from the controller */
unregisterCallbacks()326     public void unregisterCallbacks() {
327         mBackgroundHandler.removeCallbacks(mStopListeningRunnable);
328         mBackgroundHandler.post(mStopListeningRunnable);
329         mControlValueInitialized = false;
330     }
331 
332     @Override
onChanged(boolean tracking, int value, boolean stopTracking)333     public void onChanged(boolean tracking, int value, boolean stopTracking) {
334         if (mExternalChange) return;
335 
336         if (mSliderAnimator != null) {
337             mSliderAnimator.cancel();
338         }
339 
340         final float minBacklight;
341         final float maxBacklight;
342         final int metric;
343 
344 
345         metric = mAutomatic
346                 ? MetricsEvent.ACTION_BRIGHTNESS_AUTO
347                 : MetricsEvent.ACTION_BRIGHTNESS;
348         minBacklight = mBrightnessMin;
349         maxBacklight = mBrightnessMax;
350         final float valFloat = MathUtils.min(
351                 convertGammaToLinearFloat(value, minBacklight, maxBacklight),
352                 maxBacklight);
353         if (stopTracking) {
354             // TODO(brightnessfloat): change to use float value instead.
355             MetricsLogger.action(mContext, metric,
356                     BrightnessSynchronizer.brightnessFloatToInt(valFloat));
357 
358         }
359         setBrightness(valFloat);
360         if (!tracking) {
361             AsyncTask.execute(new Runnable() {
362                     public void run() {
363                         mDisplayManager.setBrightness(mDisplayId, valFloat);
364                     }
365                 });
366         }
367     }
368 
checkRestrictionAndSetEnabled()369     public void checkRestrictionAndSetEnabled() {
370         mBackgroundHandler.post(new Runnable() {
371             @Override
372             public void run() {
373                 int userId = mUserTracker.getUserId();
374                 RestrictedLockUtils.EnforcedAdmin enforcedAdmin =
375                         RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
376                                 mContext,
377                                 UserManager.DISALLOW_CONFIG_BRIGHTNESS,
378                                 userId);
379                 if (enforcedAdmin == null && UserManager.get(mContext).hasBaseUserRestriction(
380                         UserManager.DISALLOW_CONFIG_BRIGHTNESS,
381                         UserHandle.of(userId))) {
382                     enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin();
383                 }
384                 mControl.setEnforcedAdmin(enforcedAdmin);
385             }
386         });
387     }
388 
hideSlider()389     public void hideSlider() {
390         mControl.hideView();
391     }
392 
showSlider()393     public void showSlider() {
394         mControl.showView();
395     }
396 
setBrightness(float brightness)397     private void setBrightness(float brightness) {
398         mDisplayManager.setTemporaryBrightness(mDisplayId, brightness);
399     }
400 
updateVrMode(boolean isEnabled)401     private void updateVrMode(boolean isEnabled) {
402         if (mIsVrModeEnabled != isEnabled) {
403             mIsVrModeEnabled = isEnabled;
404             mBackgroundHandler.post(mUpdateSliderRunnable);
405         }
406     }
407 
updateSlider(float brightnessValue, boolean inVrMode)408     private void updateSlider(float brightnessValue, boolean inVrMode) {
409         final float min = mBrightnessMin;
410         final float max = mBrightnessMax;
411 
412         // Ensure the slider is in a fixed position first, then check if we should animate.
413         if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
414             mSliderAnimator.cancel();
415         }
416         // convertGammaToLinearFloat returns 0-1
417         if (BrightnessSynchronizer.floatEquals(brightnessValue,
418                 convertGammaToLinearFloat(mControl.getValue(), min, max))) {
419             // If the value in the slider is equal to the value on the current brightness
420             // then the slider does not need to animate, since the brightness will not change.
421             return;
422         }
423         // Returns GAMMA_SPACE_MIN - GAMMA_SPACE_MAX
424         final int sliderVal = convertLinearToGammaFloat(brightnessValue, min, max);
425         animateSliderTo(sliderVal);
426     }
427 
animateSliderTo(int target)428     private void animateSliderTo(int target) {
429         if (!mControlValueInitialized || !mControl.isVisible()) {
430             // Don't animate the first value since its default state isn't meaningful to users.
431             // We also don't want to animate slider if it's not visible - especially important when
432             // two sliders are active at the same time in split shade (one in QS and one in QQS),
433             // as this negatively affects transition between them and they share mirror slider -
434             // animating it from two different sources causes janky motion
435             mControl.setValue(target);
436             mControlValueInitialized = true;
437         }
438         mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
439         mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
440             mExternalChange = true;
441             mControl.setValue((int) animation.getAnimatedValue());
442             mExternalChange = false;
443         });
444         final long animationDuration = SLIDER_ANIMATION_DURATION * Math.abs(
445                 mControl.getValue() - target) / GAMMA_SPACE_MAX;
446         mSliderAnimator.setDuration(animationDuration);
447         mSliderAnimator.start();
448     }
449 
450 
451 
452     /** Factory for creating a {@link BrightnessController}. */
453     @AssistedFactory
454     public interface Factory {
455         /** Create a {@link BrightnessController} */
create(ToggleSlider toggleSlider)456         BrightnessController create(ToggleSlider toggleSlider);
457     }
458 }
459