diff options
Diffstat (limited to 'services/core/java/com/android/server/power')
12 files changed, 7049 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/power/DisplayBlanker.java b/services/core/java/com/android/server/power/DisplayBlanker.java new file mode 100644 index 0000000..6072053 --- /dev/null +++ b/services/core/java/com/android/server/power/DisplayBlanker.java @@ -0,0 +1,25 @@ +/* + * 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; + +/** + * Blanks or unblanks all displays. + */ +interface DisplayBlanker { + public void blankAllDisplays(); + public void unblankAllDisplays(); +} diff --git a/services/core/java/com/android/server/power/DisplayPowerController.java b/services/core/java/com/android/server/power/DisplayPowerController.java new file mode 100644 index 0000000..b8a78e3 --- /dev/null +++ b/services/core/java/com/android/server/power/DisplayPowerController.java @@ -0,0 +1,1379 @@ +/* + * 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.lights.LightsManager; +import com.android.server.twilight.TwilightListener; +import com.android.server.twilight.TwilightManager; +import com.android.server.display.DisplayManagerService; +import com.android.server.twilight.TwilightState; + +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.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.util.FloatMath; +import android.util.Slog; +import android.util.Spline; +import android.util.TimeUtils; + +import java.io.PrintWriter; + +/** + * Controls the power state of the display. + * + * Handles the proximity sensor, light sensor, and animations between states + * including the screen off animation. + * + * This component acts independently of the rest of the power manager service. + * In particular, it does not share any state and it only communicates + * via asynchronous callbacks to inform the power manager that something has + * changed. + * + * Everything this class does internally is serialized on its handler although + * it may be accessed by other threads from the outside. + * + * Note that the power manager service guarantees that it will hold a suspend + * blocker as long as the display is not ready. So most of the work done here + * does not need to worry about holding a suspend blocker unless it happens + * independently of the display ready signal. + * + * For debugging, you can make the 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; + + // If true, uses the electron beam on animation. + // We might want to turn this off if we cannot get a guarantee that the screen + // actually turns on and starts showing new content after the call to set the + // screen state returns. Playing the animation can also be somewhat slow. + private static final boolean USE_ELECTRON_BEAM_ON_ANIMATION = false; + + // If true, enables the use of the screen auto-brightness adjustment setting. + private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = + PowerManager.useScreenAutoBrightnessAdjustmentFeature(); + + // The maximum range of gamma adjustment possible using the screen + // auto-brightness adjustment setting. + private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f; + + // The minimum reduction in brightness when dimmed. + private static final int SCREEN_DIM_MINIMUM_REDUCTION = 10; + + // If true, enables the use of the current time as an auto-brightness adjustment. + // The basic idea here is to expand the dynamic range of auto-brightness + // when it is especially dark outside. The light sensor tends to perform + // poorly at low light levels so we compensate for it by making an + // assumption about the environment. + private static final boolean USE_TWILIGHT_ADJUSTMENT = + PowerManager.useTwilightAdjustmentFeature(); + + // Specifies the maximum magnitude of the time of day adjustment. + private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f; + + // The amount of time after or before sunrise over which to start adjusting + // the gamma. We want the change to happen gradually so that it is below the + // threshold of perceptibility and so that the adjustment has maximum effect + // well after dusk. + private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2; + + private static final int ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS = 250; + private static final int ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS = 400; + + private static final int MSG_UPDATE_POWER_STATE = 1; + private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2; + private static final int MSG_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 for positive or negative transitions. + private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0; + private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250; + + // Trigger proximity if distance is less than 5 cm. + private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; + + // Light sensor event rate in milliseconds. + private static final int LIGHT_SENSOR_RATE_MILLIS = 1000; + + // A rate for generating synthetic light sensor events in the case where the light + // sensor hasn't reported any new data in a while and we need it to update the + // debounce filter. We only synthesize light sensor measurements when needed. + private static final int SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS = + LIGHT_SENSOR_RATE_MILLIS * 2; + + // 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 = 40; + + // IIR filter time constants in milliseconds for computing two moving averages of + // the light samples. One is a long-term average and the other is a short-term average. + // We can use these filters to assess trends in ambient brightness. + // The short term average gives us a filtered but relatively low latency measurement. + // The long term average informs us about the overall trend. + private static final long SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 1000; + private static final long LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 5000; + + // Stability requirements in milliseconds for accepting a new brightness + // level. This is used for debouncing the light sensor. Different constants + // are used to debounce the light sensor when adapting to brighter or darker environments. + // This parameter controls how quickly brightness changes occur in response to + // an observed change in light level that exceeds the hysteresis threshold. + private static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000; + private static final long DARKENING_LIGHT_DEBOUNCE = 8000; + + // Hysteresis constraints for brightening or darkening. + // The recent lux must have changed by at least this fraction relative to the + // current ambient lux before a change will be considered. + private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f; + private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f; + + private final Object mLock = new Object(); + + // Notifier for sending asynchronous notifications. + private final Notifier mNotifier; + + // The display suspend blocker. + // Held while there are pending state change notifications. + private final SuspendBlocker mDisplaySuspendBlocker; + + // The display blanker. + private final DisplayBlanker mDisplayBlanker; + + // 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 LightsManager mLights; + + // The twilight service. + private final TwilightManager mTwilight; + + // The display manager. + private final DisplayManagerService mDisplayManager; + + // 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; + + // The minimum allowed brightness. + private final int mScreenBrightnessRangeMinimum; + + // The maximum allowed brightness. + private final int mScreenBrightnessRangeMaximum; + + // True if auto-brightness should be used. + private boolean mUseSoftwareAutoBrightnessConfig; + + // The auto-brightness spline adjustment. + // The brightness values have been scaled to a range of 0..1. + private Spline mScreenAutoBrightnessSpline; + + // 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; + + // True if we should fade the screen while turning it off, false if we should play + // a stylish electron beam animation instead. + private boolean mElectronBeamFadesConfig; + + // 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 = -1; // -1 if fully debounced + + // True if the screen was turned off because of the proximity sensor. + // When the screen turns on again, we report user activity to the power manager. + private boolean mScreenOffBecauseOfProximity; + + // True if the screen on is being blocked. + private boolean mScreenOnWasBlocked; + + // The elapsed real time when the screen on was blocked. + private long mScreenOnBlockStartRealTime; + + // 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 nominal ambient light level. + private float mAmbientLux; + + // True if mAmbientLux holds a valid value. + private boolean mAmbientLuxValid; + + // The ambient light level threshold at which to brighten or darken the screen. + private float mBrighteningLuxThreshold; + private float mDarkeningLuxThreshold; + + // The most recent light sample. + private float mLastObservedLux; + + // The time of the most light recent sample. + private long mLastObservedLuxTime; + + // The number of light samples collected since the light sensor was enabled. + private int mRecentLightSamples; + + // The long-term and short-term filtered light measurements. + private float mRecentShortTermAverageLux; + private float mRecentLongTermAverageLux; + + // The direction in which the average lux is moving relative to the current ambient lux. + // 0 if not changing or within hysteresis threshold. + // 1 if brightening beyond hysteresis threshold. + // -1 if darkening beyond hysteresis threshold. + private int mDebounceLuxDirection; + + // The time when the average lux last changed direction. + private long mDebounceLuxTime; + + // 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; + + // The last screen auto-brightness gamma. (For printing in dump() only.) + private float mLastScreenAutoBrightnessGamma = 1.0f; + + // 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; + + // Twilight changed. We might recalculate auto-brightness values. + private boolean mTwilightChanged; + + /** + * Creates the display power controller. + */ + public DisplayPowerController(Looper looper, Context context, Notifier notifier, + LightsManager lights, TwilightManager twilight, SensorManager sensorManager, + DisplayManagerService displayManager, + SuspendBlocker displaySuspendBlocker, DisplayBlanker displayBlanker, + Callbacks callbacks, Handler callbackHandler) { + mHandler = new DisplayControllerHandler(looper); + mNotifier = notifier; + mDisplaySuspendBlocker = displaySuspendBlocker; + mDisplayBlanker = displayBlanker; + mCallbacks = callbacks; + mCallbackHandler = callbackHandler; + + mLights = lights; + mTwilight = twilight; + mSensorManager = sensorManager; + mDisplayManager = displayManager; + + final Resources resources = context.getResources(); + + mScreenBrightnessDimConfig = clampAbsoluteBrightness(resources.getInteger( + com.android.internal.R.integer.config_screenBrightnessDim)); + + int screenBrightnessMinimum = Math.min(resources.getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingMinimum), + mScreenBrightnessDimConfig); + + mUseSoftwareAutoBrightnessConfig = resources.getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available); + if (mUseSoftwareAutoBrightnessConfig) { + int[] lux = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevels); + int[] screenBrightness = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); + + mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); + if (mScreenAutoBrightnessSpline == null) { + Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues " + + "(size " + screenBrightness.length + ") " + + "must be monotic and have exactly one more entry than " + + "config_autoBrightnessLevels (size " + lux.length + ") " + + "which must be strictly increasing. " + + "Auto-brightness will be disabled."); + mUseSoftwareAutoBrightnessConfig = false; + } else { + if (screenBrightness[0] < screenBrightnessMinimum) { + screenBrightnessMinimum = screenBrightness[0]; + } + } + + mLightSensorWarmUpTimeConfig = resources.getInteger( + com.android.internal.R.integer.config_lightSensorWarmupTime); + } + + mScreenBrightnessRangeMinimum = clampAbsoluteBrightness(screenBrightnessMinimum); + mScreenBrightnessRangeMaximum = PowerManager.BRIGHTNESS_ON; + + mElectronBeamFadesConfig = resources.getBoolean( + com.android.internal.R.bool.config_animateScreenLights); + + 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); + } + + if (mUseSoftwareAutoBrightnessConfig && USE_TWILIGHT_ADJUSTMENT) { + mTwilight.registerListener(mTwilightListener, mHandler); + } + } + + private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) { + try { + final int n = brightness.length; + float[] x = new float[n]; + float[] y = new float[n]; + y[0] = normalizeAbsoluteBrightness(brightness[0]); + for (int i = 1; i < n; i++) { + x[i] = lux[i - 1]; + y[i] = normalizeAbsoluteBrightness(brightness[i]); + } + + Spline spline = Spline.createMonotoneCubicSpline(x, y); + if (DEBUG) { + Slog.d(TAG, "Auto-brightness spline: " + spline); + for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { + Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); + } + } + return spline; + } catch (IllegalArgumentException ex) { + Slog.e(TAG, "Could not create auto-brightness spline.", ex); + return null; + } + } + + /** + * 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() { + mPowerState = new DisplayPowerState( + new ElectronBeam(mDisplayManager), mDisplayBlanker, + mLights.getLight(LightsManager.LIGHT_ID_BACKLIGHT)); + + 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; + boolean updateAutoBrightness = mTwilightChanged; + boolean wasDim = false; + mTwilightChanged = false; + + synchronized (mLock) { + mPendingUpdatePowerStateLocked = false; + if (mPendingRequestLocked == null) { + return; // wait until first actual power request + } + + if (mPowerRequest == null) { + mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked); + mWaitingForNegativeProximity = mPendingWaitForNegativeProximityLocked; + mPendingWaitForNegativeProximityLocked = false; + mPendingRequestChangedLocked = false; + mustInitialize = true; + } else if (mPendingRequestChangedLocked) { + if (mPowerRequest.screenAutoBrightnessAdjustment + != mPendingRequestLocked.screenAutoBrightnessAdjustment) { + updateAutoBrightness = true; + } + wasDim = (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM); + mPowerRequest.copyFrom(mPendingRequestLocked); + mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked; + mPendingWaitForNegativeProximityLocked = false; + mPendingRequestChangedLocked = false; + mDisplayReadyLocked = false; + } + + mustNotify = !mDisplayReadyLocked; + } + + // Initialize things the first time the power state is changed. + if (mustInitialize) { + initialize(); + } + + // Apply the proximity sensor. + if (mProximitySensor != null) { + if (mPowerRequest.useProximitySensor + && mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + setProximitySensorEnabled(true); + if (!mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE) { + mScreenOffBecauseOfProximity = true; + sendOnProximityPositiveWithWakelock(); + setScreenOn(false); + } + } else if (mWaitingForNegativeProximity + && mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE + && mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + setProximitySensorEnabled(true); + } else { + setProximitySensorEnabled(false); + mWaitingForNegativeProximity = false; + } + if (mScreenOffBecauseOfProximity + && mProximity != PROXIMITY_POSITIVE) { + mScreenOffBecauseOfProximity = false; + sendOnProximityNegativeWithWakelock(); + } + } else { + mWaitingForNegativeProximity = false; + } + + // Turn on the light sensor if needed. + if (mLightSensor != null) { + setLightSensorEnabled(mPowerRequest.useAutoBrightness + && wantScreenOn(mPowerRequest.screenState), updateAutoBrightness); + } + + // Set the screen brightness. + if (wantScreenOn(mPowerRequest.screenState)) { + int target; + boolean slow; + if (mScreenAutoBrightness >= 0 && mLightSensorEnabled) { + // Use current auto-brightness value. + target = mScreenAutoBrightness; + slow = mUsingScreenAutoBrightness; + 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. + target = mPowerRequest.screenBrightness; + slow = false; + mUsingScreenAutoBrightness = false; + } + if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM) { + // Dim quickly by at least some minimum amount. + target = Math.min(target - SCREEN_DIM_MINIMUM_REDUCTION, + mScreenBrightnessDimConfig); + slow = false; + } else if (wasDim) { + // Brighten quickly. + slow = false; + } + animateScreenBrightness(clampScreenBrightness(target), + slow ? BRIGHTNESS_RAMP_RATE_SLOW : BRIGHTNESS_RAMP_RATE_FAST); + } 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()) { + // Turn the screen on. The contents of the screen may not yet + // be visible if the electron beam has not been dismissed because + // its last frame of animation is solid black. + setScreenOn(true); + + if (mPowerRequest.blockScreenOn + && mPowerState.getElectronBeamLevel() == 0.0f) { + blockScreenOn(); + } else { + unblockScreenOn(); + if (USE_ELECTRON_BEAM_ON_ANIMATION) { + if (!mElectronBeamOnAnimator.isStarted()) { + if (mPowerState.getElectronBeamLevel() == 1.0f) { + mPowerState.dismissElectronBeam(); + } else if (mPowerState.prepareElectronBeam( + mElectronBeamFadesConfig ? + ElectronBeam.MODE_FADE : + ElectronBeam.MODE_WARM_UP)) { + mElectronBeamOnAnimator.start(); + } else { + mElectronBeamOnAnimator.end(); + } + } + } else { + mPowerState.setElectronBeamLevel(1.0f); + mPowerState.dismissElectronBeam(); + } + } + } + } 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( + mElectronBeamFadesConfig ? + ElectronBeam.MODE_FADE : + ElectronBeam.MODE_COOL_DOWN) + && 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 + && !mScreenOnWasBlocked + && !mElectronBeamOnAnimator.isStarted() + && !mElectronBeamOffAnimator.isStarted() + && mPowerState.waitUntilClean(mCleanListener)) { + synchronized (mLock) { + if (!mPendingRequestChangedLocked) { + mDisplayReadyLocked = true; + + if (DEBUG) { + Slog.d(TAG, "Display ready!"); + } + } + } + sendOnStateChangedWithWakelock(); + } + } + + private void blockScreenOn() { + if (!mScreenOnWasBlocked) { + mScreenOnWasBlocked = true; + if (DEBUG) { + Slog.d(TAG, "Blocked screen on."); + mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime(); + } + } + } + + private void unblockScreenOn() { + if (mScreenOnWasBlocked) { + mScreenOnWasBlocked = false; + if (DEBUG) { + Slog.d(TAG, "Unblocked screen on after " + + (SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime) + " ms"); + } + } + } + + private void setScreenOn(boolean on) { + if (!mPowerState.isScreenOn() == on) { + mPowerState.setScreenOn(on); + if (on) { + mNotifier.onScreenOn(); + } else { + mNotifier.onScreenOff(); + } + } + } + + private int clampScreenBrightness(int value) { + return clamp(value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); + } + + private static int clampAbsoluteBrightness(int value) { + return clamp(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); + } + + private static int clamp(int value, int min, int max) { + if (value <= min) { + return min; + } + if (value >= max) { + return max; + } + return value; + } + + private static float normalizeAbsoluteBrightness(int value) { + return (float)clampAbsoluteBrightness(value) / PowerManager.BRIGHTNESS_ON; + } + + 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) { + // Register the listener. + // Proximity sensor state already cleared initially. + mProximitySensorEnabled = true; + mSensorManager.registerListener(mProximitySensorListener, mProximitySensor, + SensorManager.SENSOR_DELAY_NORMAL, mHandler); + } + } else { + if (mProximitySensorEnabled) { + // Unregister the listener. + // Clear the proximity sensor state for next time. + mProximitySensorEnabled = false; + mProximity = PROXIMITY_UNKNOWN; + mPendingProximity = PROXIMITY_UNKNOWN; + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + mSensorManager.unregisterListener(mProximitySensorListener); + clearPendingProximityDebounceTime(); // release wake lock (must be last) + } + } + } + + private void handleProximitySensorEvent(long time, boolean positive) { + if (mProximitySensorEnabled) { + if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { + return; // no change + } + if (mPendingProximity == PROXIMITY_POSITIVE && positive) { + return; // no change + } + + // Only accept a proximity sensor reading if it remains + // stable for the entire debounce delay. We hold a wake lock while + // debouncing the sensor. + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + if (positive) { + mPendingProximity = PROXIMITY_POSITIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock + } else { + mPendingProximity = PROXIMITY_NEGATIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock + } + + // Debounce the new sensor reading. + debounceProximitySensor(); + } + } + + private void debounceProximitySensor() { + if (mProximitySensorEnabled + && mPendingProximity != PROXIMITY_UNKNOWN + && mPendingProximityDebounceTime >= 0) { + final long now = SystemClock.uptimeMillis(); + if (mPendingProximityDebounceTime <= now) { + // Sensor reading accepted. Apply the change then release the wake lock. + mProximity = mPendingProximity; + updatePowerState(); + clearPendingProximityDebounceTime(); // release wake lock (must be last) + } else { + // Need to wait a little longer. + // Debounce again later. We continue holding a wake lock while waiting. + Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime); + } + } + } + + private void clearPendingProximityDebounceTime() { + if (mPendingProximityDebounceTime >= 0) { + mPendingProximityDebounceTime = -1; + mDisplaySuspendBlocker.release(); // release wake lock + } + } + + private void setPendingProximityDebounceTime(long debounceTime) { + if (mPendingProximityDebounceTime < 0) { + mDisplaySuspendBlocker.acquire(); // acquire wake lock + } + mPendingProximityDebounceTime = debounceTime; + } + + private void setLightSensorEnabled(boolean enable, boolean updateAutoBrightness) { + if (enable) { + if (!mLightSensorEnabled) { + updateAutoBrightness = true; + mLightSensorEnabled = true; + mLightSensorEnableTime = SystemClock.uptimeMillis(); + mSensorManager.registerListener(mLightSensorListener, mLightSensor, + LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler); + } + } else { + if (mLightSensorEnabled) { + mLightSensorEnabled = false; + mAmbientLuxValid = false; + mRecentLightSamples = 0; + mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED); + mSensorManager.unregisterListener(mLightSensorListener); + } + } + if (updateAutoBrightness) { + updateAutoBrightness(false); + } + } + + private void handleLightSensorEvent(long time, float lux) { + mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED); + + applyLightSensorMeasurement(time, lux); + updateAmbientLux(time); + } + + private void applyLightSensorMeasurement(long time, float lux) { + // Update our filters. + mRecentLightSamples += 1; + if (mRecentLightSamples == 1) { + mRecentShortTermAverageLux = lux; + mRecentLongTermAverageLux = lux; + } else { + final long timeDelta = time - mLastObservedLuxTime; + mRecentShortTermAverageLux += (lux - mRecentShortTermAverageLux) + * timeDelta / (SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta); + mRecentLongTermAverageLux += (lux - mRecentLongTermAverageLux) + * timeDelta / (LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta); + } + + // Remember this sample value. + mLastObservedLux = lux; + mLastObservedLuxTime = time; + } + + private void setAmbientLux(float lux) { + mAmbientLux = lux; + mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS); + mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); + } + + private void updateAmbientLux(long time) { + // If the light sensor was just turned on then immediately update our initial + // estimate of the current ambient light level. + if (!mAmbientLuxValid) { + final long timeWhenSensorWarmedUp = + mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; + if (time < timeWhenSensorWarmedUp) { + mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, + timeWhenSensorWarmedUp); + return; + } + setAmbientLux(mRecentShortTermAverageLux); + mAmbientLuxValid = true; + mDebounceLuxDirection = 0; + mDebounceLuxTime = time; + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Initializing: " + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold + && mRecentLongTermAverageLux > mBrighteningLuxThreshold) { + // The ambient environment appears to be brightening. + if (mDebounceLuxDirection <= 0) { + mDebounceLuxDirection = 1; + mDebounceLuxTime = time; + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for " + + BRIGHTENING_LIGHT_DEBOUNCE + " ms: " + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + } + long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE; + if (time < debounceTime) { + mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); + return; + } + setAmbientLux(mRecentShortTermAverageLux); + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Brightened: " + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold + && mRecentLongTermAverageLux < mDarkeningLuxThreshold) { + // The ambient environment appears to be darkening. + if (mDebounceLuxDirection >= 0) { + mDebounceLuxDirection = -1; + mDebounceLuxTime = time; + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for " + + DARKENING_LIGHT_DEBOUNCE + " ms: " + + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + } + long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE; + if (time < debounceTime) { + mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); + return; + } + // Be conservative about reducing the brightness, only reduce it a little bit + // at a time to avoid having to bump it up again soon. + setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux)); + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Darkened: " + + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } else if (mDebounceLuxDirection != 0) { + // No change or change is within the hysteresis thresholds. + mDebounceLuxDirection = 0; + mDebounceLuxTime = time; + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Canceled debounce: " + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + } + + // Now that we've done all of that, we haven't yet posted a debounce + // message. So consider the case where current lux is beyond the + // threshold. It's possible that the light sensor may not report values + // if the light level does not change, so we need to occasionally + // synthesize sensor readings in order to make sure the brightness is + // adjusted accordingly. Note these thresholds may have changed since + // we entered the function because we called setAmbientLux and + // updateAutoBrightness along the way. + if (mLastObservedLux > mBrighteningLuxThreshold + || mLastObservedLux < mDarkeningLuxThreshold) { + mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, + time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS); + } + } + + private void debounceLightSensor() { + if (mLightSensorEnabled) { + long time = SystemClock.uptimeMillis(); + if (time >= mLastObservedLuxTime + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS) { + if (DEBUG) { + Slog.d(TAG, "debounceLightSensor: Synthesizing light sensor measurement " + + "after " + (time - mLastObservedLuxTime) + " ms."); + } + applyLightSensorMeasurement(time, mLastObservedLux); + } + updateAmbientLux(time); + } + } + + private void updateAutoBrightness(boolean sendUpdate) { + if (!mAmbientLuxValid) { + return; + } + + float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux); + float gamma = 1.0f; + + if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT + && mPowerRequest.screenAutoBrightnessAdjustment != 0.0f) { + final float adjGamma = FloatMath.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA, + Math.min(1.0f, Math.max(-1.0f, + -mPowerRequest.screenAutoBrightnessAdjustment))); + gamma *= adjGamma; + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma); + } + } + + if (USE_TWILIGHT_ADJUSTMENT) { + TwilightState state = mTwilight.getCurrentState(); + if (state != null && state.isNight()) { + final long now = System.currentTimeMillis(); + final float earlyGamma = + getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise()); + final float lateGamma = + getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise()); + gamma *= earlyGamma * lateGamma; + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma + + ", lateGamma=" + lateGamma); + } + } + } + + if (gamma != 1.0f) { + final float in = value; + value = FloatMath.pow(value, gamma); + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma + + ", in=" + in + ", out=" + value); + } + } + + int newScreenAutoBrightness = clampScreenBrightness( + Math.round(value * PowerManager.BRIGHTNESS_ON)); + if (mScreenAutoBrightness != newScreenAutoBrightness) { + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness=" + + mScreenAutoBrightness + ", newScreenAutoBrightness=" + + newScreenAutoBrightness); + } + + mScreenAutoBrightness = newScreenAutoBrightness; + mLastScreenAutoBrightnessGamma = gamma; + if (sendUpdate) { + sendUpdatePowerState(); + } + } + } + + private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) { + if (lastSunset < 0 || nextSunrise < 0 + || now < lastSunset || now > nextSunrise) { + return 1.0f; + } + + if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) { + return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, + (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME); + } + + if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) { + return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, + (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME); + } + + return TWILIGHT_ADJUSTMENT_MAX_GAMMA; + } + + private static float lerp(float x, float y, float alpha) { + return x + (y - x) * alpha; + } + + private void sendOnStateChangedWithWakelock() { + mDisplaySuspendBlocker.acquire(); + mCallbackHandler.post(mOnStateChangedRunnable); + } + + private final Runnable mOnStateChangedRunnable = new Runnable() { + @Override + public void run() { + mCallbacks.onStateChanged(); + mDisplaySuspendBlocker.release(); + } + }; + + private void sendOnProximityPositiveWithWakelock() { + mDisplaySuspendBlocker.acquire(); + mCallbackHandler.post(mOnProximityPositiveRunnable); + } + + private final Runnable mOnProximityPositiveRunnable = new Runnable() { + @Override + public void run() { + mCallbacks.onProximityPositive(); + mDisplaySuspendBlocker.release(); + } + }; + + private void sendOnProximityNegativeWithWakelock() { + mDisplaySuspendBlocker.acquire(); + mCallbackHandler.post(mOnProximityNegativeRunnable); + } + + private final Runnable mOnProximityNegativeRunnable = new Runnable() { + @Override + public void run() { + mCallbacks.onProximityNegative(); + mDisplaySuspendBlocker.release(); + } + }; + + public void dump(final 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(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); + pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); + pw.println(" mUseSoftwareAutoBrightnessConfig=" + + mUseSoftwareAutoBrightnessConfig); + pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); + pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); + + mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + dumpLocal(pw); + } + }, 1000); + } + + 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(" mAmbientLux=" + mAmbientLux); + pw.println(" mAmbientLuxValid=" + mAmbientLuxValid); + pw.println(" mLastObservedLux=" + mLastObservedLux); + pw.println(" mLastObservedLuxTime=" + + TimeUtils.formatUptime(mLastObservedLuxTime)); + pw.println(" mRecentLightSamples=" + mRecentLightSamples); + pw.println(" mRecentShortTermAverageLux=" + mRecentShortTermAverageLux); + pw.println(" mRecentLongTermAverageLux=" + mRecentLongTermAverageLux); + pw.println(" mDebounceLuxDirection=" + mDebounceLuxDirection); + pw.println(" mDebounceLuxTime=" + TimeUtils.formatUptime(mDebounceLuxTime)); + pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); + pw.println(" mUsingScreenAutoBrightness=" + mUsingScreenAutoBrightness); + pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); + pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState()); + + 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 onProximityPositive(); + void onProximityNegative(); + } + + private final class DisplayControllerHandler extends Handler { + public DisplayControllerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_POWER_STATE: + updatePowerState(); + break; + + case MSG_PROXIMITY_SENSOR_DEBOUNCED: + debounceProximitySensor(); + break; + + case MSG_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. + } + }; + + private final TwilightListener mTwilightListener = new TwilightListener() { + @Override + public void onTwilightStateChanged() { + mTwilightChanged = true; + updatePowerState(); + } + }; +} diff --git a/services/core/java/com/android/server/power/DisplayPowerRequest.java b/services/core/java/com/android/server/power/DisplayPowerRequest.java new file mode 100644 index 0000000..22f17d7 --- /dev/null +++ b/services/core/java/com/android/server/power/DisplayPowerRequest.java @@ -0,0 +1,117 @@ +/* + * 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 android.os.PowerManager; + +/** + * Describes the requested power state of the display. + * + * This object is intended to describe the general characteristics of the + * power state, such as whether the screen should be on or off and the current + * brightness controls leaving the {@link DisplayPowerController} to manage the + * details of how the transitions between states should occur. The goal is for + * the {@link PowerManagerService} to focus on the global power state and not + * have to micro-manage screen off animations, auto-brightness and other effects. + */ +final class DisplayPowerRequest { + public static final int SCREEN_STATE_OFF = 0; + public static final int SCREEN_STATE_DIM = 1; + public static final int SCREEN_STATE_BRIGHT = 2; + + // The requested minimum screen power state: off, dim or bright. + public int screenState; + + // If true, the proximity sensor overrides the screen state when an object is + // nearby, turning it off temporarily until the object is moved away. + public boolean useProximitySensor; + + // The desired screen brightness in the range 0 (minimum / off) to 255 (brightest). + // The display power controller may choose to clamp the brightness. + // When auto-brightness is enabled, this field should specify a nominal default + // value to use while waiting for the light sensor to report enough data. + public int screenBrightness; + + // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter). + public float screenAutoBrightnessAdjustment; + + // If true, enables automatic brightness control. + public boolean useAutoBrightness; + + // If true, prevents the screen from completely turning on if it is currently off. + // The display does not enter a "ready" state if this flag is true and screen on is + // blocked. The window manager policy blocks screen on while it prepares the keyguard to + // prevent the user from seeing intermediate updates. + // + // Technically, we may not block the screen itself from turning on (because that introduces + // extra unnecessary latency) but we do prevent content on screen from becoming + // visible to the user. + public boolean blockScreenOn; + + public DisplayPowerRequest() { + screenState = SCREEN_STATE_BRIGHT; + useProximitySensor = false; + screenBrightness = PowerManager.BRIGHTNESS_ON; + screenAutoBrightnessAdjustment = 0.0f; + useAutoBrightness = false; + blockScreenOn = false; + } + + public DisplayPowerRequest(DisplayPowerRequest other) { + copyFrom(other); + } + + public void copyFrom(DisplayPowerRequest other) { + screenState = other.screenState; + useProximitySensor = other.useProximitySensor; + screenBrightness = other.screenBrightness; + screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment; + useAutoBrightness = other.useAutoBrightness; + blockScreenOn = other.blockScreenOn; + } + + @Override + public boolean equals(Object o) { + return o instanceof DisplayPowerRequest + && equals((DisplayPowerRequest)o); + } + + public boolean equals(DisplayPowerRequest other) { + return other != null + && screenState == other.screenState + && useProximitySensor == other.useProximitySensor + && screenBrightness == other.screenBrightness + && screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment + && useAutoBrightness == other.useAutoBrightness + && blockScreenOn == other.blockScreenOn; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + @Override + public String toString() { + return "screenState=" + screenState + + ", useProximitySensor=" + useProximitySensor + + ", screenBrightness=" + screenBrightness + + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment + + ", useAutoBrightness=" + useAutoBrightness + + ", blockScreenOn=" + blockScreenOn; + } +} diff --git a/services/core/java/com/android/server/power/DisplayPowerState.java b/services/core/java/com/android/server/power/DisplayPowerState.java new file mode 100644 index 0000000..42af4b4 --- /dev/null +++ b/services/core/java/com/android/server/power/DisplayPowerState.java @@ -0,0 +1,414 @@ +/* + * 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.lights.Light; + +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.util.FloatProperty; +import android.util.IntProperty; +import android.util.Slog; +import android.view.Choreographer; + +import java.io.PrintWriter; + +/** + * Controls the display power state. + * <p> + * This component is similar in nature to a {@link View} except that it describes + * the properties of a display. When properties are changed, the component + * invalidates itself and posts a callback to apply the changes in a consistent order. + * This mechanism enables multiple properties of the display power state to be animated + * together smoothly by the animation framework. Some of the work to blank or unblank + * the display is done on a separate thread to avoid blocking the looper. + * </p><p> + * This component must only be created or accessed by the {@link Looper} thread + * that belongs to the {@link DisplayPowerController}. + * </p><p> + * We don't need to worry about holding a suspend blocker here because the + * {@link PowerManagerService} does that for us whenever there is a change + * in progress. + * </p> + */ +final class DisplayPowerState { + private static final String TAG = "DisplayPowerState"; + + private static boolean DEBUG = false; + + private final Handler mHandler; + private final Choreographer mChoreographer; + private final ElectronBeam mElectronBeam; + private final DisplayBlanker mDisplayBlanker; + private final Light mBacklight; + private final PhotonicModulator mPhotonicModulator; + + private boolean mScreenOn; + private int mScreenBrightness; + private boolean mScreenReady; + private boolean mScreenUpdatePending; + + private boolean mElectronBeamPrepared; + private float mElectronBeamLevel; + private boolean mElectronBeamReady; + private boolean mElectronBeamDrawPending; + + private Runnable mCleanListener; + + public DisplayPowerState(ElectronBeam electronBean, + DisplayBlanker displayBlanker, Light backlight) { + mHandler = new Handler(true /*async*/); + mChoreographer = Choreographer.getInstance(); + mElectronBeam = electronBean; + mDisplayBlanker = displayBlanker; + mBacklight = backlight; + mPhotonicModulator = new PhotonicModulator(); + + // At boot time, we know that the screen is on and the electron beam + // animation is not playing. We don't know the screen's brightness though, + // so prepare to set it to a known state when the state is next applied. + // Although we set the brightness to full on here, the display power controller + // will reset the brightness to a new level immediately before the changes + // actually have a chance to be applied. + mScreenOn = true; + mScreenBrightness = PowerManager.BRIGHTNESS_ON; + scheduleScreenUpdate(); + + mElectronBeamPrepared = false; + mElectronBeamLevel = 1.0f; + mElectronBeamReady = true; + } + + public static final FloatProperty<DisplayPowerState> ELECTRON_BEAM_LEVEL = + new FloatProperty<DisplayPowerState>("electronBeamLevel") { + @Override + public void setValue(DisplayPowerState object, float value) { + object.setElectronBeamLevel(value); + } + + @Override + public Float get(DisplayPowerState object) { + return object.getElectronBeamLevel(); + } + }; + + public static final IntProperty<DisplayPowerState> SCREEN_BRIGHTNESS = + new IntProperty<DisplayPowerState>("screenBrightness") { + @Override + public void setValue(DisplayPowerState object, int value) { + object.setScreenBrightness(value); + } + + @Override + public Integer get(DisplayPowerState object) { + return object.getScreenBrightness(); + } + }; + + /** + * Sets whether the screen is on or off. + */ + public void setScreenOn(boolean on) { + if (mScreenOn != on) { + if (DEBUG) { + Slog.d(TAG, "setScreenOn: on=" + on); + } + + mScreenOn = on; + mScreenReady = false; + scheduleScreenUpdate(); + } + } + + /** + * Returns true if the screen is on. + */ + public boolean isScreenOn() { + return mScreenOn; + } + + /** + * Sets the display brightness. + * + * @param brightness The brightness, ranges from 0 (minimum / off) to 255 (brightest). + */ + public void setScreenBrightness(int brightness) { + if (mScreenBrightness != brightness) { + if (DEBUG) { + Slog.d(TAG, "setScreenBrightness: brightness=" + brightness); + } + + mScreenBrightness = brightness; + if (mScreenOn) { + mScreenReady = false; + scheduleScreenUpdate(); + } + } + } + + /** + * Gets the screen brightness. + */ + public int getScreenBrightness() { + return mScreenBrightness; + } + + /** + * Prepares the electron beam to turn on or off. + * This method should be called before starting an animation because it + * can take a fair amount of time to prepare the electron beam surface. + * + * @param mode The electron beam animation mode to prepare. + * @return True if the electron beam was prepared. + */ + public boolean prepareElectronBeam(int mode) { + if (!mElectronBeam.prepare(mode)) { + mElectronBeamPrepared = false; + mElectronBeamReady = true; + return false; + } + + mElectronBeamPrepared = true; + mElectronBeamReady = false; + scheduleElectronBeamDraw(); + return true; + } + + /** + * Dismisses the electron beam surface. + */ + public void dismissElectronBeam() { + mElectronBeam.dismiss(); + mElectronBeamPrepared = false; + mElectronBeamReady = true; + } + + /** + * Sets the level of the electron beam steering current. + * + * The display is blanked when the level is 0.0. In normal use, the electron + * beam should have a value of 1.0. The electron beam is unstable in between + * these states and the picture quality may be compromised. For best effect, + * the electron beam should be warmed up or cooled off slowly. + * + * Warning: Electron beam emits harmful radiation. Avoid direct exposure to + * skin or eyes. + * + * @param level The level, ranges from 0.0 (full off) to 1.0 (full on). + */ + public void setElectronBeamLevel(float level) { + if (mElectronBeamLevel != level) { + if (DEBUG) { + Slog.d(TAG, "setElectronBeamLevel: level=" + level); + } + + mElectronBeamLevel = level; + if (mScreenOn) { + mScreenReady = false; + scheduleScreenUpdate(); // update backlight brightness + } + if (mElectronBeamPrepared) { + mElectronBeamReady = false; + scheduleElectronBeamDraw(); + } + } + } + + /** + * Gets the level of the electron beam steering current. + */ + public float getElectronBeamLevel() { + return mElectronBeamLevel; + } + + /** + * Returns true if no properties have been invalidated. + * Otherwise, returns false and promises to invoke the specified listener + * when the properties have all been applied. + * The listener always overrides any previously set listener. + */ + public boolean waitUntilClean(Runnable listener) { + if (!mScreenReady || !mElectronBeamReady) { + mCleanListener = listener; + return false; + } else { + mCleanListener = null; + return true; + } + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println("Display Power State:"); + pw.println(" mScreenOn=" + mScreenOn); + pw.println(" mScreenBrightness=" + mScreenBrightness); + pw.println(" mScreenReady=" + mScreenReady); + pw.println(" mScreenUpdatePending=" + mScreenUpdatePending); + pw.println(" mElectronBeamPrepared=" + mElectronBeamPrepared); + pw.println(" mElectronBeamLevel=" + mElectronBeamLevel); + pw.println(" mElectronBeamReady=" + mElectronBeamReady); + pw.println(" mElectronBeamDrawPending=" + mElectronBeamDrawPending); + + mPhotonicModulator.dump(pw); + mElectronBeam.dump(pw); + } + + private void scheduleScreenUpdate() { + if (!mScreenUpdatePending) { + mScreenUpdatePending = true; + postScreenUpdateThreadSafe(); + } + } + + private void postScreenUpdateThreadSafe() { + mHandler.removeCallbacks(mScreenUpdateRunnable); + mHandler.post(mScreenUpdateRunnable); + } + + private void scheduleElectronBeamDraw() { + if (!mElectronBeamDrawPending) { + mElectronBeamDrawPending = true; + mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, + mElectronBeamDrawRunnable, null); + } + } + + private void invokeCleanListenerIfNeeded() { + final Runnable listener = mCleanListener; + if (listener != null && mScreenReady && mElectronBeamReady) { + mCleanListener = null; + listener.run(); + } + } + + private final Runnable mScreenUpdateRunnable = new Runnable() { + @Override + public void run() { + mScreenUpdatePending = false; + + int brightness = mScreenOn && mElectronBeamLevel > 0f ? mScreenBrightness : 0; + if (mPhotonicModulator.setState(mScreenOn, brightness)) { + mScreenReady = true; + invokeCleanListenerIfNeeded(); + } + } + }; + + private final Runnable mElectronBeamDrawRunnable = new Runnable() { + @Override + public void run() { + mElectronBeamDrawPending = false; + + if (mElectronBeamPrepared) { + mElectronBeam.draw(mElectronBeamLevel); + } + + mElectronBeamReady = true; + invokeCleanListenerIfNeeded(); + } + }; + + /** + * Updates the state of the screen and backlight asynchronously on a separate thread. + */ + private final class PhotonicModulator { + private static final boolean INITIAL_SCREEN_ON = false; // unknown, assume off + private static final int INITIAL_BACKLIGHT = -1; // unknown + + private final Object mLock = new Object(); + + private boolean mPendingOn = INITIAL_SCREEN_ON; + private int mPendingBacklight = INITIAL_BACKLIGHT; + private boolean mActualOn = INITIAL_SCREEN_ON; + private int mActualBacklight = INITIAL_BACKLIGHT; + private boolean mChangeInProgress; + + public boolean setState(boolean on, int backlight) { + synchronized (mLock) { + if (on != mPendingOn || backlight != mPendingBacklight) { + if (DEBUG) { + Slog.d(TAG, "Requesting new screen state: on=" + on + + ", backlight=" + backlight); + } + + mPendingOn = on; + mPendingBacklight = backlight; + + if (!mChangeInProgress) { + mChangeInProgress = true; + AsyncTask.THREAD_POOL_EXECUTOR.execute(mTask); + } + } + return mChangeInProgress; + } + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println("Photonic Modulator State:"); + pw.println(" mPendingOn=" + mPendingOn); + pw.println(" mPendingBacklight=" + mPendingBacklight); + pw.println(" mActualOn=" + mActualOn); + pw.println(" mActualBacklight=" + mActualBacklight); + pw.println(" mChangeInProgress=" + mChangeInProgress); + } + + private final Runnable mTask = new Runnable() { + @Override + public void run() { + // Apply pending changes until done. + for (;;) { + final boolean on; + final boolean onChanged; + final int backlight; + final boolean backlightChanged; + synchronized (mLock) { + on = mPendingOn; + onChanged = (on != mActualOn); + backlight = mPendingBacklight; + backlightChanged = (backlight != mActualBacklight); + if (!onChanged && !backlightChanged) { + mChangeInProgress = false; + break; + } + mActualOn = on; + mActualBacklight = backlight; + } + + if (DEBUG) { + Slog.d(TAG, "Updating screen state: on=" + on + + ", backlight=" + backlight); + } + if (onChanged && on) { + mDisplayBlanker.unblankAllDisplays(); + } + if (backlightChanged) { + mBacklight.setBrightness(backlight); + } + if (onChanged && !on) { + mDisplayBlanker.blankAllDisplays(); + } + } + + // Let the outer class know that all changes have been applied. + postScreenUpdateThreadSafe(); + } + }; + } +} diff --git a/services/core/java/com/android/server/power/ElectronBeam.java b/services/core/java/com/android/server/power/ElectronBeam.java new file mode 100644 index 0000000..729bd16 --- /dev/null +++ b/services/core/java/com/android/server/power/ElectronBeam.java @@ -0,0 +1,733 @@ +/* + * 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 java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import android.graphics.PixelFormat; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES10; +import android.opengl.GLES11Ext; +import android.os.Looper; +import android.util.FloatMath; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Surface.OutOfResourcesException; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceSession; + +import com.android.server.display.DisplayManagerService; +import com.android.server.display.DisplayTransactionListener; + +/** + * Bzzzoooop! *crackle* + * <p> + * Animates a screen transition from on to off or off to on by applying + * some GL transformations to a screenshot. + * </p><p> + * This component must only be created or accessed by the {@link Looper} thread + * that belongs to the {@link DisplayPowerController}. + * </p> + */ +final class ElectronBeam { + private static final String TAG = "ElectronBeam"; + + private static final boolean DEBUG = false; + + // The layer for the electron beam surface. + // This is currently hardcoded to be one layer above the boot animation. + private static final int ELECTRON_BEAM_LAYER = 0x40000001; + + // The relative proportion of the animation to spend performing + // the horizontal stretch effect. The remainder is spent performing + // the vertical stretch effect. + private static final float HSTRETCH_DURATION = 0.5f; + private static final float VSTRETCH_DURATION = 1.0f - HSTRETCH_DURATION; + + // The number of frames to draw when preparing the animation so that it will + // be ready to run smoothly. We use 3 frames because we are triple-buffered. + // See code for details. + private static final int DEJANK_FRAMES = 3; + + // Set to true when the animation context has been fully prepared. + private boolean mPrepared; + private int mMode; + + private final DisplayManagerService mDisplayManager; + private int mDisplayLayerStack; // layer stack associated with primary display + private int mDisplayWidth; // real width, not rotated + private int mDisplayHeight; // real height, not rotated + private SurfaceSession mSurfaceSession; + private SurfaceControl mSurfaceControl; + private Surface mSurface; + private NaturalSurfaceLayout mSurfaceLayout; + private EGLDisplay mEglDisplay; + private EGLConfig mEglConfig; + private EGLContext mEglContext; + private EGLSurface mEglSurface; + private boolean mSurfaceVisible; + private float mSurfaceAlpha; + + // Texture names. We only use one texture, which contains the screenshot. + private final int[] mTexNames = new int[1]; + private boolean mTexNamesGenerated; + private final float mTexMatrix[] = new float[16]; + + // Vertex and corresponding texture coordinates. + // We have 4 2D vertices, so 8 elements. The vertices form a quad. + private final FloatBuffer mVertexBuffer = createNativeFloatBuffer(8); + private final FloatBuffer mTexCoordBuffer = createNativeFloatBuffer(8); + + /** + * Animates an electron beam warming up. + */ + public static final int MODE_WARM_UP = 0; + + /** + * Animates an electron beam shutting off. + */ + public static final int MODE_COOL_DOWN = 1; + + /** + * Animates a simple dim layer to fade the contents of the screen in or out progressively. + */ + public static final int MODE_FADE = 2; + + + public ElectronBeam(DisplayManagerService displayManager) { + mDisplayManager = displayManager; + } + + /** + * Warms up the electron beam in preparation for turning on or off. + * This method prepares a GL context, and captures a screen shot. + * + * @param mode The desired mode for the upcoming animation. + * @return True if the electron beam is ready, false if it is uncontrollable. + */ + public boolean prepare(int mode) { + if (DEBUG) { + Slog.d(TAG, "prepare: mode=" + mode); + } + + mMode = mode; + + // Get the display size and layer stack. + // This is not expected to change while the electron beam surface is showing. + DisplayInfo displayInfo = mDisplayManager.getDisplayInfo(Display.DEFAULT_DISPLAY); + mDisplayLayerStack = displayInfo.layerStack; + mDisplayWidth = displayInfo.getNaturalWidth(); + mDisplayHeight = displayInfo.getNaturalHeight(); + + // Prepare the surface for drawing. + if (!tryPrepare()) { + dismiss(); + return false; + } + + // Done. + mPrepared = true; + + // Dejanking optimization. + // Some GL drivers can introduce a lot of lag in the first few frames as they + // initialize their state and allocate graphics buffers for rendering. + // Work around this problem by rendering the first frame of the animation a few + // times. The rest of the animation should run smoothly thereafter. + // The frames we draw here aren't visible because we are essentially just + // painting the screenshot as-is. + if (mode == MODE_COOL_DOWN) { + for (int i = 0; i < DEJANK_FRAMES; i++) { + draw(1.0f); + } + } + return true; + } + + private boolean tryPrepare() { + if (createSurface()) { + if (mMode == MODE_FADE) { + return true; + } + return createEglContext() + && createEglSurface() + && captureScreenshotTextureAndSetViewport(); + } + return false; + } + + /** + * Dismisses the electron beam animation surface and cleans up. + * + * To prevent stray photons from leaking out after the electron beam has been + * turned off, it is a good idea to defer dismissing the animation until the + * electron beam has been turned back on fully. + */ + public void dismiss() { + if (DEBUG) { + Slog.d(TAG, "dismiss"); + } + + destroyScreenshotTexture(); + destroyEglSurface(); + destroySurface(); + mPrepared = false; + } + + /** + * Draws an animation frame showing the electron beam activated at the + * specified level. + * + * @param level The electron beam level. + * @return True if successful. + */ + public boolean draw(float level) { + if (DEBUG) { + Slog.d(TAG, "drawFrame: level=" + level); + } + + if (!mPrepared) { + return false; + } + + if (mMode == MODE_FADE) { + return showSurface(1.0f - level); + } + + if (!attachEglContext()) { + return false; + } + try { + // Clear frame to solid black. + GLES10.glClearColor(0f, 0f, 0f, 1f); + GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT); + + // Draw the frame. + if (level < HSTRETCH_DURATION) { + drawHStretch(1.0f - (level / HSTRETCH_DURATION)); + } else { + drawVStretch(1.0f - ((level - HSTRETCH_DURATION) / VSTRETCH_DURATION)); + } + if (checkGlErrors("drawFrame")) { + return false; + } + + EGL14.eglSwapBuffers(mEglDisplay, mEglSurface); + } finally { + detachEglContext(); + } + return showSurface(1.0f); + } + + /** + * Draws a frame where the content of the electron beam is collapsing inwards upon + * itself vertically with red / green / blue channels dispersing and eventually + * merging down to a single horizontal line. + * + * @param stretch The stretch factor. 0.0 is no collapse, 1.0 is full collapse. + */ + private void drawVStretch(float stretch) { + // compute interpolation scale factors for each color channel + final float ar = scurve(stretch, 7.5f); + final float ag = scurve(stretch, 8.0f); + final float ab = scurve(stretch, 8.5f); + if (DEBUG) { + Slog.d(TAG, "drawVStretch: stretch=" + stretch + + ", ar=" + ar + ", ag=" + ag + ", ab=" + ab); + } + + // set blending + GLES10.glBlendFunc(GLES10.GL_ONE, GLES10.GL_ONE); + GLES10.glEnable(GLES10.GL_BLEND); + + // bind vertex buffer + GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer); + GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); + + // set-up texturing + GLES10.glDisable(GLES10.GL_TEXTURE_2D); + GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); + + // bind texture and set blending for drawing planes + GLES10.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexNames[0]); + GLES10.glTexEnvx(GLES10.GL_TEXTURE_ENV, GLES10.GL_TEXTURE_ENV_MODE, + mMode == MODE_WARM_UP ? GLES10.GL_MODULATE : GLES10.GL_REPLACE); + GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR); + GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR); + GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_CLAMP_TO_EDGE); + GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_CLAMP_TO_EDGE); + GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); + GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, mTexCoordBuffer); + GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); + + // draw the red plane + setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ar); + GLES10.glColorMask(true, false, false, true); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // draw the green plane + setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag); + GLES10.glColorMask(false, true, false, true); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // draw the blue plane + setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ab); + GLES10.glColorMask(false, false, true, true); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // clean up after drawing planes + GLES10.glDisable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); + GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); + GLES10.glColorMask(true, true, true, true); + + // draw the white highlight (we use the last vertices) + if (mMode == MODE_COOL_DOWN) { + GLES10.glColor4f(ag, ag, ag, 1.0f); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + } + + // clean up + GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); + GLES10.glDisable(GLES10.GL_BLEND); + } + + /** + * Draws a frame where the electron beam has been stretched out into + * a thin white horizontal line that fades as it collapses inwards. + * + * @param stretch The stretch factor. 0.0 is maximum stretch / no fade, + * 1.0 is collapsed / maximum fade. + */ + private void drawHStretch(float stretch) { + // compute interpolation scale factor + final float ag = scurve(stretch, 8.0f); + if (DEBUG) { + Slog.d(TAG, "drawHStretch: stretch=" + stretch + ", ag=" + ag); + } + + if (stretch < 1.0f) { + // bind vertex buffer + GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer); + GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); + + // draw narrow fading white line + setHStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag); + GLES10.glColor4f(1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // clean up + GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); + } + } + + private static void setVStretchQuad(FloatBuffer vtx, float dw, float dh, float a) { + final float w = dw + (dw * a); + final float h = dh - (dh * a); + final float x = (dw - w) * 0.5f; + final float y = (dh - h) * 0.5f; + setQuad(vtx, x, y, w, h); + } + + private static void setHStretchQuad(FloatBuffer vtx, float dw, float dh, float a) { + final float w = 2 * dw * (1.0f - a); + final float h = 1.0f; + final float x = (dw - w) * 0.5f; + final float y = (dh - h) * 0.5f; + setQuad(vtx, x, y, w, h); + } + + private static void setQuad(FloatBuffer vtx, float x, float y, float w, float h) { + if (DEBUG) { + Slog.d(TAG, "setQuad: x=" + x + ", y=" + y + ", w=" + w + ", h=" + h); + } + vtx.put(0, x); + vtx.put(1, y); + vtx.put(2, x); + vtx.put(3, y + h); + vtx.put(4, x + w); + vtx.put(5, y + h); + vtx.put(6, x + w); + vtx.put(7, y); + } + + private boolean captureScreenshotTextureAndSetViewport() { + if (!attachEglContext()) { + return false; + } + try { + if (!mTexNamesGenerated) { + GLES10.glGenTextures(1, mTexNames, 0); + if (checkGlErrors("glGenTextures")) { + return false; + } + mTexNamesGenerated = true; + } + + final SurfaceTexture st = new SurfaceTexture(mTexNames[0]); + final Surface s = new Surface(st); + try { + SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay( + SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), s); + } finally { + s.release(); + } + + st.updateTexImage(); + st.getTransformMatrix(mTexMatrix); + + // Set up texture coordinates for a quad. + // We might need to change this if the texture ends up being + // a different size from the display for some reason. + mTexCoordBuffer.put(0, 0f); mTexCoordBuffer.put(1, 0f); + mTexCoordBuffer.put(2, 0f); mTexCoordBuffer.put(3, 1f); + mTexCoordBuffer.put(4, 1f); mTexCoordBuffer.put(5, 1f); + mTexCoordBuffer.put(6, 1f); mTexCoordBuffer.put(7, 0f); + + // Set up our viewport. + GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight); + GLES10.glMatrixMode(GLES10.GL_PROJECTION); + GLES10.glLoadIdentity(); + GLES10.glOrthof(0, mDisplayWidth, 0, mDisplayHeight, 0, 1); + GLES10.glMatrixMode(GLES10.GL_MODELVIEW); + GLES10.glLoadIdentity(); + GLES10.glMatrixMode(GLES10.GL_TEXTURE); + GLES10.glLoadIdentity(); + GLES10.glLoadMatrixf(mTexMatrix, 0); + } finally { + detachEglContext(); + } + return true; + } + + private void destroyScreenshotTexture() { + if (mTexNamesGenerated) { + mTexNamesGenerated = false; + if (attachEglContext()) { + try { + GLES10.glDeleteTextures(1, mTexNames, 0); + checkGlErrors("glDeleteTextures"); + } finally { + detachEglContext(); + } + } + } + } + + private boolean createEglContext() { + if (mEglDisplay == null) { + mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { + logEglError("eglGetDisplay"); + return false; + } + + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { + mEglDisplay = null; + logEglError("eglInitialize"); + return false; + } + } + + if (mEglConfig == null) { + int[] eglConfigAttribList = new int[] { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_NONE + }; + int[] numEglConfigs = new int[1]; + EGLConfig[] eglConfigs = new EGLConfig[1]; + if (!EGL14.eglChooseConfig(mEglDisplay, eglConfigAttribList, 0, + eglConfigs, 0, eglConfigs.length, numEglConfigs, 0)) { + logEglError("eglChooseConfig"); + return false; + } + mEglConfig = eglConfigs[0]; + } + + if (mEglContext == null) { + int[] eglContextAttribList = new int[] { + EGL14.EGL_NONE + }; + mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, + EGL14.EGL_NO_CONTEXT, eglContextAttribList, 0); + if (mEglContext == null) { + logEglError("eglCreateContext"); + return false; + } + } + return true; + } + + /* not used because it is too expensive to create / destroy contexts all of the time + private void destroyEglContext() { + if (mEglContext != null) { + if (!EGL14.eglDestroyContext(mEglDisplay, mEglContext)) { + logEglError("eglDestroyContext"); + } + mEglContext = null; + } + }*/ + + private boolean createSurface() { + if (mSurfaceSession == null) { + mSurfaceSession = new SurfaceSession(); + } + + SurfaceControl.openTransaction(); + try { + if (mSurfaceControl == null) { + try { + int flags; + if (mMode == MODE_FADE) { + flags = SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN; + } else { + flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN; + } + mSurfaceControl = new SurfaceControl(mSurfaceSession, + "ElectronBeam", mDisplayWidth, mDisplayHeight, + PixelFormat.OPAQUE, flags); + } catch (OutOfResourcesException ex) { + Slog.e(TAG, "Unable to create surface.", ex); + return false; + } + } + + mSurfaceControl.setLayerStack(mDisplayLayerStack); + mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight); + mSurface = new Surface(); + mSurface.copyFrom(mSurfaceControl); + + mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl); + mSurfaceLayout.onDisplayTransaction(); + } finally { + SurfaceControl.closeTransaction(); + } + return true; + } + + private boolean createEglSurface() { + if (mEglSurface == null) { + int[] eglSurfaceAttribList = new int[] { + EGL14.EGL_NONE + }; + // turn our SurfaceControl into a Surface + mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, + eglSurfaceAttribList, 0); + if (mEglSurface == null) { + logEglError("eglCreateWindowSurface"); + return false; + } + } + return true; + } + + private void destroyEglSurface() { + if (mEglSurface != null) { + if (!EGL14.eglDestroySurface(mEglDisplay, mEglSurface)) { + logEglError("eglDestroySurface"); + } + mEglSurface = null; + } + } + + private void destroySurface() { + if (mSurfaceControl != null) { + mSurfaceLayout.dispose(); + mSurfaceLayout = null; + SurfaceControl.openTransaction(); + try { + mSurfaceControl.destroy(); + mSurface.release(); + } finally { + SurfaceControl.closeTransaction(); + } + mSurfaceControl = null; + mSurfaceVisible = false; + mSurfaceAlpha = 0f; + } + } + + private boolean showSurface(float alpha) { + if (!mSurfaceVisible || mSurfaceAlpha != alpha) { + SurfaceControl.openTransaction(); + try { + mSurfaceControl.setLayer(ELECTRON_BEAM_LAYER); + mSurfaceControl.setAlpha(alpha); + mSurfaceControl.show(); + } finally { + SurfaceControl.closeTransaction(); + } + mSurfaceVisible = true; + mSurfaceAlpha = alpha; + } + return true; + } + + private boolean attachEglContext() { + if (mEglSurface == null) { + return false; + } + if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + logEglError("eglMakeCurrent"); + return false; + } + return true; + } + + private void detachEglContext() { + if (mEglDisplay != null) { + EGL14.eglMakeCurrent(mEglDisplay, + EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + } + } + + /** + * Interpolates a value in the range 0 .. 1 along a sigmoid curve + * yielding a result in the range 0 .. 1 scaled such that: + * scurve(0) == 0, scurve(0.5) == 0.5, scurve(1) == 1. + */ + private static float scurve(float value, float s) { + // A basic sigmoid has the form y = 1.0f / FloatMap.exp(-x * s). + // Here we take the input datum and shift it by 0.5 so that the + // domain spans the range -0.5 .. 0.5 instead of 0 .. 1. + final float x = value - 0.5f; + + // Next apply the sigmoid function to the scaled value + // which produces a value in the range 0 .. 1 so we subtract + // 0.5 to get a value in the range -0.5 .. 0.5 instead. + final float y = sigmoid(x, s) - 0.5f; + + // To obtain the desired boundary conditions we need to scale + // the result so that it fills a range of -1 .. 1. + final float v = sigmoid(0.5f, s) - 0.5f; + + // And finally remap the value back to a range of 0 .. 1. + return y / v * 0.5f + 0.5f; + } + + private static float sigmoid(float x, float s) { + return 1.0f / (1.0f + FloatMath.exp(-x * s)); + } + + private static FloatBuffer createNativeFloatBuffer(int size) { + ByteBuffer bb = ByteBuffer.allocateDirect(size * 4); + bb.order(ByteOrder.nativeOrder()); + return bb.asFloatBuffer(); + } + + private static void logEglError(String func) { + Slog.e(TAG, func + " failed: error " + EGL14.eglGetError(), new Throwable()); + } + + private static boolean checkGlErrors(String func) { + return checkGlErrors(func, true); + } + + private static boolean checkGlErrors(String func, boolean log) { + boolean hadError = false; + int error; + while ((error = GLES10.glGetError()) != GLES10.GL_NO_ERROR) { + if (log) { + Slog.e(TAG, func + " failed: error " + error, new Throwable()); + } + hadError = true; + } + return hadError; + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println("Electron Beam State:"); + pw.println(" mPrepared=" + mPrepared); + pw.println(" mMode=" + mMode); + pw.println(" mDisplayLayerStack=" + mDisplayLayerStack); + pw.println(" mDisplayWidth=" + mDisplayWidth); + pw.println(" mDisplayHeight=" + mDisplayHeight); + pw.println(" mSurfaceVisible=" + mSurfaceVisible); + pw.println(" mSurfaceAlpha=" + mSurfaceAlpha); + } + + /** + * Keeps a surface aligned with the natural orientation of the device. + * Updates the position and transformation of the matrix whenever the display + * is rotated. This is a little tricky because the display transaction + * callback can be invoked on any thread, not necessarily the thread that + * owns the electron beam. + */ + private static final class NaturalSurfaceLayout implements DisplayTransactionListener { + private final DisplayManagerService mDisplayManager; + private SurfaceControl mSurfaceControl; + + public NaturalSurfaceLayout(DisplayManagerService displayManager, SurfaceControl surfaceControl) { + mDisplayManager = displayManager; + mSurfaceControl = surfaceControl; + mDisplayManager.registerDisplayTransactionListener(this); + } + + public void dispose() { + synchronized (this) { + mSurfaceControl = null; + } + mDisplayManager.unregisterDisplayTransactionListener(this); + } + + @Override + public void onDisplayTransaction() { + synchronized (this) { + if (mSurfaceControl == null) { + return; + } + + DisplayInfo displayInfo = mDisplayManager.getDisplayInfo(Display.DEFAULT_DISPLAY); + switch (displayInfo.rotation) { + case Surface.ROTATION_0: + mSurfaceControl.setPosition(0, 0); + mSurfaceControl.setMatrix(1, 0, 0, 1); + break; + case Surface.ROTATION_90: + mSurfaceControl.setPosition(0, displayInfo.logicalHeight); + mSurfaceControl.setMatrix(0, -1, 1, 0); + break; + case Surface.ROTATION_180: + mSurfaceControl.setPosition(displayInfo.logicalWidth, displayInfo.logicalHeight); + mSurfaceControl.setMatrix(-1, 0, 0, -1); + break; + case Surface.ROTATION_270: + mSurfaceControl.setPosition(displayInfo.logicalWidth, 0); + mSurfaceControl.setMatrix(0, 1, -1, 0); + break; + } + } + } + } +} diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java new file mode 100644 index 0000000..264e2e9 --- /dev/null +++ b/services/core/java/com/android/server/power/Notifier.java @@ -0,0 +1,546 @@ +/* + * 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 android.app.AppOpsManager; +import com.android.internal.app.IAppOpsService; +import com.android.internal.app.IBatteryStats; +import com.android.server.EventLogTags; + +import android.app.ActivityManagerNative; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.WorkSource; +import android.provider.Settings; +import android.util.EventLog; +import android.util.Slog; +import android.view.WindowManagerPolicy; + +/** + * Sends broadcasts about important power state changes. + * <p> + * This methods of this class may be called by the power manager service while + * its lock is being held. Internally it takes care of sending broadcasts to + * notify other components of the system or applications asynchronously. + * </p><p> + * The notifier is designed to collapse unnecessary broadcasts when it is not + * possible for the system to have observed an intermediate state. + * </p><p> + * For example, if the device wakes up, goes to sleep, wakes up again and goes to + * sleep again before the wake up notification is sent, then the system will + * be told about only one wake up and sleep. However, we always notify the + * fact that at least one transition occurred. It is especially important to + * tell the system when we go to sleep so that it can lock the keyguard if needed. + * </p> + */ +final class Notifier { + private static final String TAG = "PowerManagerNotifier"; + + private static final boolean DEBUG = false; + + private static final int POWER_STATE_UNKNOWN = 0; + private static final int POWER_STATE_AWAKE = 1; + private static final int POWER_STATE_ASLEEP = 2; + + private static final int MSG_USER_ACTIVITY = 1; + private static final int MSG_BROADCAST = 2; + private static final int MSG_WIRELESS_CHARGING_STARTED = 3; + + private final Object mLock = new Object(); + + private final Context mContext; + private final IBatteryStats mBatteryStats; + private final IAppOpsService mAppOps; + private final SuspendBlocker mSuspendBlocker; + private final ScreenOnBlocker mScreenOnBlocker; + private final WindowManagerPolicy mPolicy; + + private final NotifierHandler mHandler; + private final Intent mScreenOnIntent; + private final Intent mScreenOffIntent; + + // The current power state. + private int mActualPowerState; + private int mLastGoToSleepReason; + + // True if there is a pending transition that needs to be reported. + private boolean mPendingWakeUpBroadcast; + private boolean mPendingGoToSleepBroadcast; + + // The currently broadcasted power state. This reflects what other parts of the + // system have observed. + private int mBroadcastedPowerState; + private boolean mBroadcastInProgress; + private long mBroadcastStartTime; + + // True if a user activity message should be sent. + private boolean mUserActivityPending; + + // True if the screen on blocker has been acquired. + private boolean mScreenOnBlockerAcquired; + + public Notifier(Looper looper, Context context, IBatteryStats batteryStats, + IAppOpsService appOps, SuspendBlocker suspendBlocker, ScreenOnBlocker screenOnBlocker, + WindowManagerPolicy policy) { + mContext = context; + mBatteryStats = batteryStats; + mAppOps = appOps; + mSuspendBlocker = suspendBlocker; + mScreenOnBlocker = screenOnBlocker; + mPolicy = policy; + + mHandler = new NotifierHandler(looper); + mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON); + mScreenOnIntent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF); + mScreenOffIntent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + } + + /** + * Called when a wake lock is acquired. + */ + public void onWakeLockAcquired(int flags, String tag, String packageName, + int ownerUid, int ownerPid, WorkSource workSource) { + if (DEBUG) { + Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag + + "\", packageName=" + packageName + + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + + ", workSource=" + workSource); + } + + try { + final int monitorType = getBatteryStatsWakeLockMonitorType(flags); + if (workSource != null) { + mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, monitorType); + } else { + mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType); + // XXX need to deal with disabled operations. + mAppOps.startOperation(AppOpsManager.getToken(mAppOps), + AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName); + } + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when a wake lock is released. + */ + public void onWakeLockReleased(int flags, String tag, String packageName, + int ownerUid, int ownerPid, WorkSource workSource) { + if (DEBUG) { + Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag + + "\", packageName=" + packageName + + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + + ", workSource=" + workSource); + } + + try { + final int monitorType = getBatteryStatsWakeLockMonitorType(flags); + if (workSource != null) { + mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType); + } else { + mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType); + mAppOps.finishOperation(AppOpsManager.getToken(mAppOps), + AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName); + } + } catch (RemoteException ex) { + // Ignore + } + } + + private static int getBatteryStatsWakeLockMonitorType(int flags) { + switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.PARTIAL_WAKE_LOCK: + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + return BatteryStats.WAKE_TYPE_PARTIAL; + default: + return BatteryStats.WAKE_TYPE_FULL; + } + } + + /** + * Called when the screen is turned on. + */ + public void onScreenOn() { + if (DEBUG) { + Slog.d(TAG, "onScreenOn"); + } + + try { + mBatteryStats.noteScreenOn(); + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when the screen is turned off. + */ + public void onScreenOff() { + if (DEBUG) { + Slog.d(TAG, "onScreenOff"); + } + + try { + mBatteryStats.noteScreenOff(); + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when the screen changes brightness. + */ + public void onScreenBrightness(int brightness) { + if (DEBUG) { + Slog.d(TAG, "onScreenBrightness: brightness=" + brightness); + } + + try { + mBatteryStats.noteScreenBrightness(brightness); + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when the device is waking up from sleep and the + * display is about to be turned on. + */ + public void onWakeUpStarted() { + if (DEBUG) { + Slog.d(TAG, "onWakeUpStarted"); + } + + synchronized (mLock) { + if (mActualPowerState != POWER_STATE_AWAKE) { + mActualPowerState = POWER_STATE_AWAKE; + mPendingWakeUpBroadcast = true; + if (!mScreenOnBlockerAcquired) { + mScreenOnBlockerAcquired = true; + mScreenOnBlocker.acquire(); + } + updatePendingBroadcastLocked(); + } + } + } + + /** + * Called when the device has finished waking up from sleep + * and the display has been turned on. + */ + public void onWakeUpFinished() { + if (DEBUG) { + Slog.d(TAG, "onWakeUpFinished"); + } + } + + /** + * Called when the device is going to sleep. + */ + public void onGoToSleepStarted(int reason) { + if (DEBUG) { + Slog.d(TAG, "onGoToSleepStarted"); + } + + synchronized (mLock) { + mLastGoToSleepReason = reason; + } + } + + /** + * Called when the device has finished going to sleep and the + * display has been turned off. + * + * This is a good time to make transitions that we don't want the user to see, + * such as bringing the key guard to focus. There's no guarantee for this, + * however because the user could turn the device on again at any time. + * Some things may need to be protected by other mechanisms that defer screen on. + */ + public void onGoToSleepFinished() { + if (DEBUG) { + Slog.d(TAG, "onGoToSleepFinished"); + } + + synchronized (mLock) { + if (mActualPowerState != POWER_STATE_ASLEEP) { + mActualPowerState = POWER_STATE_ASLEEP; + mPendingGoToSleepBroadcast = true; + if (mUserActivityPending) { + mUserActivityPending = false; + mHandler.removeMessages(MSG_USER_ACTIVITY); + } + updatePendingBroadcastLocked(); + } + } + } + + /** + * Called when there has been user activity. + */ + public void onUserActivity(int event, int uid) { + if (DEBUG) { + Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid); + } + + try { + mBatteryStats.noteUserActivity(uid, event); + } catch (RemoteException ex) { + // Ignore + } + + synchronized (mLock) { + if (!mUserActivityPending) { + mUserActivityPending = true; + Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + } + } + + /** + * Called when wireless charging has started so as to provide user feedback. + */ + public void onWirelessChargingStarted() { + if (DEBUG) { + Slog.d(TAG, "onWirelessChargingStarted"); + } + + mSuspendBlocker.acquire(); + Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + + private void updatePendingBroadcastLocked() { + if (!mBroadcastInProgress + && mActualPowerState != POWER_STATE_UNKNOWN + && (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast + || mActualPowerState != mBroadcastedPowerState)) { + mBroadcastInProgress = true; + mSuspendBlocker.acquire(); + Message msg = mHandler.obtainMessage(MSG_BROADCAST); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + } + + private void finishPendingBroadcastLocked() { + mBroadcastInProgress = false; + mSuspendBlocker.release(); + } + + private void sendUserActivity() { + synchronized (mLock) { + if (!mUserActivityPending) { + return; + } + mUserActivityPending = false; + } + + mPolicy.userActivity(); + } + + private void sendNextBroadcast() { + final int powerState; + final int goToSleepReason; + synchronized (mLock) { + if (mBroadcastedPowerState == POWER_STATE_UNKNOWN) { + // Broadcasted power state is unknown. Send wake up. + mPendingWakeUpBroadcast = false; + mBroadcastedPowerState = POWER_STATE_AWAKE; + } else if (mBroadcastedPowerState == POWER_STATE_AWAKE) { + // Broadcasted power state is awake. Send asleep if needed. + if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast + || mActualPowerState == POWER_STATE_ASLEEP) { + mPendingGoToSleepBroadcast = false; + mBroadcastedPowerState = POWER_STATE_ASLEEP; + } else { + finishPendingBroadcastLocked(); + return; + } + } else { + // Broadcasted power state is asleep. Send awake if needed. + if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast + || mActualPowerState == POWER_STATE_AWAKE) { + mPendingWakeUpBroadcast = false; + mBroadcastedPowerState = POWER_STATE_AWAKE; + } else { + finishPendingBroadcastLocked(); + return; + } + } + + mBroadcastStartTime = SystemClock.uptimeMillis(); + powerState = mBroadcastedPowerState; + goToSleepReason = mLastGoToSleepReason; + } + + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1); + + if (powerState == POWER_STATE_AWAKE) { + sendWakeUpBroadcast(); + } else { + sendGoToSleepBroadcast(goToSleepReason); + } + } + + private void sendWakeUpBroadcast() { + if (DEBUG) { + Slog.d(TAG, "Sending wake up broadcast."); + } + + EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0); + + mPolicy.screenTurningOn(mScreenOnListener); + + try { + ActivityManagerNative.getDefault().wakingUp(); + } catch (RemoteException e) { + // ignore it + } + + if (ActivityManagerNative.isSystemReady()) { + mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null, + mWakeUpBroadcastDone, mHandler, 0, null, null); + } else { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1); + sendNextBroadcast(); + } + } + + private final WindowManagerPolicy.ScreenOnListener mScreenOnListener = + new WindowManagerPolicy.ScreenOnListener() { + @Override + public void onScreenOn() { + synchronized (mLock) { + if (mScreenOnBlockerAcquired && !mPendingWakeUpBroadcast) { + mScreenOnBlockerAcquired = false; + mScreenOnBlocker.release(); + } + } + } + }; + + private final BroadcastReceiver mWakeUpBroadcastDone = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1, + SystemClock.uptimeMillis() - mBroadcastStartTime, 1); + sendNextBroadcast(); + } + }; + + private void sendGoToSleepBroadcast(int reason) { + if (DEBUG) { + Slog.d(TAG, "Sending go to sleep broadcast."); + } + + int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER; + switch (reason) { + case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: + why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN; + break; + case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: + why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT; + break; + } + + EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0); + + mPolicy.screenTurnedOff(why); + try { + ActivityManagerNative.getDefault().goingToSleep(); + } catch (RemoteException e) { + // ignore it. + } + + if (ActivityManagerNative.isSystemReady()) { + mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null, + mGoToSleepBroadcastDone, mHandler, 0, null, null); + } else { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1); + sendNextBroadcast(); + } + } + + private final BroadcastReceiver mGoToSleepBroadcastDone = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0, + SystemClock.uptimeMillis() - mBroadcastStartTime, 1); + sendNextBroadcast(); + } + }; + + private void playWirelessChargingStartedSound() { + final String soundPath = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.WIRELESS_CHARGING_STARTED_SOUND); + if (soundPath != null) { + final Uri soundUri = Uri.parse("file://" + soundPath); + if (soundUri != null) { + final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); + if (sfx != null) { + sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.play(); + } + } + } + + mSuspendBlocker.release(); + } + + private final class NotifierHandler extends Handler { + public NotifierHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_USER_ACTIVITY: + sendUserActivity(); + break; + + case MSG_BROADCAST: + sendNextBroadcast(); + break; + + case MSG_WIRELESS_CHARGING_STARTED: + playWirelessChargingStartedSound(); + break; + } + } + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java new file mode 100644 index 0000000..13f55e2 --- /dev/null +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -0,0 +1,2739 @@ +/* + * Copyright (C) 2007 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.internal.app.IAppOpsService; +import com.android.internal.app.IBatteryStats; +import com.android.server.BatteryService; +import com.android.server.EventLogTags; +import com.android.server.lights.LightsService; +import com.android.server.lights.Light; +import com.android.server.lights.LightsManager; +import com.android.server.twilight.TwilightManager; +import com.android.server.Watchdog; +import com.android.server.display.DisplayManagerService; +import com.android.server.dreams.DreamManagerService; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.hardware.SensorManager; +import android.hardware.SystemSensorManager; +import android.net.Uri; +import android.os.BatteryManager; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.IPowerManager; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.SystemService; +import android.os.UserHandle; +import android.os.WorkSource; +import android.provider.Settings; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.util.TimeUtils; +import android.view.WindowManagerPolicy; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +import libcore.util.Objects; + +/** + * The power manager service is responsible for coordinating power management + * functions on the device. + */ +public final class PowerManagerService extends IPowerManager.Stub + implements Watchdog.Monitor { + private static final String TAG = "PowerManagerService"; + + private static final boolean DEBUG = false; + private static final boolean DEBUG_SPEW = DEBUG && true; + + // Message: Sent when a user activity timeout occurs to update the power state. + private static final int MSG_USER_ACTIVITY_TIMEOUT = 1; + // Message: Sent when the device enters or exits a napping or dreaming state. + private static final int MSG_SANDMAN = 2; + // Message: Sent when the screen on blocker is released. + private static final int MSG_SCREEN_ON_BLOCKER_RELEASED = 3; + // Message: Sent to poll whether the boot animation has terminated. + private static final int MSG_CHECK_IF_BOOT_ANIMATION_FINISHED = 4; + + // Dirty bit: mWakeLocks changed + private static final int DIRTY_WAKE_LOCKS = 1 << 0; + // Dirty bit: mWakefulness changed + private static final int DIRTY_WAKEFULNESS = 1 << 1; + // Dirty bit: user activity was poked or may have timed out + private static final int DIRTY_USER_ACTIVITY = 1 << 2; + // Dirty bit: actual display power state was updated asynchronously + private static final int DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED = 1 << 3; + // Dirty bit: mBootCompleted changed + private static final int DIRTY_BOOT_COMPLETED = 1 << 4; + // Dirty bit: settings changed + private static final int DIRTY_SETTINGS = 1 << 5; + // Dirty bit: mIsPowered changed + private static final int DIRTY_IS_POWERED = 1 << 6; + // Dirty bit: mStayOn changed + private static final int DIRTY_STAY_ON = 1 << 7; + // Dirty bit: battery state changed + private static final int DIRTY_BATTERY_STATE = 1 << 8; + // Dirty bit: proximity state changed + private static final int DIRTY_PROXIMITY_POSITIVE = 1 << 9; + // Dirty bit: screen on blocker state became held or unheld + private static final int DIRTY_SCREEN_ON_BLOCKER_RELEASED = 1 << 10; + // Dirty bit: dock state changed + private static final int DIRTY_DOCK_STATE = 1 << 11; + + // Wakefulness: The device is asleep and can only be awoken by a call to wakeUp(). + // The screen should be off or in the process of being turned off by the display controller. + private static final int WAKEFULNESS_ASLEEP = 0; + // Wakefulness: The device is fully awake. It can be put to sleep by a call to goToSleep(). + // When the user activity timeout expires, the device may start napping or go to sleep. + private static final int WAKEFULNESS_AWAKE = 1; + // Wakefulness: The device is napping. It is deciding whether to dream or go to sleep + // but hasn't gotten around to it yet. It can be awoken by a call to wakeUp(), which + // ends the nap. User activity may brighten the screen but does not end the nap. + private static final int WAKEFULNESS_NAPPING = 2; + // Wakefulness: The device is dreaming. It can be awoken by a call to wakeUp(), + // which ends the dream. The device goes to sleep when goToSleep() is called, when + // the dream ends or when unplugged. + // User activity may brighten the screen but does not end the dream. + private static final int WAKEFULNESS_DREAMING = 3; + + // Summarizes the state of all active wakelocks. + private static final int WAKE_LOCK_CPU = 1 << 0; + private static final int WAKE_LOCK_SCREEN_BRIGHT = 1 << 1; + private static final int WAKE_LOCK_SCREEN_DIM = 1 << 2; + private static final int WAKE_LOCK_BUTTON_BRIGHT = 1 << 3; + private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4; + private static final int WAKE_LOCK_STAY_AWAKE = 1 << 5; // only set if already awake + + // Summarizes the user activity state. + private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0; + private static final int USER_ACTIVITY_SCREEN_DIM = 1 << 1; + + // Default and minimum screen off timeout in milliseconds. + private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000; + private static final int MINIMUM_SCREEN_OFF_TIMEOUT = 10 * 1000; + + // The screen dim duration, in milliseconds. + // This is subtracted from the end of the screen off timeout so the + // minimum screen off timeout should be longer than this. + private static final int SCREEN_DIM_DURATION = 7 * 1000; + + // The maximum screen dim time expressed as a ratio relative to the screen + // off timeout. If the screen off timeout is very short then we want the + // dim timeout to also be quite short so that most of the time is spent on. + // Otherwise the user won't get much screen on time before dimming occurs. + private static final float MAXIMUM_SCREEN_DIM_RATIO = 0.2f; + + // The name of the boot animation service in init.rc. + private static final String BOOT_ANIMATION_SERVICE = "bootanim"; + + // Poll interval in milliseconds for watching boot animation finished. + private static final int BOOT_ANIMATION_POLL_INTERVAL = 200; + + // If the battery level drops by this percentage and the user activity timeout + // has expired, then assume the device is receiving insufficient current to charge + // effectively and terminate the dream. + private static final int DREAM_BATTERY_LEVEL_DRAIN_CUTOFF = 5; + + private Context mContext; + private LightsManager mLightsManager; + private BatteryService mBatteryService; + private DisplayManagerService mDisplayManagerService; + private IBatteryStats mBatteryStats; + private IAppOpsService mAppOps; + private HandlerThread mHandlerThread; + private PowerManagerHandler mHandler; + private WindowManagerPolicy mPolicy; + private Notifier mNotifier; + private DisplayPowerController mDisplayPowerController; + private WirelessChargerDetector mWirelessChargerDetector; + private SettingsObserver mSettingsObserver; + private DreamManagerService mDreamManager; + private Light mAttentionLight; + + private final Object mLock = new Object(); + + // A bitfield that indicates what parts of the power state have + // changed and need to be recalculated. + private int mDirty; + + // Indicates whether the device is awake or asleep or somewhere in between. + // This is distinct from the screen power state, which is managed separately. + private int mWakefulness; + + // True if MSG_SANDMAN has been scheduled. + private boolean mSandmanScheduled; + + // Table of all suspend blockers. + // There should only be a few of these. + private final ArrayList<SuspendBlocker> mSuspendBlockers = new ArrayList<SuspendBlocker>(); + + // Table of all wake locks acquired by applications. + private final ArrayList<WakeLock> mWakeLocks = new ArrayList<WakeLock>(); + + // A bitfield that summarizes the state of all active wakelocks. + private int mWakeLockSummary; + + // If true, instructs the display controller to wait for the proximity sensor to + // go negative before turning the screen on. + private boolean mRequestWaitForNegativeProximity; + + // Timestamp of the last time the device was awoken or put to sleep. + private long mLastWakeTime; + private long mLastSleepTime; + + // True if we need to send a wake up or go to sleep finished notification + // when the display is ready. + private boolean mSendWakeUpFinishedNotificationWhenReady; + private boolean mSendGoToSleepFinishedNotificationWhenReady; + + // Timestamp of the last call to user activity. + private long mLastUserActivityTime; + private long mLastUserActivityTimeNoChangeLights; + + // A bitfield that summarizes the effect of the user activity timer. + // A zero value indicates that the user activity timer has expired. + private int mUserActivitySummary; + + // The desired display power state. The actual state may lag behind the + // requested because it is updated asynchronously by the display power controller. + private final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest(); + + // The time the screen was last turned off, in elapsedRealtime() timebase. + private long mLastScreenOffEventElapsedRealTime; + + // True if the display power state has been fully applied, which means the display + // is actually on or actually off or whatever was requested. + private boolean mDisplayReady; + + // The suspend blocker used to keep the CPU alive when an application has acquired + // a wake lock. + private final SuspendBlocker mWakeLockSuspendBlocker; + + // True if the wake lock suspend blocker has been acquired. + private boolean mHoldingWakeLockSuspendBlocker; + + // The suspend blocker used to keep the CPU alive when the display is on, the + // display is getting ready or there is user activity (in which case the display + // must be on). + private final SuspendBlocker mDisplaySuspendBlocker; + + // True if the display suspend blocker has been acquired. + private boolean mHoldingDisplaySuspendBlocker; + + // The screen on blocker used to keep the screen from turning on while the lock + // screen is coming up. + private final ScreenOnBlockerImpl mScreenOnBlocker; + + // The display blanker used to turn the screen on or off. + private final DisplayBlankerImpl mDisplayBlanker; + + // True if systemReady() has been called. + private boolean mSystemReady; + + // True if boot completed occurred. We keep the screen on until this happens. + private boolean mBootCompleted; + + // True if the device is plugged into a power source. + private boolean mIsPowered; + + // The current plug type, such as BatteryManager.BATTERY_PLUGGED_WIRELESS. + private int mPlugType; + + // The current battery level percentage. + private int mBatteryLevel; + + // The battery level percentage at the time the dream started. + // This is used to terminate a dream and go to sleep if the battery is + // draining faster than it is charging and the user activity timeout has expired. + private int mBatteryLevelWhenDreamStarted; + + // The current dock state. + private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + + // True if the device should wake up when plugged or unplugged. + private boolean mWakeUpWhenPluggedOrUnpluggedConfig; + + // True if the device should suspend when the screen is off due to proximity. + private boolean mSuspendWhenScreenOffDueToProximityConfig; + + // True if dreams are supported on this device. + private boolean mDreamsSupportedConfig; + + // Default value for dreams enabled + private boolean mDreamsEnabledByDefaultConfig; + + // Default value for dreams activate-on-sleep + private boolean mDreamsActivatedOnSleepByDefaultConfig; + + // Default value for dreams activate-on-dock + private boolean mDreamsActivatedOnDockByDefaultConfig; + + // True if dreams are enabled by the user. + private boolean mDreamsEnabledSetting; + + // True if dreams should be activated on sleep. + private boolean mDreamsActivateOnSleepSetting; + + // True if dreams should be activated on dock. + private boolean mDreamsActivateOnDockSetting; + + // The screen off timeout setting value in milliseconds. + private int mScreenOffTimeoutSetting; + + // The maximum allowable screen off timeout according to the device + // administration policy. Overrides other settings. + private int mMaximumScreenOffTimeoutFromDeviceAdmin = Integer.MAX_VALUE; + + // The stay on while plugged in setting. + // A bitfield of battery conditions under which to make the screen stay on. + private int mStayOnWhilePluggedInSetting; + + // True if the device should stay on. + private boolean mStayOn; + + // True if the proximity sensor reads a positive result. + private boolean mProximityPositive; + + // Screen brightness setting limits. + private int mScreenBrightnessSettingMinimum; + private int mScreenBrightnessSettingMaximum; + private int mScreenBrightnessSettingDefault; + + // The screen brightness setting, from 0 to 255. + // Use -1 if no value has been set. + private int mScreenBrightnessSetting; + + // The screen auto-brightness adjustment setting, from -1 to 1. + // Use 0 if there is no adjustment. + private float mScreenAutoBrightnessAdjustmentSetting; + + // The screen brightness mode. + // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants. + private int mScreenBrightnessModeSetting; + + // The screen brightness setting override from the window manager + // to allow the current foreground activity to override the brightness. + // Use -1 to disable. + private int mScreenBrightnessOverrideFromWindowManager = -1; + + // The user activity timeout override from the window manager + // to allow the current foreground activity to override the user activity timeout. + // Use -1 to disable. + private long mUserActivityTimeoutOverrideFromWindowManager = -1; + + // The screen brightness setting override from the settings application + // to temporarily adjust the brightness until next updated, + // Use -1 to disable. + private int mTemporaryScreenBrightnessSettingOverride = -1; + + // The screen brightness adjustment setting override from the settings + // application to temporarily adjust the auto-brightness adjustment factor + // until next updated, in the range -1..1. + // Use NaN to disable. + private float mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN; + + // Time when we last logged a warning about calling userActivity() without permission. + private long mLastWarningAboutUserActivityPermission = Long.MIN_VALUE; + + private native void nativeInit(); + + private static native void nativeSetPowerState(boolean screenOn, boolean screenBright); + private static native void nativeAcquireSuspendBlocker(String name); + private static native void nativeReleaseSuspendBlocker(String name); + private static native void nativeSetInteractive(boolean enable); + private static native void nativeSetAutoSuspend(boolean enable); + + public PowerManagerService() { + synchronized (mLock) { + mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks"); + mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display"); + mDisplaySuspendBlocker.acquire(); + mHoldingDisplaySuspendBlocker = true; + + mScreenOnBlocker = new ScreenOnBlockerImpl(); + mDisplayBlanker = new DisplayBlankerImpl(); + mWakefulness = WAKEFULNESS_AWAKE; + } + + nativeInit(); + nativeSetPowerState(true, true); + } + + /** + * Initialize the power manager. + * Must be called before any other functions within the power manager are called. + */ + public void init(Context context, LightsManager ls, + BatteryService bs, IBatteryStats bss, + IAppOpsService appOps, DisplayManagerService dm) { + mContext = context; + mLightsManager = ls; + mBatteryService = bs; + mBatteryStats = bss; + mAppOps = appOps; + mDisplayManagerService = dm; + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); + + Watchdog.getInstance().addMonitor(this); + Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName()); + + // Forcibly turn the screen on at boot so that it is in a known power state. + // We do this in init() rather than in the constructor because setting the + // screen state requires a call into surface flinger which then needs to call back + // into the activity manager to check permissions. Unfortunately the + // activity manager is not running when the constructor is called, so we + // have to defer setting the screen state until this point. + mDisplayBlanker.unblankAllDisplays(); + } + + public void setPolicy(WindowManagerPolicy policy) { + synchronized (mLock) { + mPolicy = policy; + } + } + + public void systemReady(TwilightManager twilight, DreamManagerService dreamManager) { + synchronized (mLock) { + mSystemReady = true; + mDreamManager = dreamManager; + + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); + mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting(); + mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting(); + + SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper()); + + // The notifier runs on the system server's main looper so as not to interfere + // with the animations and other critical functions of the power manager. + mNotifier = new Notifier(Looper.getMainLooper(), mContext, mBatteryStats, + mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"), + mScreenOnBlocker, mPolicy); + + // The display power controller runs on the power manager service's + // own handler thread to ensure timely operation. + mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(), + mContext, mNotifier, mLightsManager, twilight, sensorManager, + mDisplayManagerService, mDisplaySuspendBlocker, mDisplayBlanker, + mDisplayPowerControllerCallbacks, mHandler); + + mWirelessChargerDetector = new WirelessChargerDetector(sensorManager, + createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"), + mHandler); + mSettingsObserver = new SettingsObserver(mHandler); + mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); + + // Register for broadcasts from other components of the system. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(new BatteryReceiver(), filter, null, mHandler); + + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + mContext.registerReceiver(new BootCompletedReceiver(), filter, null, mHandler); + + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DREAMING_STARTED); + filter.addAction(Intent.ACTION_DREAMING_STOPPED); + mContext.registerReceiver(new DreamReceiver(), filter, null, mHandler); + + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_SWITCHED); + mContext.registerReceiver(new UserSwitchedReceiver(), filter, null, mHandler); + + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DOCK_EVENT); + mContext.registerReceiver(new DockReceiver(), filter, null, mHandler); + + // Register for settings changes. + final ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ENABLED), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_OFF_TIMEOUT), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.STAY_ON_WHILE_PLUGGED_IN), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS_MODE), + false, mSettingsObserver, UserHandle.USER_ALL); + + // Go. + readConfigurationLocked(); + updateSettingsLocked(); + mDirty |= DIRTY_BATTERY_STATE; + updatePowerStateLocked(); + } + } + + private void readConfigurationLocked() { + final Resources resources = mContext.getResources(); + + mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean( + com.android.internal.R.bool.config_unplugTurnsOnScreen); + mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean( + com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity); + mDreamsSupportedConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsSupported); + mDreamsEnabledByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsEnabledByDefault); + mDreamsActivatedOnSleepByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); + mDreamsActivatedOnDockByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + } + + private void updateSettingsLocked() { + final ContentResolver resolver = mContext.getContentResolver(); + + mDreamsEnabledSetting = (Settings.Secure.getIntForUser(resolver, + Settings.Secure.SCREENSAVER_ENABLED, + mDreamsEnabledByDefaultConfig ? 1 : 0, + UserHandle.USER_CURRENT) != 0); + mDreamsActivateOnSleepSetting = (Settings.Secure.getIntForUser(resolver, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, + mDreamsActivatedOnSleepByDefaultConfig ? 1 : 0, + UserHandle.USER_CURRENT) != 0); + mDreamsActivateOnDockSetting = (Settings.Secure.getIntForUser(resolver, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + mDreamsActivatedOnDockByDefaultConfig ? 1 : 0, + UserHandle.USER_CURRENT) != 0); + mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver, + Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT, + UserHandle.USER_CURRENT); + mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver, + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC); + + final int oldScreenBrightnessSetting = mScreenBrightnessSetting; + mScreenBrightnessSetting = Settings.System.getIntForUser(resolver, + Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessSettingDefault, + UserHandle.USER_CURRENT); + if (oldScreenBrightnessSetting != mScreenBrightnessSetting) { + mTemporaryScreenBrightnessSettingOverride = -1; + } + + final float oldScreenAutoBrightnessAdjustmentSetting = + mScreenAutoBrightnessAdjustmentSetting; + mScreenAutoBrightnessAdjustmentSetting = Settings.System.getFloatForUser(resolver, + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, + UserHandle.USER_CURRENT); + if (oldScreenAutoBrightnessAdjustmentSetting != mScreenAutoBrightnessAdjustmentSetting) { + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN; + } + + mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver, + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT); + + mDirty |= DIRTY_SETTINGS; + } + + private void handleSettingsChangedLocked() { + updateSettingsLocked(); + updatePowerStateLocked(); + } + + @Override // Binder call + public void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, + int uid) { + acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid)); + } + + @Override // Binder call + public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, + WorkSource ws) { + if (lock == null) { + throw new IllegalArgumentException("lock must not be null"); + } + if (packageName == null) { + throw new IllegalArgumentException("packageName must not be null"); + } + PowerManager.validateWakeLockParameters(flags, tag); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); + if (ws != null && ws.size() != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, null); + } else { + ws = null; + } + + final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); + final long ident = Binder.clearCallingIdentity(); + try { + acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName, + WorkSource ws, int uid, int pid) { + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "acquireWakeLockInternal: lock=" + Objects.hashCode(lock) + + ", flags=0x" + Integer.toHexString(flags) + + ", tag=\"" + tag + "\", ws=" + ws + ", uid=" + uid + ", pid=" + pid); + } + + WakeLock wakeLock; + int index = findWakeLockIndexLocked(lock); + if (index >= 0) { + wakeLock = mWakeLocks.get(index); + if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) { + // Update existing wake lock. This shouldn't happen but is harmless. + notifyWakeLockReleasedLocked(wakeLock); + wakeLock.updateProperties(flags, tag, packageName, ws, uid, pid); + notifyWakeLockAcquiredLocked(wakeLock); + } + } else { + wakeLock = new WakeLock(lock, flags, tag, packageName, ws, uid, pid); + try { + lock.linkToDeath(wakeLock, 0); + } catch (RemoteException ex) { + throw new IllegalArgumentException("Wake lock is already dead."); + } + notifyWakeLockAcquiredLocked(wakeLock); + mWakeLocks.add(wakeLock); + } + + applyWakeLockFlagsOnAcquireLocked(wakeLock); + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + @SuppressWarnings("deprecation") + private static boolean isScreenLock(final WakeLock wakeLock) { + switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK: + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + case PowerManager.SCREEN_DIM_WAKE_LOCK: + return true; + } + return false; + } + + private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock) { + if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0 + && isScreenLock(wakeLock)) { + wakeUpNoUpdateLocked(SystemClock.uptimeMillis()); + } + } + + @Override // Binder call + public void releaseWakeLock(IBinder lock, int flags) { + if (lock == null) { + throw new IllegalArgumentException("lock must not be null"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); + + final long ident = Binder.clearCallingIdentity(); + try { + releaseWakeLockInternal(lock, flags); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void releaseWakeLockInternal(IBinder lock, int flags) { + synchronized (mLock) { + int index = findWakeLockIndexLocked(lock); + if (index < 0) { + if (DEBUG_SPEW) { + Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock) + + " [not found], flags=0x" + Integer.toHexString(flags)); + } + return; + } + + WakeLock wakeLock = mWakeLocks.get(index); + if (DEBUG_SPEW) { + Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock) + + " [" + wakeLock.mTag + "], flags=0x" + Integer.toHexString(flags)); + } + + mWakeLocks.remove(index); + notifyWakeLockReleasedLocked(wakeLock); + wakeLock.mLock.unlinkToDeath(wakeLock, 0); + + if ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0) { + mRequestWaitForNegativeProximity = true; + } + + applyWakeLockFlagsOnReleaseLocked(wakeLock); + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + private void handleWakeLockDeath(WakeLock wakeLock) { + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "handleWakeLockDeath: lock=" + Objects.hashCode(wakeLock.mLock) + + " [" + wakeLock.mTag + "]"); + } + + int index = mWakeLocks.indexOf(wakeLock); + if (index < 0) { + return; + } + + mWakeLocks.remove(index); + notifyWakeLockReleasedLocked(wakeLock); + + applyWakeLockFlagsOnReleaseLocked(wakeLock); + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) { + if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0 + && isScreenLock(wakeLock)) { + userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, + PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS, + wakeLock.mOwnerUid); + } + } + + @Override // Binder call + public void updateWakeLockUids(IBinder lock, int[] uids) { + WorkSource ws = null; + + if (uids != null) { + ws = new WorkSource(); + // XXX should WorkSource have a way to set uids as an int[] instead of adding them + // one at a time? + for (int i = 0; i < uids.length; i++) { + ws.add(uids[i]); + } + } + updateWakeLockWorkSource(lock, ws); + } + + @Override // Binder call + public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) { + if (lock == null) { + throw new IllegalArgumentException("lock must not be null"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); + if (ws != null && ws.size() != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, null); + } else { + ws = null; + } + + final long ident = Binder.clearCallingIdentity(); + try { + updateWakeLockWorkSourceInternal(lock, ws); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void updateWakeLockWorkSourceInternal(IBinder lock, WorkSource ws) { + synchronized (mLock) { + int index = findWakeLockIndexLocked(lock); + if (index < 0) { + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock) + + " [not found], ws=" + ws); + } + throw new IllegalArgumentException("Wake lock not active"); + } + + WakeLock wakeLock = mWakeLocks.get(index); + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock) + + " [" + wakeLock.mTag + "], ws=" + ws); + } + + if (!wakeLock.hasSameWorkSource(ws)) { + notifyWakeLockReleasedLocked(wakeLock); + wakeLock.updateWorkSource(ws); + notifyWakeLockAcquiredLocked(wakeLock); + } + } + } + + private int findWakeLockIndexLocked(IBinder lock) { + final int count = mWakeLocks.size(); + for (int i = 0; i < count; i++) { + if (mWakeLocks.get(i).mLock == lock) { + return i; + } + } + return -1; + } + + private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) { + if (mSystemReady) { + wakeLock.mNotifiedAcquired = true; + mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, + wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource); + } + } + + private void notifyWakeLockReleasedLocked(WakeLock wakeLock) { + if (mSystemReady && wakeLock.mNotifiedAcquired) { + wakeLock.mNotifiedAcquired = false; + mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, + wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource); + } + } + + @Override // Binder call + public boolean isWakeLockLevelSupported(int level) { + final long ident = Binder.clearCallingIdentity(); + try { + return isWakeLockLevelSupportedInternal(level); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @SuppressWarnings("deprecation") + private boolean isWakeLockLevelSupportedInternal(int level) { + synchronized (mLock) { + switch (level) { + case PowerManager.PARTIAL_WAKE_LOCK: + case PowerManager.SCREEN_DIM_WAKE_LOCK: + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + case PowerManager.FULL_WAKE_LOCK: + return true; + + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + return mSystemReady && mDisplayPowerController.isProximitySensorAvailable(); + + default: + return false; + } + } + } + + @Override // Binder call + public void userActivity(long eventTime, int event, int flags) { + final long now = SystemClock.uptimeMillis(); + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + // Once upon a time applications could call userActivity(). + // Now we require the DEVICE_POWER permission. Log a warning and ignore the + // request instead of throwing a SecurityException so we don't break old apps. + synchronized (mLock) { + if (now >= mLastWarningAboutUserActivityPermission + (5 * 60 * 1000)) { + mLastWarningAboutUserActivityPermission = now; + Slog.w(TAG, "Ignoring call to PowerManager.userActivity() because the " + + "caller does not have DEVICE_POWER permission. " + + "Please fix your app! " + + " pid=" + Binder.getCallingPid() + + " uid=" + Binder.getCallingUid()); + } + } + return; + } + + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + userActivityInternal(eventTime, event, flags, uid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Called from native code. + private void userActivityFromNative(long eventTime, int event, int flags) { + userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID); + } + + private void userActivityInternal(long eventTime, int event, int flags, int uid) { + synchronized (mLock) { + if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) { + updatePowerStateLocked(); + } + } + } + + private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { + if (DEBUG_SPEW) { + Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime + + ", event=" + event + ", flags=0x" + Integer.toHexString(flags) + + ", uid=" + uid); + } + + if (eventTime < mLastSleepTime || eventTime < mLastWakeTime + || mWakefulness == WAKEFULNESS_ASLEEP || !mBootCompleted || !mSystemReady) { + return false; + } + + mNotifier.onUserActivity(event, uid); + + if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { + if (eventTime > mLastUserActivityTimeNoChangeLights + && eventTime > mLastUserActivityTime) { + mLastUserActivityTimeNoChangeLights = eventTime; + mDirty |= DIRTY_USER_ACTIVITY; + return true; + } + } else { + if (eventTime > mLastUserActivityTime) { + mLastUserActivityTime = eventTime; + mDirty |= DIRTY_USER_ACTIVITY; + return true; + } + } + return false; + } + + @Override // Binder call + public void wakeUp(long eventTime) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + wakeUpInternal(eventTime); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Called from native code. + private void wakeUpFromNative(long eventTime) { + wakeUpInternal(eventTime); + } + + private void wakeUpInternal(long eventTime) { + synchronized (mLock) { + if (wakeUpNoUpdateLocked(eventTime)) { + updatePowerStateLocked(); + } + } + } + + private boolean wakeUpNoUpdateLocked(long eventTime) { + if (DEBUG_SPEW) { + Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime); + } + + if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE + || !mBootCompleted || !mSystemReady) { + return false; + } + + switch (mWakefulness) { + case WAKEFULNESS_ASLEEP: + Slog.i(TAG, "Waking up from sleep..."); + sendPendingNotificationsLocked(); + mNotifier.onWakeUpStarted(); + mSendWakeUpFinishedNotificationWhenReady = true; + break; + case WAKEFULNESS_DREAMING: + Slog.i(TAG, "Waking up from dream..."); + break; + case WAKEFULNESS_NAPPING: + Slog.i(TAG, "Waking up from nap..."); + break; + } + + mLastWakeTime = eventTime; + mWakefulness = WAKEFULNESS_AWAKE; + mDirty |= DIRTY_WAKEFULNESS; + + userActivityNoUpdateLocked( + eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + return true; + } + + @Override // Binder call + public void goToSleep(long eventTime, int reason) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + goToSleepInternal(eventTime, reason); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Called from native code. + private void goToSleepFromNative(long eventTime, int reason) { + goToSleepInternal(eventTime, reason); + } + + private void goToSleepInternal(long eventTime, int reason) { + synchronized (mLock) { + if (goToSleepNoUpdateLocked(eventTime, reason)) { + updatePowerStateLocked(); + } + } + } + + @SuppressWarnings("deprecation") + private boolean goToSleepNoUpdateLocked(long eventTime, int reason) { + if (DEBUG_SPEW) { + Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime + ", reason=" + reason); + } + + if (eventTime < mLastWakeTime || mWakefulness == WAKEFULNESS_ASLEEP + || !mBootCompleted || !mSystemReady) { + return false; + } + + switch (reason) { + case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: + Slog.i(TAG, "Going to sleep due to device administration policy..."); + break; + case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: + Slog.i(TAG, "Going to sleep due to screen timeout..."); + break; + default: + Slog.i(TAG, "Going to sleep by user request..."); + reason = PowerManager.GO_TO_SLEEP_REASON_USER; + break; + } + + sendPendingNotificationsLocked(); + mNotifier.onGoToSleepStarted(reason); + mSendGoToSleepFinishedNotificationWhenReady = true; + + mLastSleepTime = eventTime; + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_ASLEEP; + + // Report the number of wake locks that will be cleared by going to sleep. + int numWakeLocksCleared = 0; + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK: + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + case PowerManager.SCREEN_DIM_WAKE_LOCK: + numWakeLocksCleared += 1; + break; + } + } + EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numWakeLocksCleared); + return true; + } + + @Override // Binder call + public void nap(long eventTime) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + napInternal(eventTime); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void napInternal(long eventTime) { + synchronized (mLock) { + if (napNoUpdateLocked(eventTime)) { + updatePowerStateLocked(); + } + } + } + + private boolean napNoUpdateLocked(long eventTime) { + if (DEBUG_SPEW) { + Slog.d(TAG, "napNoUpdateLocked: eventTime=" + eventTime); + } + + if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE + || !mBootCompleted || !mSystemReady) { + return false; + } + + Slog.i(TAG, "Nap time..."); + + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_NAPPING; + return true; + } + + /** + * Updates the global power state based on dirty bits recorded in mDirty. + * + * This is the main function that performs power state transitions. + * We centralize them here so that we can recompute the power state completely + * each time something important changes, and ensure that we do it the same + * way each time. The point is to gather all of the transition logic here. + */ + private void updatePowerStateLocked() { + if (!mSystemReady || mDirty == 0) { + return; + } + + // Phase 0: Basic state updates. + updateIsPoweredLocked(mDirty); + updateStayOnLocked(mDirty); + + // Phase 1: Update wakefulness. + // Loop because the wake lock and user activity computations are influenced + // by changes in wakefulness. + final long now = SystemClock.uptimeMillis(); + int dirtyPhase2 = 0; + for (;;) { + int dirtyPhase1 = mDirty; + dirtyPhase2 |= dirtyPhase1; + mDirty = 0; + + updateWakeLockSummaryLocked(dirtyPhase1); + updateUserActivitySummaryLocked(now, dirtyPhase1); + if (!updateWakefulnessLocked(dirtyPhase1)) { + break; + } + } + + // Phase 2: Update dreams and display power state. + updateDreamLocked(dirtyPhase2); + updateDisplayPowerStateLocked(dirtyPhase2); + + // Phase 3: Send notifications, if needed. + if (mDisplayReady) { + sendPendingNotificationsLocked(); + } + + // Phase 4: Update suspend blocker. + // Because we might release the last suspend blocker here, we need to make sure + // we finished everything else first! + updateSuspendBlockerLocked(); + } + + private void sendPendingNotificationsLocked() { + if (mSendWakeUpFinishedNotificationWhenReady) { + mSendWakeUpFinishedNotificationWhenReady = false; + mNotifier.onWakeUpFinished(); + } + if (mSendGoToSleepFinishedNotificationWhenReady) { + mSendGoToSleepFinishedNotificationWhenReady = false; + mNotifier.onGoToSleepFinished(); + } + } + + /** + * Updates the value of mIsPowered. + * Sets DIRTY_IS_POWERED if a change occurred. + */ + private void updateIsPoweredLocked(int dirty) { + if ((dirty & DIRTY_BATTERY_STATE) != 0) { + final boolean wasPowered = mIsPowered; + final int oldPlugType = mPlugType; + mIsPowered = mBatteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + mPlugType = mBatteryService.getPlugType(); + mBatteryLevel = mBatteryService.getBatteryLevel(); + + if (DEBUG) { + Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered + + ", mIsPowered=" + mIsPowered + + ", oldPlugType=" + oldPlugType + + ", mPlugType=" + mPlugType + + ", mBatteryLevel=" + mBatteryLevel); + } + + if (wasPowered != mIsPowered || oldPlugType != mPlugType) { + mDirty |= DIRTY_IS_POWERED; + + // Update wireless dock detection state. + final boolean dockedOnWirelessCharger = mWirelessChargerDetector.update( + mIsPowered, mPlugType, mBatteryLevel); + + // Treat plugging and unplugging the devices as a user activity. + // Users find it disconcerting when they plug or unplug the device + // and it shuts off right away. + // Some devices also wake the device when plugged or unplugged because + // they don't have a charging LED. + final long now = SystemClock.uptimeMillis(); + if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType, + dockedOnWirelessCharger)) { + wakeUpNoUpdateLocked(now); + } + userActivityNoUpdateLocked( + now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + + // Tell the notifier whether wireless charging has started so that + // it can provide feedback to the user. + if (dockedOnWirelessCharger) { + mNotifier.onWirelessChargingStarted(); + } + } + } + } + + private boolean shouldWakeUpWhenPluggedOrUnpluggedLocked( + boolean wasPowered, int oldPlugType, boolean dockedOnWirelessCharger) { + // Don't wake when powered unless configured to do so. + if (!mWakeUpWhenPluggedOrUnpluggedConfig) { + return false; + } + + // Don't wake when undocked from wireless charger. + // See WirelessChargerDetector for justification. + if (wasPowered && !mIsPowered + && oldPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) { + return false; + } + + // Don't wake when docked on wireless charger unless we are certain of it. + // See WirelessChargerDetector for justification. + if (!wasPowered && mIsPowered + && mPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS + && !dockedOnWirelessCharger) { + return false; + } + + // If already dreaming and becoming powered, then don't wake. + if (mIsPowered && (mWakefulness == WAKEFULNESS_NAPPING + || mWakefulness == WAKEFULNESS_DREAMING)) { + return false; + } + + // Otherwise wake up! + return true; + } + + /** + * Updates the value of mStayOn. + * Sets DIRTY_STAY_ON if a change occurred. + */ + private void updateStayOnLocked(int dirty) { + if ((dirty & (DIRTY_BATTERY_STATE | DIRTY_SETTINGS)) != 0) { + final boolean wasStayOn = mStayOn; + if (mStayOnWhilePluggedInSetting != 0 + && !isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { + mStayOn = mBatteryService.isPowered(mStayOnWhilePluggedInSetting); + } else { + mStayOn = false; + } + + if (mStayOn != wasStayOn) { + mDirty |= DIRTY_STAY_ON; + } + } + } + + /** + * Updates the value of mWakeLockSummary to summarize the state of all active wake locks. + * Note that most wake-locks are ignored when the system is asleep. + * + * This function must have no other side-effects. + */ + @SuppressWarnings("deprecation") + private void updateWakeLockSummaryLocked(int dirty) { + if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) { + mWakeLockSummary = 0; + + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.PARTIAL_WAKE_LOCK: + mWakeLockSummary |= WAKE_LOCK_CPU; + break; + case PowerManager.FULL_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_CPU + | WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT; + if (mWakefulness == WAKEFULNESS_AWAKE) { + mWakeLockSummary |= WAKE_LOCK_STAY_AWAKE; + } + } + break; + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_SCREEN_BRIGHT; + if (mWakefulness == WAKEFULNESS_AWAKE) { + mWakeLockSummary |= WAKE_LOCK_STAY_AWAKE; + } + } + break; + case PowerManager.SCREEN_DIM_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_SCREEN_DIM; + if (mWakefulness == WAKEFULNESS_AWAKE) { + mWakeLockSummary |= WAKE_LOCK_STAY_AWAKE; + } + } + break; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_PROXIMITY_SCREEN_OFF; + } + break; + } + } + + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness=" + + wakefulnessToString(mWakefulness) + + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)); + } + } + } + + /** + * Updates the value of mUserActivitySummary to summarize the user requested + * state of the system such as whether the screen should be bright or dim. + * Note that user activity is ignored when the system is asleep. + * + * This function must have no other side-effects. + */ + private void updateUserActivitySummaryLocked(long now, int dirty) { + // Update the status of the user activity timeout timer. + if ((dirty & (DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) { + mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); + + long nextTimeout = 0; + if (mWakefulness != WAKEFULNESS_ASLEEP) { + final int screenOffTimeout = getScreenOffTimeoutLocked(); + final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); + + mUserActivitySummary = 0; + if (mLastUserActivityTime >= mLastWakeTime) { + nextTimeout = mLastUserActivityTime + + screenOffTimeout - screenDimDuration; + if (now < nextTimeout) { + mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT; + } else { + nextTimeout = mLastUserActivityTime + screenOffTimeout; + if (now < nextTimeout) { + mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM; + } + } + } + if (mUserActivitySummary == 0 + && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) { + nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout; + if (now < nextTimeout + && mDisplayPowerRequest.screenState + != DisplayPowerRequest.SCREEN_STATE_OFF) { + mUserActivitySummary = mDisplayPowerRequest.screenState + == DisplayPowerRequest.SCREEN_STATE_BRIGHT ? + USER_ACTIVITY_SCREEN_BRIGHT : USER_ACTIVITY_SCREEN_DIM; + } + } + if (mUserActivitySummary != 0) { + Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, nextTimeout); + } + } else { + mUserActivitySummary = 0; + } + + if (DEBUG_SPEW) { + Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness=" + + wakefulnessToString(mWakefulness) + + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout)); + } + } + } + + /** + * Called when a user activity timeout has occurred. + * Simply indicates that something about user activity has changed so that the new + * state can be recomputed when the power state is updated. + * + * This function must have no other side-effects besides setting the dirty + * bit and calling update power state. Wakefulness transitions are handled elsewhere. + */ + private void handleUserActivityTimeout() { // runs on handler thread + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "handleUserActivityTimeout"); + } + + mDirty |= DIRTY_USER_ACTIVITY; + updatePowerStateLocked(); + } + } + + private int getScreenOffTimeoutLocked() { + int timeout = mScreenOffTimeoutSetting; + if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { + timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin); + } + if (mUserActivityTimeoutOverrideFromWindowManager >= 0) { + timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager); + } + return Math.max(timeout, MINIMUM_SCREEN_OFF_TIMEOUT); + } + + private int getScreenDimDurationLocked(int screenOffTimeout) { + return Math.min(SCREEN_DIM_DURATION, + (int)(screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO)); + } + + /** + * Updates the wakefulness of the device. + * + * This is the function that decides whether the device should start napping + * based on the current wake locks and user activity state. It may modify mDirty + * if the wakefulness changes. + * + * Returns true if the wakefulness changed and we need to restart power state calculation. + */ + private boolean updateWakefulnessLocked(int dirty) { + boolean changed = false; + if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED + | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE + | DIRTY_DOCK_STATE)) != 0) { + if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) { + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakefulnessLocked: Bed time..."); + } + final long time = SystemClock.uptimeMillis(); + if (shouldNapAtBedTimeLocked()) { + changed = napNoUpdateLocked(time); + } else { + changed = goToSleepNoUpdateLocked(time, + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + } + } + } + return changed; + } + + /** + * Returns true if the device should automatically nap and start dreaming when the user + * activity timeout has expired and it's bedtime. + */ + private boolean shouldNapAtBedTimeLocked() { + return mDreamsActivateOnSleepSetting + || (mDreamsActivateOnDockSetting + && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED); + } + + /** + * Returns true if the device should go to sleep now. + * Also used when exiting a dream to determine whether we should go back + * to being fully awake or else go to sleep for good. + */ + private boolean isItBedTimeYetLocked() { + return mBootCompleted && !isBeingKeptAwakeLocked(); + } + + /** + * Returns true if the device is being kept awake by a wake lock, user activity + * or the stay on while powered setting. We also keep the phone awake when + * the proximity sensor returns a positive result so that the device does not + * lock while in a phone call. This function only controls whether the device + * will go to sleep or dream which is independent of whether it will be allowed + * to suspend. + */ + private boolean isBeingKeptAwakeLocked() { + return mStayOn + || mProximityPositive + || (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0 + || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT + | USER_ACTIVITY_SCREEN_DIM)) != 0; + } + + /** + * Determines whether to post a message to the sandman to update the dream state. + */ + private void updateDreamLocked(int dirty) { + if ((dirty & (DIRTY_WAKEFULNESS + | DIRTY_USER_ACTIVITY + | DIRTY_WAKE_LOCKS + | DIRTY_BOOT_COMPLETED + | DIRTY_SETTINGS + | DIRTY_IS_POWERED + | DIRTY_STAY_ON + | DIRTY_PROXIMITY_POSITIVE + | DIRTY_BATTERY_STATE)) != 0) { + scheduleSandmanLocked(); + } + } + + private void scheduleSandmanLocked() { + if (!mSandmanScheduled) { + mSandmanScheduled = true; + Message msg = mHandler.obtainMessage(MSG_SANDMAN); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + } + + /** + * Called when the device enters or exits a napping or dreaming state. + * + * We do this asynchronously because we must call out of the power manager to start + * the dream and we don't want to hold our lock while doing so. There is a risk that + * the device will wake or go to sleep in the meantime so we have to handle that case. + */ + private void handleSandman() { // runs on handler thread + // Handle preconditions. + boolean startDreaming = false; + synchronized (mLock) { + mSandmanScheduled = false; + boolean canDream = canDreamLocked(); + if (DEBUG_SPEW) { + Slog.d(TAG, "handleSandman: canDream=" + canDream + + ", mWakefulness=" + wakefulnessToString(mWakefulness)); + } + + if (canDream && mWakefulness == WAKEFULNESS_NAPPING) { + startDreaming = true; + } + } + + // Start dreaming if needed. + // We only control the dream on the handler thread, so we don't need to worry about + // concurrent attempts to start or stop the dream. + boolean isDreaming = false; + if (mDreamManager != null) { + if (startDreaming) { + mDreamManager.startDream(); + } + isDreaming = mDreamManager.isDreaming(); + } + + // Update dream state. + // We might need to stop the dream again if the preconditions changed. + boolean continueDreaming = false; + synchronized (mLock) { + if (isDreaming && canDreamLocked()) { + if (mWakefulness == WAKEFULNESS_NAPPING) { + mWakefulness = WAKEFULNESS_DREAMING; + mDirty |= DIRTY_WAKEFULNESS; + mBatteryLevelWhenDreamStarted = mBatteryLevel; + updatePowerStateLocked(); + continueDreaming = true; + } else if (mWakefulness == WAKEFULNESS_DREAMING) { + if (!isBeingKeptAwakeLocked() + && mBatteryLevel < mBatteryLevelWhenDreamStarted + - DREAM_BATTERY_LEVEL_DRAIN_CUTOFF) { + // If the user activity timeout expired and the battery appears + // to be draining faster than it is charging then stop dreaming + // and go to sleep. + Slog.i(TAG, "Stopping dream because the battery appears to " + + "be draining faster than it is charging. " + + "Battery level when dream started: " + + mBatteryLevelWhenDreamStarted + "%. " + + "Battery level now: " + mBatteryLevel + "%."); + } else { + continueDreaming = true; + } + } + } + if (!continueDreaming) { + handleDreamFinishedLocked(); + } + } + + // Stop dreaming if needed. + // It's possible that something else changed to make us need to start the dream again. + // If so, then the power manager will have posted another message to the handler + // to take care of it later. + if (mDreamManager != null) { + if (!continueDreaming) { + mDreamManager.stopDream(); + } + } + } + + /** + * Returns true if the device is allowed to dream in its current state + * assuming that it is currently napping or dreaming. + */ + private boolean canDreamLocked() { + return mDreamsSupportedConfig + && mDreamsEnabledSetting + && mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF + && mBootCompleted + && (mIsPowered || isBeingKeptAwakeLocked()); + } + + /** + * Called when a dream is ending to figure out what to do next. + */ + private void handleDreamFinishedLocked() { + if (mWakefulness == WAKEFULNESS_NAPPING + || mWakefulness == WAKEFULNESS_DREAMING) { + if (isItBedTimeYetLocked()) { + goToSleepNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + updatePowerStateLocked(); + } else { + wakeUpNoUpdateLocked(SystemClock.uptimeMillis()); + updatePowerStateLocked(); + } + } + } + + private void handleScreenOnBlockerReleased() { + synchronized (mLock) { + mDirty |= DIRTY_SCREEN_ON_BLOCKER_RELEASED; + updatePowerStateLocked(); + } + } + + /** + * Updates the display power state asynchronously. + * When the update is finished, mDisplayReady will be set to true. The display + * controller posts a message to tell us when the actual display power state + * has been updated so we come back here to double-check and finish up. + * + * This function recalculates the display power state each time. + */ + private void updateDisplayPowerStateLocked(int dirty) { + if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS + | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED + | DIRTY_SETTINGS | DIRTY_SCREEN_ON_BLOCKER_RELEASED)) != 0) { + int newScreenState = getDesiredScreenPowerStateLocked(); + if (newScreenState != mDisplayPowerRequest.screenState) { + if (newScreenState == DisplayPowerRequest.SCREEN_STATE_OFF + && mDisplayPowerRequest.screenState + != DisplayPowerRequest.SCREEN_STATE_OFF) { + mLastScreenOffEventElapsedRealTime = SystemClock.elapsedRealtime(); + } + + mDisplayPowerRequest.screenState = newScreenState; + nativeSetPowerState( + newScreenState != DisplayPowerRequest.SCREEN_STATE_OFF, + newScreenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT); + } + + int screenBrightness = mScreenBrightnessSettingDefault; + float screenAutoBrightnessAdjustment = 0.0f; + boolean autoBrightness = (mScreenBrightnessModeSetting == + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { + screenBrightness = mScreenBrightnessOverrideFromWindowManager; + autoBrightness = false; + } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) { + screenBrightness = mTemporaryScreenBrightnessSettingOverride; + } else if (isValidBrightness(mScreenBrightnessSetting)) { + screenBrightness = mScreenBrightnessSetting; + } + if (autoBrightness) { + screenBrightness = mScreenBrightnessSettingDefault; + if (isValidAutoBrightnessAdjustment( + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) { + screenAutoBrightnessAdjustment = + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride; + } else if (isValidAutoBrightnessAdjustment( + mScreenAutoBrightnessAdjustmentSetting)) { + screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting; + } + } + screenBrightness = Math.max(Math.min(screenBrightness, + mScreenBrightnessSettingMaximum), mScreenBrightnessSettingMinimum); + screenAutoBrightnessAdjustment = Math.max(Math.min( + screenAutoBrightnessAdjustment, 1.0f), -1.0f); + mDisplayPowerRequest.screenBrightness = screenBrightness; + mDisplayPowerRequest.screenAutoBrightnessAdjustment = + screenAutoBrightnessAdjustment; + mDisplayPowerRequest.useAutoBrightness = autoBrightness; + + mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); + + mDisplayPowerRequest.blockScreenOn = mScreenOnBlocker.isHeld(); + + mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest, + mRequestWaitForNegativeProximity); + mRequestWaitForNegativeProximity = false; + + if (DEBUG_SPEW) { + Slog.d(TAG, "updateScreenStateLocked: mDisplayReady=" + mDisplayReady + + ", newScreenState=" + newScreenState + + ", mWakefulness=" + mWakefulness + + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + + ", mBootCompleted=" + mBootCompleted); + } + } + } + + private static boolean isValidBrightness(int value) { + return value >= 0 && value <= 255; + } + + private static boolean isValidAutoBrightnessAdjustment(float value) { + // Handles NaN by always returning false. + return value >= -1.0f && value <= 1.0f; + } + + private int getDesiredScreenPowerStateLocked() { + if (mWakefulness == WAKEFULNESS_ASLEEP) { + return DisplayPowerRequest.SCREEN_STATE_OFF; + } + + if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0 + || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 + || !mBootCompleted) { + return DisplayPowerRequest.SCREEN_STATE_BRIGHT; + } + + return DisplayPowerRequest.SCREEN_STATE_DIM; + } + + private final DisplayPowerController.Callbacks mDisplayPowerControllerCallbacks = + new DisplayPowerController.Callbacks() { + @Override + public void onStateChanged() { + synchronized (mLock) { + mDirty |= DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED; + updatePowerStateLocked(); + } + } + + @Override + public void onProximityPositive() { + synchronized (mLock) { + mProximityPositive = true; + mDirty |= DIRTY_PROXIMITY_POSITIVE; + updatePowerStateLocked(); + } + } + + @Override + public void onProximityNegative() { + synchronized (mLock) { + mProximityPositive = false; + mDirty |= DIRTY_PROXIMITY_POSITIVE; + userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + updatePowerStateLocked(); + } + } + }; + + private boolean shouldUseProximitySensorLocked() { + return (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0; + } + + /** + * Updates the suspend blocker that keeps the CPU alive. + * + * This function must have no other side-effects. + */ + private void updateSuspendBlockerLocked() { + final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0); + final boolean needDisplaySuspendBlocker = needDisplaySuspendBlocker(); + + // First acquire suspend blockers if needed. + if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) { + mWakeLockSuspendBlocker.acquire(); + mHoldingWakeLockSuspendBlocker = true; + } + if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) { + mDisplaySuspendBlocker.acquire(); + mHoldingDisplaySuspendBlocker = true; + } + + // Then release suspend blockers if needed. + if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) { + mWakeLockSuspendBlocker.release(); + mHoldingWakeLockSuspendBlocker = false; + } + if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) { + mDisplaySuspendBlocker.release(); + mHoldingDisplaySuspendBlocker = false; + } + } + + /** + * Return true if we must keep a suspend blocker active on behalf of the display. + * We do so if the screen is on or is in transition between states. + */ + private boolean needDisplaySuspendBlocker() { + if (!mDisplayReady) { + return true; + } + if (mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + // If we asked for the screen to be on but it is off due to the proximity + // sensor then we may suspend but only if the configuration allows it. + // On some hardware it may not be safe to suspend because the proximity + // sensor may not be correctly configured as a wake-up source. + if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive + || !mSuspendWhenScreenOffDueToProximityConfig) { + return true; + } + } + return false; + } + + @Override // Binder call + public boolean isScreenOn() { + final long ident = Binder.clearCallingIdentity(); + try { + return isScreenOnInternal(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private boolean isScreenOnInternal() { + synchronized (mLock) { + return !mSystemReady + || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF; + } + } + + private void handleBatteryStateChangedLocked() { + mDirty |= DIRTY_BATTERY_STATE; + updatePowerStateLocked(); + } + + private void startWatchingForBootAnimationFinished() { + mHandler.sendEmptyMessage(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED); + } + + private void checkIfBootAnimationFinished() { + if (DEBUG) { + Slog.d(TAG, "Check if boot animation finished..."); + } + + if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) { + mHandler.sendEmptyMessageDelayed(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED, + BOOT_ANIMATION_POLL_INTERVAL); + return; + } + + synchronized (mLock) { + if (!mBootCompleted) { + Slog.i(TAG, "Boot animation finished."); + handleBootCompletedLocked(); + } + } + } + + private void handleBootCompletedLocked() { + final long now = SystemClock.uptimeMillis(); + mBootCompleted = true; + mDirty |= DIRTY_BOOT_COMPLETED; + userActivityNoUpdateLocked( + now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + updatePowerStateLocked(); + } + + /** + * Reboots the device. + * + * @param confirm If true, shows a reboot confirmation dialog. + * @param reason The reason for the reboot, or null if none. + * @param wait If true, this call waits for the reboot to complete and does not return. + */ + @Override // Binder call + public void reboot(boolean confirm, String reason, boolean wait) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); + + final long ident = Binder.clearCallingIdentity(); + try { + shutdownOrRebootInternal(false, confirm, reason, wait); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * Shuts down the device. + * + * @param confirm If true, shows a shutdown confirmation dialog. + * @param wait If true, this call waits for the shutdown to complete and does not return. + */ + @Override // Binder call + public void shutdown(boolean confirm, boolean wait) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); + + final long ident = Binder.clearCallingIdentity(); + try { + shutdownOrRebootInternal(true, confirm, null, wait); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm, + final String reason, boolean wait) { + if (mHandler == null || !mSystemReady) { + throw new IllegalStateException("Too early to call shutdown() or reboot()"); + } + + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (this) { + if (shutdown) { + ShutdownThread.shutdown(mContext, confirm); + } else { + ShutdownThread.reboot(mContext, reason, confirm); + } + } + } + }; + + // ShutdownThread must run on a looper capable of displaying the UI. + Message msg = Message.obtain(mHandler, runnable); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + + // PowerManager.reboot() is documented not to return so just wait for the inevitable. + if (wait) { + synchronized (runnable) { + while (true) { + try { + runnable.wait(); + } catch (InterruptedException e) { + } + } + } + } + } + + /** + * Crash the runtime (causing a complete restart of the Android framework). + * Requires REBOOT permission. Mostly for testing. Should not return. + */ + @Override // Binder call + public void crash(String message) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); + + final long ident = Binder.clearCallingIdentity(); + try { + crashInternal(message); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void crashInternal(final String message) { + Thread t = new Thread("PowerManagerService.crash()") { + @Override + public void run() { + throw new RuntimeException(message); + } + }; + try { + t.start(); + t.join(); + } catch (InterruptedException e) { + Log.wtf(TAG, e); + } + } + + /** + * Set the setting that determines whether the device stays on when plugged in. + * The argument is a bit string, with each bit specifying a power source that, + * when the device is connected to that source, causes the device to stay on. + * See {@link android.os.BatteryManager} for the list of power sources that + * can be specified. Current values include {@link android.os.BatteryManager#BATTERY_PLUGGED_AC} + * and {@link android.os.BatteryManager#BATTERY_PLUGGED_USB} + * + * Used by "adb shell svc power stayon ..." + * + * @param val an {@code int} containing the bits that specify which power sources + * should cause the device to stay on. + */ + @Override // Binder call + public void setStayOnSetting(int val) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setStayOnSettingInternal(val); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setStayOnSettingInternal(int val) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val); + } + + /** + * Used by device administration to set the maximum screen off timeout. + * + * This method must only be called by the device administration policy manager. + */ + @Override // Binder call + public void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs) { + final long ident = Binder.clearCallingIdentity(); + try { + setMaximumScreenOffTimeoutFromDeviceAdminInternal(timeMs); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) { + synchronized (mLock) { + mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + + private boolean isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() { + return mMaximumScreenOffTimeoutFromDeviceAdmin >= 0 + && mMaximumScreenOffTimeoutFromDeviceAdmin < Integer.MAX_VALUE; + } + + /** + * Used by the phone application to make the attention LED flash when ringing. + */ + @Override // Binder call + public void setAttentionLight(boolean on, int color) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setAttentionLightInternal(on, color); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setAttentionLightInternal(boolean on, int color) { + Light light; + synchronized (mLock) { + if (!mSystemReady) { + return; + } + light = mAttentionLight; + } + + // Control light outside of lock. + light.setFlashing(color, Light.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0); + } + + /** + * Used by the Watchdog. + */ + public long timeSinceScreenWasLastOn() { + synchronized (mLock) { + if (mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + return 0; + } + return SystemClock.elapsedRealtime() - mLastScreenOffEventElapsedRealTime; + } + } + + /** + * Used by the window manager to override the screen brightness based on the + * current foreground activity. + * + * This method must only be called by the window manager. + * + * @param brightness The overridden brightness, or -1 to disable the override. + */ + public void setScreenBrightnessOverrideFromWindowManager(int brightness) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setScreenBrightnessOverrideFromWindowManagerInternal(brightness); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setScreenBrightnessOverrideFromWindowManagerInternal(int brightness) { + synchronized (mLock) { + if (mScreenBrightnessOverrideFromWindowManager != brightness) { + mScreenBrightnessOverrideFromWindowManager = brightness; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + + /** + * Used by the window manager to override the button brightness based on the + * current foreground activity. + * + * This method must only be called by the window manager. + * + * @param brightness The overridden brightness, or -1 to disable the override. + */ + public void setButtonBrightnessOverrideFromWindowManager(int brightness) { + // Do nothing. + // Button lights are not currently supported in the new implementation. + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + } + + /** + * Used by the window manager to override the user activity timeout based on the + * current foreground activity. It can only be used to make the timeout shorter + * than usual, not longer. + * + * This method must only be called by the window manager. + * + * @param timeoutMillis The overridden timeout, or -1 to disable the override. + */ + public void setUserActivityTimeoutOverrideFromWindowManager(long timeoutMillis) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setUserActivityTimeoutOverrideFromWindowManagerInternal(timeoutMillis); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setUserActivityTimeoutOverrideFromWindowManagerInternal(long timeoutMillis) { + synchronized (mLock) { + if (mUserActivityTimeoutOverrideFromWindowManager != timeoutMillis) { + mUserActivityTimeoutOverrideFromWindowManager = timeoutMillis; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + + /** + * Used by the settings application and brightness control widgets to + * temporarily override the current screen brightness setting so that the + * user can observe the effect of an intended settings change without applying + * it immediately. + * + * The override will be canceled when the setting value is next updated. + * + * @param brightness The overridden brightness. + * + * @see android.provider.Settings.System#SCREEN_BRIGHTNESS + */ + @Override // Binder call + public void setTemporaryScreenBrightnessSettingOverride(int brightness) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setTemporaryScreenBrightnessSettingOverrideInternal(brightness); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setTemporaryScreenBrightnessSettingOverrideInternal(int brightness) { + synchronized (mLock) { + if (mTemporaryScreenBrightnessSettingOverride != brightness) { + mTemporaryScreenBrightnessSettingOverride = brightness; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + + /** + * Used by the settings application and brightness control widgets to + * temporarily override the current screen auto-brightness adjustment setting so that the + * user can observe the effect of an intended settings change without applying + * it immediately. + * + * The override will be canceled when the setting value is next updated. + * + * @param adj The overridden brightness, or Float.NaN to disable the override. + * + * @see Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ + */ + @Override // Binder call + public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(adj); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(float adj) { + synchronized (mLock) { + // Note: This condition handles NaN because NaN is not equal to any other + // value, including itself. + if (mTemporaryScreenAutoBrightnessAdjustmentSettingOverride != adj) { + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = adj; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + + /** + * Low-level function turn the device off immediately, without trying + * to be clean. Most people should use {@link ShutdownThread} for a clean shutdown. + */ + public static void lowLevelShutdown() { + SystemProperties.set("sys.powerctl", "shutdown"); + } + + /** + * Low-level function to reboot the device. On success, this function + * doesn't return. If more than 5 seconds passes from the time, + * a reboot is requested, this method returns. + * + * @param reason code to pass to the kernel (e.g. "recovery"), or null. + */ + public static void lowLevelReboot(String reason) { + if (reason == null) { + reason = ""; + } + SystemProperties.set("sys.powerctl", "reboot," + reason); + try { + Thread.sleep(20000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @Override // Watchdog.Monitor implementation + public void monitor() { + // Grab and release lock for watchdog monitor to detect deadlocks. + synchronized (mLock) { + } + } + + @Override // Binder call + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump PowerManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("POWER MANAGER (dumpsys power)\n"); + + final DisplayPowerController dpc; + final WirelessChargerDetector wcd; + synchronized (mLock) { + pw.println("Power Manager State:"); + pw.println(" mDirty=0x" + Integer.toHexString(mDirty)); + pw.println(" mWakefulness=" + wakefulnessToString(mWakefulness)); + pw.println(" mIsPowered=" + mIsPowered); + pw.println(" mPlugType=" + mPlugType); + pw.println(" mBatteryLevel=" + mBatteryLevel); + pw.println(" mBatteryLevelWhenDreamStarted=" + mBatteryLevelWhenDreamStarted); + pw.println(" mDockState=" + mDockState); + pw.println(" mStayOn=" + mStayOn); + pw.println(" mProximityPositive=" + mProximityPositive); + pw.println(" mBootCompleted=" + mBootCompleted); + pw.println(" mSystemReady=" + mSystemReady); + pw.println(" mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)); + pw.println(" mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)); + pw.println(" mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity); + pw.println(" mSandmanScheduled=" + mSandmanScheduled); + pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime)); + pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime)); + pw.println(" mSendWakeUpFinishedNotificationWhenReady=" + + mSendWakeUpFinishedNotificationWhenReady); + pw.println(" mSendGoToSleepFinishedNotificationWhenReady=" + + mSendGoToSleepFinishedNotificationWhenReady); + pw.println(" mLastUserActivityTime=" + TimeUtils.formatUptime(mLastUserActivityTime)); + pw.println(" mLastUserActivityTimeNoChangeLights=" + + TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights)); + pw.println(" mDisplayReady=" + mDisplayReady); + pw.println(" mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker); + pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker); + + pw.println(); + pw.println("Settings and Configuration:"); + pw.println(" mWakeUpWhenPluggedOrUnpluggedConfig=" + + mWakeUpWhenPluggedOrUnpluggedConfig); + pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + + mSuspendWhenScreenOffDueToProximityConfig); + pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); + pw.println(" mDreamsEnabledByDefaultConfig=" + mDreamsEnabledByDefaultConfig); + pw.println(" mDreamsActivatedOnSleepByDefaultConfig=" + + mDreamsActivatedOnSleepByDefaultConfig); + pw.println(" mDreamsActivatedOnDockByDefaultConfig=" + + mDreamsActivatedOnDockByDefaultConfig); + pw.println(" mDreamsEnabledSetting=" + mDreamsEnabledSetting); + pw.println(" mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting); + pw.println(" mDreamsActivateOnDockSetting=" + mDreamsActivateOnDockSetting); + pw.println(" mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting); + pw.println(" mMaximumScreenOffTimeoutFromDeviceAdmin=" + + mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced=" + + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")"); + pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting); + pw.println(" mScreenBrightnessSetting=" + mScreenBrightnessSetting); + pw.println(" mScreenAutoBrightnessAdjustmentSetting=" + + mScreenAutoBrightnessAdjustmentSetting); + pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting); + pw.println(" mScreenBrightnessOverrideFromWindowManager=" + + mScreenBrightnessOverrideFromWindowManager); + pw.println(" mUserActivityTimeoutOverrideFromWindowManager=" + + mUserActivityTimeoutOverrideFromWindowManager); + pw.println(" mTemporaryScreenBrightnessSettingOverride=" + + mTemporaryScreenBrightnessSettingOverride); + pw.println(" mTemporaryScreenAutoBrightnessAdjustmentSettingOverride=" + + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride); + pw.println(" mScreenBrightnessSettingMinimum=" + mScreenBrightnessSettingMinimum); + pw.println(" mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum); + pw.println(" mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault); + + final int screenOffTimeout = getScreenOffTimeoutLocked(); + final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); + pw.println(); + pw.println("Screen off timeout: " + screenOffTimeout + " ms"); + pw.println("Screen dim duration: " + screenDimDuration + " ms"); + + pw.println(); + pw.println("Wake Locks: size=" + mWakeLocks.size()); + for (WakeLock wl : mWakeLocks) { + pw.println(" " + wl); + } + + pw.println(); + pw.println("Suspend Blockers: size=" + mSuspendBlockers.size()); + for (SuspendBlocker sb : mSuspendBlockers) { + pw.println(" " + sb); + } + + pw.println(); + pw.println("Screen On Blocker: " + mScreenOnBlocker); + + pw.println(); + pw.println("Display Blanker: " + mDisplayBlanker); + + dpc = mDisplayPowerController; + wcd = mWirelessChargerDetector; + } + + if (dpc != null) { + dpc.dump(pw); + } + + if (wcd != null) { + wcd.dump(pw); + } + } + + private SuspendBlocker createSuspendBlockerLocked(String name) { + SuspendBlocker suspendBlocker = new SuspendBlockerImpl(name); + mSuspendBlockers.add(suspendBlocker); + return suspendBlocker; + } + + private static String wakefulnessToString(int wakefulness) { + switch (wakefulness) { + case WAKEFULNESS_ASLEEP: + return "Asleep"; + case WAKEFULNESS_AWAKE: + return "Awake"; + case WAKEFULNESS_DREAMING: + return "Dreaming"; + case WAKEFULNESS_NAPPING: + return "Napping"; + default: + return Integer.toString(wakefulness); + } + } + + private static WorkSource copyWorkSource(WorkSource workSource) { + return workSource != null ? new WorkSource(workSource) : null; + } + + private final class BatteryReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + handleBatteryStateChangedLocked(); + } + } + } + + private final class BootCompletedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + // This is our early signal that the system thinks it has finished booting. + // However, the boot animation may still be running for a few more seconds + // since it is ultimately in charge of when it terminates. + // Defer transitioning into the boot completed state until the animation exits. + // We do this so that the screen does not start to dim prematurely before + // the user has actually had a chance to interact with the device. + startWatchingForBootAnimationFinished(); + } + } + + private final class DreamReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + scheduleSandmanLocked(); + } + } + } + + private final class UserSwitchedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + handleSettingsChangedLocked(); + } + } + } + + private final class DockReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (mDockState != dockState) { + mDockState = dockState; + mDirty |= DIRTY_DOCK_STATE; + updatePowerStateLocked(); + } + } + } + } + + private final class SettingsObserver extends ContentObserver { + public SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + synchronized (mLock) { + handleSettingsChangedLocked(); + } + } + } + + /** + * Handler for asynchronous operations performed by the power manager. + */ + private final class PowerManagerHandler extends Handler { + public PowerManagerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_USER_ACTIVITY_TIMEOUT: + handleUserActivityTimeout(); + break; + case MSG_SANDMAN: + handleSandman(); + break; + case MSG_SCREEN_ON_BLOCKER_RELEASED: + handleScreenOnBlockerReleased(); + break; + case MSG_CHECK_IF_BOOT_ANIMATION_FINISHED: + checkIfBootAnimationFinished(); + break; + } + } + } + + /** + * Represents a wake lock that has been acquired by an application. + */ + private final class WakeLock implements IBinder.DeathRecipient { + public final IBinder mLock; + public int mFlags; + public String mTag; + public final String mPackageName; + public WorkSource mWorkSource; + public final int mOwnerUid; + public final int mOwnerPid; + public boolean mNotifiedAcquired; + + public WakeLock(IBinder lock, int flags, String tag, String packageName, + WorkSource workSource, int ownerUid, int ownerPid) { + mLock = lock; + mFlags = flags; + mTag = tag; + mPackageName = packageName; + mWorkSource = copyWorkSource(workSource); + mOwnerUid = ownerUid; + mOwnerPid = ownerPid; + } + + @Override + public void binderDied() { + PowerManagerService.this.handleWakeLockDeath(this); + } + + public boolean hasSameProperties(int flags, String tag, WorkSource workSource, + int ownerUid, int ownerPid) { + return mFlags == flags + && mTag.equals(tag) + && hasSameWorkSource(workSource) + && mOwnerUid == ownerUid + && mOwnerPid == ownerPid; + } + + public void updateProperties(int flags, String tag, String packageName, + WorkSource workSource, int ownerUid, int ownerPid) { + if (!mPackageName.equals(packageName)) { + throw new IllegalStateException("Existing wake lock package name changed: " + + mPackageName + " to " + packageName); + } + if (mOwnerUid != ownerUid) { + throw new IllegalStateException("Existing wake lock uid changed: " + + mOwnerUid + " to " + ownerUid); + } + if (mOwnerPid != ownerPid) { + throw new IllegalStateException("Existing wake lock pid changed: " + + mOwnerPid + " to " + ownerPid); + } + mFlags = flags; + mTag = tag; + updateWorkSource(workSource); + } + + public boolean hasSameWorkSource(WorkSource workSource) { + return Objects.equal(mWorkSource, workSource); + } + + public void updateWorkSource(WorkSource workSource) { + mWorkSource = copyWorkSource(workSource); + } + + @Override + public String toString() { + return getLockLevelString() + + " '" + mTag + "'" + getLockFlagsString() + + " (uid=" + mOwnerUid + ", pid=" + mOwnerPid + ", ws=" + mWorkSource + ")"; + } + + private String getLockLevelString() { + switch (mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK: + return "FULL_WAKE_LOCK "; + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + return "SCREEN_BRIGHT_WAKE_LOCK "; + case PowerManager.SCREEN_DIM_WAKE_LOCK: + return "SCREEN_DIM_WAKE_LOCK "; + case PowerManager.PARTIAL_WAKE_LOCK: + return "PARTIAL_WAKE_LOCK "; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + return "PROXIMITY_SCREEN_OFF_WAKE_LOCK"; + default: + return "??? "; + } + } + + private String getLockFlagsString() { + String result = ""; + if ((mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { + result += " ACQUIRE_CAUSES_WAKEUP"; + } + if ((mFlags & PowerManager.ON_AFTER_RELEASE) != 0) { + result += " ON_AFTER_RELEASE"; + } + return result; + } + } + + private final class SuspendBlockerImpl implements SuspendBlocker { + private final String mName; + private int mReferenceCount; + + public SuspendBlockerImpl(String name) { + mName = name; + } + + @Override + protected void finalize() throws Throwable { + try { + if (mReferenceCount != 0) { + Log.wtf(TAG, "Suspend blocker \"" + mName + + "\" was finalized without being released!"); + mReferenceCount = 0; + nativeReleaseSuspendBlocker(mName); + } + } finally { + super.finalize(); + } + } + + @Override + public void acquire() { + synchronized (this) { + mReferenceCount += 1; + if (mReferenceCount == 1) { + if (DEBUG_SPEW) { + Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\"."); + } + nativeAcquireSuspendBlocker(mName); + } + } + } + + @Override + public void release() { + synchronized (this) { + mReferenceCount -= 1; + if (mReferenceCount == 0) { + if (DEBUG_SPEW) { + Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\"."); + } + nativeReleaseSuspendBlocker(mName); + } else if (mReferenceCount < 0) { + Log.wtf(TAG, "Suspend blocker \"" + mName + + "\" was released without being acquired!", new Throwable()); + mReferenceCount = 0; + } + } + } + + @Override + public String toString() { + synchronized (this) { + return mName + ": ref count=" + mReferenceCount; + } + } + } + + private final class ScreenOnBlockerImpl implements ScreenOnBlocker { + private int mNestCount; + + public boolean isHeld() { + synchronized (this) { + return mNestCount != 0; + } + } + + @Override + public void acquire() { + synchronized (this) { + mNestCount += 1; + if (DEBUG) { + Slog.d(TAG, "Screen on blocked: mNestCount=" + mNestCount); + } + } + } + + @Override + public void release() { + synchronized (this) { + mNestCount -= 1; + if (mNestCount < 0) { + Log.wtf(TAG, "Screen on blocker was released without being acquired!", + new Throwable()); + mNestCount = 0; + } + if (mNestCount == 0) { + mHandler.sendEmptyMessage(MSG_SCREEN_ON_BLOCKER_RELEASED); + } + if (DEBUG) { + Slog.d(TAG, "Screen on unblocked: mNestCount=" + mNestCount); + } + } + } + + @Override + public String toString() { + synchronized (this) { + return "held=" + (mNestCount != 0) + ", mNestCount=" + mNestCount; + } + } + } + + private final class DisplayBlankerImpl implements DisplayBlanker { + private boolean mBlanked; + + @Override + public void blankAllDisplays() { + synchronized (this) { + mBlanked = true; + mDisplayManagerService.blankAllDisplaysFromPowerManager(); + nativeSetInteractive(false); + nativeSetAutoSuspend(true); + } + } + + @Override + public void unblankAllDisplays() { + synchronized (this) { + nativeSetAutoSuspend(false); + nativeSetInteractive(true); + mDisplayManagerService.unblankAllDisplaysFromPowerManager(); + mBlanked = false; + } + } + + @Override + public String toString() { + synchronized (this) { + return "blanked=" + mBlanked; + } + } + } +} diff --git a/services/core/java/com/android/server/power/RampAnimator.java b/services/core/java/com/android/server/power/RampAnimator.java new file mode 100644 index 0000000..4a4f080 --- /dev/null +++ b/services/core/java/com/android/server/power/RampAnimator.java @@ -0,0 +1,137 @@ +/* + * 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 android.animation.ValueAnimator; +import android.util.IntProperty; +import android.view.Choreographer; + +/** + * A custom animator that progressively updates a property value at + * a given variable rate until it reaches a particular target value. + */ +final class RampAnimator<T> { + private final T mObject; + private final IntProperty<T> mProperty; + private final Choreographer mChoreographer; + + private int mCurrentValue; + private int mTargetValue; + private int mRate; + + private boolean mAnimating; + private float mAnimatedValue; // higher precision copy of mCurrentValue + private long mLastFrameTimeNanos; + + private boolean mFirstTime = true; + + public RampAnimator(T object, IntProperty<T> property) { + mObject = object; + mProperty = property; + mChoreographer = Choreographer.getInstance(); + } + + /** + * Starts animating towards the specified value. + * + * If this is the first time the property is being set, the value jumps + * directly to the target. + * + * @param target The target value. + * @param rate The convergence rate, in units per second. + * @return True if the target differs from the previous target. + */ + public boolean animateTo(int target, int rate) { + // Immediately jump to the target the first time. + if (mFirstTime) { + mFirstTime = false; + mProperty.setValue(mObject, target); + mCurrentValue = target; + return true; + } + + // Adjust the rate based on the closest target. + // If a faster rate is specified, then use the new rate so that we converge + // more rapidly based on the new request. + // If a slower rate is specified, then use the new rate only if the current + // value is somewhere in between the new and the old target meaning that + // we will be ramping in a different direction to get there. + // Otherwise, continue at the previous rate. + if (!mAnimating + || rate > mRate + || (target <= mCurrentValue && mCurrentValue <= mTargetValue) + || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { + mRate = rate; + } + + final boolean changed = (mTargetValue != target); + mTargetValue = target; + + // Start animating. + if (!mAnimating && target != mCurrentValue) { + mAnimating = true; + mAnimatedValue = mCurrentValue; + mLastFrameTimeNanos = System.nanoTime(); + postCallback(); + } + + return changed; + } + + private void postCallback() { + mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mCallback, null); + } + + private final Runnable mCallback = new Runnable() { + @Override // Choreographer callback + public void run() { + final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); + final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos) + * 0.000000001f; + mLastFrameTimeNanos = frameTimeNanos; + + // Advance the animated value towards the target at the specified rate + // and clamp to the target. This gives us the new current value but + // we keep the animated value around to allow for fractional increments + // towards the target. + final float scale = ValueAnimator.getDurationScale(); + if (scale == 0) { + // Animation off. + mAnimatedValue = mTargetValue; + } else { + final float amount = timeDelta * mRate / scale; + if (mTargetValue > mCurrentValue) { + mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); + } else { + mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); + } + } + final int oldCurrentValue = mCurrentValue; + mCurrentValue = Math.round(mAnimatedValue); + + if (oldCurrentValue != mCurrentValue) { + mProperty.setValue(mObject, mCurrentValue); + } + + if (mTargetValue != mCurrentValue) { + postCallback(); + } else { + mAnimating = false; + } + } + }; +} diff --git a/services/core/java/com/android/server/power/ScreenOnBlocker.java b/services/core/java/com/android/server/power/ScreenOnBlocker.java new file mode 100644 index 0000000..dbbbc6d --- /dev/null +++ b/services/core/java/com/android/server/power/ScreenOnBlocker.java @@ -0,0 +1,41 @@ +/* + * 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; + +/** + * Low-level screen on blocker mechanism which is used to keep the screen off + * or the contents of the screen hidden until the window manager is ready to show new content. + */ +interface ScreenOnBlocker { + /** + * Acquires the screen on blocker. + * Prevents the screen from turning on. + * + * Calls to acquire() nest and must be matched by the same number + * of calls to release(). + */ + void acquire(); + + /** + * Releases the screen on blocker. + * Allows the screen to turn on. + * + * It is an error to call release() if the screen on blocker has not been acquired. + * The system may crash. + */ + void release(); +} diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java new file mode 100644 index 0000000..88a27f5 --- /dev/null +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2008 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 android.app.ActivityManagerNative; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.IActivityManager; +import android.app.ProgressDialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.IBluetoothManager; +import android.nfc.NfcAdapter; +import android.nfc.INfcAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.Vibrator; +import android.os.SystemVibrator; +import android.os.storage.IMountService; +import android.os.storage.IMountShutdownObserver; + +import com.android.internal.telephony.ITelephony; + +import android.util.Log; +import android.view.WindowManager; + +public final class ShutdownThread extends Thread { + // constants + private static final String TAG = "ShutdownThread"; + private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500; + // maximum time we wait for the shutdown broadcast before going on. + private static final int MAX_BROADCAST_TIME = 10*1000; + private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000; + private static final int MAX_RADIO_WAIT_TIME = 12*1000; + + // length of vibration before shutting down + private static final int SHUTDOWN_VIBRATE_MS = 500; + + // state tracking + private static Object sIsStartedGuard = new Object(); + private static boolean sIsStarted = false; + + private static boolean mReboot; + private static boolean mRebootSafeMode; + private static String mRebootReason; + + // Provides shutdown assurance in case the system_server is killed + public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested"; + + // Indicates whether we are rebooting into safe mode + public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode"; + + // static instance of this thread + private static final ShutdownThread sInstance = new ShutdownThread(); + + private final Object mActionDoneSync = new Object(); + private boolean mActionDone; + private Context mContext; + private PowerManager mPowerManager; + private PowerManager.WakeLock mCpuWakeLock; + private PowerManager.WakeLock mScreenWakeLock; + private Handler mHandler; + + private static AlertDialog sConfirmDialog; + + private ShutdownThread() { + } + + /** + * Request a clean shutdown, waiting for subsystems to clean up their + * state etc. Must be called from a Looper thread in which its UI + * is shown. + * + * @param context Context used to display the shutdown progress dialog. + * @param confirm true if user confirmation is needed before shutting down. + */ + public static void shutdown(final Context context, boolean confirm) { + mReboot = false; + mRebootSafeMode = false; + shutdownInner(context, confirm); + } + + static void shutdownInner(final Context context, boolean confirm) { + // ensure that only one thread is trying to power down. + // any additional calls are just returned + synchronized (sIsStartedGuard) { + if (sIsStarted) { + Log.d(TAG, "Request to shutdown already running, returning."); + return; + } + } + + final int longPressBehavior = context.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnPowerBehavior); + final int resourceId = mRebootSafeMode + ? com.android.internal.R.string.reboot_safemode_confirm + : (longPressBehavior == 2 + ? com.android.internal.R.string.shutdown_confirm_question + : com.android.internal.R.string.shutdown_confirm); + + Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior); + + if (confirm) { + final CloseDialogReceiver closer = new CloseDialogReceiver(context); + if (sConfirmDialog != null) { + sConfirmDialog.dismiss(); + } + sConfirmDialog = new AlertDialog.Builder(context) + .setTitle(mRebootSafeMode + ? com.android.internal.R.string.reboot_safemode_title + : com.android.internal.R.string.power_off) + .setMessage(resourceId) + .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + beginShutdownSequence(context); + } + }) + .setNegativeButton(com.android.internal.R.string.no, null) + .create(); + closer.dialog = sConfirmDialog; + sConfirmDialog.setOnDismissListener(closer); + sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + sConfirmDialog.show(); + } else { + beginShutdownSequence(context); + } + } + + private static class CloseDialogReceiver extends BroadcastReceiver + implements DialogInterface.OnDismissListener { + private Context mContext; + public Dialog dialog; + + CloseDialogReceiver(Context context) { + mContext = context; + IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + dialog.cancel(); + } + + public void onDismiss(DialogInterface unused) { + mContext.unregisterReceiver(this); + } + } + + /** + * Request a clean shutdown, waiting for subsystems to clean up their + * state etc. Must be called from a Looper thread in which its UI + * is shown. + * + * @param context Context used to display the shutdown progress dialog. + * @param reason code to pass to the kernel (e.g. "recovery"), or null. + * @param confirm true if user confirmation is needed before shutting down. + */ + public static void reboot(final Context context, String reason, boolean confirm) { + mReboot = true; + mRebootSafeMode = false; + mRebootReason = reason; + shutdownInner(context, confirm); + } + + /** + * Request a reboot into safe mode. Must be called from a Looper thread in which its UI + * is shown. + * + * @param context Context used to display the shutdown progress dialog. + * @param confirm true if user confirmation is needed before shutting down. + */ + public static void rebootSafeMode(final Context context, boolean confirm) { + mReboot = true; + mRebootSafeMode = true; + mRebootReason = null; + shutdownInner(context, confirm); + } + + private static void beginShutdownSequence(Context context) { + synchronized (sIsStartedGuard) { + if (sIsStarted) { + Log.d(TAG, "Shutdown sequence already running, returning."); + return; + } + sIsStarted = true; + } + + // throw up an indeterminate system dialog to indicate radio is + // shutting down. + ProgressDialog pd = new ProgressDialog(context); + pd.setTitle(context.getText(com.android.internal.R.string.power_off)); + pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); + pd.setIndeterminate(true); + pd.setCancelable(false); + pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + + pd.show(); + + sInstance.mContext = context; + sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + + // make sure we never fall asleep again + sInstance.mCpuWakeLock = null; + try { + sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu"); + sInstance.mCpuWakeLock.setReferenceCounted(false); + sInstance.mCpuWakeLock.acquire(); + } catch (SecurityException e) { + Log.w(TAG, "No permission to acquire wake lock", e); + sInstance.mCpuWakeLock = null; + } + + // also make sure the screen stays on for better user experience + sInstance.mScreenWakeLock = null; + if (sInstance.mPowerManager.isScreenOn()) { + try { + sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock( + PowerManager.FULL_WAKE_LOCK, TAG + "-screen"); + sInstance.mScreenWakeLock.setReferenceCounted(false); + sInstance.mScreenWakeLock.acquire(); + } catch (SecurityException e) { + Log.w(TAG, "No permission to acquire wake lock", e); + sInstance.mScreenWakeLock = null; + } + } + + // start the thread that initiates shutdown + sInstance.mHandler = new Handler() { + }; + sInstance.start(); + } + + void actionDone() { + synchronized (mActionDoneSync) { + mActionDone = true; + mActionDoneSync.notifyAll(); + } + } + + /** + * Makes sure we handle the shutdown gracefully. + * Shuts off power regardless of radio and bluetooth state if the alloted time has passed. + */ + public void run() { + BroadcastReceiver br = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + // We don't allow apps to cancel this, so ignore the result. + actionDone(); + } + }; + + /* + * Write a system property in case the system_server reboots before we + * get to the actual hardware restart. If that happens, we'll retry at + * the beginning of the SystemServer startup. + */ + { + String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : ""); + SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); + } + + /* + * If we are rebooting into safe mode, write a system property + * indicating so. + */ + if (mRebootSafeMode) { + SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); + } + + Log.i(TAG, "Sending shutdown broadcast..."); + + // First send the high-level shut down broadcast. + mActionDone = false; + Intent intent = new Intent(Intent.ACTION_SHUTDOWN); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendOrderedBroadcastAsUser(intent, + UserHandle.ALL, null, br, mHandler, 0, null, null); + + final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; + synchronized (mActionDoneSync) { + while (!mActionDone) { + long delay = endTime - SystemClock.elapsedRealtime(); + if (delay <= 0) { + Log.w(TAG, "Shutdown broadcast timed out"); + break; + } + try { + mActionDoneSync.wait(delay); + } catch (InterruptedException e) { + } + } + } + + Log.i(TAG, "Shutting down activity manager..."); + + final IActivityManager am = + ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); + if (am != null) { + try { + am.shutdown(MAX_BROADCAST_TIME); + } catch (RemoteException e) { + } + } + + // Shutdown radios. + shutdownRadios(MAX_RADIO_WAIT_TIME); + + // Shutdown MountService to ensure media is in a safe state + IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { + public void onShutDownComplete(int statusCode) throws RemoteException { + Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown"); + actionDone(); + } + }; + + Log.i(TAG, "Shutting down MountService"); + + // Set initial variables and time out time. + mActionDone = false; + final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME; + synchronized (mActionDoneSync) { + try { + final IMountService mount = IMountService.Stub.asInterface( + ServiceManager.checkService("mount")); + if (mount != null) { + mount.shutdown(observer); + } else { + Log.w(TAG, "MountService unavailable for shutdown"); + } + } catch (Exception e) { + Log.e(TAG, "Exception during MountService shutdown", e); + } + while (!mActionDone) { + long delay = endShutTime - SystemClock.elapsedRealtime(); + if (delay <= 0) { + Log.w(TAG, "Shutdown wait timed out"); + break; + } + try { + mActionDoneSync.wait(delay); + } catch (InterruptedException e) { + } + } + } + + rebootOrShutdown(mReboot, mRebootReason); + } + + private void shutdownRadios(int timeout) { + // If a radio is wedged, disabling it may hang so we do this work in another thread, + // just in case. + final long endTime = SystemClock.elapsedRealtime() + timeout; + final boolean[] done = new boolean[1]; + Thread t = new Thread() { + public void run() { + boolean nfcOff; + boolean bluetoothOff; + boolean radioOff; + + final INfcAdapter nfc = + INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc")); + final ITelephony phone = + ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); + final IBluetoothManager bluetooth = + IBluetoothManager.Stub.asInterface(ServiceManager.checkService( + BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE)); + + try { + nfcOff = nfc == null || + nfc.getState() == NfcAdapter.STATE_OFF; + if (!nfcOff) { + Log.w(TAG, "Turning off NFC..."); + nfc.disable(false); // Don't persist new state + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during NFC shutdown", ex); + nfcOff = true; + } + + try { + bluetoothOff = bluetooth == null || !bluetooth.isEnabled(); + if (!bluetoothOff) { + Log.w(TAG, "Disabling Bluetooth..."); + bluetooth.disable(false); // disable but don't persist new state + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during bluetooth shutdown", ex); + bluetoothOff = true; + } + + try { + radioOff = phone == null || !phone.isRadioOn(); + if (!radioOff) { + Log.w(TAG, "Turning off radio..."); + phone.setRadio(false); + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during radio shutdown", ex); + radioOff = true; + } + + Log.i(TAG, "Waiting for NFC, Bluetooth and Radio..."); + + while (SystemClock.elapsedRealtime() < endTime) { + if (!bluetoothOff) { + try { + bluetoothOff = !bluetooth.isEnabled(); + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during bluetooth shutdown", ex); + bluetoothOff = true; + } + if (bluetoothOff) { + Log.i(TAG, "Bluetooth turned off."); + } + } + if (!radioOff) { + try { + radioOff = !phone.isRadioOn(); + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during radio shutdown", ex); + radioOff = true; + } + if (radioOff) { + Log.i(TAG, "Radio turned off."); + } + } + if (!nfcOff) { + try { + nfcOff = nfc.getState() == NfcAdapter.STATE_OFF; + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during NFC shutdown", ex); + nfcOff = true; + } + if (radioOff) { + Log.i(TAG, "NFC turned off."); + } + } + + if (radioOff && bluetoothOff && nfcOff) { + Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete."); + done[0] = true; + break; + } + SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); + } + } + }; + + t.start(); + try { + t.join(timeout); + } catch (InterruptedException ex) { + } + if (!done[0]) { + Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown."); + } + } + + /** + * Do not call this directly. Use {@link #reboot(Context, String, boolean)} + * or {@link #shutdown(Context, boolean)} instead. + * + * @param reboot true to reboot or false to shutdown + * @param reason reason for reboot + */ + public static void rebootOrShutdown(boolean reboot, String reason) { + if (reboot) { + Log.i(TAG, "Rebooting, reason: " + reason); + PowerManagerService.lowLevelReboot(reason); + Log.e(TAG, "Reboot failed, will attempt shutdown instead"); + } else if (SHUTDOWN_VIBRATE_MS > 0) { + // vibrate before shutting down + Vibrator vibrator = new SystemVibrator(); + try { + vibrator.vibrate(SHUTDOWN_VIBRATE_MS); + } catch (Exception e) { + // Failure to vibrate shouldn't interrupt shutdown. Just log it. + Log.w(TAG, "Failed to vibrate during shutdown.", e); + } + + // vibrator is asynchronous so we need to wait to avoid shutting down too soon. + try { + Thread.sleep(SHUTDOWN_VIBRATE_MS); + } catch (InterruptedException unused) { + } + } + + // Shutdown power + Log.i(TAG, "Performing low-level shutdown..."); + PowerManagerService.lowLevelShutdown(); + } +} diff --git a/services/core/java/com/android/server/power/SuspendBlocker.java b/services/core/java/com/android/server/power/SuspendBlocker.java new file mode 100644 index 0000000..70b278a --- /dev/null +++ b/services/core/java/com/android/server/power/SuspendBlocker.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Low-level suspend blocker mechanism equivalent to holding a partial wake lock. + * + * This interface is used internally to avoid introducing internal dependencies + * on the high-level wake lock mechanism. + */ +interface SuspendBlocker { + /** + * Acquires the suspend blocker. + * Prevents the CPU from going to sleep. + * + * Calls to acquire() nest and must be matched by the same number + * of calls to release(). + */ + void acquire(); + + /** + * Releases the suspend blocker. + * Allows the CPU to go to sleep if no other suspend blockers are held. + * + * It is an error to call release() if the suspend blocker has not been acquired. + * The system may crash. + */ + void release(); +} diff --git a/services/core/java/com/android/server/power/WirelessChargerDetector.java b/services/core/java/com/android/server/power/WirelessChargerDetector.java new file mode 100644 index 0000000..38f5d77 --- /dev/null +++ b/services/core/java/com/android/server/power/WirelessChargerDetector.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2013 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 android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.BatteryManager; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.Slog; +import android.util.TimeUtils; + +import java.io.PrintWriter; + +/** + * Implements heuristics to detect docking or undocking from a wireless charger. + * <p> + * Some devices have wireless charging circuits that are unable to detect when the + * device is resting on a wireless charger except when the device is actually + * receiving power from the charger. The device may stop receiving power + * if the battery is already nearly full or if it is too hot. As a result, we cannot + * always rely on the battery service wireless plug signal to accurately indicate + * whether the device has been docked or undocked from a wireless charger. + * </p><p> + * This is a problem because the power manager typically wakes up the screen and + * plays a tone when the device is docked in a wireless charger. It is important + * for the system to suppress spurious docking and undocking signals because they + * can be intrusive for the user (especially if they cause a tone to be played + * late at night for no apparent reason). + * </p><p> + * To avoid spurious signals, we apply some special policies to wireless chargers. + * </p><p> + * 1. Don't wake the device when undocked from the wireless charger because + * it might be that the device is still resting on the wireless charger + * but is not receiving power anymore because the battery is full. + * Ideally we would wake the device if we could be certain that the user had + * picked it up from the wireless charger but due to hardware limitations we + * must be more conservative. + * </p><p> + * 2. Don't wake the device when docked on a wireless charger if the + * battery already appears to be mostly full. This situation may indicate + * that the device was resting on the charger the whole time and simply + * wasn't receiving power because the battery was already full. We can't tell + * whether the device was just placed on the charger or whether it has + * been there for half of the night slowly discharging until it reached + * the point where it needed to start charging again. So we suppress docking + * signals that occur when the battery level is above a given threshold. + * </p><p> + * 3. Don't wake the device when docked on a wireless charger if it does + * not appear to have moved since it was last undocked because it may + * be that the prior undocking signal was spurious. We use the gravity + * sensor to detect this case. + * </p> + */ +final class WirelessChargerDetector { + private static final String TAG = "WirelessChargerDetector"; + private static final boolean DEBUG = false; + + // The minimum amount of time to spend watching the sensor before making + // a determination of whether movement occurred. + private static final long SETTLE_TIME_MILLIS = 800; + + // The sensor sampling interval. + private static final int SAMPLING_INTERVAL_MILLIS = 50; + + // The minimum number of samples that must be collected. + private static final int MIN_SAMPLES = 3; + + // Upper bound on the battery charge percentage in order to consider turning + // the screen on when the device starts charging wirelessly. + private static final int WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT = 95; + + // To detect movement, we compute the angle between the gravity vector + // at rest and the current gravity vector. This field specifies the + // cosine of the maximum angle variance that we tolerate while at rest. + private static final double MOVEMENT_ANGLE_COS_THRESHOLD = Math.cos(5 * Math.PI / 180); + + // Sanity thresholds for the gravity vector. + private static final double MIN_GRAVITY = SensorManager.GRAVITY_EARTH - 1.0f; + private static final double MAX_GRAVITY = SensorManager.GRAVITY_EARTH + 1.0f; + + private final Object mLock = new Object(); + + private final SensorManager mSensorManager; + private final SuspendBlocker mSuspendBlocker; + private final Handler mHandler; + + // The gravity sensor, or null if none. + private Sensor mGravitySensor; + + // Previously observed wireless power state. + private boolean mPoweredWirelessly; + + // True if the device is thought to be at rest on a wireless charger. + private boolean mAtRest; + + // The gravity vector most recently observed while at rest. + private float mRestX, mRestY, mRestZ; + + /* These properties are only meaningful while detection is in progress. */ + + // True if detection is in progress. + // The suspend blocker is held while this is the case. + private boolean mDetectionInProgress; + + // The time when detection was last performed. + private long mDetectionStartTime; + + // True if the rest position should be updated if at rest. + // Otherwise, the current rest position is simply checked and cleared if movement + // is detected but no new rest position is stored. + private boolean mMustUpdateRestPosition; + + // The total number of samples collected. + private int mTotalSamples; + + // The number of samples collected that showed evidence of not being at rest. + private int mMovingSamples; + + // The value of the first sample that was collected. + private float mFirstSampleX, mFirstSampleY, mFirstSampleZ; + + // The value of the last sample that was collected. + private float mLastSampleX, mLastSampleY, mLastSampleZ; + + public WirelessChargerDetector(SensorManager sensorManager, + SuspendBlocker suspendBlocker, Handler handler) { + mSensorManager = sensorManager; + mSuspendBlocker = suspendBlocker; + mHandler = handler; + + mGravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); + } + + public void dump(PrintWriter pw) { + synchronized (mLock) { + pw.println(); + pw.println("Wireless Charger Detector State:"); + pw.println(" mGravitySensor=" + mGravitySensor); + pw.println(" mPoweredWirelessly=" + mPoweredWirelessly); + pw.println(" mAtRest=" + mAtRest); + pw.println(" mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ); + pw.println(" mDetectionInProgress=" + mDetectionInProgress); + pw.println(" mDetectionStartTime=" + (mDetectionStartTime == 0 ? "0 (never)" + : TimeUtils.formatUptime(mDetectionStartTime))); + pw.println(" mMustUpdateRestPosition=" + mMustUpdateRestPosition); + pw.println(" mTotalSamples=" + mTotalSamples); + pw.println(" mMovingSamples=" + mMovingSamples); + pw.println(" mFirstSampleX=" + mFirstSampleX + + ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ); + pw.println(" mLastSampleX=" + mLastSampleX + + ", mLastSampleY=" + mLastSampleY + ", mLastSampleZ=" + mLastSampleZ); + } + } + + /** + * Updates the charging state and returns true if docking was detected. + * + * @param isPowered True if the device is powered. + * @param plugType The current plug type. + * @param batteryLevel The current battery level. + * @return True if the device is determined to have just been docked on a wireless + * charger, after suppressing spurious docking or undocking signals. + */ + public boolean update(boolean isPowered, int plugType, int batteryLevel) { + synchronized (mLock) { + final boolean wasPoweredWirelessly = mPoweredWirelessly; + + if (isPowered && plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) { + // The device is receiving power from the wireless charger. + // Update the rest position asynchronously. + mPoweredWirelessly = true; + mMustUpdateRestPosition = true; + startDetectionLocked(); + } else { + // The device may or may not be on the wireless charger depending on whether + // the unplug signal that we received was spurious. + mPoweredWirelessly = false; + if (mAtRest) { + if (plugType != 0 && plugType != BatteryManager.BATTERY_PLUGGED_WIRELESS) { + // The device was plugged into a new non-wireless power source. + // It's safe to assume that it is no longer on the wireless charger. + mMustUpdateRestPosition = false; + clearAtRestLocked(); + } else { + // The device may still be on the wireless charger but we don't know. + // Check whether the device has remained at rest on the charger + // so that we will know to ignore the next wireless plug event + // if needed. + startDetectionLocked(); + } + } + } + + // Report that the device has been docked only if the device just started + // receiving power wirelessly, has a high enough battery level that we + // can be assured that charging was not delayed due to the battery previously + // having been full, and the device is not known to already be at rest + // on the wireless charger from earlier. + return mPoweredWirelessly && !wasPoweredWirelessly + && batteryLevel < WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT + && !mAtRest; + } + } + + private void startDetectionLocked() { + if (!mDetectionInProgress && mGravitySensor != null) { + if (mSensorManager.registerListener(mListener, mGravitySensor, + SAMPLING_INTERVAL_MILLIS * 1000)) { + mSuspendBlocker.acquire(); + mDetectionInProgress = true; + mDetectionStartTime = SystemClock.uptimeMillis(); + mTotalSamples = 0; + mMovingSamples = 0; + + Message msg = Message.obtain(mHandler, mSensorTimeout); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, SETTLE_TIME_MILLIS); + } + } + } + + private void finishDetectionLocked() { + if (mDetectionInProgress) { + mSensorManager.unregisterListener(mListener); + mHandler.removeCallbacks(mSensorTimeout); + + if (mMustUpdateRestPosition) { + clearAtRestLocked(); + if (mTotalSamples < MIN_SAMPLES) { + Slog.w(TAG, "Wireless charger detector is broken. Only received " + + mTotalSamples + " samples from the gravity sensor but we " + + "need at least " + MIN_SAMPLES + " and we expect to see " + + "about " + SETTLE_TIME_MILLIS / SAMPLING_INTERVAL_MILLIS + + " on average."); + } else if (mMovingSamples == 0) { + mAtRest = true; + mRestX = mLastSampleX; + mRestY = mLastSampleY; + mRestZ = mLastSampleZ; + } + mMustUpdateRestPosition = false; + } + + if (DEBUG) { + Slog.d(TAG, "New state: mAtRest=" + mAtRest + + ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ + + ", mTotalSamples=" + mTotalSamples + + ", mMovingSamples=" + mMovingSamples); + } + + mDetectionInProgress = false; + mSuspendBlocker.release(); + } + } + + private void processSampleLocked(float x, float y, float z) { + if (mDetectionInProgress) { + mLastSampleX = x; + mLastSampleY = y; + mLastSampleZ = z; + + mTotalSamples += 1; + if (mTotalSamples == 1) { + // Save information about the first sample collected. + mFirstSampleX = x; + mFirstSampleY = y; + mFirstSampleZ = z; + } else { + // Determine whether movement has occurred relative to the first sample. + if (hasMoved(mFirstSampleX, mFirstSampleY, mFirstSampleZ, x, y, z)) { + mMovingSamples += 1; + } + } + + // Clear the at rest flag if movement has occurred relative to the rest sample. + if (mAtRest && hasMoved(mRestX, mRestY, mRestZ, x, y, z)) { + if (DEBUG) { + Slog.d(TAG, "No longer at rest: " + + "mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ + + ", x=" + x + ", y=" + y + ", z=" + z); + } + clearAtRestLocked(); + } + } + } + + private void clearAtRestLocked() { + mAtRest = false; + mRestX = 0; + mRestY = 0; + mRestZ = 0; + } + + private static boolean hasMoved(float x1, float y1, float z1, + float x2, float y2, float z2) { + final double dotProduct = (x1 * x2) + (y1 * y2) + (z1 * z2); + final double mag1 = Math.sqrt((x1 * x1) + (y1 * y1) + (z1 * z1)); + final double mag2 = Math.sqrt((x2 * x2) + (y2 * y2) + (z2 * z2)); + if (mag1 < MIN_GRAVITY || mag1 > MAX_GRAVITY + || mag2 < MIN_GRAVITY || mag2 > MAX_GRAVITY) { + if (DEBUG) { + Slog.d(TAG, "Weird gravity vector: mag1=" + mag1 + ", mag2=" + mag2); + } + return true; + } + final boolean moved = (dotProduct < mag1 * mag2 * MOVEMENT_ANGLE_COS_THRESHOLD); + if (DEBUG) { + Slog.d(TAG, "Check: moved=" + moved + + ", x1=" + x1 + ", y1=" + y1 + ", z1=" + z1 + + ", x2=" + x2 + ", y2=" + y2 + ", z2=" + z2 + + ", angle=" + (Math.acos(dotProduct / mag1 / mag2) * 180 / Math.PI) + + ", dotProduct=" + dotProduct + + ", mag1=" + mag1 + ", mag2=" + mag2); + } + return moved; + } + + private final SensorEventListener mListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + synchronized (mLock) { + processSampleLocked(event.values[0], event.values[1], event.values[2]); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + + private final Runnable mSensorTimeout = new Runnable() { + @Override + public void run() { + synchronized (mLock) { + finishDetectionLocked(); + } + } + }; +} |