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