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