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.ContentResolver; 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.hardware.display.DisplayManager.DisplayListener; 30 import android.net.Uri; 31 import android.os.AsyncTask; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.os.PowerManager; 35 import android.os.RemoteException; 36 import android.os.ServiceManager; 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 com.android.internal.display.BrightnessSynchronizer; 46 import com.android.internal.logging.MetricsLogger; 47 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 48 import com.android.settingslib.RestrictedLockUtilsInternal; 49 import com.android.systemui.broadcast.BroadcastDispatcher; 50 import com.android.systemui.dagger.qualifiers.Background; 51 import com.android.systemui.settings.CurrentUserTracker; 52 import com.android.systemui.statusbar.policy.BrightnessMirrorController; 53 54 import javax.inject.Inject; 55 56 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController { 57 private static final String TAG = "StatusBar.BrightnessController"; 58 private static final int SLIDER_ANIMATION_DURATION = 3000; 59 60 private static final int MSG_UPDATE_SLIDER = 1; 61 private static final int MSG_ATTACH_LISTENER = 2; 62 private static final int MSG_DETACH_LISTENER = 3; 63 private static final int MSG_VR_MODE_CHANGED = 4; 64 65 private static final Uri BRIGHTNESS_MODE_URI = 66 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE); 67 private static final Uri BRIGHTNESS_FOR_VR_FLOAT_URI = 68 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT); 69 70 private final float mMinimumBacklightForVr; 71 private final float mMaximumBacklightForVr; 72 73 private final int mDisplayId; 74 private final Context mContext; 75 private final ToggleSlider mControl; 76 private final DisplayManager mDisplayManager; 77 private final CurrentUserTracker mUserTracker; 78 private final IVrManager mVrManager; 79 80 private final Handler mBackgroundHandler; 81 private final BrightnessObserver mBrightnessObserver; 82 83 private final DisplayListener mDisplayListener = new DisplayListener() { 84 @Override 85 public void onDisplayAdded(int displayId) {} 86 87 @Override 88 public void onDisplayRemoved(int displayId) {} 89 90 @Override 91 public void onDisplayChanged(int displayId) { 92 mBackgroundHandler.post(mUpdateSliderRunnable); 93 } 94 }; 95 96 private volatile boolean mAutomatic; // Brightness adjusted automatically using ambient light. 97 private volatile boolean mIsVrModeEnabled; 98 private boolean mListening; 99 private boolean mExternalChange; 100 private boolean mControlValueInitialized; 101 private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN; 102 private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX; 103 104 private ValueAnimator mSliderAnimator; 105 106 @Override setMirror(BrightnessMirrorController controller)107 public void setMirror(BrightnessMirrorController controller) { 108 mControl.setMirrorControllerAndMirror(controller); 109 } 110 111 /** ContentObserver to watch brightness */ 112 private class BrightnessObserver extends ContentObserver { 113 BrightnessObserver(Handler handler)114 BrightnessObserver(Handler handler) { 115 super(handler); 116 } 117 118 @Override onChange(boolean selfChange, Uri uri)119 public void onChange(boolean selfChange, Uri uri) { 120 if (selfChange) return; 121 122 if (BRIGHTNESS_MODE_URI.equals(uri)) { 123 mBackgroundHandler.post(mUpdateModeRunnable); 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 } 132 startObserving()133 public void startObserving() { 134 final ContentResolver cr = mContext.getContentResolver(); 135 cr.unregisterContentObserver(this); 136 cr.registerContentObserver( 137 BRIGHTNESS_MODE_URI, 138 false, this, UserHandle.USER_ALL); 139 cr.registerContentObserver( 140 BRIGHTNESS_FOR_VR_FLOAT_URI, 141 false, this, UserHandle.USER_ALL); 142 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler, 143 DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); 144 } 145 stopObserving()146 public void stopObserving() { 147 final ContentResolver cr = mContext.getContentResolver(); 148 cr.unregisterContentObserver(this); 149 mDisplayManager.unregisterDisplayListener(mDisplayListener); 150 } 151 152 } 153 154 private final Runnable mStartListeningRunnable = new Runnable() { 155 @Override 156 public void run() { 157 if (mListening) { 158 return; 159 } 160 mListening = true; 161 162 if (mVrManager != null) { 163 try { 164 mVrManager.registerListener(mVrStateCallbacks); 165 mIsVrModeEnabled = mVrManager.getVrModeState(); 166 } catch (RemoteException e) { 167 Log.e(TAG, "Failed to register VR mode state listener: ", e); 168 } 169 } 170 171 mBrightnessObserver.startObserving(); 172 mUserTracker.startTracking(); 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 mHandler.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 mUserTracker.stopTracking(); 201 202 mHandler.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 UserHandle.USER_CURRENT); 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 mHandler.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 mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0) 247 .sendToTarget(); 248 } 249 }; 250 251 private final Handler mHandler = new Handler() { 252 @Override 253 public void 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 super.handleMessage(msg); 271 } 272 } finally { 273 mExternalChange = false; 274 } 275 } 276 }; 277 BrightnessController( Context context, ToggleSlider control, BroadcastDispatcher broadcastDispatcher, @Background Handler bgHandler)278 public BrightnessController( 279 Context context, 280 ToggleSlider control, 281 BroadcastDispatcher broadcastDispatcher, 282 @Background Handler bgHandler) { 283 mContext = context; 284 mControl = control; 285 mControl.setMax(GAMMA_SPACE_MAX); 286 mBackgroundHandler = bgHandler; 287 mUserTracker = new CurrentUserTracker(broadcastDispatcher) { 288 @Override 289 public void onUserSwitched(int newUserId) { 290 mBackgroundHandler.post(mUpdateModeRunnable); 291 mBackgroundHandler.post(mUpdateSliderRunnable); 292 } 293 }; 294 mBrightnessObserver = new BrightnessObserver(mHandler); 295 296 mDisplayId = mContext.getDisplayId(); 297 PowerManager pm = context.getSystemService(PowerManager.class); 298 mMinimumBacklightForVr = pm.getBrightnessConstraint( 299 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR); 300 mMaximumBacklightForVr = pm.getBrightnessConstraint( 301 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR); 302 303 mDisplayManager = context.getSystemService(DisplayManager.class); 304 mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService( 305 Context.VR_SERVICE)); 306 } 307 registerCallbacks()308 public void registerCallbacks() { 309 mBackgroundHandler.post(mStartListeningRunnable); 310 } 311 312 /** Unregister all call backs, both to and from the controller */ unregisterCallbacks()313 public void unregisterCallbacks() { 314 mBackgroundHandler.post(mStopListeningRunnable); 315 mControlValueInitialized = false; 316 } 317 318 @Override onChanged(boolean tracking, int value, boolean stopTracking)319 public void onChanged(boolean tracking, int value, boolean stopTracking) { 320 if (mExternalChange) return; 321 322 if (mSliderAnimator != null) { 323 mSliderAnimator.cancel(); 324 } 325 326 final float minBacklight; 327 final float maxBacklight; 328 final int metric; 329 330 if (mIsVrModeEnabled) { 331 metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR; 332 minBacklight = mMinimumBacklightForVr; 333 maxBacklight = mMaximumBacklightForVr; 334 } else { 335 metric = mAutomatic 336 ? MetricsEvent.ACTION_BRIGHTNESS_AUTO 337 : MetricsEvent.ACTION_BRIGHTNESS; 338 minBacklight = mBrightnessMin; 339 maxBacklight = mBrightnessMax; 340 } 341 final float valFloat = MathUtils.min( 342 convertGammaToLinearFloat(value, minBacklight, maxBacklight), 343 maxBacklight); 344 if (stopTracking) { 345 // TODO(brightnessfloat): change to use float value instead. 346 MetricsLogger.action(mContext, metric, 347 BrightnessSynchronizer.brightnessFloatToInt(valFloat)); 348 349 } 350 setBrightness(valFloat); 351 if (!tracking) { 352 AsyncTask.execute(new Runnable() { 353 public void run() { 354 mDisplayManager.setBrightness(mDisplayId, valFloat); 355 } 356 }); 357 } 358 } 359 checkRestrictionAndSetEnabled()360 public void checkRestrictionAndSetEnabled() { 361 mBackgroundHandler.post(new Runnable() { 362 @Override 363 public void run() { 364 mControl.setEnforcedAdmin( 365 RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, 366 UserManager.DISALLOW_CONFIG_BRIGHTNESS, 367 mUserTracker.getCurrentUserId())); 368 } 369 }); 370 } 371 hideSlider()372 public void hideSlider() { 373 mControl.hideView(); 374 } 375 showSlider()376 public void showSlider() { 377 mControl.showView(); 378 } 379 setBrightness(float brightness)380 private void setBrightness(float brightness) { 381 mDisplayManager.setTemporaryBrightness(mDisplayId, brightness); 382 } 383 updateVrMode(boolean isEnabled)384 private void updateVrMode(boolean isEnabled) { 385 if (mIsVrModeEnabled != isEnabled) { 386 mIsVrModeEnabled = isEnabled; 387 mBackgroundHandler.post(mUpdateSliderRunnable); 388 } 389 } 390 updateSlider(float brightnessValue, boolean inVrMode)391 private void updateSlider(float brightnessValue, boolean inVrMode) { 392 final float min; 393 final float max; 394 if (inVrMode) { 395 min = mMinimumBacklightForVr; 396 max = mMaximumBacklightForVr; 397 } else { 398 min = mBrightnessMin; 399 max = mBrightnessMax; 400 } 401 // convertGammaToLinearFloat returns 0-1 402 if (BrightnessSynchronizer.floatEquals(brightnessValue, 403 convertGammaToLinearFloat(mControl.getValue(), min, max))) { 404 // If the value in the slider is equal to the value on the current brightness 405 // then the slider does not need to animate, since the brightness will not change. 406 return; 407 } 408 // Returns GAMMA_SPACE_MIN - GAMMA_SPACE_MAX 409 final int sliderVal = convertLinearToGammaFloat(brightnessValue, min, max); 410 animateSliderTo(sliderVal); 411 } 412 animateSliderTo(int target)413 private void animateSliderTo(int target) { 414 if (!mControlValueInitialized || !mControl.isVisible()) { 415 // Don't animate the first value since its default state isn't meaningful to users. 416 // We also don't want to animate slider if it's not visible - especially important when 417 // two sliders are active at the same time in split shade (one in QS and one in QQS), 418 // as this negatively affects transition between them and they share mirror slider - 419 // animating it from two different sources causes janky motion 420 mControl.setValue(target); 421 mControlValueInitialized = true; 422 } 423 if (mSliderAnimator != null && mSliderAnimator.isStarted()) { 424 mSliderAnimator.cancel(); 425 } 426 mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target); 427 mSliderAnimator.addUpdateListener((ValueAnimator animation) -> { 428 mExternalChange = true; 429 mControl.setValue((int) animation.getAnimatedValue()); 430 mExternalChange = false; 431 }); 432 final long animationDuration = SLIDER_ANIMATION_DURATION * Math.abs( 433 mControl.getValue() - target) / GAMMA_SPACE_MAX; 434 mSliderAnimator.setDuration(animationDuration); 435 mSliderAnimator.start(); 436 } 437 438 /** Factory for creating a {@link BrightnessController}. */ 439 public static class Factory { 440 private final Context mContext; 441 private final BroadcastDispatcher mBroadcastDispatcher; 442 private final Handler mBackgroundHandler; 443 444 @Inject Factory( Context context, BroadcastDispatcher broadcastDispatcher, @Background Handler bgHandler)445 public Factory( 446 Context context, 447 BroadcastDispatcher broadcastDispatcher, 448 @Background Handler bgHandler) { 449 mContext = context; 450 mBroadcastDispatcher = broadcastDispatcher; 451 mBackgroundHandler = bgHandler; 452 } 453 454 /** Create a {@link BrightnessController} */ create(ToggleSlider toggleSlider)455 public BrightnessController create(ToggleSlider toggleSlider) { 456 return new BrightnessController( 457 mContext, 458 toggleSlider, 459 mBroadcastDispatcher, 460 mBackgroundHandler); 461 } 462 } 463 464 } 465