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.content.Context; 20 import android.database.ContentObserver; 21 import android.hardware.display.BrightnessInfo; 22 import android.net.Uri; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.os.IThermalEventListener; 26 import android.os.IThermalService; 27 import android.os.PowerManager; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.os.SystemClock; 31 import android.os.Temperature; 32 import android.os.UserHandle; 33 import android.provider.Settings; 34 import android.util.MathUtils; 35 import android.util.Slog; 36 import android.util.TimeUtils; 37 import android.view.SurfaceControlHdrLayerInfoListener; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.util.FrameworkStatsLog; 41 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; 42 import com.android.server.display.DisplayManagerService.Clock; 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 private static final boolean DEBUG = false; 60 61 @VisibleForTesting 62 static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY; 63 64 public interface HdrBrightnessDeviceConfig { getHdrBrightnessFromSdr(float sdrBrightness)65 float getHdrBrightnessFromSdr(float sdrBrightness); 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 SkinThermalStatusObserver mSkinThermalStatusObserver; 75 private final Context mContext; 76 private final SettingsObserver mSettingsObserver; 77 private final Injector mInjector; 78 79 private HdrListener mHdrListener; 80 private HighBrightnessModeData mHbmData; 81 private HdrBrightnessDeviceConfig mHdrBrightnessCfg; 82 private IBinder mRegisteredDisplayToken; 83 84 private boolean mIsInAllowedAmbientRange = false; 85 private boolean mIsTimeAvailable = false; 86 private boolean mIsAutoBrightnessEnabled = false; 87 private boolean mIsAutoBrightnessOffByState = false; 88 89 // The following values are typically reported by DisplayPowerController. 90 // This value includes brightness throttling effects. 91 private float mBrightness; 92 // This value excludes brightness throttling effects. 93 private float mUnthrottledBrightness; 94 private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason = 95 BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; 96 97 private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 98 private boolean mIsHdrLayerPresent = false; 99 private boolean mIsThermalStatusWithinLimit = true; 100 private boolean mIsBlockedByLowPowerMode = false; 101 private int mWidth; 102 private int mHeight; 103 private float mAmbientLux; 104 private int mDisplayStatsId; 105 private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; 106 107 /** 108 * If HBM is currently running, this is the start time and set of all events, 109 * for the current HBM session. 110 */ 111 private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null; 112 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)113 HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, 114 String displayUniqueId, float brightnessMin, float brightnessMax, 115 HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, 116 Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) { 117 this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin, 118 brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context); 119 } 120 121 @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)122 HighBrightnessModeController(Injector injector, Handler handler, int width, int height, 123 IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, 124 HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, 125 Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) { 126 mInjector = injector; 127 mContext = context; 128 mClock = injector.getClock(); 129 mHandler = handler; 130 mBrightness = brightnessMin; 131 mBrightnessMin = brightnessMin; 132 mBrightnessMax = brightnessMax; 133 mHbmChangeCallback = hbmChangeCallback; 134 mHighBrightnessModeMetadata = hbmMetadata; 135 mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); 136 mSettingsObserver = new SettingsObserver(mHandler); 137 mRecalcRunnable = this::recalculateTimeAllowance; 138 mHdrListener = new HdrListener(); 139 140 resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg); 141 } 142 setAutoBrightnessEnabled(int state)143 void setAutoBrightnessEnabled(int state) { 144 final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; 145 mIsAutoBrightnessOffByState = 146 state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE; 147 if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) { 148 return; 149 } 150 if (DEBUG) { 151 Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )"); 152 } 153 mIsAutoBrightnessEnabled = isEnabled; 154 mIsInAllowedAmbientRange = false; // reset when auto-brightness switches 155 recalculateTimeAllowance(); 156 } 157 getCurrentBrightnessMin()158 float getCurrentBrightnessMin() { 159 return mBrightnessMin; 160 } 161 getCurrentBrightnessMax()162 float getCurrentBrightnessMax() { 163 if (!deviceSupportsHbm() || isCurrentlyAllowed()) { 164 // Either the device doesn't support HBM, or HBM range is currently allowed (device 165 // it in a high-lux environment). In either case, return the highest brightness 166 // level supported by the device. 167 return mBrightnessMax; 168 } else { 169 // Hbm is not allowed, only allow up to the brightness where we 170 // transition to high brightness mode. 171 return mHbmData.transitionPoint; 172 } 173 } 174 getNormalBrightnessMax()175 float getNormalBrightnessMax() { 176 return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax; 177 } 178 getHdrBrightnessValue()179 float getHdrBrightnessValue() { 180 if (mHdrBrightnessCfg != null) { 181 float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(mBrightness); 182 if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) { 183 return hdrBrightness; 184 } 185 } 186 187 // For HDR brightness, we take the current brightness and scale it to the max. The reason 188 // we do this is because we want brightness to go to HBM max when it would normally go 189 // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition 190 // point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should 191 // automatically scale the brightness without forcing the user to adjust to higher values. 192 return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(), 193 mBrightnessMin, mBrightnessMax, mBrightness); 194 } 195 onAmbientLuxChange(float ambientLux)196 void onAmbientLuxChange(float ambientLux) { 197 mAmbientLux = ambientLux; 198 if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) { 199 return; 200 } 201 202 final boolean isHighLux = (ambientLux >= mHbmData.minimumLux); 203 if (isHighLux != mIsInAllowedAmbientRange) { 204 mIsInAllowedAmbientRange = isHighLux; 205 recalculateTimeAllowance(); 206 } 207 } 208 onBrightnessChanged(float brightness, float unthrottledBrightness, @BrightnessInfo.BrightnessMaxReason int throttlingReason)209 void onBrightnessChanged(float brightness, float unthrottledBrightness, 210 @BrightnessInfo.BrightnessMaxReason int throttlingReason) { 211 if (!deviceSupportsHbm()) { 212 return; 213 } 214 mBrightness = brightness; 215 mUnthrottledBrightness = unthrottledBrightness; 216 mThrottlingReason = throttlingReason; 217 218 // If we are starting or ending a high brightness mode session, store the current 219 // session in mRunningStartTimeMillis, or the old one in mEvents. 220 final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); 221 final boolean wasHbmDrainingAvailableTime = runningStartTime != -1; 222 final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint 223 && !mIsHdrLayerPresent; 224 if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) { 225 final long currentTime = mClock.uptimeMillis(); 226 if (shouldHbmDrainAvailableTime) { 227 mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime); 228 } else { 229 final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime); 230 mHighBrightnessModeMetadata.addHbmEvent(hbmEvent); 231 mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1); 232 233 if (DEBUG) { 234 Slog.d(TAG, "New HBM event: " 235 + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst()); 236 } 237 } 238 } 239 240 recalculateTimeAllowance(); 241 } 242 getHighBrightnessMode()243 int getHighBrightnessMode() { 244 return mHbmMode; 245 } 246 getTransitionPoint()247 float getTransitionPoint() { 248 if (deviceSupportsHbm()) { 249 return mHbmData.transitionPoint; 250 } else { 251 return HBM_TRANSITION_POINT_INVALID; 252 } 253 } 254 stop()255 void stop() { 256 registerHdrListener(null /*displayToken*/); 257 mSkinThermalStatusObserver.stopObserving(); 258 mSettingsObserver.stopObserving(); 259 } 260 setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo)261 void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) { 262 mHighBrightnessModeMetadata = hbmInfo; 263 } 264 resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg)265 void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, 266 HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) { 267 mWidth = width; 268 mHeight = height; 269 mHbmData = hbmData; 270 mHdrBrightnessCfg = hdrBrightnessCfg; 271 mDisplayStatsId = displayUniqueId.hashCode(); 272 273 unregisterHdrListener(); 274 mSkinThermalStatusObserver.stopObserving(); 275 mSettingsObserver.stopObserving(); 276 if (deviceSupportsHbm()) { 277 registerHdrListener(displayToken); 278 recalculateTimeAllowance(); 279 if (mHbmData.thermalStatusLimit > PowerManager.THERMAL_STATUS_NONE) { 280 mIsThermalStatusWithinLimit = true; 281 mSkinThermalStatusObserver.startObserving(); 282 } 283 if (!mHbmData.allowInLowPowerMode) { 284 mIsBlockedByLowPowerMode = false; 285 mSettingsObserver.startObserving(); 286 } 287 } 288 } 289 dump(PrintWriter pw)290 void dump(PrintWriter pw) { 291 mHandler.runWithScissors(() -> dumpLocal(pw), 1000); 292 } 293 294 @VisibleForTesting getHdrListener()295 HdrListener getHdrListener() { 296 return mHdrListener; 297 } 298 dumpLocal(PrintWriter pw)299 private void dumpLocal(PrintWriter pw) { 300 pw.println("HighBrightnessModeController:"); 301 pw.println(" mBrightness=" + mBrightness); 302 pw.println(" mUnthrottledBrightness=" + mUnthrottledBrightness); 303 pw.println(" mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason)); 304 pw.println(" mCurrentMin=" + getCurrentBrightnessMin()); 305 pw.println(" mCurrentMax=" + getCurrentBrightnessMax()); 306 pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode) 307 + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR 308 ? "(" + getHdrBrightnessValue() + ")" : "")); 309 pw.println(" mHbmStatsState=" + hbmStatsStateToString(mHbmStatsState)); 310 pw.println(" mHbmData=" + mHbmData); 311 pw.println(" mAmbientLux=" + mAmbientLux 312 + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)")); 313 pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange); 314 pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled); 315 pw.println(" mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState); 316 pw.println(" mIsHdrLayerPresent=" + mIsHdrLayerPresent); 317 pw.println(" mBrightnessMin=" + mBrightnessMin); 318 pw.println(" mBrightnessMax=" + mBrightnessMax); 319 pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis())); 320 pw.println(" mIsTimeAvailable= " + mIsTimeAvailable); 321 pw.println(" mRunningStartTimeMillis=" 322 + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis())); 323 pw.println(" mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit); 324 pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode); 325 pw.println(" width*height=" + mWidth + "*" + mHeight); 326 pw.println(" mEvents="); 327 final long currentTime = mClock.uptimeMillis(); 328 long lastStartTime = currentTime; 329 long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); 330 if (runningStartTimeMillis != -1) { 331 lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime)); 332 } 333 for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) { 334 if (lastStartTime > event.getEndTimeMillis()) { 335 pw.println(" event: [normal brightness]: " 336 + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis())); 337 } 338 lastStartTime = dumpHbmEvent(pw, event); 339 } 340 341 mSkinThermalStatusObserver.dump(pw); 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 && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode); 364 } 365 deviceSupportsHbm()366 private boolean deviceSupportsHbm() { 367 return mHbmData != 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 + ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled 462 + ", mIsTimeAvailable: " + mIsTimeAvailable 463 + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange 464 + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit 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 final float transitionPoint = mHbmData.transitionPoint; 495 int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; 496 if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR 497 && getHdrBrightnessValue() > transitionPoint) { 498 state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR; 499 } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT 500 && mBrightness > transitionPoint) { 501 state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT; 502 } 503 if (state == mHbmStatsState) { 504 return; 505 } 506 507 int reason = 508 FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN; 509 final boolean oldHbmSv = (mHbmStatsState 510 == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); 511 final boolean newHbmSv = 512 (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); 513 if (oldHbmSv && !newHbmSv) { 514 // HighBrightnessModeController (HBMC) currently supports throttling from two sources: 515 // 1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling() 516 // 2. External, received from HBMC.onBrightnessChanged() 517 // TODO(b/216373254): Deprecate internal throttling source 518 final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit; 519 final boolean externalThermalThrottling = 520 mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness... 521 mBrightness <= transitionPoint && // ...but we got NBM, because of... 522 mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals. 523 524 // If more than one conditions are flipped and turn off HBM sunlight 525 // visibility, only one condition will be reported to make it simple. 526 if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) { 527 reason = FrameworkStatsLog 528 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF; 529 } else if (!mIsAutoBrightnessEnabled) { 530 reason = FrameworkStatsLog 531 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF; 532 } else if (!mIsInAllowedAmbientRange) { 533 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP; 534 } else if (!mIsTimeAvailable) { 535 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT; 536 } else if (internalThermalThrottling || externalThermalThrottling) { 537 reason = FrameworkStatsLog 538 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT; 539 } else if (mIsHdrLayerPresent) { 540 reason = FrameworkStatsLog 541 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING; 542 } else if (mIsBlockedByLowPowerMode) { 543 reason = FrameworkStatsLog 544 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON; 545 } else if (mBrightness <= mHbmData.transitionPoint) { 546 // This must be after external thermal check. 547 reason = FrameworkStatsLog 548 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS; 549 } 550 } 551 552 mInjector.reportHbmStateChange(mDisplayStatsId, state, reason); 553 mHbmStatsState = state; 554 } 555 hbmStatsStateToString(int hbmStatsState)556 private String hbmStatsStateToString(int hbmStatsState) { 557 switch (hbmStatsState) { 558 case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF: 559 return "HBM_OFF"; 560 case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR: 561 return "HBM_ON_HDR"; 562 case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT: 563 return "HBM_ON_SUNLIGHT"; 564 default: 565 return String.valueOf(hbmStatsState); 566 } 567 } 568 calculateHighBrightnessMode()569 private int calculateHighBrightnessMode() { 570 if (!deviceSupportsHbm()) { 571 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 572 } else if (mIsHdrLayerPresent) { 573 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; 574 } else if (isCurrentlyAllowed()) { 575 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; 576 } 577 578 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 579 } 580 registerHdrListener(IBinder displayToken)581 private void registerHdrListener(IBinder displayToken) { 582 if (mRegisteredDisplayToken == displayToken) { 583 return; 584 } 585 586 unregisterHdrListener(); 587 mRegisteredDisplayToken = displayToken; 588 if (mRegisteredDisplayToken != null) { 589 mHdrListener.register(mRegisteredDisplayToken); 590 } 591 } 592 unregisterHdrListener()593 private void unregisterHdrListener() { 594 if (mRegisteredDisplayToken != null) { 595 mHdrListener.unregister(mRegisteredDisplayToken); 596 mIsHdrLayerPresent = false; 597 } 598 } 599 600 @VisibleForTesting 601 class HdrListener extends SurfaceControlHdrLayerInfoListener { 602 @Override onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags)603 public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, 604 int maxW, int maxH, int flags) { 605 mHandler.post(() -> { 606 mIsHdrLayerPresent = numberOfHdrLayers > 0 607 && (float) (maxW * maxH) >= ((float) (mWidth * mHeight) 608 * mHbmData.minimumHdrPercentOfScreen); 609 // Calling the brightness update so that we can recalculate 610 // brightness with HDR in mind. 611 onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason); 612 }); 613 } 614 } 615 616 private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { 617 private final Injector mInjector; 618 private final Handler mHandler; 619 620 private IThermalService mThermalService; 621 private boolean mStarted; 622 SkinThermalStatusObserver(Injector injector, Handler handler)623 SkinThermalStatusObserver(Injector injector, Handler handler) { 624 mInjector = injector; 625 mHandler = handler; 626 } 627 628 @Override notifyThrottling(Temperature temp)629 public void notifyThrottling(Temperature temp) { 630 if (DEBUG) { 631 Slog.d(TAG, "New thermal throttling status " 632 + ", current thermal status = " + temp.getStatus() 633 + ", threshold = " + mHbmData.thermalStatusLimit); 634 } 635 mHandler.post(() -> { 636 mIsThermalStatusWithinLimit = temp.getStatus() <= mHbmData.thermalStatusLimit; 637 // This recalculates HbmMode and runs mHbmChangeCallback if the mode has changed 638 updateHbmMode(); 639 }); 640 } 641 startObserving()642 void startObserving() { 643 if (mStarted) { 644 if (DEBUG) { 645 Slog.d(TAG, "Thermal status observer already started"); 646 } 647 return; 648 } 649 mThermalService = mInjector.getThermalService(); 650 if (mThermalService == null) { 651 Slog.w(TAG, "Could not observe thermal status. Service not available"); 652 return; 653 } 654 try { 655 // We get a callback immediately upon registering so there's no need to query 656 // for the current value. 657 mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN); 658 mStarted = true; 659 } catch (RemoteException e) { 660 Slog.e(TAG, "Failed to register thermal status listener", e); 661 } 662 } 663 stopObserving()664 void stopObserving() { 665 mIsThermalStatusWithinLimit = true; 666 if (!mStarted) { 667 if (DEBUG) { 668 Slog.d(TAG, "Stop skipped because thermal status observer not started"); 669 } 670 return; 671 } 672 try { 673 mThermalService.unregisterThermalEventListener(this); 674 mStarted = false; 675 } catch (RemoteException e) { 676 Slog.e(TAG, "Failed to unregister thermal status listener", e); 677 } 678 mThermalService = null; 679 } 680 dump(PrintWriter writer)681 void dump(PrintWriter writer) { 682 writer.println(" SkinThermalStatusObserver:"); 683 writer.println(" mStarted: " + mStarted); 684 if (mThermalService != null) { 685 writer.println(" ThermalService available"); 686 } else { 687 writer.println(" ThermalService not available"); 688 } 689 } 690 } 691 692 private final class SettingsObserver extends ContentObserver { 693 private final Uri mLowPowerModeSetting = Settings.Global.getUriFor( 694 Settings.Global.LOW_POWER_MODE); 695 private boolean mStarted; 696 SettingsObserver(Handler handler)697 SettingsObserver(Handler handler) { 698 super(handler); 699 } 700 701 @Override onChange(boolean selfChange, Uri uri)702 public void onChange(boolean selfChange, Uri uri) { 703 updateLowPower(); 704 } 705 startObserving()706 void startObserving() { 707 if (!mStarted) { 708 mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting, 709 false /*notifyForDescendants*/, this, UserHandle.USER_ALL); 710 mStarted = true; 711 updateLowPower(); 712 } 713 } 714 stopObserving()715 void stopObserving() { 716 mIsBlockedByLowPowerMode = false; 717 if (mStarted) { 718 mContext.getContentResolver().unregisterContentObserver(this); 719 mStarted = false; 720 } 721 } 722 updateLowPower()723 private void updateLowPower() { 724 final boolean isLowPowerMode = isLowPowerMode(); 725 if (isLowPowerMode == mIsBlockedByLowPowerMode) { 726 return; 727 } 728 if (DEBUG) { 729 Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode); 730 } 731 mIsBlockedByLowPowerMode = isLowPowerMode; 732 // this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed 733 updateHbmMode(); 734 } 735 isLowPowerMode()736 private boolean isLowPowerMode() { 737 return Settings.Global.getInt( 738 mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0; 739 } 740 } 741 742 public static class Injector { getClock()743 public Clock getClock() { 744 return SystemClock::uptimeMillis; 745 } 746 getThermalService()747 public IThermalService getThermalService() { 748 return IThermalService.Stub.asInterface( 749 ServiceManager.getService(Context.THERMAL_SERVICE)); 750 } 751 reportHbmStateChange(int display, int state, int reason)752 public void reportHbmStateChange(int display, int state, int reason) { 753 FrameworkStatsLog.write( 754 FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason); 755 } 756 } 757 } 758