summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/power/DisplayPowerController.java
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2012-07-27 15:51:34 -0700
committerJeff Brown <jeffbrown@google.com>2012-08-15 03:06:24 -0700
commit9630704ed3b265f008a8f64ec60a33cf9dcd3345 (patch)
tree0c905e55ac062b625bf7a9ced250f05213d7873f /services/java/com/android/server/power/DisplayPowerController.java
parentff7e6ef4f18ff94a9836492ff3ccd1ba7f6804f3 (diff)
downloadframeworks_base-9630704ed3b265f008a8f64ec60a33cf9dcd3345.zip
frameworks_base-9630704ed3b265f008a8f64ec60a33cf9dcd3345.tar.gz
frameworks_base-9630704ed3b265f008a8f64ec60a33cf9dcd3345.tar.bz2
Power manager rewrite.
The major goal of this rewrite is to make it easier to implement power management policies correctly. According, the new implementation primarily uses state-based rather than event-based triggers for applying changes to the current power state. For example, when an application requests that the proximity sensor be used to manage the screen state (by way of a wake lock), the power manager makes note of the fact that the set of wake locks changed. Then it executes a common update function that recalculates the entire state, first looking at wake locks, then considering user activity, and eventually determining whether the screen should be turned on or off. At this point it may make a request to a component called the DisplayPowerController to asynchronously update the display's powe state. Likewise, DisplayPowerController makes note of the updated power request and schedules its own update function to figure out what needs to be changed. The big benefit of this approach is that it's easy to mutate multiple properties of the power state simultaneously then apply their joint effects together all at once. Transitions between states are detected and resolved by the update in a consistent manner. The new power manager service has is implemented as a set of loosely coupled components. For the most part, information only flows one way through these components (by issuing a request to that component) although some components support sending a message back to indicate when the work has been completed. For example, the DisplayPowerController posts a callback runnable asynchronously to tell the PowerManagerService when the display is ready. An important feature of this approach is that each component neatly encapsulates its state and maintains its own invariants. Moreover, we do not need to worry about deadlocks or awkward mutual exclusion semantics because most of the requests are asynchronous. The benefits of this design are especially apparent in the implementation of the screen on / off and brightness control animations which are able to take advantage of framework features like properties, ObjectAnimator and Choreographer. The screen on / off animation is now the responsibility of the power manager (instead of surface flinger). This change makes it much easier to ensure that the animation is properly coordinated with other power state changes and eliminates the cause of race conditions in the older implementation. The because of the userActivity() function has been changed so that it never wakes the device from sleep. This change removes ambiguity around forcing or disabling user activity for various purposes. To wake the device, use wakeUp(). To put it to sleep, use goToSleep(). Simple. The power manager service interface and API has been significantly simplified and consolidated. Also fixed some inconsistencies related to how the minimum and maximum screen brightness setting was presented in brightness control widgets and enforced behind the scenes. At present the following features are implemented: - Wake locks. - User activity. - Wake up / go to sleep. - Power state broadcasts. - Battery stats and event log notifications. - Dreams. - Proximity screen off. - Animated screen on / off transitions. - Auto-dimming. - Auto-brightness control for the screen backlight with different timeouts for ramping up versus ramping down. - Auto-on when plugged or unplugged. - Stay on when plugged. - Device administration maximum user activity timeout. - Application controlled brightness via window manager. The following features are not yet implemented: - Reduced user activity timeout for the key guard. - Reduced user activity timeout for the phone application. - Coordinating screen on barriers with the window manager. - Preventing auto-rotation during power state changes. - Auto-brightness adjustment setting (feature was disabled in previous version of the power manager service pending an improved UI design so leaving it out for now). - Interpolated brightness control (a proposed new scheme for more compactly specifying auto-brightness levels in config.xml). - Button / keyboard backlight control. - Change window manager to associated WorkSource with KEEP_SCREEN_ON_FLAG wake lock instead of talking directly to the battery stats service. - Optionally support animating screen brightness when turning on/off instead of playing electron beam animation (config_animateScreenLights). Change-Id: I1d7a52e98f0449f76d70bf421f6a7f245957d1d7
Diffstat (limited to 'services/java/com/android/server/power/DisplayPowerController.java')
-rw-r--r--services/java/com/android/server/power/DisplayPowerController.java992
1 files changed, 992 insertions, 0 deletions
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
new file mode 100644
index 0000000..50d3f81
--- /dev/null
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -0,0 +1,992 @@
+/*
+ * Copyright (C) 2012 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.power;
+
+import com.android.server.LightsService;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.SystemSensorManager;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * 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 electron beam and brightness animations run
+ * slower by changing the "animator duration scale" option in Development Settings.
+ */
+final class DisplayPowerController {
+ private static final String TAG = "DisplayPowerController";
+
+ private static boolean DEBUG = false;
+ private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+ private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
+
+ private static final int ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS = 300;
+ private static final int ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS = 600;
+
+ private static final int MSG_UPDATE_POWER_STATE = 1;
+ private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
+ private static final int MSG_LIGHT_SENSOR_DEBOUNCED = 3;
+
+ 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.
+ private static final int PROXIMITY_SENSOR_DEBOUNCE_DELAY = 250;
+
+ // Trigger proximity if distance is less than 5 cm.
+ private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
+
+ // Light sensor event rate in microseconds.
+ private static final int LIGHT_SENSOR_RATE = 1000000;
+
+ // Brightness animation ramp rate in brightness units per second.
+ private static final int BRIGHTNESS_RAMP_RATE_FAST = 200;
+ private static final int BRIGHTNESS_RAMP_RATE_SLOW = 50;
+
+ // Filter time constant in milliseconds for computing a moving
+ // average of light samples. Different constants are used
+ // to adapt to brighter or dimmer environments.
+ private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 2500; // 2.5 sec
+ private static final long DIMMING_LIGHT_TIME_CONSTANT = 10000; // 10 sec
+
+ private final Object mLock = new Object();
+
+ // Notifier for sending asynchronous notifications.
+ private final Notifier mNotifier;
+
+ // A suspend blocker.
+ private final SuspendBlocker mSuspendBlocker;
+
+ // 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 Callbacks mCallbacks;
+ private Handler mCallbackHandler;
+
+ // The lights service.
+ private final LightsService mLights;
+
+ // The sensor manager.
+ private final SensorManager mSensorManager;
+
+ // The proximity sensor, or null if not available or needed.
+ private Sensor mProximitySensor;
+
+ // The light sensor, or null if not available or needed.
+ private Sensor mLightSensor;
+
+ // The dim screen brightness.
+ private final int mScreenBrightnessDimConfig;
+
+ // Auto-brightness.
+ private boolean mUseSoftwareAutoBrightnessConfig;
+ private int[] mAutoBrightnessLevelsConfig;
+ private int[] mAutoBrightnessLcdBacklightValuesConfig;
+
+ // Amount of time to delay auto-brightness after screen on while waiting for
+ // the light sensor to warm-up in milliseconds.
+ // May be 0 if no warm-up is required.
+ private int mLightSensorWarmUpTimeConfig;
+
+ // The pending power request.
+ // Initially null until the first call to requestPowerState.
+ // Guarded by mLock.
+ private DisplayPowerRequest mPendingRequestLocked;
+
+ // True if a request has been made to wait for the proximity sensor to go negative.
+ // Guarded by mLock.
+ private boolean mPendingWaitForNegativeProximityLocked;
+
+ // True if the pending power request or wait for negative proximity flag
+ // has been changed since the last update occurred.
+ // Guarded by 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.
+ // Guarded by mLock.
+ private boolean mDisplayReadyLocked;
+
+ // Set to true if a power state update is required.
+ // Guarded by 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;
+
+ // 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;
+
+ // 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;
+
+ // Set to true if the light sensor is enabled.
+ private boolean mLightSensorEnabled;
+
+ // The time when the light sensor was enabled.
+ private long mLightSensorEnableTime;
+
+ // The currently accepted average light sensor value.
+ private float mLightMeasurement;
+
+ // True if the light sensor measurement is valid.
+ private boolean mLightMeasurementValid;
+
+ // The number of light sensor samples that have been collected since the
+ // last time a light sensor reading was accepted.
+ private int mRecentLightSamples;
+
+ // The moving average of recent light sensor values.
+ private float mRecentLightAverage;
+
+ // True if recent light samples are getting brighter than the previous
+ // stable light measurement.
+ private boolean mRecentLightBrightening;
+
+ // The time constant to use for filtering based on whether the
+ // light appears to be brightening or dimming.
+ private long mRecentLightTimeConstant;
+
+ // The most recent light sample.
+ private float mLastLightSample;
+
+ // The time of the most light recent sample.
+ private long mLastLightSampleTime;
+
+ // The upcoming debounce light sensor time.
+ // This is only valid when mLightMeasurementValue && mRecentLightSamples >= 1.
+ private long mPendingLightSensorDebounceTime;
+
+ // The screen brightness level that has been chosen by the auto-brightness
+ // algorithm. The actual brightness should ramp towards this value.
+ // We preserve this value even when we stop using the light sensor so
+ // that we can quickly revert to the previous auto-brightness level
+ // while the light sensor warms up.
+ // Use -1 if there is no current auto-brightness value available.
+ private int mScreenAutoBrightness = -1;
+
+ // True if the screen auto-brightness value is actually being used to
+ // set the display brightness.
+ private boolean mUsingScreenAutoBrightness;
+
+ // Animators.
+ private ObjectAnimator mElectronBeamOnAnimator;
+ private ObjectAnimator mElectronBeamOffAnimator;
+ private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+
+ /**
+ * Creates the display power controller.
+ */
+ public DisplayPowerController(Looper looper, Context context, Notifier notifier,
+ LightsService lights, SuspendBlocker suspendBlocker,
+ Callbacks callbacks, Handler callbackHandler) {
+ mHandler = new DisplayControllerHandler(looper);
+ mNotifier = notifier;
+ mSuspendBlocker = suspendBlocker;
+ mCallbacks = callbacks;
+ mCallbackHandler = callbackHandler;
+
+ mLights = lights;
+ mSensorManager = new SystemSensorManager(mHandler.getLooper());
+
+ final Resources resources = context.getResources();
+ mScreenBrightnessDimConfig = resources.getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDim);
+ mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available);
+ if (mUseSoftwareAutoBrightnessConfig) {
+ mAutoBrightnessLevelsConfig = resources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevels);
+ mAutoBrightnessLcdBacklightValuesConfig = resources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+ if (mAutoBrightnessLcdBacklightValuesConfig.length
+ != mAutoBrightnessLevelsConfig.length + 1) {
+ Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues "
+ + "(size " + mAutoBrightnessLcdBacklightValuesConfig.length + ") "
+ + "should have exactly one more entry than "
+ + "config_autoBrightnessLevels (size "
+ + mAutoBrightnessLevelsConfig.length + "). "
+ + "Auto-brightness will be disabled.");
+ mUseSoftwareAutoBrightnessConfig = false;
+ }
+
+ mLightSensorWarmUpTimeConfig = resources.getInteger(
+ com.android.internal.R.integer.config_lightSensorWarmupTime);
+ }
+
+ if (!DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ if (mProximitySensor != null) {
+ mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
+ TYPICAL_PROXIMITY_THRESHOLD);
+ }
+ }
+
+ if (mUseSoftwareAutoBrightnessConfig
+ && !DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
+ mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+ }
+ }
+
+ /**
+ * Returns true if the proximity sensor screen-off function is available.
+ */
+ public boolean isProximitySensorAvailable() {
+ return mProximitySensor != null;
+ }
+
+ /**
+ * 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 Callbacks#onStateChanged()} then try
+ * the request again later until the state converges.
+ */
+ public boolean requestPowerState(DisplayPowerRequest request,
+ boolean waitForNegativeProximity) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestPowerState: "
+ + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
+ }
+
+ synchronized (mLock) {
+ 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 (changed && !mPendingRequestChangedLocked) {
+ mPendingRequestChangedLocked = true;
+ sendUpdatePowerStateLocked();
+ }
+
+ return mDisplayReadyLocked;
+ }
+ }
+
+ private void sendUpdatePowerState() {
+ synchronized (mLock) {
+ sendUpdatePowerStateLocked();
+ }
+ }
+
+ private void sendUpdatePowerStateLocked() {
+ if (!mPendingUpdatePowerStateLocked) {
+ mPendingUpdatePowerStateLocked = true;
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private void initialize() {
+ final Executor executor = AsyncTask.THREAD_POOL_EXECUTOR;
+ mPowerState = new DisplayPowerState(new ElectronBeam(),
+ new PhotonicModulator(executor,
+ mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT),
+ mSuspendBlocker));
+
+ mElectronBeamOnAnimator = ObjectAnimator.ofFloat(
+ mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 0.0f, 1.0f);
+ mElectronBeamOnAnimator.setDuration(ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS);
+ mElectronBeamOnAnimator.addListener(mAnimatorListener);
+
+ mElectronBeamOffAnimator = ObjectAnimator.ofFloat(
+ mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 1.0f, 0.0f);
+ mElectronBeamOffAnimator.setDuration(ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS);
+ mElectronBeamOffAnimator.addListener(mAnimatorListener);
+
+ mScreenBrightnessRampAnimator = new RampAnimator<DisplayPowerState>(
+ mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS);
+ }
+
+ 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 void updatePowerState() {
+ // Update the power state request.
+ final boolean mustNotify;
+ boolean mustInitialize = false;
+ synchronized (mLock) {
+ mPendingUpdatePowerStateLocked = false;
+ if (mPendingRequestLocked == null) {
+ return; // wait until first actual power request
+ }
+
+ if (mPowerRequest == null) {
+ mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
+ mWaitingForNegativeProximity = mPendingWaitForNegativeProximityLocked;
+ mPendingRequestChangedLocked = false;
+ mustInitialize = true;
+ } else if (mPendingRequestChangedLocked) {
+ mPowerRequest.copyFrom(mPendingRequestLocked);
+ mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+ mPendingRequestChangedLocked = false;
+ mDisplayReadyLocked = false;
+ }
+
+ mustNotify = !mDisplayReadyLocked;
+ }
+
+ // Initialize things the first time the power state is changed.
+ if (mustInitialize) {
+ initialize();
+ }
+
+ // Clear a request to wait for negative proximity if needed.
+ if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_OFF
+ || mProximity == PROXIMITY_NEGATIVE
+ || mProximitySensor == null) {
+ mWaitingForNegativeProximity = false;
+ }
+
+ // Turn on the proximity sensor if needed.
+ if (mProximitySensor != null) {
+ setProximitySensorEnabled(mPowerRequest.useProximitySensor
+ || mWaitingForNegativeProximity);
+ if (mProximitySensorEnabled && mProximity == PROXIMITY_POSITIVE) {
+ mScreenOffBecauseOfProximity = true;
+ setScreenOn(false);
+ } else if (mScreenOffBecauseOfProximity) {
+ mScreenOffBecauseOfProximity = false;
+ sendOnProximityNegative();
+ }
+ }
+
+ // Turn on the light sensor if needed.
+ if (mLightSensor != null) {
+ setLightSensorEnabled(mPowerRequest.useAutoBrightness
+ && wantScreenOn(mPowerRequest.screenState));
+ }
+
+ // Set the screen brightness.
+ if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM) {
+ // Screen is dimmed. Overrides everything else.
+ animateScreenBrightness(mScreenBrightnessDimConfig, BRIGHTNESS_RAMP_RATE_FAST);
+ mUsingScreenAutoBrightness = false;
+ } else if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT) {
+ if (mScreenAutoBrightness >= 0 && mLightSensorEnabled) {
+ // Use current auto-brightness value.
+ animateScreenBrightness(
+ Math.max(mScreenAutoBrightness, mScreenBrightnessDimConfig),
+ mUsingScreenAutoBrightness ? BRIGHTNESS_RAMP_RATE_SLOW :
+ BRIGHTNESS_RAMP_RATE_FAST);
+ mUsingScreenAutoBrightness = true;
+ } else {
+ // Light sensor is disabled or not ready yet.
+ // Use the current brightness setting from the request, which is expected
+ // provide a nominal default value for the case where auto-brightness
+ // is not ready yet.
+ animateScreenBrightness(
+ Math.max(mPowerRequest.screenBrightness, mScreenBrightnessDimConfig),
+ BRIGHTNESS_RAMP_RATE_FAST);
+ mUsingScreenAutoBrightness = false;
+ }
+ } else {
+ // Screen is off. Don't bother changing the brightness.
+ mUsingScreenAutoBrightness = false;
+ }
+
+ // Animate the screen on or off.
+ if (!mScreenOffBecauseOfProximity) {
+ if (wantScreenOn(mPowerRequest.screenState)) {
+ // Want screen on.
+ // Wait for previous off animation to complete beforehand.
+ // It is relatively short but if we cancel it and switch to the
+ // on animation immediately then the results are pretty ugly.
+ if (!mElectronBeamOffAnimator.isStarted()) {
+ setScreenOn(true);
+ if (!mElectronBeamOnAnimator.isStarted()) {
+ if (mPowerState.getElectronBeamLevel() == 1.0f) {
+ mPowerState.dismissElectronBeam();
+ } else if (mPowerState.prepareElectronBeam(true)) {
+ mElectronBeamOnAnimator.start();
+ } else {
+ mElectronBeamOnAnimator.end();
+ }
+ }
+ }
+ } else {
+ // Want screen off.
+ // Wait for previous on animation to complete beforehand.
+ if (!mElectronBeamOnAnimator.isStarted()) {
+ if (!mElectronBeamOffAnimator.isStarted()) {
+ if (mPowerState.getElectronBeamLevel() == 0.0f) {
+ setScreenOn(false);
+ } else if (mPowerState.prepareElectronBeam(false)
+ && mPowerState.isScreenOn()) {
+ mElectronBeamOffAnimator.start();
+ } else {
+ mElectronBeamOffAnimator.end();
+ }
+ }
+ }
+ }
+ }
+
+ // Report whether the display is ready for use.
+ // We mostly care about the screen state here, ignoring brightness changes
+ // which will be handled asynchronously.
+ if (mustNotify
+ && !mElectronBeamOnAnimator.isStarted()
+ && !mElectronBeamOffAnimator.isStarted()
+ && mPowerState.waitUntilClean(mCleanListener)) {
+ synchronized (mLock) {
+ if (!mPendingRequestChangedLocked) {
+ mDisplayReadyLocked = true;
+ }
+ }
+ sendOnStateChanged();
+ }
+ }
+
+ private void setScreenOn(boolean on) {
+ if (!mPowerState.isScreenOn() == on) {
+ mPowerState.setScreenOn(on);
+ if (on) {
+ mNotifier.onScreenOn();
+ } else {
+ mNotifier.onScreenOff();
+ }
+ }
+ }
+
+ private void animateScreenBrightness(int target, int rate) {
+ if (mScreenBrightnessRampAnimator.animateTo(target, rate)) {
+ mNotifier.onScreenBrightness(target);
+ }
+ }
+
+ private final Runnable mCleanListener = new Runnable() {
+ @Override
+ public void run() {
+ sendUpdatePowerState();
+ }
+ };
+
+ private void setProximitySensorEnabled(boolean enable) {
+ if (enable) {
+ if (!mProximitySensorEnabled) {
+ mProximitySensorEnabled = true;
+ mPendingProximity = PROXIMITY_UNKNOWN;
+ mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
+ SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+ }
+ } else {
+ if (mProximitySensorEnabled) {
+ mProximitySensorEnabled = false;
+ mProximity = PROXIMITY_UNKNOWN;
+ mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ mSensorManager.unregisterListener(mProximitySensorListener);
+ }
+ }
+ }
+
+ private void handleProximitySensorEvent(long time, boolean positive) {
+ 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.
+ mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ mPendingProximity = positive ? PROXIMITY_POSITIVE : PROXIMITY_NEGATIVE;
+ mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_DEBOUNCE_DELAY;
+ debounceProximitySensor();
+ }
+
+ private void debounceProximitySensor() {
+ if (mPendingProximity != PROXIMITY_UNKNOWN) {
+ final long now = SystemClock.uptimeMillis();
+ if (mPendingProximityDebounceTime <= now) {
+ mProximity = mPendingProximity;
+ sendUpdatePowerState();
+ } else {
+ Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
+ }
+ }
+ }
+
+ private void setLightSensorEnabled(boolean enable) {
+ if (enable) {
+ if (!mLightSensorEnabled) {
+ mLightSensorEnabled = true;
+ mLightSensorEnableTime = SystemClock.uptimeMillis();
+ mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+ LIGHT_SENSOR_RATE, mHandler);
+ }
+ } else {
+ if (mLightSensorEnabled) {
+ mLightSensorEnabled = false;
+ mLightMeasurementValid = false;
+ updateAutoBrightness(false);
+ mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED);
+ mSensorManager.unregisterListener(mLightSensorListener);
+ }
+ }
+ }
+
+ private void handleLightSensorEvent(long time, float lux) {
+ // Take the first few readings during the warm-up period and apply them
+ // immediately without debouncing.
+ if (!mLightMeasurementValid
+ || (time - mLightSensorEnableTime) < mLightSensorWarmUpTimeConfig) {
+ mLightMeasurement = lux;
+ mLightMeasurementValid = true;
+ mRecentLightSamples = 0;
+ updateAutoBrightness(true);
+ }
+
+ // Update our moving average.
+ if (lux != mLightMeasurement && (mRecentLightSamples == 0
+ || (lux < mLightMeasurement && mRecentLightBrightening)
+ || (lux > mLightMeasurement && !mRecentLightBrightening))) {
+ // If the newest light sample doesn't seem to be going in the
+ // same general direction as recent samples, then start over.
+ setRecentLight(time, lux, lux > mLightMeasurement);
+ mPendingLightSensorDebounceTime = time + mRecentLightTimeConstant;
+ } else if (mRecentLightSamples >= 1) {
+ // Add the newest light sample to the moving average.
+ accumulateRecentLight(time, lux);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "handleLightSensorEvent: lux=" + lux
+ + ", mLightMeasurementValid=" + mLightMeasurementValid
+ + ", mLightMeasurement=" + mLightMeasurement
+ + ", mRecentLightSamples=" + mRecentLightSamples
+ + ", mRecentLightAverage=" + mRecentLightAverage
+ + ", mRecentLightBrightening=" + mRecentLightBrightening
+ + ", mRecentLightTimeConstant=" + mRecentLightTimeConstant
+ + ", mPendingLightSensorDebounceTime="
+ + TimeUtils.formatUptime(mPendingLightSensorDebounceTime));
+ }
+
+ // Debounce.
+ mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED);
+ debounceLightSensor();
+ }
+
+ private void setRecentLight(long time, float lux, boolean brightening) {
+ mRecentLightBrightening = brightening;
+ mRecentLightTimeConstant = brightening ?
+ BRIGHTENING_LIGHT_TIME_CONSTANT : DIMMING_LIGHT_TIME_CONSTANT;
+ mRecentLightSamples = 1;
+ mRecentLightAverage = lux;
+ mLastLightSample = lux;
+ mLastLightSampleTime = time;
+ }
+
+ private void accumulateRecentLight(long time, float lux) {
+ final long timeDelta = time - mLastLightSampleTime;
+ mRecentLightSamples += 1;
+ mRecentLightAverage += (lux - mRecentLightAverage) *
+ timeDelta / (mRecentLightTimeConstant + timeDelta);
+ mLastLightSample = lux;
+ mLastLightSampleTime = time;
+ }
+
+ private void debounceLightSensor() {
+ if (mLightMeasurementValid && mRecentLightSamples >= 1) {
+ final long now = SystemClock.uptimeMillis();
+ if (mPendingLightSensorDebounceTime <= now) {
+ accumulateRecentLight(now, mLastLightSample);
+ mLightMeasurement = mRecentLightAverage;
+
+ if (DEBUG) {
+ Slog.d(TAG, "debounceLightSensor: Accepted new measurement "
+ + mLightMeasurement + " after "
+ + (now - mPendingLightSensorDebounceTime
+ + mRecentLightTimeConstant) + " ms based on "
+ + mRecentLightSamples + " recent samples.");
+ }
+
+ updateAutoBrightness(true);
+
+ // Now that we have debounced the light sensor data, we have the
+ // option of either leaving the sensor in a debounced state or
+ // restarting the debounce cycle by setting mRecentLightSamples to 0.
+ //
+ // If we leave the sensor debounced, then new average light measurements
+ // may be accepted immediately as long as they are trending in the same
+ // direction as they were before. If the measurements start
+ // jittering or trending in the opposite direction then the debounce
+ // cycle will automatically be restarted. The benefit is that the
+ // auto-brightness control can be more responsive to changes over a
+ // broad range.
+ //
+ // For now, we choose to be more responsive and leave the following line
+ // commented out.
+ //
+ // mRecentLightSamples = 0;
+ } else {
+ Message msg = mHandler.obtainMessage(MSG_LIGHT_SENSOR_DEBOUNCED);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, mPendingLightSensorDebounceTime);
+ }
+ }
+ }
+
+ private void updateAutoBrightness(boolean sendUpdate) {
+ if (!mLightMeasurementValid) {
+ return;
+ }
+
+ final int newScreenAutoBrightness = mapLuxToBrightness(mLightMeasurement,
+ mAutoBrightnessLevelsConfig,
+ mAutoBrightnessLcdBacklightValuesConfig);
+ if (mScreenAutoBrightness != newScreenAutoBrightness) {
+ if (DEBUG) {
+ Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
+ + mScreenAutoBrightness);
+ }
+
+ mScreenAutoBrightness = newScreenAutoBrightness;
+ if (sendUpdate) {
+ sendUpdatePowerState();
+ }
+ }
+ }
+
+ /**
+ * Maps a light sensor measurement in lux to a brightness value given
+ * a table of lux breakpoint values and a table of brightnesses that
+ * is one element larger.
+ */
+ private static int mapLuxToBrightness(float lux,
+ int[] fromLux, int[] toBrightness) {
+ // TODO implement interpolation and possibly range expansion
+ int level = 0;
+ final int count = fromLux.length;
+ while (level < count && lux >= fromLux[level]) {
+ level += 1;
+ }
+ return toBrightness[level];
+ }
+
+ private void sendOnStateChanged() {
+ mCallbackHandler.post(mOnStateChangedRunnable);
+ }
+
+ private final Runnable mOnStateChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mCallbacks.onStateChanged();
+ }
+ };
+
+ private void sendOnProximityNegative() {
+ mCallbackHandler.post(mOnProximityNegativeRunnable);
+ }
+
+ private final Runnable mOnProximityNegativeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mCallbacks.onProximityNegative();
+ }
+ };
+
+ public void dump(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("Display 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 Controller Configuration:");
+ pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
+ pw.println(" mUseSoftwareAutoBrightnessConfig="
+ + mUseSoftwareAutoBrightnessConfig);
+ pw.println(" mAutoBrightnessLevelsConfig="
+ + Arrays.toString(mAutoBrightnessLevelsConfig));
+ pw.println(" mAutoBrightnessLcdBacklightValuesConfig="
+ + Arrays.toString(mAutoBrightnessLcdBacklightValuesConfig));
+ pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
+
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ dumpLocal(pw);
+ } else {
+ final StringWriter out = new StringWriter();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Message msg = Message.obtain(mHandler, new Runnable() {
+ @Override
+ public void run() {
+ PrintWriter localpw = new PrintWriter(out);
+ try {
+ dumpLocal(localpw);
+ } finally {
+ localpw.flush();
+ latch.countDown();
+ }
+ }
+ });
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ try {
+ latch.await();
+ pw.print(out.toString());
+ } catch (InterruptedException ex) {
+ pw.println();
+ pw.println("Failed to dump thread state due to interrupted exception!");
+ }
+ }
+ }
+
+ private void dumpLocal(PrintWriter pw) {
+ pw.println();
+ pw.println("Display Controller Thread State:");
+ pw.println(" mPowerRequest=" + mPowerRequest);
+ 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(" mLightSensor=" + mLightSensor);
+ pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
+ pw.println(" mLightSensorEnableTime="
+ + TimeUtils.formatUptime(mLightSensorEnableTime));
+ pw.println(" mLightMeasurement=" + mLightMeasurement);
+ pw.println(" mLightMeasurementValid=" + mLightMeasurementValid);
+ pw.println(" mLastLightSample=" + mLastLightSample);
+ pw.println(" mLastLightSampleTime="
+ + TimeUtils.formatUptime(mLastLightSampleTime));
+ pw.println(" mRecentLightSamples=" + mRecentLightSamples);
+ pw.println(" mRecentLightAverage=" + mRecentLightAverage);
+ pw.println(" mRecentLightBrightening=" + mRecentLightBrightening);
+ pw.println(" mRecentLightTimeConstant=" + mRecentLightTimeConstant);
+ pw.println(" mPendingLightSensorDebounceTime="
+ + TimeUtils.formatUptime(mPendingLightSensorDebounceTime));
+ pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness);
+ pw.println(" mUsingScreenAutoBrightness=" + mUsingScreenAutoBrightness);
+
+ if (mElectronBeamOnAnimator != null) {
+ pw.println(" mElectronBeamOnAnimator.isStarted()=" +
+ mElectronBeamOnAnimator.isStarted());
+ }
+ if (mElectronBeamOffAnimator != null) {
+ pw.println(" mElectronBeamOffAnimator.isStarted()=" +
+ mElectronBeamOffAnimator.isStarted());
+ }
+
+ if (mPowerState != null) {
+ mPowerState.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 boolean wantScreenOn(int state) {
+ switch (state) {
+ case DisplayPowerRequest.SCREEN_STATE_BRIGHT:
+ case DisplayPowerRequest.SCREEN_STATE_DIM:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Asynchronous callbacks from the power controller to the power manager service.
+ */
+ public interface Callbacks {
+ void onStateChanged();
+ void onProximityNegative();
+ }
+
+ private final class DisplayControllerHandler extends Handler {
+ public DisplayControllerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @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_LIGHT_SENSOR_DEBOUNCED:
+ debounceLightSensor();
+ break;
+ }
+ }
+ }
+
+ private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mProximitySensorEnabled) {
+ final long time = SystemClock.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 SensorEventListener mLightSensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mLightSensorEnabled) {
+ final long time = SystemClock.uptimeMillis();
+ final float lux = event.values[0];
+ handleLightSensorEvent(time, lux);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Not used.
+ }
+ };
+}