Allow phone to enter suspend with positive proximity. As part of the power manager rewrite in JB MR1, we removed the ability for the phone to suspend with positive proximity because it was not clear that the proximity sensor was always correctly registered as a wake-up source. The sensor service itself does not contain any code to manage wake-ups. Therefore proximity sensor based wake-up relies on the sensor driver acquiring a timed wake lock when the sensor reports a negative result. This behavior is not very well defined in the sensor HAL so there is a chance that it will not work reliably on all devices. This change adds a new config.xml resource to specify whether the device should be allowed to suspend when the screen is off due to positive proximity. Devices that support this feature should set the "config_suspendWhenScreenOffDueToProximity" resource to "true" in their resource overlays. The feature is disabled by default. Bug: 9760828 Change-Id: Ic65ab7df0357271b133e2e44f5e35e7756e1e9e0
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ab95d40..83e5ae9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml
@@ -450,6 +450,13 @@ The default is false. --> <bool name="config_lidControlsSleep">false</bool> + <!-- Indicate whether to allow the device to suspend when the screen is off + due to the proximity sensor. This resource should only be set to true + if the sensor HAL correctly handles the proximity sensor as a wake-up source. + Otherwise, the device may fail to wake out of suspend reliably. + The default is false. --> + <bool name="config_suspendWhenScreenOffDueToProximity">false</bool> + <!-- Control the behavior when the user long presses the power button. 0 - Nothing 1 - Global actions menu
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7b2a1b4..ecdf188 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml
@@ -260,6 +260,7 @@ <java-symbol type="bool" name="config_sip_wifi_only" /> <java-symbol type="bool" name="config_sms_capable" /> <java-symbol type="bool" name="config_sms_utf8_support" /> + <java-symbol type="bool" name="config_suspendWhenScreenOffDueToProximity" /> <java-symbol type="bool" name="config_swipeDisambiguation" /> <java-symbol type="bool" name="config_syncstorageengine_masterSyncAutomatically" /> <java-symbol type="bool" name="config_telephony_use_own_number_for_voicemail" />
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index b5010f2..976a328 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -29,7 +29,6 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.hardware.SystemSensorManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -119,7 +118,7 @@ // 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 = 500; + 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; @@ -164,6 +163,10 @@ // 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; @@ -271,7 +274,7 @@ // The raw non-debounced proximity sensor state. private int mPendingProximity = PROXIMITY_UNKNOWN; - private long mPendingProximityDebounceTime; + 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. @@ -346,10 +349,11 @@ public DisplayPowerController(Looper looper, Context context, Notifier notifier, LightsService lights, TwilightService twilight, SensorManager sensorManager, DisplayManagerService displayManager, - DisplayBlanker displayBlanker, + SuspendBlocker displaySuspendBlocker, DisplayBlanker displayBlanker, Callbacks callbacks, Handler callbackHandler) { mHandler = new DisplayControllerHandler(looper); mNotifier = notifier; + mDisplaySuspendBlocker = displaySuspendBlocker; mDisplayBlanker = displayBlanker; mCallbacks = callbacks; mCallbackHandler = callbackHandler; @@ -601,7 +605,7 @@ if (!mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = true; - sendOnProximityPositive(); + sendOnProximityPositiveWithWakelock(); setScreenOn(false); } } else if (mWaitingForNegativeProximity @@ -616,7 +620,7 @@ if (mScreenOffBecauseOfProximity && mProximity != PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = false; - sendOnProximityNegative(); + sendOnProximityNegativeWithWakelock(); } } else { mWaitingForNegativeProximity = false; @@ -737,7 +741,7 @@ } } } - sendOnStateChanged(); + sendOnStateChangedWithWakelock(); } } @@ -810,49 +814,67 @@ private void setProximitySensorEnabled(boolean enable) { if (enable) { if (!mProximitySensorEnabled) { + // Register the listener. + // Proximity sensor state already cleared initially. mProximitySensorEnabled = true; - mPendingProximity = PROXIMITY_UNKNOWN; 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 (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { - return; // no change - } - if (mPendingProximity == PROXIMITY_POSITIVE && positive) { - return; // no change - } + 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. - mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); - if (positive) { - mPendingProximity = PROXIMITY_POSITIVE; - mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY; - } else { - mPendingProximity = PROXIMITY_NEGATIVE; - mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY; + // 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(); } - debounceProximitySensor(); } private void debounceProximitySensor() { - if (mPendingProximity != PROXIMITY_UNKNOWN) { + 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; - sendUpdatePowerState(); + 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); @@ -860,6 +882,20 @@ } } + 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) { @@ -1120,7 +1156,8 @@ return x + (y - x) * alpha; } - private void sendOnStateChanged() { + private void sendOnStateChangedWithWakelock() { + mDisplaySuspendBlocker.acquire(); mCallbackHandler.post(mOnStateChangedRunnable); } @@ -1128,10 +1165,12 @@ @Override public void run() { mCallbacks.onStateChanged(); + mDisplaySuspendBlocker.release(); } }; - private void sendOnProximityPositive() { + private void sendOnProximityPositiveWithWakelock() { + mDisplaySuspendBlocker.acquire(); mCallbackHandler.post(mOnProximityPositiveRunnable); } @@ -1139,10 +1178,12 @@ @Override public void run() { mCallbacks.onProximityPositive(); + mDisplaySuspendBlocker.release(); } }; - private void sendOnProximityNegative() { + private void sendOnProximityNegativeWithWakelock() { + mDisplaySuspendBlocker.acquire(); mCallbackHandler.post(mOnProximityNegativeRunnable); } @@ -1150,6 +1191,7 @@ @Override public void run() { mCallbacks.onProximityNegative(); + mDisplaySuspendBlocker.release(); } };
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index 2167f67..fe09a33 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -63,7 +63,6 @@ import android.view.WindowManagerPolicy; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; @@ -287,6 +286,9 @@ // 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; @@ -447,7 +449,7 @@ // own handler thread to ensure timely operation. mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(), mContext, mNotifier, mLightsService, twilight, sensorManager, - mDisplayManagerService, mDisplayBlanker, + mDisplayManagerService, mDisplaySuspendBlocker, mDisplayBlanker, mDisplayPowerControllerCallbacks, mHandler); mWirelessChargerDetector = new WirelessChargerDetector(sensorManager, @@ -514,6 +516,8 @@ 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( @@ -639,6 +643,7 @@ } } + @SuppressWarnings("deprecation") private static boolean isScreenLock(final WakeLock wakeLock) { switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.FULL_WAKE_LOCK: @@ -817,6 +822,7 @@ } } + @SuppressWarnings("deprecation") private boolean isWakeLockLevelSupportedInternal(int level) { synchronized (mLock) { switch (level) { @@ -1005,6 +1011,7 @@ } } + @SuppressWarnings("deprecation") private boolean goToSleepNoUpdateLocked(long eventTime, int reason) { if (DEBUG_SPEW) { Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime + ", reason=" + reason); @@ -1261,6 +1268,7 @@ * * 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; @@ -1299,7 +1307,7 @@ break; case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: if (mWakefulness != WAKEFULNESS_ASLEEP) { - mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_PROXIMITY_SCREEN_OFF; + mWakeLockSummary |= WAKE_LOCK_PROXIMITY_SCREEN_OFF; } break; } @@ -1458,7 +1466,11 @@ /** * Returns true if the device is being kept awake by a wake lock, user activity - * or the stay on while powered setting. + * 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 @@ -1749,10 +1761,8 @@ * This function must have no other side-effects. */ private void updateSuspendBlockerLocked() { - final boolean needWakeLockSuspendBlocker = (mWakeLockSummary != 0); - final boolean needDisplaySuspendBlocker = (mUserActivitySummary != 0 - || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF - || !mDisplayReady || !mBootCompleted); + final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0); + final boolean needDisplaySuspendBlocker = needDisplaySuspendBlocker(); // First acquire suspend blockers if needed. if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) { @@ -1775,6 +1785,27 @@ } } + /** + * 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(); @@ -2115,7 +2146,7 @@ * * @param brightness The overridden brightness. * - * @see Settings.System#SCREEN_BRIGHTNESS + * @see android.provider.Settings.System#SCREEN_BRIGHTNESS */ @Override // Binder call public void setTemporaryScreenBrightnessSettingOverride(int brightness) { @@ -2255,7 +2286,16 @@ 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);