diff options
Diffstat (limited to 'services/java/com/android/server/power/DisplayPowerController.java')
-rw-r--r-- | services/java/com/android/server/power/DisplayPowerController.java | 992 |
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 000000000000..50d3f81374df --- /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. + } + }; +} |