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