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.server.display; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.database.ContentObserver; 22 import android.hardware.display.BrightnessInfo; 23 import android.net.Uri; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.PowerManager; 27 import android.os.SystemClock; 28 import android.os.Trace; 29 import android.os.UserHandle; 30 import android.provider.Settings; 31 import android.util.MathUtils; 32 import android.util.Slog; 33 import android.util.TimeUtils; 34 import android.view.SurfaceControlHdrLayerInfoListener; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.internal.display.BrightnessSynchronizer; 38 import com.android.internal.util.FrameworkStatsLog; 39 import com.android.server.display.DisplayManagerService.Clock; 40 import com.android.server.display.config.HighBrightnessModeData; 41 import com.android.server.display.feature.DisplayManagerFlags; 42 import com.android.server.display.utils.DebugUtils; 43 44 import java.io.PrintWriter; 45 import java.util.ArrayDeque; 46 import java.util.Iterator; 47 48 /** 49 * Controls the status of high-brightness mode for devices that support it. This class assumes that 50 * an instance is always created even if a device does not support high-brightness mode (HBM); in 51 * the case where it is not supported, the majority of the logic is skipped. On devices that support 52 * HBM, we keep track of the ambient lux as well as historical usage of HBM to determine when HBM is 53 * allowed and not. This class's output is simply a brightness-range maximum value (queried via 54 * {@link #getCurrentBrightnessMax}) that changes depending on whether HBM is enabled or not. 55 */ 56 class HighBrightnessModeController { 57 private static final String TAG = "HighBrightnessModeController"; 58 59 // To enable these logs, run: 60 // 'adb shell setprop persist.log.tag.HighBrightnessModeController DEBUG && adb reboot' 61 private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); 62 63 @VisibleForTesting 64 static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY; 65 66 private static final float DEFAULT_MAX_DESIRED_HDR_SDR_RATIO = 1.0f; 67 68 public interface HdrBrightnessDeviceConfig { 69 // maxDesiredHdrSdrRatio will restrict the HDR brightness if the ratio is less than 70 // Float.POSITIVE_INFINITY getHdrBrightnessFromSdr(float sdrBrightness, float maxDesiredHdrSdrRatio)71 float getHdrBrightnessFromSdr(float sdrBrightness, float maxDesiredHdrSdrRatio); 72 } 73 74 private final float mBrightnessMin; 75 private final float mBrightnessMax; 76 private final Handler mHandler; 77 private final Runnable mHbmChangeCallback; 78 private final Runnable mRecalcRunnable; 79 private final Clock mClock; 80 private final Context mContext; 81 private final SettingsObserver mSettingsObserver; 82 private final Injector mInjector; 83 84 private HdrListener mHdrListener; 85 86 @Nullable 87 private HighBrightnessModeData mHbmData; 88 private HdrBrightnessDeviceConfig mHdrBrightnessCfg; 89 private IBinder mRegisteredDisplayToken; 90 91 private boolean mIsInAllowedAmbientRange = false; 92 private boolean mIsTimeAvailable = false; 93 private boolean mIsAutoBrightnessEnabled = false; 94 private boolean mIsAutoBrightnessOffByState = false; 95 96 // The following values are typically reported by DisplayPowerController. 97 // This value includes brightness throttling effects. 98 private float mBrightness; 99 // This value excludes brightness throttling effects. 100 private float mUnthrottledBrightness; 101 private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason = 102 BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; 103 104 private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 105 @VisibleForTesting 106 boolean mIsHdrLayerPresent = false; 107 // mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping 108 private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO; 109 private boolean mForceHbmChangeCallback = false; 110 private boolean mIsBlockedByLowPowerMode = false; 111 private int mWidth; 112 private int mHeight; 113 private float mAmbientLux; 114 private int mDisplayStatsId; 115 private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; 116 117 /** 118 * If HBM is currently running, this is the start time and set of all events, 119 * for the current HBM session. 120 */ 121 @Nullable 122 private HighBrightnessModeMetadata mHighBrightnessModeMetadata; 123 124 /** 125 * If {@link DisplayManagerFlags#useNewHdrBrightnessModifier()} is ON, hdr boost is handled by 126 * {@link com.android.server.display.brightness.clamper.HdrBrightnessModifier} and should be 127 * disabled in this class. After flag is cleaned up, this field together with HDR handling 128 * should be cleaned up from this class. 129 */ 130 private boolean mHdrBoostDisabled = false; 131 HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context)132 HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, 133 String displayUniqueId, float brightnessMin, float brightnessMax, 134 HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, 135 Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) { 136 this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin, 137 brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context); 138 } 139 140 @VisibleForTesting HighBrightnessModeController(Injector injector, Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context)141 HighBrightnessModeController(Injector injector, Handler handler, int width, int height, 142 IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, 143 HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, 144 Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) { 145 mInjector = injector; 146 mContext = context; 147 mClock = injector.getClock(); 148 mHandler = handler; 149 mBrightness = brightnessMin; 150 mBrightnessMin = brightnessMin; 151 mBrightnessMax = brightnessMax; 152 mHbmChangeCallback = hbmChangeCallback; 153 mHighBrightnessModeMetadata = hbmMetadata; 154 mSettingsObserver = new SettingsObserver(mHandler); 155 mRecalcRunnable = this::recalculateTimeAllowance; 156 mHdrListener = new HdrListener(); 157 158 resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg); 159 } 160 setAutoBrightnessEnabled(int state)161 void setAutoBrightnessEnabled(int state) { 162 final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; 163 mIsAutoBrightnessOffByState = 164 state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE; 165 if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) { 166 return; 167 } 168 if (DEBUG) { 169 Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )"); 170 } 171 mIsAutoBrightnessEnabled = isEnabled; 172 mIsInAllowedAmbientRange = false; // reset when auto-brightness switches 173 recalculateTimeAllowance(); 174 } 175 getCurrentBrightnessMin()176 float getCurrentBrightnessMin() { 177 return mBrightnessMin; 178 } 179 getCurrentBrightnessMax()180 float getCurrentBrightnessMax() { 181 if (!deviceSupportsHbm() || isHbmCurrentlyAllowed()) { 182 // Either the device doesn't support HBM, or HBM range is currently allowed (device 183 // it in a high-lux environment). In either case, return the highest brightness 184 // level supported by the device. 185 return mBrightnessMax; 186 } else { 187 // Hbm is not allowed, only allow up to the brightness where we 188 // transition to high brightness mode. 189 return mHbmData.transitionPoint; 190 } 191 } 192 getNormalBrightnessMax()193 float getNormalBrightnessMax() { 194 return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax; 195 } 196 getHdrBrightnessValue()197 float getHdrBrightnessValue() { 198 if (mHdrBrightnessCfg != null) { 199 float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr( 200 mBrightness, mMaxDesiredHdrSdrRatio); 201 if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) { 202 return hdrBrightness; 203 } 204 } 205 206 // For HDR brightness, we take the current brightness and scale it to the max. The reason 207 // we do this is because we want brightness to go to HBM max when it would normally go 208 // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition 209 // point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should 210 // automatically scale the brightness without forcing the user to adjust to higher values. 211 return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(), 212 mBrightnessMin, mBrightnessMax, mBrightness); 213 } 214 onAmbientLuxChange(float ambientLux)215 void onAmbientLuxChange(float ambientLux) { 216 mAmbientLux = ambientLux; 217 if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) { 218 return; 219 } 220 221 final boolean isHighLux = (ambientLux >= mHbmData.minimumLux); 222 if (isHighLux != mIsInAllowedAmbientRange) { 223 mIsInAllowedAmbientRange = isHighLux; 224 recalculateTimeAllowance(); 225 } 226 } 227 onBrightnessChanged(float brightness, float unthrottledBrightness, @BrightnessInfo.BrightnessMaxReason int throttlingReason)228 void onBrightnessChanged(float brightness, float unthrottledBrightness, 229 @BrightnessInfo.BrightnessMaxReason int throttlingReason) { 230 if (!deviceSupportsHbm()) { 231 return; 232 } 233 mBrightness = brightness; 234 mUnthrottledBrightness = unthrottledBrightness; 235 mThrottlingReason = throttlingReason; 236 237 // If we are starting or ending a high brightness mode session, store the current 238 // session in mRunningStartTimeMillis, or the old one in mEvents. 239 final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); 240 final boolean wasHbmDrainingAvailableTime = runningStartTime != -1; 241 final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint 242 && !mIsHdrLayerPresent; 243 if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) { 244 final long currentTime = mClock.uptimeMillis(); 245 if (shouldHbmDrainAvailableTime) { 246 mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime); 247 } else { 248 final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime); 249 mHighBrightnessModeMetadata.addHbmEvent(hbmEvent); 250 mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1); 251 252 if (DEBUG) { 253 Slog.d(TAG, "New HBM event: " 254 + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst()); 255 } 256 } 257 } 258 259 recalculateTimeAllowance(); 260 } 261 getHighBrightnessMode()262 int getHighBrightnessMode() { 263 return mHbmMode; 264 } 265 getTransitionPoint()266 float getTransitionPoint() { 267 if (deviceSupportsHbm()) { 268 return mHbmData.transitionPoint; 269 } else { 270 return HBM_TRANSITION_POINT_INVALID; 271 } 272 } 273 stop()274 void stop() { 275 registerHdrListener(null /*displayToken*/); 276 mSettingsObserver.stopObserving(); 277 } 278 setHighBrightnessModeMetadata(@ullable HighBrightnessModeMetadata hbmInfo)279 void setHighBrightnessModeMetadata(@Nullable HighBrightnessModeMetadata hbmInfo) { 280 mHighBrightnessModeMetadata = hbmInfo; 281 } 282 resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg)283 void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, 284 HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) { 285 mWidth = width; 286 mHeight = height; 287 mHbmData = hbmData; 288 mHdrBrightnessCfg = hdrBrightnessCfg; 289 mDisplayStatsId = displayUniqueId.hashCode(); 290 291 unregisterHdrListener(); 292 mSettingsObserver.stopObserving(); 293 if (deviceSupportsHbm()) { 294 registerHdrListener(displayToken); 295 recalculateTimeAllowance(); 296 if (!mHbmData.allowInLowPowerMode) { 297 mIsBlockedByLowPowerMode = false; 298 mSettingsObserver.startObserving(); 299 } 300 } 301 } 302 dump(PrintWriter pw)303 void dump(PrintWriter pw) { 304 mHandler.runWithScissors(() -> dumpLocal(pw), 1000); 305 } 306 307 @VisibleForTesting getHdrListener()308 HdrListener getHdrListener() { 309 return mHdrListener; 310 } 311 dumpLocal(PrintWriter pw)312 private void dumpLocal(PrintWriter pw) { 313 pw.println("HighBrightnessModeController:"); 314 pw.println(" mBrightness=" + mBrightness); 315 pw.println(" mUnthrottledBrightness=" + mUnthrottledBrightness); 316 pw.println(" mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason)); 317 pw.println(" mCurrentMin=" + getCurrentBrightnessMin()); 318 pw.println(" mCurrentMax=" + getCurrentBrightnessMax()); 319 pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode) 320 + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR 321 ? "(" + getHdrBrightnessValue() + ")" : "")); 322 pw.println(" mHbmStatsState=" + hbmStatsStateToString(mHbmStatsState)); 323 pw.println(" mHbmData=" + mHbmData); 324 pw.println(" mAmbientLux=" + mAmbientLux 325 + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)")); 326 pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange); 327 pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled); 328 pw.println(" mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState); 329 pw.println(" mIsHdrLayerPresent=" + mIsHdrLayerPresent); 330 pw.println(" mBrightnessMin=" + mBrightnessMin); 331 pw.println(" mBrightnessMax=" + mBrightnessMax); 332 pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis())); 333 pw.println(" mIsTimeAvailable= " + mIsTimeAvailable); 334 pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode); 335 pw.println(" width*height=" + mWidth + "*" + mHeight); 336 pw.println(" mHdrBoostDisabled=" + mHdrBoostDisabled); 337 338 if (mHighBrightnessModeMetadata != null) { 339 pw.println(" mRunningStartTimeMillis=" 340 + TimeUtils.formatUptime( 341 mHighBrightnessModeMetadata.getRunningStartTimeMillis())); 342 pw.println(" mEvents="); 343 final long currentTime = mClock.uptimeMillis(); 344 long lastStartTime = currentTime; 345 long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); 346 if (runningStartTimeMillis != -1) { 347 lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime)); 348 } 349 for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) { 350 if (lastStartTime > event.getEndTimeMillis()) { 351 pw.println(" event: [normal brightness]: " 352 + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis())); 353 } 354 lastStartTime = dumpHbmEvent(pw, event); 355 } 356 } else { 357 pw.println(" mHighBrightnessModeMetadata=null"); 358 } 359 } 360 dumpHbmEvent(PrintWriter pw, HbmEvent event)361 private long dumpHbmEvent(PrintWriter pw, HbmEvent event) { 362 final long duration = event.getEndTimeMillis() - event.getStartTimeMillis(); 363 pw.println(" event: [" 364 + TimeUtils.formatUptime(event.getStartTimeMillis()) + ", " 365 + TimeUtils.formatUptime(event.getEndTimeMillis()) + "] (" 366 + TimeUtils.formatDuration(duration) + ")"); 367 return event.getStartTimeMillis(); 368 } 369 isHbmCurrentlyAllowed()370 boolean isHbmCurrentlyAllowed() { 371 // Returns true if HBM is allowed (above the ambient lux threshold) and there's still 372 // time within the current window for additional HBM usage. We return false if there is an 373 // HDR layer because we don't want the brightness MAX to change for HDR, which has its 374 // brightness scaled in a different way than sunlight HBM that doesn't require changing 375 // the MAX. HDR also needs to work under manual brightness which never adjusts the 376 // brightness maximum; so we implement HDR-HBM in a way that doesn't adjust the max. 377 // See {@link #getHdrBrightnessValue}. 378 return !mIsHdrLayerPresent 379 && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange 380 && !mIsBlockedByLowPowerMode); 381 } 382 deviceSupportsHbm()383 boolean deviceSupportsHbm() { 384 return mHbmData != null && mHighBrightnessModeMetadata != null; 385 } 386 disableHdrBoost()387 void disableHdrBoost() { 388 mHdrBoostDisabled = true; 389 unregisterHdrListener(); 390 } 391 /** 392 * Hdr boost can be applied by 393 * {@link com.android.server.display.brightness.clamper.HdrBrightnessModifier}, in this case 394 * HBMController should not consume HBM time budget 395 */ onHdrBoostApplied(boolean applied)396 void onHdrBoostApplied(boolean applied) { 397 // We need to update mIsHdrLayerPresent flag only if HDR boost is controlled by other 398 // component and disabled here 399 if (mHdrBoostDisabled) { 400 mIsHdrLayerPresent = applied; 401 } 402 } 403 calculateRemainingTime(long currentTime)404 private long calculateRemainingTime(long currentTime) { 405 if (!deviceSupportsHbm()) { 406 return 0; 407 } 408 409 long timeAlreadyUsed = 0; 410 411 // First, lets see how much time we've taken for any currently running 412 // session of HBM. 413 long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); 414 if (runningStartTimeMillis > 0) { 415 if (runningStartTimeMillis > currentTime) { 416 Slog.e(TAG, "Start time set to the future. curr: " + currentTime 417 + ", start: " + runningStartTimeMillis); 418 mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime); 419 runningStartTimeMillis = currentTime; 420 } 421 timeAlreadyUsed = currentTime - runningStartTimeMillis; 422 } 423 424 if (DEBUG) { 425 Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed); 426 } 427 428 // Next, lets iterate through the history of previous sessions and add those times. 429 final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis; 430 Iterator<HbmEvent> it = mHighBrightnessModeMetadata.getHbmEventQueue().iterator(); 431 while (it.hasNext()) { 432 final HbmEvent event = it.next(); 433 434 // If this event ended before the current Timing window, discard forever and ever. 435 if (event.getEndTimeMillis() < windowstartTimeMillis) { 436 it.remove(); 437 continue; 438 } 439 440 final long startTimeMillis = Math.max(event.getStartTimeMillis(), 441 windowstartTimeMillis); 442 timeAlreadyUsed += event.getEndTimeMillis() - startTimeMillis; 443 } 444 445 if (DEBUG) { 446 Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed); 447 } 448 449 return Math.max(0, mHbmData.timeMaxMillis - timeAlreadyUsed); 450 } 451 452 /** 453 * Recalculates the allowable HBM time. 454 */ recalculateTimeAllowance()455 private void recalculateTimeAllowance() { 456 final long currentTime = mClock.uptimeMillis(); 457 final long remainingTime = calculateRemainingTime(currentTime); 458 459 // We allow HBM if there is more than the minimum required time available 460 // or if brightness is already in the high range, if there is any time left at all. 461 final boolean isAllowedWithoutRestrictions = remainingTime >= mHbmData.timeMinMillis; 462 final boolean isOnlyAllowedToStayOn = !isAllowedWithoutRestrictions 463 && remainingTime > 0 && mBrightness > mHbmData.transitionPoint; 464 mIsTimeAvailable = isAllowedWithoutRestrictions || isOnlyAllowedToStayOn; 465 466 // Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or 467 // brightness change doesn't happen before then. 468 long nextTimeout = -1; 469 final ArrayDeque<HbmEvent> hbmEvents = mHighBrightnessModeMetadata.getHbmEventQueue(); 470 if (mBrightness > mHbmData.transitionPoint) { 471 // if we're in high-lux now, timeout when we run out of allowed time. 472 nextTimeout = currentTime + remainingTime; 473 } else if (!mIsTimeAvailable && hbmEvents.size() > 0) { 474 // If we are not allowed...timeout when the oldest event moved outside of the timing 475 // window by at least minTime. Basically, we're calculating the soonest time we can 476 // get {@code timeMinMillis} back to us. 477 final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis; 478 final HbmEvent lastEvent = hbmEvents.peekLast(); 479 final long startTimePlusMinMillis = 480 Math.max(windowstartTimeMillis, lastEvent.getStartTimeMillis()) 481 + mHbmData.timeMinMillis; 482 final long timeWhenMinIsGainedBack = 483 currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime; 484 nextTimeout = timeWhenMinIsGainedBack; 485 } 486 487 if (DEBUG) { 488 Slog.d(TAG, "HBM recalculated. IsAllowedWithoutRestrictions: " 489 + isAllowedWithoutRestrictions 490 + ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn 491 + ", remainingAllowedTime: " + remainingTime 492 + ", isLuxHigh: " + mIsInAllowedAmbientRange 493 + ", isHBMCurrentlyAllowed: " + isHbmCurrentlyAllowed() 494 + ", isHdrLayerPresent: " + mIsHdrLayerPresent 495 + ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio 496 + ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled 497 + ", mIsTimeAvailable: " + mIsTimeAvailable 498 + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange 499 + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode 500 + ", mBrightness: " + mBrightness 501 + ", mUnthrottledBrightness: " + mUnthrottledBrightness 502 + ", mThrottlingReason: " 503 + BrightnessInfo.briMaxReasonToString(mThrottlingReason) 504 + ", RunningStartTimeMillis: " 505 + mHighBrightnessModeMetadata.getRunningStartTimeMillis() 506 + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1) 507 + ", events: " + hbmEvents); 508 } 509 510 if (nextTimeout != -1) { 511 mHandler.removeCallbacks(mRecalcRunnable); 512 mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1); 513 } 514 // Update the state of the world 515 updateHbmMode(); 516 } 517 updateHbmMode()518 private void updateHbmMode() { 519 int newHbmMode = calculateHighBrightnessMode(); 520 updateHbmStats(newHbmMode); 521 if (mHbmMode != newHbmMode || mForceHbmChangeCallback) { 522 mForceHbmChangeCallback = false; 523 mHbmMode = newHbmMode; 524 mHbmChangeCallback.run(); 525 } 526 } 527 updateHbmStats(int newMode)528 private void updateHbmStats(int newMode) { 529 int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; 530 if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR 531 && getHdrBrightnessValue() > mHbmData.transitionPoint) { 532 state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR; 533 } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT 534 && mBrightness > mHbmData.transitionPoint) { 535 state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT; 536 } 537 if (state == mHbmStatsState) { 538 return; 539 } 540 541 int reason = 542 FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN; 543 final boolean oldHbmSv = (mHbmStatsState 544 == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); 545 final boolean newHbmSv = 546 (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); 547 if (oldHbmSv && !newHbmSv) { 548 // If more than one conditions are flipped and turn off HBM sunlight 549 // visibility, only one condition will be reported to make it simple. 550 if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) { 551 reason = FrameworkStatsLog 552 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF; 553 } else if (!mIsAutoBrightnessEnabled) { 554 reason = FrameworkStatsLog 555 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF; 556 } else if (!mIsInAllowedAmbientRange) { 557 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP; 558 } else if (!mIsTimeAvailable) { 559 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT; 560 } else if (isThermalThrottlingActive()) { 561 reason = FrameworkStatsLog 562 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT; 563 } else if (mIsHdrLayerPresent) { 564 reason = FrameworkStatsLog 565 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING; 566 } else if (mIsBlockedByLowPowerMode) { 567 reason = FrameworkStatsLog 568 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON; 569 } else if (mBrightness <= mHbmData.transitionPoint) { 570 // This must be after external thermal check. 571 reason = FrameworkStatsLog 572 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS; 573 } 574 } 575 576 mInjector.reportHbmStateChange(mDisplayStatsId, state, reason); 577 mHbmStatsState = state; 578 } 579 580 @VisibleForTesting isThermalThrottlingActive()581 boolean isThermalThrottlingActive() { 582 // We would've liked HBM, but we got NBM (normal brightness mode) because of thermals. 583 return mUnthrottledBrightness > mHbmData.transitionPoint 584 && mBrightness <= mHbmData.transitionPoint 585 && mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; 586 } 587 hbmStatsStateToString(int hbmStatsState)588 private String hbmStatsStateToString(int hbmStatsState) { 589 switch (hbmStatsState) { 590 case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF: 591 return "HBM_OFF"; 592 case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR: 593 return "HBM_ON_HDR"; 594 case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT: 595 return "HBM_ON_SUNLIGHT"; 596 default: 597 return String.valueOf(hbmStatsState); 598 } 599 } 600 calculateHighBrightnessMode()601 private int calculateHighBrightnessMode() { 602 if (!deviceSupportsHbm()) { 603 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 604 } else if (mIsHdrLayerPresent) { 605 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; 606 } else if (isHbmCurrentlyAllowed()) { 607 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; 608 } 609 610 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 611 } 612 registerHdrListener(IBinder displayToken)613 private void registerHdrListener(IBinder displayToken) { 614 if (mHdrBoostDisabled) { 615 return; 616 } 617 if (mRegisteredDisplayToken == displayToken) { 618 return; 619 } 620 621 unregisterHdrListener(); 622 mRegisteredDisplayToken = displayToken; 623 if (mRegisteredDisplayToken != null) { 624 mHdrListener.register(mRegisteredDisplayToken); 625 } 626 } 627 unregisterHdrListener()628 private void unregisterHdrListener() { 629 if (mRegisteredDisplayToken != null) { 630 mHdrListener.unregister(mRegisteredDisplayToken); 631 mIsHdrLayerPresent = false; 632 } 633 } 634 635 @VisibleForTesting 636 class HdrListener extends SurfaceControlHdrLayerInfoListener { 637 @Override onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio)638 public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, 639 int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio) { 640 mHandler.post(() -> { 641 Trace.traceBegin(Trace.TRACE_TAG_POWER, "HBMController#onHdrInfoChanged"); 642 mIsHdrLayerPresent = numberOfHdrLayers > 0 643 && (float) (maxW * maxH) >= ((float) (mWidth * mHeight) 644 * mHbmData.minimumHdrPercentOfScreen); 645 646 float candidateDesiredHdrSdrRatio = 647 mIsHdrLayerPresent && mHdrBrightnessCfg != null 648 ? maxDesiredHdrSdrRatio 649 : DEFAULT_MAX_DESIRED_HDR_SDR_RATIO; 650 651 if (candidateDesiredHdrSdrRatio < 1.0f) { 652 Slog.w(TAG, "Ignoring invalid desired HDR/SDR Ratio: " 653 + candidateDesiredHdrSdrRatio); 654 candidateDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO; 655 } 656 657 if (!BrightnessSynchronizer.floatEquals( 658 mMaxDesiredHdrSdrRatio, candidateDesiredHdrSdrRatio)) { 659 mForceHbmChangeCallback = true; 660 mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio; 661 } 662 663 // Calling the brightness update so that we can recalculate 664 // brightness with HDR in mind. 665 onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason); 666 Trace.traceEnd(Trace.TRACE_TAG_POWER); 667 }); 668 } 669 } 670 671 private final class SettingsObserver extends ContentObserver { 672 private final Uri mLowPowerModeSetting = Settings.Global.getUriFor( 673 Settings.Global.LOW_POWER_MODE); 674 private boolean mStarted; 675 SettingsObserver(Handler handler)676 SettingsObserver(Handler handler) { 677 super(handler); 678 } 679 680 @Override onChange(boolean selfChange, Uri uri)681 public void onChange(boolean selfChange, Uri uri) { 682 updateLowPower(); 683 } 684 startObserving()685 void startObserving() { 686 if (!mStarted) { 687 mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting, 688 false /*notifyForDescendants*/, this, UserHandle.USER_ALL); 689 mStarted = true; 690 updateLowPower(); 691 } 692 } 693 stopObserving()694 void stopObserving() { 695 mIsBlockedByLowPowerMode = false; 696 if (mStarted) { 697 mContext.getContentResolver().unregisterContentObserver(this); 698 mStarted = false; 699 } 700 } 701 updateLowPower()702 private void updateLowPower() { 703 final boolean isLowPowerMode = isLowPowerMode(); 704 if (isLowPowerMode == mIsBlockedByLowPowerMode) { 705 return; 706 } 707 if (DEBUG) { 708 Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode); 709 } 710 mIsBlockedByLowPowerMode = isLowPowerMode; 711 // this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed 712 updateHbmMode(); 713 } 714 isLowPowerMode()715 private boolean isLowPowerMode() { 716 return Settings.Global.getInt( 717 mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0; 718 } 719 } 720 721 public static class Injector { getClock()722 public Clock getClock() { 723 return SystemClock::uptimeMillis; 724 } 725 reportHbmStateChange(int display, int state, int reason)726 public void reportHbmStateChange(int display, int state, int reason) { 727 FrameworkStatsLog.write( 728 FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason); 729 } 730 } 731 } 732