blob: 11160a532609497d657fa266b7f1811506ea40bb [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.display;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.BrightnessInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
import android.util.Slog;
import android.util.TimeUtils;
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import com.android.server.display.DisplayManagerService.Clock;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Iterator;
/**
* Controls the status of high-brightness mode for devices that support it. This class assumes that
* an instance is always created even if a device does not support high-brightness mode (HBM); in
* the case where it is not supported, the majority of the logic is skipped. On devices that support
* HBM, we keep track of the ambient lux as well as historical usage of HBM to determine when HBM is
* allowed and not. This class's output is simply a brightness-range maximum value (queried via
* {@link #getCurrentBrightnessMax}) that changes depending on whether HBM is enabled or not.
*/
class HighBrightnessModeController {
private static final String TAG = "HighBrightnessModeController";
private static final boolean DEBUG = false;
@VisibleForTesting
static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
private static final float DEFAULT_MAX_DESIRED_HDR_SDR_RATIO = 1.0f;
public interface HdrBrightnessDeviceConfig {
// maxDesiredHdrSdrRatio will restrict the HDR brightness if the ratio is less than
// Float.POSITIVE_INFINITY
float getHdrBrightnessFromSdr(float sdrBrightness, float maxDesiredHdrSdrRatio);
}
private final float mBrightnessMin;
private final float mBrightnessMax;
private final Handler mHandler;
private final Runnable mHbmChangeCallback;
private final Runnable mRecalcRunnable;
private final Clock mClock;
private final Context mContext;
private final SettingsObserver mSettingsObserver;
private final Injector mInjector;
private HdrListener mHdrListener;
private HighBrightnessModeData mHbmData;
private HdrBrightnessDeviceConfig mHdrBrightnessCfg;
private IBinder mRegisteredDisplayToken;
private boolean mIsInAllowedAmbientRange = false;
private boolean mIsTimeAvailable = false;
private boolean mIsAutoBrightnessEnabled = false;
private boolean mIsAutoBrightnessOffByState = false;
// The following values are typically reported by DisplayPowerController.
// This value includes brightness throttling effects.
private float mBrightness;
// This value excludes brightness throttling effects.
private float mUnthrottledBrightness;
private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason =
BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
private boolean mIsHdrLayerPresent = false;
// mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping
private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
private boolean mIsBlockedByLowPowerMode = false;
private int mWidth;
private int mHeight;
private float mAmbientLux;
private int mDisplayStatsId;
private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
/**
* If HBM is currently running, this is the start time and set of all events,
* for the current HBM session.
*/
private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null;
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) {
this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context);
}
@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) {
mInjector = injector;
mContext = context;
mClock = injector.getClock();
mHandler = handler;
mBrightness = brightnessMin;
mBrightnessMin = brightnessMin;
mBrightnessMax = brightnessMax;
mHbmChangeCallback = hbmChangeCallback;
mHighBrightnessModeMetadata = hbmMetadata;
mSettingsObserver = new SettingsObserver(mHandler);
mRecalcRunnable = this::recalculateTimeAllowance;
mHdrListener = new HdrListener();
resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg);
}
void setAutoBrightnessEnabled(int state) {
final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
mIsAutoBrightnessOffByState =
state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) {
return;
}
if (DEBUG) {
Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )");
}
mIsAutoBrightnessEnabled = isEnabled;
mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
recalculateTimeAllowance();
}
float getCurrentBrightnessMin() {
return mBrightnessMin;
}
float getCurrentBrightnessMax() {
if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
// Either the device doesn't support HBM, or HBM range is currently allowed (device
// it in a high-lux environment). In either case, return the highest brightness
// level supported by the device.
return mBrightnessMax;
} else {
// Hbm is not allowed, only allow up to the brightness where we
// transition to high brightness mode.
return mHbmData.transitionPoint;
}
}
float getNormalBrightnessMax() {
return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax;
}
float getHdrBrightnessValue() {
if (mHdrBrightnessCfg != null) {
float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(
mBrightness, mMaxDesiredHdrSdrRatio);
if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) {
return hdrBrightness;
}
}
// For HDR brightness, we take the current brightness and scale it to the max. The reason
// we do this is because we want brightness to go to HBM max when it would normally go
// to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition
// point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should
// automatically scale the brightness without forcing the user to adjust to higher values.
return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(),
mBrightnessMin, mBrightnessMax, mBrightness);
}
void onAmbientLuxChange(float ambientLux) {
mAmbientLux = ambientLux;
if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
return;
}
final boolean isHighLux = (ambientLux >= mHbmData.minimumLux);
if (isHighLux != mIsInAllowedAmbientRange) {
mIsInAllowedAmbientRange = isHighLux;
recalculateTimeAllowance();
}
}
void onBrightnessChanged(float brightness, float unthrottledBrightness,
@BrightnessInfo.BrightnessMaxReason int throttlingReason) {
if (!deviceSupportsHbm()) {
return;
}
mBrightness = brightness;
mUnthrottledBrightness = unthrottledBrightness;
mThrottlingReason = throttlingReason;
// If we are starting or ending a high brightness mode session, store the current
// session in mRunningStartTimeMillis, or the old one in mEvents.
final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
final boolean wasHbmDrainingAvailableTime = runningStartTime != -1;
final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
&& !mIsHdrLayerPresent;
if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
final long currentTime = mClock.uptimeMillis();
if (shouldHbmDrainAvailableTime) {
mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
} else {
final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime);
mHighBrightnessModeMetadata.addHbmEvent(hbmEvent);
mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1);
if (DEBUG) {
Slog.d(TAG, "New HBM event: "
+ mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst());
}
}
}
recalculateTimeAllowance();
}
int getHighBrightnessMode() {
return mHbmMode;
}
float getTransitionPoint() {
if (deviceSupportsHbm()) {
return mHbmData.transitionPoint;
} else {
return HBM_TRANSITION_POINT_INVALID;
}
}
void stop() {
registerHdrListener(null /*displayToken*/);
mSettingsObserver.stopObserving();
}
void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) {
mHighBrightnessModeMetadata = hbmInfo;
}
void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) {
mWidth = width;
mHeight = height;
mHbmData = hbmData;
mHdrBrightnessCfg = hdrBrightnessCfg;
mDisplayStatsId = displayUniqueId.hashCode();
unregisterHdrListener();
mSettingsObserver.stopObserving();
if (deviceSupportsHbm()) {
registerHdrListener(displayToken);
recalculateTimeAllowance();
if (!mHbmData.allowInLowPowerMode) {
mIsBlockedByLowPowerMode = false;
mSettingsObserver.startObserving();
}
}
}
void dump(PrintWriter pw) {
mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
}
@VisibleForTesting
HdrListener getHdrListener() {
return mHdrListener;
}
private void dumpLocal(PrintWriter pw) {
pw.println("HighBrightnessModeController:");
pw.println(" mBrightness=" + mBrightness);
pw.println(" mUnthrottledBrightness=" + mUnthrottledBrightness);
pw.println(" mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason));
pw.println(" mCurrentMin=" + getCurrentBrightnessMin());
pw.println(" mCurrentMax=" + getCurrentBrightnessMax());
pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+ (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
? "(" + getHdrBrightnessValue() + ")" : ""));
pw.println(" mHbmStatsState=" + hbmStatsStateToString(mHbmStatsState));
pw.println(" mHbmData=" + mHbmData);
pw.println(" mAmbientLux=" + mAmbientLux
+ (mIsAutoBrightnessEnabled ? "" : " (old/invalid)"));
pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
pw.println(" mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState);
pw.println(" mIsHdrLayerPresent=" + mIsHdrLayerPresent);
pw.println(" mBrightnessMin=" + mBrightnessMin);
pw.println(" mBrightnessMax=" + mBrightnessMax);
pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
pw.println(" mIsTimeAvailable= " + mIsTimeAvailable);
pw.println(" mRunningStartTimeMillis="
+ TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
pw.println(" width*height=" + mWidth + "*" + mHeight);
pw.println(" mEvents=");
final long currentTime = mClock.uptimeMillis();
long lastStartTime = currentTime;
long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
if (runningStartTimeMillis != -1) {
lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
}
for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
if (lastStartTime > event.getEndTimeMillis()) {
pw.println(" event: [normal brightness]: "
+ TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
}
lastStartTime = dumpHbmEvent(pw, event);
}
}
private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
final long duration = event.getEndTimeMillis() - event.getStartTimeMillis();
pw.println(" event: ["
+ TimeUtils.formatUptime(event.getStartTimeMillis()) + ", "
+ TimeUtils.formatUptime(event.getEndTimeMillis()) + "] ("
+ TimeUtils.formatDuration(duration) + ")");
return event.getStartTimeMillis();
}
private boolean isCurrentlyAllowed() {
// Returns true if HBM is allowed (above the ambient lux threshold) and there's still
// time within the current window for additional HBM usage. We return false if there is an
// HDR layer because we don't want the brightness MAX to change for HDR, which has its
// brightness scaled in a different way than sunlight HBM that doesn't require changing
// the MAX. HDR also needs to work under manual brightness which never adjusts the
// brightness maximum; so we implement HDR-HBM in a way that doesn't adjust the max.
// See {@link #getHdrBrightnessValue}.
return !mIsHdrLayerPresent
&& (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
&& !mIsBlockedByLowPowerMode);
}
private boolean deviceSupportsHbm() {
return mHbmData != null;
}
private long calculateRemainingTime(long currentTime) {
if (!deviceSupportsHbm()) {
return 0;
}
long timeAlreadyUsed = 0;
// First, lets see how much time we've taken for any currently running
// session of HBM.
long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
if (runningStartTimeMillis > 0) {
if (runningStartTimeMillis > currentTime) {
Slog.e(TAG, "Start time set to the future. curr: " + currentTime
+ ", start: " + runningStartTimeMillis);
mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
runningStartTimeMillis = currentTime;
}
timeAlreadyUsed = currentTime - runningStartTimeMillis;
}
if (DEBUG) {
Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed);
}
// Next, lets iterate through the history of previous sessions and add those times.
final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
Iterator<HbmEvent> it = mHighBrightnessModeMetadata.getHbmEventQueue().iterator();
while (it.hasNext()) {
final HbmEvent event = it.next();
// If this event ended before the current Timing window, discard forever and ever.
if (event.getEndTimeMillis() < windowstartTimeMillis) {
it.remove();
continue;
}
final long startTimeMillis = Math.max(event.getStartTimeMillis(),
windowstartTimeMillis);
timeAlreadyUsed += event.getEndTimeMillis() - startTimeMillis;
}
if (DEBUG) {
Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed);
}
return Math.max(0, mHbmData.timeMaxMillis - timeAlreadyUsed);
}
/**
* Recalculates the allowable HBM time.
*/
private void recalculateTimeAllowance() {
final long currentTime = mClock.uptimeMillis();
final long remainingTime = calculateRemainingTime(currentTime);
// We allow HBM if there is more than the minimum required time available
// or if brightness is already in the high range, if there is any time left at all.
final boolean isAllowedWithoutRestrictions = remainingTime >= mHbmData.timeMinMillis;
final boolean isOnlyAllowedToStayOn = !isAllowedWithoutRestrictions
&& remainingTime > 0 && mBrightness > mHbmData.transitionPoint;
mIsTimeAvailable = isAllowedWithoutRestrictions || isOnlyAllowedToStayOn;
// Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or
// brightness change doesn't happen before then.
long nextTimeout = -1;
final ArrayDeque<HbmEvent> hbmEvents = mHighBrightnessModeMetadata.getHbmEventQueue();
if (mBrightness > mHbmData.transitionPoint) {
// if we're in high-lux now, timeout when we run out of allowed time.
nextTimeout = currentTime + remainingTime;
} else if (!mIsTimeAvailable && hbmEvents.size() > 0) {
// If we are not allowed...timeout when the oldest event moved outside of the timing
// window by at least minTime. Basically, we're calculating the soonest time we can
// get {@code timeMinMillis} back to us.
final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
final HbmEvent lastEvent = hbmEvents.peekLast();
final long startTimePlusMinMillis =
Math.max(windowstartTimeMillis, lastEvent.getStartTimeMillis())
+ mHbmData.timeMinMillis;
final long timeWhenMinIsGainedBack =
currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime;
nextTimeout = timeWhenMinIsGainedBack;
}
if (DEBUG) {
Slog.d(TAG, "HBM recalculated. IsAllowedWithoutRestrictions: "
+ isAllowedWithoutRestrictions
+ ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
+ ", remainingAllowedTime: " + remainingTime
+ ", isLuxHigh: " + mIsInAllowedAmbientRange
+ ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
+ ", isHdrLayerPresent: " + mIsHdrLayerPresent
+ ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
+ ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled
+ ", mIsTimeAvailable: " + mIsTimeAvailable
+ ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
+ ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
+ ", mBrightness: " + mBrightness
+ ", mUnthrottledBrightness: " + mUnthrottledBrightness
+ ", mThrottlingReason: "
+ BrightnessInfo.briMaxReasonToString(mThrottlingReason)
+ ", RunningStartTimeMillis: "
+ mHighBrightnessModeMetadata.getRunningStartTimeMillis()
+ ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
+ ", events: " + hbmEvents);
}
if (nextTimeout != -1) {
mHandler.removeCallbacks(mRecalcRunnable);
mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1);
}
// Update the state of the world
updateHbmMode();
}
private void updateHbmMode() {
int newHbmMode = calculateHighBrightnessMode();
updateHbmStats(newHbmMode);
if (mHbmMode != newHbmMode) {
mHbmMode = newHbmMode;
mHbmChangeCallback.run();
}
}
private void updateHbmStats(int newMode) {
int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
&& getHdrBrightnessValue() > mHbmData.transitionPoint) {
state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
} else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
&& mBrightness > mHbmData.transitionPoint) {
state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
}
if (state == mHbmStatsState) {
return;
}
int reason =
FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN;
final boolean oldHbmSv = (mHbmStatsState
== FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
final boolean newHbmSv =
(state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
if (oldHbmSv && !newHbmSv) {
// If more than one conditions are flipped and turn off HBM sunlight
// visibility, only one condition will be reported to make it simple.
if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF;
} else if (!mIsAutoBrightnessEnabled) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF;
} else if (!mIsInAllowedAmbientRange) {
reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
} else if (!mIsTimeAvailable) {
reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
} else if (isThermalThrottlingActive()) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
} else if (mIsHdrLayerPresent) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING;
} else if (mIsBlockedByLowPowerMode) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
} else if (mBrightness <= mHbmData.transitionPoint) {
// This must be after external thermal check.
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS;
}
}
mInjector.reportHbmStateChange(mDisplayStatsId, state, reason);
mHbmStatsState = state;
}
@VisibleForTesting
boolean isThermalThrottlingActive() {
// We would've liked HBM, but we got NBM (normal brightness mode) because of thermals.
return mUnthrottledBrightness > mHbmData.transitionPoint
&& mBrightness <= mHbmData.transitionPoint
&& mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
}
private String hbmStatsStateToString(int hbmStatsState) {
switch (hbmStatsState) {
case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF:
return "HBM_OFF";
case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR:
return "HBM_ON_HDR";
case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT:
return "HBM_ON_SUNLIGHT";
default:
return String.valueOf(hbmStatsState);
}
}
private int calculateHighBrightnessMode() {
if (!deviceSupportsHbm()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
} else if (mIsHdrLayerPresent) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
} else if (isCurrentlyAllowed()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
}
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
}
private void registerHdrListener(IBinder displayToken) {
if (mRegisteredDisplayToken == displayToken) {
return;
}
unregisterHdrListener();
mRegisteredDisplayToken = displayToken;
if (mRegisteredDisplayToken != null) {
mHdrListener.register(mRegisteredDisplayToken);
}
}
private void unregisterHdrListener() {
if (mRegisteredDisplayToken != null) {
mHdrListener.unregister(mRegisteredDisplayToken);
mIsHdrLayerPresent = false;
}
}
@VisibleForTesting
class HdrListener extends SurfaceControlHdrLayerInfoListener {
@Override
public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio) {
mHandler.post(() -> {
mIsHdrLayerPresent = numberOfHdrLayers > 0
&& (float) (maxW * maxH) >= ((float) (mWidth * mHeight)
* mHbmData.minimumHdrPercentOfScreen);
final float candidateDesiredHdrSdrRatio =
mIsHdrLayerPresent && mHdrBrightnessCfg != null
? maxDesiredHdrSdrRatio
: DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
if (candidateDesiredHdrSdrRatio >= 1.0f) {
mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio;
} else {
Slog.w(TAG, "Ignoring invalid desired HDR/SDR Ratio: "
+ candidateDesiredHdrSdrRatio);
mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
}
// Calling the brightness update so that we can recalculate
// brightness with HDR in mind.
onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason);
});
}
}
private final class SettingsObserver extends ContentObserver {
private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
Settings.Global.LOW_POWER_MODE);
private boolean mStarted;
SettingsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
updateLowPower();
}
void startObserving() {
if (!mStarted) {
mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting,
false /*notifyForDescendants*/, this, UserHandle.USER_ALL);
mStarted = true;
updateLowPower();
}
}
void stopObserving() {
mIsBlockedByLowPowerMode = false;
if (mStarted) {
mContext.getContentResolver().unregisterContentObserver(this);
mStarted = false;
}
}
private void updateLowPower() {
final boolean isLowPowerMode = isLowPowerMode();
if (isLowPowerMode == mIsBlockedByLowPowerMode) {
return;
}
if (DEBUG) {
Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode);
}
mIsBlockedByLowPowerMode = isLowPowerMode;
// this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
updateHbmMode();
}
private boolean isLowPowerMode() {
return Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0;
}
}
public static class Injector {
public Clock getClock() {
return SystemClock::uptimeMillis;
}
public void reportHbmStateChange(int display, int state, int reason) {
FrameworkStatsLog.write(
FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
}
}
}