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