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