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