/* * 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 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( 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. } }; }