diff options
| author | 2022-09-22 17:10:20 +0000 | |
|---|---|---|
| committer | 2022-09-22 17:10:20 +0000 | |
| commit | 5705cc1c8beca52710a1041e4d2ad57df89c13df (patch) | |
| tree | d8a5e1513107b75ee10b542a982d9f5c84fbb076 | |
| parent | 4f3efa9730becb02be275c04a2eaa12b0f06c99c (diff) | |
| parent | 433cfd2d4cf77ad748f40284121e52b4f8b39437 (diff) | |
Merge "Add duplicated DisplayPowerController"
4 files changed, 3312 insertions, 5 deletions
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 9ab78bc9cb32..2f1f98b22e0f 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2562,10 +2562,10 @@ public final class DisplayManagerService extends SystemService { final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore, display, mSyncRoot); - final DisplayPowerController displayPowerController; + final DisplayPowerControllerInterface displayPowerController; if (SystemProperties.getInt(PROP_USE_NEW_DISPLAY_POWER_CONTROLLER, 0) == 1) { - displayPowerController = new DisplayPowerController( + displayPowerController = new DisplayPowerController2( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, () -> handleBrightnessChange(display)); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 5a65afe4cf3b..61225d7dee80 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -798,8 +798,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * Notified when the display is changed. We use this to apply any changes that might be needed * when displays get swapped on foldable devices. For example, different brightness properties * of each display need to be properly reflected in AutomaticBrightnessController. + * + * Make sure DisplayManagerService.mSyncRoot is held when this is called */ - @GuardedBy("DisplayManagerService.mSyncRoot") @Override public void onDisplayChanged() { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); @@ -1896,7 +1897,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }, () -> { - sendUpdatePowerStateLocked(); + sendUpdatePowerState(); postBrightnessChangeRunnable(); // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. if (mAutomaticBrightnessController != null) { @@ -1912,7 +1913,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null; return new BrightnessThrottler(mHandler, data, () -> { - sendUpdatePowerStateLocked(); + sendUpdatePowerState(); postBrightnessChangeRunnable(); }, mUniqueDisplayId); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java new file mode 100644 index 000000000000..dc7db10123db --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -0,0 +1,3071 @@ +/* + * Copyright (C) 2022 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.animation.Animator; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.display.AmbientBrightnessDayStats; +import android.hardware.display.BrightnessChangeEvent; +import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.metrics.LogMaker; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.FloatProperty; +import android.util.Log; +import android.util.MathUtils; +import android.util.MutableFloat; +import android.util.MutableInt; +import android.util.Slog; +import android.util.TimeUtils; +import android.view.Display; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IBatteryStats; +import com.android.internal.display.BrightnessSynchronizer; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.RingBuffer; +import com.android.server.LocalServices; +import com.android.server.am.BatteryStatsService; +import com.android.server.display.RampAnimator.DualRampAnimator; +import com.android.server.display.brightness.BrightnessEvent; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; +import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; +import com.android.server.display.utils.SensorUtils; +import com.android.server.display.whitebalance.DisplayWhiteBalanceController; +import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory; +import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings; +import com.android.server.policy.WindowManagerPolicy; + +import java.io.PrintWriter; + +/** + * Controls the power state of the display. + * + * Handles the proximity sensor, light sensor, and animations between states + * including the screen off animation. + * + * This component acts independently of the rest of the power manager service. + * In particular, it does not share any state and it only communicates + * via asynchronous callbacks to inform the power manager that something has + * changed. + * + * Everything this class does internally is serialized on its handler although + * it may be accessed by other threads from the outside. + * + * Note that the power manager service guarantees that it will hold a suspend + * blocker as long as the display is not ready. So most of the work done here + * does not need to worry about holding a suspend blocker unless it happens + * independently of the display ready signal. + * + * For debugging, you can make the color fade and brightness animations run + * slower by changing the "animator duration scale" option in Development Settings. + */ +final class DisplayPowerController2 implements AutomaticBrightnessController.Callbacks, + DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface { + private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked"; + private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked"; + + private static final boolean DEBUG = false; + private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; + + // If true, uses the color fade on animation. + // We might want to turn this off if we cannot get a guarantee that the screen + // actually turns on and starts showing new content after the call to set the + // screen state returns. Playing the animation can also be somewhat slow. + private static final boolean USE_COLOR_FADE_ON_ANIMATION = false; + + private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f; + + private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250; + private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400; + + private static final int MSG_UPDATE_POWER_STATE = 1; + private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2; + private static final int MSG_SCREEN_ON_UNBLOCKED = 3; + private static final int MSG_SCREEN_OFF_UNBLOCKED = 4; + private static final int MSG_CONFIGURE_BRIGHTNESS = 5; + private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6; + private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7; + private static final int MSG_IGNORE_PROXIMITY = 8; + private static final int MSG_STOP = 9; + private static final int MSG_UPDATE_BRIGHTNESS = 10; + private static final int MSG_UPDATE_RBC = 11; + private static final int MSG_BRIGHTNESS_RAMP_DONE = 12; + private static final int MSG_STATSD_HBM_BRIGHTNESS = 13; + + private static final int PROXIMITY_UNKNOWN = -1; + private static final int PROXIMITY_NEGATIVE = 0; + private static final int PROXIMITY_POSITIVE = 1; + + // Proximity sensor debounce delay in milliseconds for positive or negative transitions. + private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0; + private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250; + + private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500; + + // Trigger proximity if distance is less than 5 cm. + private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; + + // State machine constants for tracking initial brightness ramp skipping when enabled. + private static final int RAMP_STATE_SKIP_NONE = 0; + private static final int RAMP_STATE_SKIP_INITIAL = 1; + private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2; + + private static final int REPORTED_TO_POLICY_UNREPORTED = -1; + private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0; + private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1; + private static final int REPORTED_TO_POLICY_SCREEN_ON = 2; + private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3; + + private static final int RINGBUFFER_MAX = 100; + + private final String mTag; + + private final Object mLock = new Object(); + + private final Context mContext; + + // Our handler. + private final DisplayControllerHandler mHandler; + + // Asynchronous callbacks into the power manager service. + // Only invoked from the handler thread while no locks are held. + private final DisplayPowerCallbacks mCallbacks; + + // Battery stats. + @Nullable + private final IBatteryStats mBatteryStats; + + // The sensor manager. + private final SensorManager mSensorManager; + + // The window manager policy. + private final WindowManagerPolicy mWindowManagerPolicy; + + // The display blanker. + private final DisplayBlanker mBlanker; + + // The LogicalDisplay tied to this DisplayPowerController2. + private final LogicalDisplay mLogicalDisplay; + + // The ID of the LogicalDisplay tied to this DisplayPowerController2. + private final int mDisplayId; + + // The unique ID of the primary display device currently tied to this logical display + private String mUniqueDisplayId; + + // Tracker for brightness changes. + @Nullable + private final BrightnessTracker mBrightnessTracker; + + // Tracker for brightness settings changes. + private final SettingsObserver mSettingsObserver; + + // The proximity sensor, or null if not available or needed. + private Sensor mProximitySensor; + + // The doze screen brightness. + private final float mScreenBrightnessDozeConfig; + + // The dim screen brightness. + private final float mScreenBrightnessDimConfig; + + // The minimum dim amount to use if the screen brightness is already below + // mScreenBrightnessDimConfig. + private final float mScreenBrightnessMinimumDimAmount; + + private final float mScreenBrightnessDefault; + + // The minimum allowed brightness while in VR. + private final float mScreenBrightnessForVrRangeMinimum; + + // The maximum allowed brightness while in VR. + private final float mScreenBrightnessForVrRangeMaximum; + + // The default screen brightness for VR. + private final float mScreenBrightnessForVrDefault; + + // True if auto-brightness should be used. + private boolean mUseSoftwareAutoBrightnessConfig; + + // True if should use light sensor to automatically determine doze screen brightness. + private final boolean mAllowAutoBrightnessWhileDozingConfig; + + // Whether or not the color fade on screen on / off is enabled. + private final boolean mColorFadeEnabled; + + @GuardedBy("mCachedBrightnessInfo") + private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo(); + + private DisplayDevice mDisplayDevice; + + // True if we should fade the screen while turning it off, false if we should play + // a stylish color fade animation instead. + private final boolean mColorFadeFadesConfig; + + // True if we need to fake a transition to off when coming out of a doze state. + // Some display hardware will blank itself when coming out of doze in order to hide + // artifacts. For these displays we fake a transition into OFF so that policy can appropriately + // blank itself and begin an appropriate power on animation. + private final boolean mDisplayBlanksAfterDozeConfig; + + // True if there are only buckets of brightness values when the display is in the doze state, + // rather than a full range of values. If this is true, then we'll avoid animating the screen + // brightness since it'd likely be multiple jarring brightness transitions instead of just one + // to reach the final state. + private final boolean mBrightnessBucketsInDozeConfig; + + private final Clock mClock; + private final Injector mInjector; + + // Maximum time a ramp animation can take. + private long mBrightnessRampIncreaseMaxTimeMillis; + private long mBrightnessRampDecreaseMaxTimeMillis; + + // The pending power request. + // Initially null until the first call to requestPowerState. + @GuardedBy("mLock") + private DisplayPowerRequest mPendingRequestLocked; + + // True if a request has been made to wait for the proximity sensor to go negative. + @GuardedBy("mLock") + private boolean mPendingWaitForNegativeProximityLocked; + + // True if the pending power request or wait for negative proximity flag + // has been changed since the last update occurred. + @GuardedBy("mLock") + private boolean mPendingRequestChangedLocked; + + // Set to true when the important parts of the pending power request have been applied. + // The important parts are mainly the screen state. Brightness changes may occur + // concurrently. + @GuardedBy("mLock") + private boolean mDisplayReadyLocked; + + // Set to true if a power state update is required. + @GuardedBy("mLock") + private boolean mPendingUpdatePowerStateLocked; + + /* The following state must only be accessed by the handler thread. */ + + // The currently requested power state. + // The power controller will progressively update its internal state to match + // the requested power state. Initially null until the first update. + private DisplayPowerRequest mPowerRequest; + + // The current power state. + // Must only be accessed on the handler thread. + private DisplayPowerState mPowerState; + + // True if the device should wait for negative proximity sensor before + // waking up the screen. This is set to false as soon as a negative + // proximity sensor measurement is observed or when the device is forced to + // go to sleep by the user. While true, the screen remains off. + private boolean mWaitingForNegativeProximity; + + // True if the device should not take into account the proximity sensor + // until either the proximity sensor state changes, or there is no longer a + // request to listen to proximity sensor. + private boolean mIgnoreProximityUntilChanged; + + // The actual proximity sensor threshold value. + private float mProximityThreshold; + + // Set to true if the proximity sensor listener has been registered + // with the sensor manager. + private boolean mProximitySensorEnabled; + + // The debounced proximity sensor state. + private int mProximity = PROXIMITY_UNKNOWN; + + // The raw non-debounced proximity sensor state. + private int mPendingProximity = PROXIMITY_UNKNOWN; + private long mPendingProximityDebounceTime = -1; // -1 if fully debounced + + // True if the screen was turned off because of the proximity sensor. + // When the screen turns on again, we report user activity to the power manager. + private boolean mScreenOffBecauseOfProximity; + + // The currently active screen on unblocker. This field is non-null whenever + // we are waiting for a callback to release it and unblock the screen. + private ScreenOnUnblocker mPendingScreenOnUnblocker; + private ScreenOffUnblocker mPendingScreenOffUnblocker; + + // True if we were in the process of turning off the screen. + // This allows us to recover more gracefully from situations where we abort + // turning off the screen. + private boolean mPendingScreenOff; + + // True if we have unfinished business and are holding a suspend blocker. + private boolean mUnfinishedBusiness; + + // The elapsed real time when the screen on was blocked. + private long mScreenOnBlockStartRealTime; + private long mScreenOffBlockStartRealTime; + + // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields. + private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED; + + // If the last recorded screen state was dozing or not. + private boolean mDozing; + + // Remembers whether certain kinds of brightness adjustments + // were recently applied so that we can decide how to transition. + private boolean mAppliedAutoBrightness; + private boolean mAppliedDimming; + private boolean mAppliedLowPower; + private boolean mAppliedScreenBrightnessOverride; + private boolean mAppliedTemporaryBrightness; + private boolean mAppliedTemporaryAutoBrightnessAdjustment; + private boolean mAppliedBrightnessBoost; + private boolean mAppliedThrottling; + + // Reason for which the brightness was last changed. See {@link BrightnessReason} for more + // information. + // At the time of this writing, this value is changed within updatePowerState() only, which is + // limited to the thread used by DisplayControllerHandler. + private final BrightnessReason mBrightnessReason = new BrightnessReason(); + private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason(); + + // Brightness animation ramp rates in brightness units per second + private float mBrightnessRampRateFastDecrease; + private float mBrightnessRampRateFastIncrease; + private float mBrightnessRampRateSlowDecrease; + private float mBrightnessRampRateSlowIncrease; + + // Report HBM brightness change to StatsD + private int mDisplayStatsId; + private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN; + + // Whether or not to skip the initial brightness ramps into STATE_ON. + private final boolean mSkipScreenOnBrightnessRamp; + + // Display white balance components. + @Nullable + private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings; + @Nullable + private final DisplayWhiteBalanceController mDisplayWhiteBalanceController; + + @Nullable + private final ColorDisplayServiceInternal mCdsi; + private float[] mNitsRange; + + private final HighBrightnessModeController mHbmController; + + private final BrightnessThrottler mBrightnessThrottler; + + private final BrightnessSetting mBrightnessSetting; + + private final Runnable mOnBrightnessChangeRunnable; + + private final BrightnessEvent mLastBrightnessEvent; + private final BrightnessEvent mTempBrightnessEvent; + + // Keeps a record of brightness changes for dumpsys. + private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer; + + // A record of state for skipping brightness ramps. + private int mSkipRampState = RAMP_STATE_SKIP_NONE; + + // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL. + private float mInitialAutoBrightness; + + // The controller for the automatic brightness level. + @Nullable + private AutomaticBrightnessController mAutomaticBrightnessController; + + private Sensor mLightSensor; + + // The mappers between ambient lux, display backlight values, and display brightness. + // We will switch between the idle mapper and active mapper in AutomaticBrightnessController. + // Mapper used for active (normal) screen brightness mode + @Nullable + private BrightnessMappingStrategy mInteractiveModeBrightnessMapper; + // Mapper used for idle screen brightness mode + @Nullable + private BrightnessMappingStrategy mIdleModeBrightnessMapper; + + // The current brightness configuration. + @Nullable + private BrightnessConfiguration mBrightnessConfiguration; + + // The last brightness that was set by the user and not temporary. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded. + private float mLastUserSetScreenBrightness = Float.NaN; + + // The screen brightness setting has changed but not taken effect yet. If this is different + // from the current screen brightness setting then this is coming from something other than us + // and should be considered a user interaction. + private float mPendingScreenBrightnessSetting; + + // The last observed screen brightness setting, either set by us or by the settings app on + // behalf of the user. + private float mCurrentScreenBrightnessSetting; + + // The temporary screen brightness. Typically set when a user is interacting with the + // brightness slider but hasn't settled on a choice yet. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set. + private float mTemporaryScreenBrightness; + + // The current screen brightness while in VR mode. + private float mScreenBrightnessForVr; + + // The last auto brightness adjustment that was set by the user and not temporary. Set to + // Float.NaN when an auto-brightness adjustment hasn't been recorded yet. + private float mAutoBrightnessAdjustment; + + // The pending auto brightness adjustment that will take effect on the next power state update. + private float mPendingAutoBrightnessAdjustment; + + // The temporary auto brightness adjustment. Typically set when a user is interacting with the + // adjustment slider but hasn't settled on a choice yet. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set. + private float mTemporaryAutoBrightnessAdjustment; + + private boolean mIsRbcActive; + + // Whether there's a callback to tell listeners the display has changed scheduled to run. When + // true it implies a wakelock is being held to guarantee the update happens before we collapse + // into suspend and so needs to be cleaned up if the thread is exiting. + // Should only be accessed on the Handler thread. + private boolean mOnStateChangedPending; + + // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many + // suspend blocker acquisitions are pending when shutting down this DPC. + // Should only be accessed on the Handler thread. + private int mOnProximityPositiveMessages; + private int mOnProximityNegativeMessages; + + // Animators. + private ObjectAnimator mColorFadeOnAnimator; + private ObjectAnimator mColorFadeOffAnimator; + private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; + private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener; + + // True if this DisplayPowerController2 has been stopped and should no longer be running. + private boolean mStopped; + + private DisplayDeviceConfig mDisplayDeviceConfig; + + // Identifiers for suspend blocker acuisition requests + private final String mSuspendBlockerIdUnfinishedBusiness; + private final String mSuspendBlockerIdOnStateChanged; + private final String mSuspendBlockerIdProxPositive; + private final String mSuspendBlockerIdProxNegative; + private final String mSuspendBlockerIdProxDebounce; + + /** + * Creates the display power controller. + */ + DisplayPowerController2(Context context, Injector injector, + DisplayPowerCallbacks callbacks, Handler handler, + SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, + BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, + Runnable onBrightnessChangeRunnable) { + + mInjector = injector != null ? injector : new Injector(); + mClock = mInjector.getClock(); + mLogicalDisplay = logicalDisplay; + mDisplayId = mLogicalDisplay.getDisplayIdLocked(); + mTag = "DisplayPowerController2[" + mDisplayId + "]"; + mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId); + mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId); + mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId); + mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId); + mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId); + + mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + mDisplayStatsId = mUniqueDisplayId.hashCode(); + mHandler = new DisplayControllerHandler(handler.getLooper()); + mLastBrightnessEvent = new BrightnessEvent(mDisplayId); + mTempBrightnessEvent = new BrightnessEvent(mDisplayId); + + if (mDisplayId == Display.DEFAULT_DISPLAY) { + mBatteryStats = BatteryStatsService.getService(); + } else { + mBatteryStats = null; + } + + mSettingsObserver = new SettingsObserver(mHandler); + mCallbacks = callbacks; + mSensorManager = sensorManager; + mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class); + mBlanker = blanker; + mContext = context; + mBrightnessTracker = brightnessTracker; + // TODO: b/186428377 update brightness setting when display changes + mBrightnessSetting = brightnessSetting; + mOnBrightnessChangeRunnable = onBrightnessChangeRunnable; + + PowerManager pm = context.getSystemService(PowerManager.class); + + final Resources resources = context.getResources(); + + // DOZE AND DIM SETTINGS + mScreenBrightnessDozeConfig = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)); + mScreenBrightnessDimConfig = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)); + mScreenBrightnessMinimumDimAmount = resources.getFloat( + R.dimen.config_screenBrightnessMinimumDimAmountFloat); + + + // NORMAL SCREEN SETTINGS + mScreenBrightnessDefault = clampAbsoluteBrightness( + mLogicalDisplay.getDisplayInfoLocked().brightnessDefault); + + // VR SETTINGS + mScreenBrightnessForVrDefault = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR)); + mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR)); + mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR)); + + // Check the setting, but also verify that it is the default display. Only the default + // display has an automatic brightness controller running. + // TODO: b/179021925 - Fix to work with multiple displays + mUseSoftwareAutoBrightnessConfig = resources.getBoolean( + R.bool.config_automatic_brightness_available) + && mDisplayId == Display.DEFAULT_DISPLAY; + + mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean( + R.bool.config_allowAutoBrightnessWhileDozing); + + mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked() + .getDisplayDeviceConfig(); + + loadBrightnessRampRates(); + mSkipScreenOnBrightnessRamp = resources.getBoolean( + R.bool.config_skipScreenOnBrightnessRamp); + + mHbmController = createHbmControllerLocked(); + + mBrightnessThrottler = createBrightnessThrottlerLocked(); + + // Seed the cached brightness + saveBrightnessInfo(getScreenBrightnessSetting()); + + DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null; + DisplayWhiteBalanceController displayWhiteBalanceController = null; + if (mDisplayId == Display.DEFAULT_DISPLAY) { + try { + displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler); + displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler, + mSensorManager, resources); + displayWhiteBalanceSettings.setCallbacks(this); + displayWhiteBalanceController.setCallbacks(this); + } catch (Exception e) { + Slog.e(mTag, "failed to set up display white-balance: " + e); + } + } + mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings; + mDisplayWhiteBalanceController = displayWhiteBalanceController; + + loadNitsRange(resources); + + if (mDisplayId == Display.DEFAULT_DISPLAY) { + mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class); + boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() { + @Override + public void onReduceBrightColorsActivationChanged(boolean activated, + boolean userInitiated) { + applyReduceBrightColorsSplineAdjustment(); + + } + + @Override + public void onReduceBrightColorsStrengthChanged(int strength) { + applyReduceBrightColorsSplineAdjustment(); + } + }); + if (active) { + applyReduceBrightColorsSplineAdjustment(); + } + } else { + mCdsi = null; + } + + setUpAutoBrightness(resources, handler); + + mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic(); + mColorFadeFadesConfig = resources.getBoolean( + R.bool.config_animateScreenLights); + + mDisplayBlanksAfterDozeConfig = resources.getBoolean( + R.bool.config_displayBlanksAfterDoze); + + mBrightnessBucketsInDozeConfig = resources.getBoolean( + R.bool.config_displayBrightnessBucketsInDoze); + + loadProximitySensor(); + + mCurrentScreenBrightnessSetting = getScreenBrightnessSetting(); + mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); + mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); + mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + + } + + private void applyReduceBrightColorsSplineAdjustment() { + mHandler.obtainMessage(MSG_UPDATE_RBC).sendToTarget(); + sendUpdatePowerState(); + } + + private void handleRbcChanged() { + if (mAutomaticBrightnessController == null) { + return; + } + if ((!mAutomaticBrightnessController.isInIdleMode() + && mInteractiveModeBrightnessMapper == null) + || (mAutomaticBrightnessController.isInIdleMode() + && mIdleModeBrightnessMapper == null)) { + Log.w(mTag, "No brightness mapping available to recalculate splines for this mode"); + return; + } + + float[] adjustedNits = new float[mNitsRange.length]; + for (int i = 0; i < mNitsRange.length; i++) { + adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]); + } + mIsRbcActive = mCdsi.isReduceBrightColorsActivated(); + mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits); + } + + /** + * Returns true if the proximity sensor screen-off function is available. + */ + @Override + public boolean isProximitySensorAvailable() { + return mProximitySensor != null; + } + + /** + * Get the {@link BrightnessChangeEvent}s for the specified user. + * + * @param userId userId to fetch data for + * @param includePackage if false will null out the package name in events + */ + @Nullable + @Override + public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents( + @UserIdInt int userId, boolean includePackage) { + if (mBrightnessTracker == null) { + return null; + } + return mBrightnessTracker.getEvents(userId, includePackage); + } + + @Override + public void onSwitchUser(@UserIdInt int newUserId) { + handleSettingsChange(true /* userSwitch */); + if (mBrightnessTracker != null) { + mBrightnessTracker.onSwitchUser(newUserId); + } + } + + @Nullable + @Override + public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats( + @UserIdInt int userId) { + if (mBrightnessTracker == null) { + return null; + } + return mBrightnessTracker.getAmbientBrightnessStats(userId); + } + + /** + * Persist the brightness slider events and ambient brightness stats to disk. + */ + @Override + public void persistBrightnessTrackerState() { + if (mBrightnessTracker != null) { + mBrightnessTracker.persistBrightnessTrackerState(); + } + } + + /** + * Requests a new power state. + * The controller makes a copy of the provided object and then + * begins adjusting the power state to match what was requested. + * + * @param request The requested power state. + * @param waitForNegativeProximity If true, issues a request to wait for + * negative proximity before turning the screen back on, + * assuming the screen + * was turned off by the proximity sensor. + * @return True if display is ready, false if there are important changes that must + * be made asynchronously (such as turning the screen on), in which case the caller + * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()} + * then try the request again later until the state converges. + */ + public boolean requestPowerState(DisplayPowerRequest request, + boolean waitForNegativeProximity) { + if (DEBUG) { + Slog.d(mTag, "requestPowerState: " + + request + ", waitForNegativeProximity=" + waitForNegativeProximity); + } + + synchronized (mLock) { + if (mStopped) { + return true; + } + + boolean changed = false; + + if (waitForNegativeProximity + && !mPendingWaitForNegativeProximityLocked) { + mPendingWaitForNegativeProximityLocked = true; + changed = true; + } + + if (mPendingRequestLocked == null) { + mPendingRequestLocked = new DisplayPowerRequest(request); + changed = true; + } else if (!mPendingRequestLocked.equals(request)) { + mPendingRequestLocked.copyFrom(request); + changed = true; + } + + if (changed) { + mDisplayReadyLocked = false; + if (!mPendingRequestChangedLocked) { + mPendingRequestChangedLocked = true; + sendUpdatePowerStateLocked(); + } + } + + return mDisplayReadyLocked; + } + } + + @Override + public BrightnessConfiguration getDefaultBrightnessConfiguration() { + if (mAutomaticBrightnessController == null) { + return null; + } + return mAutomaticBrightnessController.getDefaultConfig(); + } + + /** + * Notified when the display is changed. We use this to apply any changes that might be needed + * when displays get swapped on foldable devices. For example, different brightness properties + * of each display need to be properly reflected in AutomaticBrightnessController. + * + * Make sure DisplayManagerService.mSyncRoot lock is held when this is called + */ + @Override + public void onDisplayChanged() { + final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + if (device == null) { + Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: " + + mLogicalDisplay.getDisplayIdLocked()); + return; + } + + final String uniqueId = device.getUniqueId(); + final DisplayDeviceConfig config = device.getDisplayDeviceConfig(); + final IBinder token = device.getDisplayTokenLocked(); + final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + mHandler.post(() -> { + if (mDisplayDevice != device) { + mDisplayDevice = device; + mUniqueDisplayId = uniqueId; + mDisplayStatsId = mUniqueDisplayId.hashCode(); + mDisplayDeviceConfig = config; + loadFromDisplayDeviceConfig(token, info); + updatePowerState(); + } + }); + } + + /** + * Called when the displays are preparing to transition from one device state to another. + * This process involves turning off some displays so we need updatePowerState() to run and + * calculate the new state. + */ + @Override + public void onDeviceStateTransition() { + sendUpdatePowerState(); + } + + /** + * Unregisters all listeners and interrupts all running threads; halting future work. + * + * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when + * the {@link #mDisplayId display} has been removed. + */ + @Override + public void stop() { + synchronized (mLock) { + mStopped = true; + Message msg = mHandler.obtainMessage(MSG_STOP); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); + + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setEnabled(false); + } + + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.stop(); + } + + if (mBrightnessSetting != null) { + mBrightnessSetting.unregisterListener(mBrightnessSettingListener); + } + + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } + } + + private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) { + // All properties that depend on the associated DisplayDevice and the DDC must be + // updated here. + loadBrightnessRampRates(); + loadProximitySensor(); + loadNitsRange(mContext.getResources()); + setUpAutoBrightness(mContext.getResources(), mHandler); + reloadReduceBrightColours(); + if (mScreenBrightnessRampAnimator != null) { + mScreenBrightnessRampAnimator.setAnimationTimeLimits( + mBrightnessRampIncreaseMaxTimeMillis, + mBrightnessRampDecreaseMaxTimeMillis); + } + mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, + mDisplayDeviceConfig.getHighBrightnessModeData(), + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }); + mBrightnessThrottler.resetThrottlingData( + mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId); + } + + private void sendUpdatePowerState() { + synchronized (mLock) { + sendUpdatePowerStateLocked(); + } + } + + @GuardedBy("mLock") + private void sendUpdatePowerStateLocked() { + if (!mStopped && !mPendingUpdatePowerStateLocked) { + mPendingUpdatePowerStateLocked = true; + Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); + } + } + + private void initialize(int displayState) { + mPowerState = mInjector.getDisplayPowerState(mBlanker, + mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState); + + if (mColorFadeEnabled) { + mColorFadeOnAnimator = ObjectAnimator.ofFloat( + mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f); + mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS); + mColorFadeOnAnimator.addListener(mAnimatorListener); + + mColorFadeOffAnimator = ObjectAnimator.ofFloat( + mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f); + mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS); + mColorFadeOffAnimator.addListener(mAnimatorListener); + } + + mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState, + DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT, + DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT); + mScreenBrightnessRampAnimator.setAnimationTimeLimits( + mBrightnessRampIncreaseMaxTimeMillis, + mBrightnessRampDecreaseMaxTimeMillis); + mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener); + + noteScreenState(mPowerState.getScreenState()); + noteScreenBrightness(mPowerState.getScreenBrightness()); + + // Initialize all of the brightness tracking state + final float brightness = convertToNits(mPowerState.getScreenBrightness()); + if (brightness >= PowerManager.BRIGHTNESS_MIN) { + mBrightnessTracker.start(brightness); + } + mBrightnessSettingListener = brightnessValue -> { + Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue); + mHandler.sendMessage(msg); + }; + + mBrightnessSetting.registerListener(mBrightnessSettingListener); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT), + false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ), + false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); + } + + private void setUpAutoBrightness(Resources resources, Handler handler) { + if (!mUseSoftwareAutoBrightnessConfig) { + return; + } + + final boolean isIdleScreenBrightnessEnabled = resources.getBoolean( + R.bool.config_enableIdleScreenBrightnessMode); + mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources, + mDisplayDeviceConfig, mDisplayWhiteBalanceController); + if (isIdleScreenBrightnessEnabled) { + mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources, + mDisplayDeviceConfig, mDisplayWhiteBalanceController); + } + + if (mInteractiveModeBrightnessMapper != null) { + final float dozeScaleFactor = resources.getFraction( + R.fraction.config_screenAutoBrightnessDozeScaleFactor, + 1, 1); + + int[] ambientBrighteningThresholds = resources.getIntArray( + R.array.config_ambientBrighteningThresholds); + int[] ambientDarkeningThresholds = resources.getIntArray( + R.array.config_ambientDarkeningThresholds); + int[] ambientThresholdLevels = resources.getIntArray( + R.array.config_ambientThresholdLevels); + float ambientDarkeningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(); + float ambientBrighteningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(); + HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels( + ambientBrighteningThresholds, ambientDarkeningThresholds, + ambientThresholdLevels, ambientDarkeningMinThreshold, + ambientBrighteningMinThreshold); + + int[] screenBrighteningThresholds = resources.getIntArray( + R.array.config_screenBrighteningThresholds); + int[] screenDarkeningThresholds = resources.getIntArray( + R.array.config_screenDarkeningThresholds); + int[] screenThresholdLevels = resources.getIntArray( + R.array.config_screenThresholdLevels); + float screenDarkeningMinThreshold = + mDisplayDeviceConfig.getScreenDarkeningMinThreshold(); + float screenBrighteningMinThreshold = + mDisplayDeviceConfig.getScreenBrighteningMinThreshold(); + HysteresisLevels screenBrightnessThresholds = new HysteresisLevels( + screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels, + screenDarkeningMinThreshold, screenBrighteningMinThreshold); + + // Idle screen thresholds + float screenDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(); + float screenBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(); + HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels( + screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels, + screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle); + + // Idle ambient thresholds + float ambientDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(); + float ambientBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(); + HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels( + ambientBrighteningThresholds, ambientDarkeningThresholds, + ambientThresholdLevels, ambientDarkeningMinThresholdIdle, + ambientBrighteningMinThresholdIdle); + + long brighteningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounce(); + long darkeningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounce(); + boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( + R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); + + int lightSensorWarmUpTimeConfig = resources.getInteger( + R.integer.config_lightSensorWarmupTime); + int lightSensorRate = resources.getInteger( + R.integer.config_autoBrightnessLightSensorRate); + int initialLightSensorRate = resources.getInteger( + R.integer.config_autoBrightnessInitialLightSensorRate); + if (initialLightSensorRate == -1) { + initialLightSensorRate = lightSensorRate; + } else if (initialLightSensorRate > lightSensorRate) { + Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate (" + + initialLightSensorRate + ") to be less than or equal to " + + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ")."); + } + + loadAmbientLightSensor(); + if (mBrightnessTracker != null) { + mBrightnessTracker.setLightSensor(mLightSensor); + } + + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.stop(); + } + mAutomaticBrightnessController = new AutomaticBrightnessController(this, + handler.getLooper(), mSensorManager, mLightSensor, + mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, + lightSensorRate, initialLightSensorRate, brighteningLightDebounce, + darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, + ambientBrightnessThresholds, screenBrightnessThresholds, + ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, + mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper, + mDisplayDeviceConfig.getAmbientHorizonShort(), + mDisplayDeviceConfig.getAmbientHorizonLong()); + + mBrightnessEventRingBuffer = + new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX); + } else { + mUseSoftwareAutoBrightnessConfig = false; + } + } + + private void loadBrightnessRampRates() { + mBrightnessRampRateFastDecrease = mDisplayDeviceConfig.getBrightnessRampFastDecrease(); + mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease(); + mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease(); + mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease(); + mBrightnessRampDecreaseMaxTimeMillis = + mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(); + mBrightnessRampIncreaseMaxTimeMillis = + mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(); + } + + private void loadNitsRange(Resources resources) { + if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) { + mNitsRange = mDisplayDeviceConfig.getNits(); + } else { + Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back"); + mNitsRange = BrightnessMappingStrategy.getFloatArray(resources + .obtainTypedArray(R.array.config_screenBrightnessNits)); + } + } + + private void reloadReduceBrightColours() { + if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) { + applyReduceBrightColorsSplineAdjustment(); + } + } + + @Override + public void setAutomaticScreenBrightnessMode(boolean isIdle) { + if (mAutomaticBrightnessController != null) { + if (isIdle) { + mAutomaticBrightnessController.switchToIdleMode(); + } else { + mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode(); + } + } + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle); + } + } + + private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + sendUpdatePowerState(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + + @Override + public void onAnimationCancel(Animator animation) { + } + }; + + private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() { + @Override + public void onAnimationEnd() { + sendUpdatePowerState(); + Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE); + mHandler.sendMessage(msg); + } + }; + + /** Clean up all resources that are accessed via the {@link #mHandler} thread. */ + private void cleanupHandlerThreadAfterStop() { + setProximitySensorEnabled(false); + mHbmController.stop(); + mBrightnessThrottler.stop(); + mHandler.removeCallbacksAndMessages(null); + + // Release any outstanding wakelocks we're still holding because of pending messages. + if (mUnfinishedBusiness) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness); + mUnfinishedBusiness = false; + } + if (mOnStateChangedPending) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged); + mOnStateChangedPending = false; + } + for (int i = 0; i < mOnProximityPositiveMessages; i++) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive); + } + mOnProximityPositiveMessages = 0; + for (int i = 0; i < mOnProximityNegativeMessages; i++) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative); + } + mOnProximityNegativeMessages = 0; + + final float brightness = mPowerState != null + ? mPowerState.getScreenBrightness() + : PowerManager.BRIGHTNESS_MIN; + reportStats(brightness); + + if (mPowerState != null) { + mPowerState.stop(); + mPowerState = null; + } + } + + private void updatePowerState() { + if (DEBUG) { + Trace.beginSection("DisplayPowerController#updatePowerState"); + } + updatePowerStateInternal(); + if (DEBUG) { + Trace.endSection(); + } + } + + private void updatePowerStateInternal() { + // Update the power state request. + final boolean mustNotify; + final int previousPolicy; + boolean mustInitialize = false; + int brightnessAdjustmentFlags = 0; + mBrightnessReasonTemp.set(null); + mTempBrightnessEvent.reset(); + synchronized (mLock) { + if (mStopped) { + return; + } + mPendingUpdatePowerStateLocked = false; + if (mPendingRequestLocked == null) { + return; // wait until first actual power request + } + + if (mPowerRequest == null) { + mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked); + updatePendingProximityRequestsLocked(); + mPendingRequestChangedLocked = false; + mustInitialize = true; + // Assume we're on and bright until told otherwise, since that's the state we turn + // on in. + previousPolicy = DisplayPowerRequest.POLICY_BRIGHT; + } else if (mPendingRequestChangedLocked) { + previousPolicy = mPowerRequest.policy; + mPowerRequest.copyFrom(mPendingRequestLocked); + updatePendingProximityRequestsLocked(); + mPendingRequestChangedLocked = false; + mDisplayReadyLocked = false; + } else { + previousPolicy = mPowerRequest.policy; + } + + mustNotify = !mDisplayReadyLocked; + } + + // Compute the basic display state using the policy. + // We might override this below based on other factors. + // Initialise brightness as invalid. + int state; + float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; + boolean performScreenOffTransition = false; + switch (mPowerRequest.policy) { + case DisplayPowerRequest.POLICY_OFF: + state = Display.STATE_OFF; + performScreenOffTransition = true; + break; + case DisplayPowerRequest.POLICY_DOZE: + if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) { + state = mPowerRequest.dozeScreenState; + } else { + state = Display.STATE_DOZE; + } + if (!mAllowAutoBrightnessWhileDozingConfig) { + brightnessState = mPowerRequest.dozeScreenBrightness; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE); + } + break; + case DisplayPowerRequest.POLICY_VR: + state = Display.STATE_VR; + break; + case DisplayPowerRequest.POLICY_DIM: + case DisplayPowerRequest.POLICY_BRIGHT: + default: + state = Display.STATE_ON; + break; + } + assert (state != Display.STATE_UNKNOWN); + + boolean skipRampBecauseOfProximityChangeToNegative = false; + // Apply the proximity sensor. + if (mProximitySensor != null) { + if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) { + // At this point the policy says that the screen should be on, but we've been + // asked to listen to the prox sensor to adjust the display state, so lets make + // sure the sensor is on. + setProximitySensorEnabled(true); + if (!mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE + && !mIgnoreProximityUntilChanged) { + // Prox sensor already reporting "near" so we should turn off the screen. + // Also checked that we aren't currently set to ignore the proximity sensor + // temporarily. + mScreenOffBecauseOfProximity = true; + sendOnProximityPositiveWithWakelock(); + } + } else if (mWaitingForNegativeProximity + && mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE + && state != Display.STATE_OFF) { + // The policy says that we should have the screen on, but it's off due to the prox + // and we've been asked to wait until the screen is far from the user to turn it + // back on. Let keep the prox sensor on so we can tell when it's far again. + setProximitySensorEnabled(true); + } else { + // We haven't been asked to use the prox sensor and we're not waiting on the screen + // to turn back on...so lets shut down the prox sensor. + setProximitySensorEnabled(false); + mWaitingForNegativeProximity = false; + } + + if (mScreenOffBecauseOfProximity + && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) { + // The screen *was* off due to prox being near, but now it's "far" so lets turn + // the screen back on. Also turn it back on if we've been asked to ignore the + // prox sensor temporarily. + mScreenOffBecauseOfProximity = false; + skipRampBecauseOfProximityChangeToNegative = true; + sendOnProximityNegativeWithWakelock(); + } + } else { + mWaitingForNegativeProximity = false; + mIgnoreProximityUntilChanged = false; + } + + if (!mLogicalDisplay.isEnabled() + || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + || mScreenOffBecauseOfProximity) { + state = Display.STATE_OFF; + } + + // Initialize things the first time the power state is changed. + if (mustInitialize) { + initialize(state); + } + + // Animate the screen state change unless already animating. + // The transition may be deferred, so after this point we will use the + // actual state instead of the desired one. + final int oldState = mPowerState.getScreenState(); + animateScreenStateChange(state, performScreenOffTransition); + state = mPowerState.getScreenState(); + + if (state == Display.STATE_OFF) { + brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF); + } + + // Always use the VR brightness when in the VR state. + if (state == Display.STATE_VR) { + brightnessState = mScreenBrightnessForVr; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR); + } + + if ((Float.isNaN(brightnessState)) + && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) { + brightnessState = mPowerRequest.screenBrightnessOverride; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE); + mAppliedScreenBrightnessOverride = true; + } else { + mAppliedScreenBrightnessOverride = false; + } + + final boolean autoBrightnessEnabledInDoze = + mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state); + final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness + && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) + && Float.isNaN(brightnessState) + && mAutomaticBrightnessController != null; + final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness + && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze); + final int autoBrightnessState = autoBrightnessEnabled + ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED + : autoBrightnessDisabledDueToDisplayOff + ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE + : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED; + + final boolean userSetBrightnessChanged = updateUserSetScreenBrightness(); + + // Use the temporary screen brightness if there isn't an override, either from + // WindowManager or based on the display state. + if (isValidBrightnessValue(mTemporaryScreenBrightness)) { + brightnessState = mTemporaryScreenBrightness; + mAppliedTemporaryBrightness = true; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY); + } else { + mAppliedTemporaryBrightness = false; + } + + final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment(); + + // Use the autobrightness adjustment override if set. + final float autoBrightnessAdjustment; + if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) { + autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment; + brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP; + mAppliedTemporaryAutoBrightnessAdjustment = true; + } else { + autoBrightnessAdjustment = mAutoBrightnessAdjustment; + brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO; + mAppliedTemporaryAutoBrightnessAdjustment = false; + } + // Apply brightness boost. + // We do this here after deciding whether auto-brightness is enabled so that we don't + // disable the light sensor during this temporary state. That way when boost ends we will + // be able to resume normal auto-brightness behavior without any delay. + if (mPowerRequest.boostScreenBrightness + && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) { + brightnessState = PowerManager.BRIGHTNESS_MAX; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST); + mAppliedBrightnessBoost = true; + } else { + mAppliedBrightnessBoost = false; + } + + // If the brightness is already set then it's been overridden by something other than the + // user, or is a temporary adjustment. + boolean userInitiatedChange = (Float.isNaN(brightnessState)) + && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged); + boolean hadUserBrightnessPoint = false; + // Configure auto-brightness. + if (mAutomaticBrightnessController != null) { + hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints(); + mAutomaticBrightnessController.configure(autoBrightnessState, + mBrightnessConfiguration, + mLastUserSetScreenBrightness, + userSetBrightnessChanged, autoBrightnessAdjustment, + autoBrightnessAdjustmentChanged, mPowerRequest.policy); + } + + if (mBrightnessTracker != null) { + mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration); + } + + boolean updateScreenBrightnessSetting = false; + + // Apply auto-brightness. + boolean slowChange = false; + if (Float.isNaN(brightnessState)) { + float newAutoBrightnessAdjustment = autoBrightnessAdjustment; + if (autoBrightnessEnabled) { + brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness( + mTempBrightnessEvent); + newAutoBrightnessAdjustment = + mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment(); + } + if (isValidBrightnessValue(brightnessState) + || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) { + // Use current auto-brightness value and slowly adjust to changes. + brightnessState = clampScreenBrightness(brightnessState); + if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) { + slowChange = true; // slowly adapt to auto-brightness + } + updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState; + mAppliedAutoBrightness = true; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC); + } else { + mAppliedAutoBrightness = false; + } + if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) { + // If the autobrightness controller has decided to change the adjustment value + // used, make sure that's reflected in settings. + putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment); + } else { + // Adjustment values resulted in no change + brightnessAdjustmentFlags = 0; + } + } else { + // Any non-auto-brightness values such as override or temporary should still be subject + // to clamping so that they don't go beyond the current max as specified by HBM + // Controller. + brightnessState = clampScreenBrightness(brightnessState); + mAppliedAutoBrightness = false; + brightnessAdjustmentFlags = 0; + } + + // Use default brightness when dozing unless overridden. + if ((Float.isNaN(brightnessState)) + && Display.isDozeState(state)) { + brightnessState = clampScreenBrightness(mScreenBrightnessDozeConfig); + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); + } + + // Apply manual brightness. + if (Float.isNaN(brightnessState)) { + brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting); + if (brightnessState != mCurrentScreenBrightnessSetting) { + // The manually chosen screen brightness is outside of the currently allowed + // range (i.e., high-brightness-mode), make sure we tell the rest of the system + // by updating the setting. + updateScreenBrightnessSetting = true; + } + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL); + } + + // Now that a desired brightness has been calculated, apply brightness throttling. The + // dimming and low power transformations that follow can only dim brightness further. + // + // We didn't do this earlier through brightness clamping because we need to know both + // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations. + // Note throttling effectively changes the allowed brightness range, so, similarly to HBM, + // we broadcast this change through setting. + final float unthrottledBrightnessState = brightnessState; + if (mBrightnessThrottler.isThrottled()) { + mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap()); + brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap()); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED); + if (!mAppliedThrottling) { + // Brightness throttling is needed, so do so quickly. + // Later, when throttling is removed, we let other mechanisms decide on speed. + slowChange = false; + } + mAppliedThrottling = true; + } else if (mAppliedThrottling) { + mAppliedThrottling = false; + } + + if (updateScreenBrightnessSetting) { + // Tell the rest of the system about the new brightness in case we had to change it + // for things like auto-brightness or high-brightness-mode. Note that we do this + // before applying the low power or dim transformations so that the slider + // accurately represents the full possible range, even if they range changes what + // it means in absolute terms. + updateScreenBrightnessSetting(brightnessState); + } + + // Apply dimming by at least some minimum amount when user activity + // timeout is about to expire. + if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { + brightnessState = Math.max( + Math.min(brightnessState - mScreenBrightnessMinimumDimAmount, + mScreenBrightnessDimConfig), + PowerManager.BRIGHTNESS_MIN); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED); + } + if (!mAppliedDimming) { + slowChange = false; + } + mAppliedDimming = true; + } else if (mAppliedDimming) { + slowChange = false; + mAppliedDimming = false; + } + // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor + // as long as it is above the minimum threshold. + if (mPowerRequest.lowPowerMode) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { + final float brightnessFactor = + Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1); + final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor); + brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER); + } + if (!mAppliedLowPower) { + slowChange = false; + } + mAppliedLowPower = true; + } else if (mAppliedLowPower) { + slowChange = false; + mAppliedLowPower = false; + } + + // The current brightness to use has been calculated at this point, and HbmController should + // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it + // here instead of having HbmController listen to the brightness setting because certain + // brightness sources (such as an app override) are not saved to the setting, but should be + // reflected in HBM calculations. + mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, + mBrightnessThrottler.getBrightnessMaxReason()); + + // Animate the screen brightness when the screen is on or dozing. + // Skip the animation when the screen is off or suspended or transition to/from VR. + boolean brightnessAdjusted = false; + final boolean brightnessIsTemporary = + mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment; + if (!mPendingScreenOff) { + if (mSkipScreenOnBrightnessRamp) { + if (state == Display.STATE_ON) { + if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) { + mInitialAutoBrightness = brightnessState; + mSkipRampState = RAMP_STATE_SKIP_INITIAL; + } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL + && mUseSoftwareAutoBrightnessConfig + && !BrightnessSynchronizer.floatEquals(brightnessState, + mInitialAutoBrightness)) { + mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT; + } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) { + mSkipRampState = RAMP_STATE_SKIP_NONE; + } + } else { + mSkipRampState = RAMP_STATE_SKIP_NONE; + } + } + + final boolean wasOrWillBeInVr = + (state == Display.STATE_VR || oldState == Display.STATE_VR); + final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState + != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative; + // While dozing, sometimes the brightness is split into buckets. Rather than animating + // through the buckets, which is unlikely to be smooth in the first place, just jump + // right to the suggested brightness. + final boolean hasBrightnessBuckets = + Display.isDozeState(state) && mBrightnessBucketsInDozeConfig; + // If the color fade is totally covering the screen then we can change the backlight + // level without it being a noticeable jump since any actual content isn't yet visible. + final boolean isDisplayContentVisible = + mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f; + // We only want to animate the brightness if it is between 0.0f and 1.0f. + // brightnessState can contain the values -1.0f and NaN, which we do not want to + // animate to. To avoid this, we check the value first. + // If the brightnessState is off (-1.0f) we still want to animate to the minimum + // brightness (0.0f) to accommodate for LED displays, which can appear bright to the + // user even when the display is all black. We also clamp here in case some + // transformations to the brightness have pushed it outside of the currently + // allowed range. + float animateValue = clampScreenBrightness(brightnessState); + + // If there are any HDR layers on the screen, we have a special brightness value that we + // use instead. We still preserve the calculated brightness for Standard Dynamic Range + // (SDR) layers, but the main brightness value will be the one for HDR. + float sdrAnimateValue = animateValue; + // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be + // done in HighBrightnessModeController. + if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + && ((mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0 + || (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER) + == 0)) { + // We want to scale HDR brightness level with the SDR level + animateValue = mHbmController.getHdrBrightnessValue(); + } + + final float currentBrightness = mPowerState.getScreenBrightness(); + final float currentSdrBrightness = mPowerState.getSdrScreenBrightness(); + if (isValidBrightnessValue(animateValue) + && (animateValue != currentBrightness + || sdrAnimateValue != currentSdrBrightness)) { + if (initialRampSkip || hasBrightnessBuckets + || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) { + animateScreenBrightness(animateValue, sdrAnimateValue, + SCREEN_ANIMATION_RATE_MINIMUM); + } else { + boolean isIncreasing = animateValue > currentBrightness; + final float rampSpeed; + if (isIncreasing && slowChange) { + rampSpeed = mBrightnessRampRateSlowIncrease; + } else if (isIncreasing && !slowChange) { + rampSpeed = mBrightnessRampRateFastIncrease; + } else if (!isIncreasing && slowChange) { + rampSpeed = mBrightnessRampRateSlowDecrease; + } else { + rampSpeed = mBrightnessRampRateFastDecrease; + } + animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed); + } + } + + // Report brightness to brightnesstracker: + // If brightness is not temporary (ie the slider has been released) + // AND if we are not in idle screen brightness mode. + if (!brightnessIsTemporary + && (mAutomaticBrightnessController != null + && !mAutomaticBrightnessController.isInIdleMode())) { + if (userInitiatedChange && (mAutomaticBrightnessController == null + || !mAutomaticBrightnessController.hasValidAmbientLux())) { + // If we don't have a valid lux reading we can't report a valid + // slider event so notify as if the system changed the brightness. + userInitiatedChange = false; + } + notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange, + hadUserBrightnessPoint); + } + + // We save the brightness info *after* the brightness setting has been changed and + // adjustments made so that the brightness info reflects the latest value. + brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); + } else { + brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting()); + } + + // Only notify if the brightness adjustment is not temporary (i.e. slider has been released) + if (brightnessAdjusted && !brightnessIsTemporary) { + postBrightnessChangeRunnable(); + } + + // Log any changes to what is currently driving the brightness setting. + if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) { + Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '" + + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags) + + "', previous reason: '" + mBrightnessReason + "'."); + mBrightnessReason.set(mBrightnessReasonTemp); + } else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL + && userSetBrightnessChanged) { + Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment."); + } + + + // Log brightness events when a detail of significance has changed. Generally this is the + // brightness itself changing, but also includes data like HBM cap, thermal throttling + // brightness cap, RBC state, etc. + mTempBrightnessEvent.setTime(System.currentTimeMillis()); + mTempBrightnessEvent.setBrightness(brightnessState); + mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId); + mTempBrightnessEvent.setReason(mBrightnessReason); + mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax()); + mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode()); + mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() + | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0) + | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)); + mTempBrightnessEvent.setRbcStrength(mCdsi != null + ? mCdsi.getReduceBrightColorsStrength() : -1); + mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor); + // Temporary is what we use during slider interactions. We avoid logging those so that + // we don't spam logcat when the slider is being used. + boolean tempToTempTransition = + mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY + && mLastBrightnessEvent.getReason().getReason() + == BrightnessReason.REASON_TEMPORARY; + if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition) + || brightnessAdjustmentFlags != 0) { + float lastBrightness = mLastBrightnessEvent.getBrightness(); + mTempBrightnessEvent.setInitialBrightness(lastBrightness); + mTempBrightnessEvent.setFastAmbientLux( + mAutomaticBrightnessController == null + ? -1f : mAutomaticBrightnessController.getFastAmbientLux()); + mTempBrightnessEvent.setSlowAmbientLux( + mAutomaticBrightnessController == null + ? -1f : mAutomaticBrightnessController.getSlowAmbientLux()); + mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness); + mLastBrightnessEvent.copyFrom(mTempBrightnessEvent); + BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent); + // Adjustment flags (and user-set flag) only get added after the equality checks since + // they are transient. + newEvent.setAdjustmentFlags(brightnessAdjustmentFlags); + newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged + ? BrightnessEvent.FLAG_USER_SET : 0)); + Slog.i(mTag, newEvent.toString(/* includeTime= */ false)); + + if (userSetBrightnessChanged) { + logManualBrightnessEvent(newEvent); + } + if (mBrightnessEventRingBuffer != null) { + mBrightnessEventRingBuffer.append(newEvent); + } + } + + // Update display white-balance. + if (mDisplayWhiteBalanceController != null) { + if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) { + mDisplayWhiteBalanceController.setEnabled(true); + mDisplayWhiteBalanceController.updateDisplayColorTemperature(); + } else { + mDisplayWhiteBalanceController.setEnabled(false); + } + } + + // Determine whether the display is ready for use in the newly requested state. + // Note that we do not wait for the brightness ramp animation to complete before + // reporting the display is ready because we only need to ensure the screen is in the + // right power state even as it continues to converge on the desired brightness. + final boolean ready = mPendingScreenOnUnblocker == null + && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted() + && !mColorFadeOffAnimator.isStarted())) + && mPowerState.waitUntilClean(mCleanListener); + final boolean finished = ready + && !mScreenBrightnessRampAnimator.isAnimating(); + + // Notify policy about screen turned on. + if (ready && state != Display.STATE_OFF + && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON); + mWindowManagerPolicy.screenTurnedOn(mDisplayId); + } + + // Grab a wake lock if we have unfinished business. + if (!finished && !mUnfinishedBusiness) { + if (DEBUG) { + Slog.d(mTag, "Unfinished business..."); + } + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness); + mUnfinishedBusiness = true; + } + + // Notify the power manager when ready. + if (ready && mustNotify) { + // Send state change. + synchronized (mLock) { + if (!mPendingRequestChangedLocked) { + mDisplayReadyLocked = true; + + if (DEBUG) { + Slog.d(mTag, "Display ready!"); + } + } + } + sendOnStateChangedWithWakelock(); + } + + // Release the wake lock when we have no unfinished business. + if (finished && mUnfinishedBusiness) { + if (DEBUG) { + Slog.d(mTag, "Finished business..."); + } + mUnfinishedBusiness = false; + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness); + } + + // Record if dozing for future comparison. + mDozing = state != Display.STATE_ON; + + if (previousPolicy != mPowerRequest.policy) { + logDisplayPolicyChanged(mPowerRequest.policy); + } + } + + @Override + public void updateBrightness() { + sendUpdatePowerState(); + } + + /** + * Ignores the proximity sensor until the sensor state changes, but only if the sensor is + * currently enabled and forcing the screen to be dark. + */ + @Override + public void ignoreProximitySensorUntilChanged() { + mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY); + } + + @Override + public void setBrightnessConfiguration(BrightnessConfiguration c) { + Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c); + msg.sendToTarget(); + } + + @Override + public void setTemporaryBrightness(float brightness) { + Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS, + Float.floatToIntBits(brightness), 0 /*unused*/); + msg.sendToTarget(); + } + + @Override + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT, + Float.floatToIntBits(adjustment), 0 /*unused*/); + msg.sendToTarget(); + } + + @Override + public BrightnessInfo getBrightnessInfo() { + synchronized (mCachedBrightnessInfo) { + return new BrightnessInfo( + mCachedBrightnessInfo.brightness.value, + mCachedBrightnessInfo.adjustedBrightness.value, + mCachedBrightnessInfo.brightnessMin.value, + mCachedBrightnessInfo.brightnessMax.value, + mCachedBrightnessInfo.hbmMode.value, + mCachedBrightnessInfo.hbmTransitionPoint.value, + mCachedBrightnessInfo.brightnessMaxReason.value); + } + } + + private boolean saveBrightnessInfo(float brightness) { + return saveBrightnessInfo(brightness, brightness); + } + + private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { + synchronized (mCachedBrightnessInfo) { + final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(), + mBrightnessThrottler.getBrightnessCap()); + final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(), + mBrightnessThrottler.getBrightnessCap()); + boolean changed = false; + + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness, + brightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness, + adjustedBrightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin, + minBrightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax, + maxBrightness); + changed |= + mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, + mHbmController.getHighBrightnessMode()); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, + mHbmController.getTransitionPoint()); + changed |= + mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, + mBrightnessThrottler.getBrightnessMaxReason()); + + return changed; + } + } + + void postBrightnessChangeRunnable() { + mHandler.post(mOnBrightnessChangeRunnable); + } + + private HighBrightnessModeController createHbmControllerLocked() { + final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); + final IBinder displayToken = + mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked(); + final String displayUniqueId = + mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + final DisplayDeviceConfig.HighBrightnessModeData hbmData = + ddConfig != null ? ddConfig.getHighBrightnessModeData() : null; + final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken, + displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }, + () -> { + sendUpdatePowerState(); + postBrightnessChangeRunnable(); + // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.update(); + } + }, mContext); + } + + private BrightnessThrottler createBrightnessThrottlerLocked() { + final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); + final DisplayDeviceConfig.BrightnessThrottlingData data = + ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null; + return new BrightnessThrottler(mHandler, data, + () -> { + sendUpdatePowerState(); + postBrightnessChangeRunnable(); + }, mUniqueDisplayId); + } + + private void blockScreenOn() { + if (mPendingScreenOnUnblocker == null) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); + mPendingScreenOnUnblocker = new ScreenOnUnblocker(); + mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime(); + Slog.i(mTag, "Blocking screen on until initial contents have been drawn."); + } + } + + private void unblockScreenOn() { + if (mPendingScreenOnUnblocker != null) { + mPendingScreenOnUnblocker = null; + long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime; + Slog.i(mTag, "Unblocked screen on after " + delay + " ms"); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); + } + } + + private void blockScreenOff() { + if (mPendingScreenOffUnblocker == null) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0); + mPendingScreenOffUnblocker = new ScreenOffUnblocker(); + mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime(); + Slog.i(mTag, "Blocking screen off"); + } + } + + private void unblockScreenOff() { + if (mPendingScreenOffUnblocker != null) { + mPendingScreenOffUnblocker = null; + long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime; + Slog.i(mTag, "Unblocked screen off after " + delay + " ms"); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0); + } + } + + private boolean setScreenState(int state) { + return setScreenState(state, false /*reportOnly*/); + } + + private boolean setScreenState(int state, boolean reportOnly) { + final boolean isOff = (state == Display.STATE_OFF); + + if (mPowerState.getScreenState() != state + || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) { + // If we are trying to turn screen off, give policy a chance to do something before we + // actually turn the screen off. + if (isOff && !mScreenOffBecauseOfProximity) { + if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON + || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF); + blockScreenOff(); + mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker); + unblockScreenOff(); + } else if (mPendingScreenOffUnblocker != null) { + // Abort doing the state change until screen off is unblocked. + return false; + } + } + + if (!reportOnly && mPowerState.getScreenState() != state) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state); + // TODO(b/153319140) remove when we can get this from the above trace invocation + SystemProperties.set("debug.tracing.screen_state", String.valueOf(state)); + mPowerState.setScreenState(state); + // Tell battery stats about the transition. + noteScreenState(state); + } + } + + // Tell the window manager policy when the screen is turned off or on unless it's due + // to the proximity sensor. We temporarily block turning the screen on until the + // window manager is ready by leaving a black surface covering the screen. + // This surface is essentially the final state of the color fade animation and + // it is only removed once the window manager tells us that the activity has + // finished drawing underneath. + if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF + && !mScreenOffBecauseOfProximity) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF); + unblockScreenOn(); + mWindowManagerPolicy.screenTurnedOff(mDisplayId); + } else if (!isOff + && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) { + + // We told policy already that screen was turning off, but now we changed our minds. + // Complete the full state transition on -> turningOff -> off. + unblockScreenOff(); + mWindowManagerPolicy.screenTurnedOff(mDisplayId); + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF); + } + if (!isOff + && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF + || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON); + if (mPowerState.getColorFadeLevel() == 0.0f) { + blockScreenOn(); + } else { + unblockScreenOn(); + } + mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker); + } + + // Return true if the screen isn't blocked. + return mPendingScreenOnUnblocker == null; + } + + private void setReportedScreenState(int state) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state); + mReportedScreenStateToPolicy = state; + } + + private void loadAmbientLightSensor() { + DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor(); + final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY + ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK; + mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name, + fallbackType); + } + + private void loadProximitySensor() { + if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) { + return; + } + final DisplayDeviceConfig.SensorData proxSensor = + mDisplayDeviceConfig.getProximitySensor(); + final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY + ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK; + mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name, + fallbackType); + if (mProximitySensor != null) { + mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(), + TYPICAL_PROXIMITY_THRESHOLD); + } + } + + private float clampScreenBrightnessForVr(float value) { + return MathUtils.constrain( + value, mScreenBrightnessForVrRangeMinimum, + mScreenBrightnessForVrRangeMaximum); + } + + private float clampScreenBrightness(float value) { + if (Float.isNaN(value)) { + value = PowerManager.BRIGHTNESS_MIN; + } + return MathUtils.constrain(value, + mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax()); + } + + // Checks whether the brightness is within the valid brightness range, not including off. + private boolean isValidBrightnessValue(float brightness) { + return brightness >= PowerManager.BRIGHTNESS_MIN + && brightness <= PowerManager.BRIGHTNESS_MAX; + } + + private void animateScreenBrightness(float target, float sdrTarget, float rate) { + if (DEBUG) { + Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget + + ", rate=" + rate); + } + if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target); + // TODO(b/153319140) remove when we can get this from the above trace invocation + SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target)); + noteScreenBrightness(target); + } + } + + private void animateScreenStateChange(int target, boolean performScreenOffTransition) { + // If there is already an animation in progress, don't interfere with it. + if (mColorFadeEnabled + && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) { + if (target != Display.STATE_ON) { + return; + } + // If display state changed to on, proceed and stop the color fade and turn screen on. + mPendingScreenOff = false; + } + + if (mDisplayBlanksAfterDozeConfig + && Display.isDozeState(mPowerState.getScreenState()) + && !Display.isDozeState(target)) { + // Skip the screen off animation and add a black surface to hide the + // contents of the screen. + mPowerState.prepareColorFade(mContext, + mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP); + if (mColorFadeOffAnimator != null) { + mColorFadeOffAnimator.end(); + } + // Some display hardware will blank itself on the transition between doze and non-doze + // but still on display states. In this case we want to report to policy that the + // display has turned off so it can prepare the appropriate power on animation, but we + // don't want to actually transition to the fully off state since that takes + // significantly longer to transition from. + setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/); + } + + // If we were in the process of turning off the screen but didn't quite + // finish. Then finish up now to prevent a jarring transition back + // to screen on if we skipped blocking screen on as usual. + if (mPendingScreenOff && target != Display.STATE_OFF) { + setScreenState(Display.STATE_OFF); + mPendingScreenOff = false; + mPowerState.dismissColorFadeResources(); + } + + if (target == Display.STATE_ON) { + // Want screen on. The contents of the screen may not yet + // be visible if the color fade has not been dismissed because + // its last frame of animation is solid black. + if (!setScreenState(Display.STATE_ON)) { + return; // screen on blocked + } + if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) { + // Perform screen on animation. + if (mPowerState.getColorFadeLevel() == 1.0f) { + mPowerState.dismissColorFade(); + } else if (mPowerState.prepareColorFade(mContext, + mColorFadeFadesConfig + ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) { + mColorFadeOnAnimator.start(); + } else { + mColorFadeOnAnimator.end(); + } + } else { + // Skip screen on animation. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } + } else if (target == Display.STATE_VR) { + // Wait for brightness animation to complete beforehand when entering VR + // from screen on to prevent a perceptible jump because brightness may operate + // differently when the display is configured for dozing. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() == Display.STATE_ON) { + return; + } + + // Set screen state. + if (!setScreenState(Display.STATE_VR)) { + return; // screen on blocked + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else if (target == Display.STATE_DOZE) { + // Want screen dozing. + // Wait for brightness animation to complete beforehand when entering doze + // from screen on to prevent a perceptible jump because brightness may operate + // differently when the display is configured for dozing. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() == Display.STATE_ON) { + return; + } + + // Set screen state. + if (!setScreenState(Display.STATE_DOZE)) { + return; // screen on blocked + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else if (target == Display.STATE_DOZE_SUSPEND) { + // Want screen dozing and suspended. + // Wait for brightness animation to complete beforehand unless already + // suspended because we may not be able to change it after suspension. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) { + return; + } + + // If not already suspending, temporarily set the state to doze until the + // screen on is unblocked, then suspend. + if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) { + if (!setScreenState(Display.STATE_DOZE)) { + return; // screen on blocked + } + setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else if (target == Display.STATE_ON_SUSPEND) { + // Want screen full-power and suspended. + // Wait for brightness animation to complete beforehand unless already + // suspended because we may not be able to change it after suspension. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) { + return; + } + + // If not already suspending, temporarily set the state to on until the + // screen on is unblocked, then suspend. + if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) { + if (!setScreenState(Display.STATE_ON)) { + return; + } + setScreenState(Display.STATE_ON_SUSPEND); + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else { + // Want screen off. + mPendingScreenOff = true; + if (!mColorFadeEnabled) { + mPowerState.setColorFadeLevel(0.0f); + } + + if (mPowerState.getColorFadeLevel() == 0.0f) { + // Turn the screen off. + // A black surface is already hiding the contents of the screen. + setScreenState(Display.STATE_OFF); + mPendingScreenOff = false; + mPowerState.dismissColorFadeResources(); + } else if (performScreenOffTransition + && mPowerState.prepareColorFade(mContext, + mColorFadeFadesConfig + ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN) + && mPowerState.getScreenState() != Display.STATE_OFF) { + // Perform the screen off animation. + mColorFadeOffAnimator.start(); + } else { + // Skip the screen off animation and add a black surface to hide the + // contents of the screen. + mColorFadeOffAnimator.end(); + } + } + } + + private final Runnable mCleanListener = this::sendUpdatePowerState; + + private void setProximitySensorEnabled(boolean enable) { + if (enable) { + if (!mProximitySensorEnabled) { + // Register the listener. + // Proximity sensor state already cleared initially. + mProximitySensorEnabled = true; + mIgnoreProximityUntilChanged = false; + mSensorManager.registerListener(mProximitySensorListener, mProximitySensor, + SensorManager.SENSOR_DELAY_NORMAL, mHandler); + } + } else { + if (mProximitySensorEnabled) { + // Unregister the listener. + // Clear the proximity sensor state for next time. + mProximitySensorEnabled = false; + mProximity = PROXIMITY_UNKNOWN; + mIgnoreProximityUntilChanged = false; + mPendingProximity = PROXIMITY_UNKNOWN; + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + mSensorManager.unregisterListener(mProximitySensorListener); + clearPendingProximityDebounceTime(); // release wake lock (must be last) + } + } + } + + private void handleProximitySensorEvent(long time, boolean positive) { + if (mProximitySensorEnabled) { + if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { + return; // no change + } + if (mPendingProximity == PROXIMITY_POSITIVE && positive) { + return; // no change + } + + // Only accept a proximity sensor reading if it remains + // stable for the entire debounce delay. We hold a wake lock while + // debouncing the sensor. + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + if (positive) { + mPendingProximity = PROXIMITY_POSITIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock + } else { + mPendingProximity = PROXIMITY_NEGATIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock + } + + // Debounce the new sensor reading. + debounceProximitySensor(); + } + } + + private void debounceProximitySensor() { + if (mProximitySensorEnabled + && mPendingProximity != PROXIMITY_UNKNOWN + && mPendingProximityDebounceTime >= 0) { + final long now = mClock.uptimeMillis(); + if (mPendingProximityDebounceTime <= now) { + if (mProximity != mPendingProximity) { + // if the status of the sensor changed, stop ignoring. + mIgnoreProximityUntilChanged = false; + Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]"); + } + // Sensor reading accepted. Apply the change then release the wake lock. + mProximity = mPendingProximity; + updatePowerState(); + clearPendingProximityDebounceTime(); // release wake lock (must be last) + } else { + // Need to wait a little longer. + // Debounce again later. We continue holding a wake lock while waiting. + Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED); + mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime); + } + } + } + + private void clearPendingProximityDebounceTime() { + if (mPendingProximityDebounceTime >= 0) { + mPendingProximityDebounceTime = -1; + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce); + } + } + + private void setPendingProximityDebounceTime(long debounceTime) { + if (mPendingProximityDebounceTime < 0) { + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce); + } + mPendingProximityDebounceTime = debounceTime; + } + + private void sendOnStateChangedWithWakelock() { + if (!mOnStateChangedPending) { + mOnStateChangedPending = true; + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged); + mHandler.post(mOnStateChangedRunnable); + } + } + + private void logDisplayPolicyChanged(int newPolicy) { + LogMaker log = new LogMaker(MetricsEvent.DISPLAY_POLICY); + log.setType(MetricsEvent.TYPE_UPDATE); + log.setSubtype(newPolicy); + MetricsLogger.action(log); + } + + private void handleSettingsChange(boolean userSwitch) { + mPendingScreenBrightnessSetting = getScreenBrightnessSetting(); + mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); + if (userSwitch) { + // Don't treat user switches as user initiated change. + setCurrentScreenBrightness(mPendingScreenBrightnessSetting); + updateAutoBrightnessAdjustment(); + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.resetShortTermModel(); + } + } + // We don't bother with a pending variable for VR screen brightness since we just + // immediately adapt to it. + mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); + sendUpdatePowerState(); + } + + private float getAutoBrightnessAdjustmentSetting() { + final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT); + return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj); + } + + @Override + public float getScreenBrightnessSetting() { + float brightness = mBrightnessSetting.getBrightness(); + if (Float.isNaN(brightness)) { + brightness = mScreenBrightnessDefault; + } + return clampAbsoluteBrightness(brightness); + } + + private float getScreenBrightnessForVrSetting() { + final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault, + UserHandle.USER_CURRENT); + return clampScreenBrightnessForVr(brightnessFloat); + } + + @Override + public void setBrightness(float brightnessValue) { + // Update the setting, which will eventually call back into DPC to have us actually update + // the display with the new value. + mBrightnessSetting.setBrightness(brightnessValue); + } + + private void updateScreenBrightnessSetting(float brightnessValue) { + if (!isValidBrightnessValue(brightnessValue) + || brightnessValue == mCurrentScreenBrightnessSetting) { + return; + } + setCurrentScreenBrightness(brightnessValue); + mBrightnessSetting.setBrightness(brightnessValue); + } + + private void setCurrentScreenBrightness(float brightnessValue) { + if (brightnessValue != mCurrentScreenBrightnessSetting) { + mCurrentScreenBrightnessSetting = brightnessValue; + postBrightnessChangeRunnable(); + } + } + + private void putAutoBrightnessAdjustmentSetting(float adjustment) { + if (mDisplayId == Display.DEFAULT_DISPLAY) { + mAutoBrightnessAdjustment = adjustment; + Settings.System.putFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment, + UserHandle.USER_CURRENT); + } + } + + private boolean updateAutoBrightnessAdjustment() { + if (Float.isNaN(mPendingAutoBrightnessAdjustment)) { + return false; + } + if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) { + mPendingAutoBrightnessAdjustment = Float.NaN; + return false; + } + mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment; + mPendingAutoBrightnessAdjustment = Float.NaN; + mTemporaryAutoBrightnessAdjustment = Float.NaN; + return true; + } + + // We want to return true if the user has set the screen brightness. + // RBC on, off, and intensity changes will return false. + // Slider interactions whilst in RBC will return true, just as when in non-rbc. + private boolean updateUserSetScreenBrightness() { + if ((Float.isNaN(mPendingScreenBrightnessSetting) + || mPendingScreenBrightnessSetting < 0.0f)) { + return false; + } + if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) { + mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + return false; + } + setCurrentScreenBrightness(mPendingScreenBrightnessSetting); + mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting; + mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + return true; + } + + private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, + boolean hadUserDataPoint) { + final float brightnessInNits = convertToNits(brightness); + if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f + && mAutomaticBrightnessController != null) { + // We only want to track changes on devices that can actually map the display backlight + // values into a physical brightness unit since the value provided by the API is in + // nits and not using the arbitrary backlight units. + final float powerFactor = mPowerRequest.lowPowerMode + ? mPowerRequest.screenLowPowerBrightnessFactor + : 1.0f; + mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated, + powerFactor, hadUserDataPoint, + mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId); + } + } + + private float convertToNits(float brightness) { + if (mAutomaticBrightnessController == null) { + return -1f; + } + return mAutomaticBrightnessController.convertToNits(brightness); + } + + @GuardedBy("mLock") + private void updatePendingProximityRequestsLocked() { + mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked; + mPendingWaitForNegativeProximityLocked = false; + + if (mIgnoreProximityUntilChanged) { + // Also, lets stop waiting for negative proximity if we're ignoring it. + mWaitingForNegativeProximity = false; + } + } + + private void ignoreProximitySensorUntilChangedInternal() { + if (!mIgnoreProximityUntilChanged + && mProximity == PROXIMITY_POSITIVE) { + // Only ignore if it is still reporting positive (near) + mIgnoreProximityUntilChanged = true; + Slog.i(mTag, "Ignoring proximity"); + updatePowerState(); + } + } + + private final Runnable mOnStateChangedRunnable = new Runnable() { + @Override + public void run() { + mOnStateChangedPending = false; + mCallbacks.onStateChanged(); + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged); + } + }; + + private void sendOnProximityPositiveWithWakelock() { + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive); + mHandler.post(mOnProximityPositiveRunnable); + mOnProximityPositiveMessages++; + } + + private final Runnable mOnProximityPositiveRunnable = new Runnable() { + @Override + public void run() { + mOnProximityPositiveMessages--; + mCallbacks.onProximityPositive(); + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive); + } + }; + + private void sendOnProximityNegativeWithWakelock() { + mOnProximityNegativeMessages++; + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative); + mHandler.post(mOnProximityNegativeRunnable); + } + + private final Runnable mOnProximityNegativeRunnable = new Runnable() { + @Override + public void run() { + mOnProximityNegativeMessages--; + mCallbacks.onProximityNegative(); + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative); + } + }; + + @Override + public void dump(final PrintWriter pw) { + synchronized (mLock) { + pw.println(); + pw.println("Display Power Controller:"); + pw.println(" mDisplayId=" + mDisplayId); + pw.println(" mLightSensor=" + mLightSensor); + + pw.println(); + pw.println("Display Power Controller Locked State:"); + pw.println(" mDisplayReadyLocked=" + mDisplayReadyLocked); + pw.println(" mPendingRequestLocked=" + mPendingRequestLocked); + pw.println(" mPendingRequestChangedLocked=" + mPendingRequestChangedLocked); + pw.println(" mPendingWaitForNegativeProximityLocked=" + + mPendingWaitForNegativeProximityLocked); + pw.println(" mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked); + } + + pw.println(); + pw.println("Display Power Controller Configuration:"); + pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault); + pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig); + pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); + pw.println(" mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum); + pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum); + pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault); + pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); + pw.println(" mAllowAutoBrightnessWhileDozingConfig=" + + mAllowAutoBrightnessWhileDozingConfig); + pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp); + pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig); + pw.println(" mColorFadeEnabled=" + mColorFadeEnabled); + synchronized (mCachedBrightnessInfo) { + pw.println(" mCachedBrightnessInfo.brightness=" + + mCachedBrightnessInfo.brightness.value); + pw.println(" mCachedBrightnessInfo.adjustedBrightness=" + + mCachedBrightnessInfo.adjustedBrightness.value); + pw.println(" mCachedBrightnessInfo.brightnessMin=" + + mCachedBrightnessInfo.brightnessMin.value); + pw.println(" mCachedBrightnessInfo.brightnessMax=" + + mCachedBrightnessInfo.brightnessMax.value); + pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value); + pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" + + mCachedBrightnessInfo.hbmTransitionPoint.value); + pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" + + mCachedBrightnessInfo.brightnessMaxReason.value); + } + pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); + pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); + + mHandler.runWithScissors(() -> dumpLocal(pw), 1000); + } + + private void dumpLocal(PrintWriter pw) { + pw.println(); + pw.println("Display Power Controller Thread State:"); + pw.println(" mPowerRequest=" + mPowerRequest); + pw.println(" mUnfinishedBusiness=" + mUnfinishedBusiness); + pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity); + pw.println(" mProximitySensor=" + mProximitySensor); + pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled); + pw.println(" mProximityThreshold=" + mProximityThreshold); + pw.println(" mProximity=" + proximityToString(mProximity)); + pw.println(" mPendingProximity=" + proximityToString(mPendingProximity)); + pw.println(" mPendingProximityDebounceTime=" + + TimeUtils.formatUptime(mPendingProximityDebounceTime)); + pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity); + pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness); + pw.println(" mPendingScreenBrightnessSetting=" + + mPendingScreenBrightnessSetting); + pw.println(" mTemporaryScreenBrightness=" + mTemporaryScreenBrightness); + pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); + pw.println(" mBrightnessReason=" + mBrightnessReason); + pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment); + pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment); + pw.println(" mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr); + pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness); + pw.println(" mAppliedDimming=" + mAppliedDimming); + pw.println(" mAppliedLowPower=" + mAppliedLowPower); + pw.println(" mAppliedThrottling=" + mAppliedThrottling); + pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride); + pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness); + pw.println(" mAppliedTemporaryAutoBrightnessAdjustment=" + + mAppliedTemporaryAutoBrightnessAdjustment); + pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost); + pw.println(" mDozing=" + mDozing); + pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState)); + pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime); + pw.println(" mScreenOffBlockStartRealTime=" + mScreenOffBlockStartRealTime); + pw.println(" mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker); + pw.println(" mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker); + pw.println(" mPendingScreenOff=" + mPendingScreenOff); + pw.println(" mReportedToPolicy=" + + reportedToPolicyToString(mReportedScreenStateToPolicy)); + pw.println(" mIsRbcActive=" + mIsRbcActive); + pw.println(" mOnStateChangePending=" + mOnStateChangedPending); + pw.println(" mOnProximityPositiveMessages=" + mOnProximityPositiveMessages); + pw.println(" mOnProximityNegativeMessages=" + mOnProximityNegativeMessages); + + if (mScreenBrightnessRampAnimator != null) { + pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" + + mScreenBrightnessRampAnimator.isAnimating()); + } + + if (mColorFadeOnAnimator != null) { + pw.println(" mColorFadeOnAnimator.isStarted()=" + + mColorFadeOnAnimator.isStarted()); + } + if (mColorFadeOffAnimator != null) { + pw.println(" mColorFadeOffAnimator.isStarted()=" + + mColorFadeOffAnimator.isStarted()); + } + + if (mPowerState != null) { + mPowerState.dump(pw); + } + + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.dump(pw); + dumpBrightnessEvents(pw); + } + + if (mHbmController != null) { + mHbmController.dump(pw); + } + + if (mBrightnessThrottler != null) { + mBrightnessThrottler.dump(pw); + } + + pw.println(); + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.dump(pw); + mDisplayWhiteBalanceSettings.dump(pw); + } + } + + private static String proximityToString(int state) { + switch (state) { + case PROXIMITY_UNKNOWN: + return "Unknown"; + case PROXIMITY_NEGATIVE: + return "Negative"; + case PROXIMITY_POSITIVE: + return "Positive"; + default: + return Integer.toString(state); + } + } + + private static String reportedToPolicyToString(int state) { + switch (state) { + case REPORTED_TO_POLICY_SCREEN_OFF: + return "REPORTED_TO_POLICY_SCREEN_OFF"; + case REPORTED_TO_POLICY_SCREEN_TURNING_ON: + return "REPORTED_TO_POLICY_SCREEN_TURNING_ON"; + case REPORTED_TO_POLICY_SCREEN_ON: + return "REPORTED_TO_POLICY_SCREEN_ON"; + default: + return Integer.toString(state); + } + } + + private static String skipRampStateToString(int state) { + switch (state) { + case RAMP_STATE_SKIP_NONE: + return "RAMP_STATE_SKIP_NONE"; + case RAMP_STATE_SKIP_INITIAL: + return "RAMP_STATE_SKIP_INITIAL"; + case RAMP_STATE_SKIP_AUTOBRIGHT: + return "RAMP_STATE_SKIP_AUTOBRIGHT"; + default: + return Integer.toString(state); + } + } + + private void dumpBrightnessEvents(PrintWriter pw) { + int size = mBrightnessEventRingBuffer.size(); + if (size < 1) { + pw.println("No Automatic Brightness Adjustments"); + return; + } + + pw.println("Automatic Brightness Adjustments Last " + size + " Events: "); + BrightnessEvent[] eventArray = mBrightnessEventRingBuffer.toArray(); + for (int i = 0; i < mBrightnessEventRingBuffer.size(); i++) { + pw.println(" " + eventArray[i].toString()); + } + } + + private static float clampAbsoluteBrightness(float value) { + return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX); + } + + private static float clampAutoBrightnessAdjustment(float value) { + return MathUtils.constrain(value, -1.0f, 1.0f); + } + + private void noteScreenState(int screenState) { + if (mBatteryStats != null) { + try { + // TODO(multi-display): make this multi-display + mBatteryStats.noteScreenState(screenState); + } catch (RemoteException e) { + // same process + } + } + } + + private void noteScreenBrightness(float brightness) { + if (mBatteryStats != null) { + try { + // TODO(brightnessfloat): change BatteryStats to use float + mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt( + brightness)); + } catch (RemoteException e) { + // same process + } + } + } + + private void reportStats(float brightness) { + if (mLastStatsBrightness == brightness) { + return; + } + + float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX; + synchronized (mCachedBrightnessInfo) { + if (mCachedBrightnessInfo.hbmTransitionPoint == null) { + return; + } + hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value; + } + + final boolean aboveTransition = brightness > hbmTransitionPoint; + final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint; + + if (aboveTransition || oldAboveTransition) { + mLastStatsBrightness = brightness; + mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS); + if (aboveTransition != oldAboveTransition) { + // report immediately + logHbmBrightnessStats(brightness, mDisplayStatsId); + } else { + // delay for rate limiting + Message msg = mHandler.obtainMessage(); + msg.what = MSG_STATSD_HBM_BRIGHTNESS; + msg.arg1 = Float.floatToIntBits(brightness); + msg.arg2 = mDisplayStatsId; + mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS); + } + } + } + + private void logHbmBrightnessStats(float brightness, int displayStatsId) { + synchronized (mHandler) { + FrameworkStatsLog.write( + FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness); + } + } + + private void logManualBrightnessEvent(BrightnessEvent event) { + float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f; + int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1; + float appliedHbmMaxNits = + event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF + ? -1f : convertToNits(event.getHbmMax()); + // thermalCapNits set to -1 if not currently capping max brightness + float appliedThermalCapNits = + event.getThermalMax() == PowerManager.BRIGHTNESS_MAX + ? -1f : convertToNits(event.getThermalMax()); + + FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, + convertToNits(event.getInitialBrightness()), + convertToNits(event.getBrightness()), + event.getSlowAmbientLux(), + event.getPhysicalDisplayId(), + event.isShortTermModelActive(), + appliedLowPowerMode, + appliedRbcStrength, + appliedHbmMaxNits, + appliedThermalCapNits, + event.isAutomaticBrightnessEnabled(), + FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL); + } + + private final class DisplayControllerHandler extends Handler { + DisplayControllerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_POWER_STATE: + updatePowerState(); + break; + + case MSG_PROXIMITY_SENSOR_DEBOUNCED: + debounceProximitySensor(); + break; + + case MSG_SCREEN_ON_UNBLOCKED: + if (mPendingScreenOnUnblocker == msg.obj) { + unblockScreenOn(); + updatePowerState(); + } + break; + case MSG_SCREEN_OFF_UNBLOCKED: + if (mPendingScreenOffUnblocker == msg.obj) { + unblockScreenOff(); + updatePowerState(); + } + break; + case MSG_CONFIGURE_BRIGHTNESS: + mBrightnessConfiguration = (BrightnessConfiguration) msg.obj; + updatePowerState(); + break; + + case MSG_SET_TEMPORARY_BRIGHTNESS: + // TODO: Should we have a a timeout for the temporary brightness? + mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1); + updatePowerState(); + break; + + case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT: + mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1); + updatePowerState(); + break; + + case MSG_IGNORE_PROXIMITY: + ignoreProximitySensorUntilChangedInternal(); + break; + + case MSG_STOP: + cleanupHandlerThreadAfterStop(); + break; + + case MSG_UPDATE_BRIGHTNESS: + if (mStopped) { + return; + } + handleSettingsChange(false /*userSwitch*/); + break; + + case MSG_UPDATE_RBC: + handleRbcChanged(); + break; + + case MSG_BRIGHTNESS_RAMP_DONE: + if (mPowerState != null) { + final float brightness = mPowerState.getScreenBrightness(); + reportStats(brightness); + } + break; + + case MSG_STATSD_HBM_BRIGHTNESS: + logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2); + break; + } + } + } + + private final SensorEventListener mProximitySensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mProximitySensorEnabled) { + final long time = mClock.uptimeMillis(); + final float distance = event.values[0]; + boolean positive = distance >= 0.0f && distance < mProximityThreshold; + handleProximitySensorEvent(time, positive); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + + + private final class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + handleSettingsChange(false /* userSwitch */); + } + } + + private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener { + @Override + public void onScreenOn() { + Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this); + mHandler.sendMessage(msg); + } + } + + private final class ScreenOffUnblocker implements WindowManagerPolicy.ScreenOffListener { + @Override + public void onScreenOff() { + Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this); + mHandler.sendMessage(msg); + } + } + + @Override + public void setAutoBrightnessLoggingEnabled(boolean enabled) { + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.setLoggingEnabled(enabled); + } + } + + @Override // DisplayWhiteBalanceController.Callbacks + public void updateWhiteBalance() { + sendUpdatePowerState(); + } + + @Override + public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) { + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setLoggingEnabled(enabled); + mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled); + } + } + + @Override + public void setAmbientColorTemperatureOverride(float cct) { + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct); + // The ambient color temperature override is only applied when the ambient color + // temperature changes or is updated, so it doesn't necessarily change the screen color + // temperature immediately. So, let's make it! + sendUpdatePowerState(); + } + } + + @VisibleForTesting + String getSuspendBlockerUnfinishedBusinessId(int displayId) { + return "[" + displayId + "]unfinished business"; + } + + String getSuspendBlockerOnStateChangedId(int displayId) { + return "[" + displayId + "]on state changed"; + } + + String getSuspendBlockerProxPositiveId(int displayId) { + return "[" + displayId + "]prox positive"; + } + + String getSuspendBlockerProxNegativeId(int displayId) { + return "[" + displayId + "]prox negative"; + } + + @VisibleForTesting + String getSuspendBlockerProxDebounceId(int displayId) { + return "[" + displayId + "]prox debounce"; + } + + /** Functional interface for providing time. */ + @VisibleForTesting + interface Clock { + /** + * Returns current time in milliseconds since boot, not counting time spent in deep sleep. + */ + long uptimeMillis(); + } + + @VisibleForTesting + static class Injector { + Clock getClock() { + return SystemClock::uptimeMillis; + } + + DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, + int displayId, int displayState) { + return new DisplayPowerState(blanker, colorFade, displayId, displayState); + } + + DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps, + FloatProperty<DisplayPowerState> firstProperty, + FloatProperty<DisplayPowerState> secondProperty) { + return new DualRampAnimator(dps, firstProperty, secondProperty); + } + } + + static class CachedBrightnessInfo { + public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat adjustedBrightness = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat brightnessMin = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat brightnessMax = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); + public MutableFloat hbmTransitionPoint = + new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID); + public MutableInt brightnessMaxReason = + new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE); + + public boolean checkAndSetFloat(MutableFloat mf, float f) { + if (mf.value != f) { + mf.value = f; + return true; + } + return false; + } + + public boolean checkAndSetInt(MutableInt mi, int i) { + if (mi.value != i) { + mi.value = i; + return true; + } + return false; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java new file mode 100644 index 000000000000..1a5d496f2002 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.os.Handler; +import android.os.PowerManager; +import android.os.test.TestLooper; +import android.util.FloatProperty; +import android.view.Display; +import android.view.DisplayInfo; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.display.RampAnimator.DualRampAnimator; +import com.android.server.policy.WindowManagerPolicy; +import com.android.server.testutils.OffsettableClock; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class DisplayPowerController2Test { + private static final String UNIQUE_DISPLAY_ID = "unique_id_test123"; + private static final int DISPLAY_ID = 42; + + private OffsettableClock mClock; + private TestLooper mTestLooper; + private Handler mHandler; + private DisplayPowerController2.Injector mInjector; + private Context mContextSpy; + + @Mock + private DisplayPowerCallbacks mDisplayPowerCallbacksMock; + @Mock + private SensorManager mSensorManagerMock; + @Mock + private DisplayBlanker mDisplayBlankerMock; + @Mock + private LogicalDisplay mLogicalDisplayMock; + @Mock + private DisplayDevice mDisplayDeviceMock; + @Mock + private BrightnessTracker mBrightnessTrackerMock; + @Mock + private BrightnessSetting mBrightnessSettingMock; + @Mock + private WindowManagerPolicy mWindowManagerPolicyMock; + @Mock + private PowerManager mPowerManagerMock; + @Mock + private Resources mResourcesMock; + @Mock + private DisplayDeviceConfig mDisplayDeviceConfigMock; + @Mock + private DisplayPowerState mDisplayPowerStateMock; + @Mock + private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock; + + @Captor + private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + mHandler = new Handler(mTestLooper.getLooper()); + mInjector = new DisplayPowerController2.Injector() { + @Override + DisplayPowerController2.Clock getClock() { + return mClock::now; + } + + @Override + DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, + int displayId, int displayState) { + return mDisplayPowerStateMock; + } + + @Override + DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps, + FloatProperty<DisplayPowerState> firstProperty, + FloatProperty<DisplayPowerState> secondProperty) { + return mDualRampAnimatorMock; + } + }; + + addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock); + + when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock); + when(mContextSpy.getResources()).thenReturn(mResourcesMock); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(WindowManagerPolicy.class); + } + + @Test + public void testReleaseProxSuspendBlockersOnExit() throws Exception { + setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID); + + Sensor proxSensor = setUpProxSensor(); + + DisplayPowerController2 dpc = new DisplayPowerController2( + mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler, + mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock, + mBrightnessTrackerMock, mBrightnessSettingMock, () -> { + }); + + when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON); + // send a display power request + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + dpr.useProximitySensor = true; + dpc.requestPowerState(dpr, false /* waitForNegativeProximity */); + + // Run updatePowerState to start listener for the prox sensor + advanceTime(1); + + SensorEventListener listener = getSensorEventListener(proxSensor); + assertNotNull(listener); + + listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */)); + advanceTime(1); + + // two times, one for unfinished business and one for proximity + verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker( + dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID)); + verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker( + dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID)); + + dpc.stop(); + advanceTime(1); + + // two times, one for unfinished business and one for proximity + verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker( + dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID)); + verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker( + dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID)); + } + + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + + private Sensor setUpProxSensor() throws Exception { + Sensor proxSensor = TestUtils.createSensor( + Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY); + when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL))) + .thenReturn(List.of(proxSensor)); + return proxSensor; + } + + private SensorEventListener getSensorEventListener(Sensor sensor) { + verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(), + eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class)); + return mSensorEventListenerCaptor.getValue(); + } + + private void setUpDisplay(int displayId, String uniqueId) { + DisplayInfo info = new DisplayInfo(); + DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); + + when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); + when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock); + when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); + when(mLogicalDisplayMock.isEnabled()).thenReturn(true); + when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED); + when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); + when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId); + when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock); + when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn( + new DisplayDeviceConfig.SensorData() { + { + type = Sensor.STRING_TYPE_PROXIMITY; + name = null; + } + }); + when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); + } +} |