diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 13e4e38df5f6..7cd90450a63b 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -68,4 +68,6 @@ interface IPowerManager // controls whether PowerManager should doze after the screen turns off or not void setDozeAfterScreenOff(boolean on); + + void wakeUpWithProximityCheck(long time, String reason, String opPackageName); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 6ad0ff33d47a..dcb5f2280fbd 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -981,6 +981,22 @@ public void wakeUp(long time, String reason) { } } + /** + * Forces the device to wake up from sleep only if + * nothing is blocking the proximity sensor + * + * @see #wakeUp + * + * @hide + */ + public void wakeUpWithProximityCheck(long time, String reason) { + try { + mService.wakeUpWithProximityCheck(time, reason, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Forces the device to start napping. *

diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 70a028ac6e22..9d75dd2c98dc 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3904,6 +3904,11 @@ public boolean validate(@Nullable String value) { public static final String DOCK_SOUNDS_ENABLED = Global.DOCK_SOUNDS_ENABLED; private static final Validator DOCK_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** + * Check the proximity sensor during wakeup + * @hide + */ + public static final String PROXIMITY_ON_WAKE = "proximity_on_wake"; /** * Whether to play sounds when the keyguard is shown and dismissed. diff --git a/core/java/com/android/internal/os/DeviceKeyHandler.java b/core/java/com/android/internal/os/DeviceKeyHandler.java new file mode 100644 index 000000000000..e7d103dd6bee --- /dev/null +++ b/core/java/com/android/internal/os/DeviceKeyHandler.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 The CyanogenMod 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.internal.os; + +import android.view.KeyEvent; + +public interface DeviceKeyHandler { + + /** + * Invoked when an unknown key was detected by the system, letting the device handle + * this special keys prior to pass the key to the active app. + * + * @param event The key event to be handled + * @return If the event is consume + */ + public boolean handleKeyEvent(KeyEvent event); +} diff --git a/core/res/res/values/custom_config.xml b/core/res/res/values/custom_config.xml index a6dd60c2310c..50ad87f0fd3f 100644 --- a/core/res/res/values/custom_config.xml +++ b/core/res/res/values/custom_config.xml @@ -127,4 +127,17 @@ true + + false + 250 + false + + + + + + + diff --git a/core/res/res/values/custom_symbols.xml b/core/res/res/values/custom_symbols.xml index f4bee99864a7..46e2195c6e4d 100644 --- a/core/res/res/values/custom_symbols.xml +++ b/core/res/res/values/custom_symbols.xml @@ -109,4 +109,17 @@ + + + + + + + + + + + + + diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index fadf88e81ccf..abe653d8e0a6 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -172,6 +172,7 @@ import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; +import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; @@ -283,6 +284,9 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.policy.IKeyguardDismissCallback; +import com.android.internal.os.DeviceKeyHandler; +import com.android.internal.policy.PhoneWindow; +import com.android.internal.policy.IKeyguardService; import com.android.internal.policy.IShortcutService; import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.policy.PhoneWindow; @@ -310,6 +314,9 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.List; +import java.lang.reflect.Constructor; + +import dalvik.system.PathClassLoader; /** * WindowManagerPolicy implementation for the Android phone UI. This @@ -505,6 +512,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** Amount of time (in milliseconds) a toast window can be shown. */ public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds + private DeviceKeyHandler mDeviceKeyHandler; + /** * Lock protecting internal state. Must not call out into window * manager with lock held. (This lock will be acquired in places @@ -2789,6 +2798,28 @@ public void onShowingChanged() { mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); mProximityWakeLock = mContext.getSystemService(PowerManager.class) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ProximityWakeLock"); + + String deviceKeyHandlerLib = mContext.getResources().getString( + com.android.internal.R.string.config_deviceKeyHandlerLib); + + String deviceKeyHandlerClass = mContext.getResources().getString( + com.android.internal.R.string.config_deviceKeyHandlerClass); + + if (!deviceKeyHandlerLib.isEmpty() && !deviceKeyHandlerClass.isEmpty()) { + PathClassLoader loader = new PathClassLoader(deviceKeyHandlerLib, + getClass().getClassLoader()); + try { + Class klass = loader.loadClass(deviceKeyHandlerClass); + Constructor constructor = klass.getConstructor(Context.class); + mDeviceKeyHandler = (DeviceKeyHandler) constructor.newInstance( + mContext); + if(DEBUG) Slog.d(TAG, "Device key handler loaded"); + } catch (Exception e) { + Slog.w(TAG, "Could not instantiate device key handler " + + deviceKeyHandlerClass + " from class " + + deviceKeyHandlerLib, e); + } + } } /** @@ -5028,6 +5059,18 @@ public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int p return -1; } + // Specific device key handling + if (mDeviceKeyHandler != null) { + try { + // The device only should consume known keys. + if (mDeviceKeyHandler.handleKeyEvent(event)) { + return -1; + } + } catch (Exception e) { + Slog.w(TAG, "Could not dispatch event to device key handler", e); + } + } + if (down) { long shortcutCode = keyCode; if (event.isCtrlPressed()) { @@ -7178,7 +7221,8 @@ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { if (isValidGlobalKey(keyCode) && mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) { if (isWakeKey) { - wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey, "android.policy:KEY"); + wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey, + "android.policy:KEY", true); } return result; } @@ -7195,6 +7239,18 @@ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { // Trigger haptic feedback only for "real" events. && source != InputDevice.SOURCE_CUSTOM; + // Specific device key handling + if (mDeviceKeyHandler != null) { + try { + // The device only should consume known keys. + if (mDeviceKeyHandler.handleKeyEvent(event)) { + return 0; + } + } catch (Exception e) { + Slog.w(TAG, "Could not dispatch event to device key handler", e); + } + } + // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_BACK: { @@ -7543,7 +7599,8 @@ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { } if (isWakeKey) { - wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey, "android.policy:KEY"); + wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey, "android.policy:KEY", + event.getKeyCode() == KeyEvent.KEYCODE_WAKEUP); // Check prox only on wake key } return result; @@ -8020,6 +8077,11 @@ private void wakeUpFromPowerKey(long eventTime) { } private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason) { + return wakeUp(wakeTime, wakeInTheaterMode, reason, false); + } + + private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason, + final boolean withProximityCheck) { final boolean theaterModeEnabled = isTheaterModeEnabled(); if (!wakeInTheaterMode && theaterModeEnabled) { return false; @@ -8030,7 +8092,11 @@ private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason) Settings.Global.THEATER_MODE_ON, 0); } - mPowerManager.wakeUp(wakeTime, reason); + if (withProximityCheck) { + mPowerManager.wakeUpWithProximityCheck(wakeTime, reason); + } else { + mPowerManager.wakeUp(wakeTime, reason); + } return true; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 296c735e244f..aa1638055d78 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -33,6 +33,9 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.SystemSensorManager; import android.hardware.display.DisplayManagerInternal; @@ -69,6 +72,8 @@ import android.service.dreams.DreamManagerInternal; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; +import android.telephony.TelephonyManager; +import android.util.EventLog; import android.util.KeyValueListParser; import android.util.PrintWriterPrinter; import android.util.Log; @@ -130,6 +135,8 @@ public final class PowerManagerService extends SystemService // Message: Polling to look for long held wake locks. private static final int MSG_CHECK_FOR_LONG_WAKELOCKS = 4; + private static final int MSG_WAKE_UP = 5; + // Dirty bit: mWakeLocks changed private static final int DIRTY_WAKE_LOCKS = 1 << 0; // Dirty bit: mWakefulness changed @@ -225,6 +232,8 @@ public final class PowerManagerService extends SystemService // Persistent property for last reboot reason private static final String LAST_REBOOT_PROPERTY = "persist.sys.boot.reason"; + + private static final float PROXIMITY_NEAR_THRESHOLD = 5.0f; private final Context mContext; private final ServiceThread mHandlerThread; @@ -671,6 +680,17 @@ void dumpProto(ProtoOutputStream proto) { private static native void nativeSendPowerHint(int hintId, int data); private static native void nativeSetFeature(int featureId, int data); + // Whether proximity check on wake is enabled by default + private boolean mProximityWakeEnabledByDefaultConfig; + + private boolean mProximityWakeSupported; + private boolean mProximityWakeEnabled; + private int mProximityTimeOut; + private SensorManager mSensorManager; + private Sensor mProximitySensor; + private SensorEventListener mProximityListener; + private android.os.PowerManager.WakeLock mProximityWakeLock; + public PowerManagerService(Context context) { super(context); mContext = context; @@ -810,6 +830,10 @@ public void systemReady(IAppOpsService appOps) { // Shouldn't happen since in-process. } + // Initialize proximity sensor + mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + // Go. readConfigurationLocked(); updateSettingsLocked(); @@ -869,6 +893,9 @@ public void systemReady(IAppOpsService appOps) { resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.NAVIGATION_BAR_ENABLED), false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.PROXIMITY_ON_WAKE), + false, mSettingsObserver, UserHandle.USER_ALL); IVrManager vrManager = (IVrManager) getBinderService(Context.VR_SERVICE); if (vrManager != null) { try { @@ -937,6 +964,16 @@ private void readConfigurationLocked() { com.android.internal.R.fraction.config_maximumScreenDimRatio, 1, 1); mSupportsDoubleTapWakeConfig = resources.getBoolean( com.android.internal.R.bool.config_supportDoubleTapWake); + mProximityWakeSupported = resources.getBoolean( + com.android.internal.R.bool.config_proximityCheckOnWake); + mProximityWakeEnabledByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_proximityCheckOnWakeEnabledByDefault); + mProximityTimeOut = resources.getInteger( + com.android.internal.R.integer.config_proximityCheckTimeout); + if (mProximityWakeSupported) { + mProximityWakeLock = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE)) + .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ProximityWakeLock"); + } } private void updateSettingsLocked() { @@ -1002,6 +1039,10 @@ private void updateSettingsLocked() { Settings.System.NAVIGATION_BAR_ENABLED, 0, UserHandle.USER_CURRENT) != 0; mButtonBrightnessEnabled &= !navBarEnabled; + mProximityWakeEnabled = Settings.System.getInt(resolver, + Settings.System.PROXIMITY_ON_WAKE, + mProximityWakeEnabledByDefaultConfig ? 1 : 0) == 1; + mDirty |= DIRTY_SETTINGS; } @@ -3963,6 +4004,10 @@ public void handleMessage(Message msg) { case MSG_CHECK_FOR_LONG_WAKELOCKS: checkForLongWakeLocks(); break; + case MSG_WAKE_UP: + cleanupProximity(); + ((Runnable) msg.obj).run(); + break; } } } @@ -4382,6 +4427,19 @@ public void userActivity(long eventTime, int event, int flags) { @Override // Binder call public void wakeUp(long eventTime, String reason, String opPackageName) { + wakeUp(eventTime, reason, opPackageName, false); + } + + @Override // Binder call + public void wakeUpWithProximityCheck(long eventTime, String reason, String opPackageName) { + wakeUp(eventTime, reason, opPackageName, true); + } + + /** + * @hide + */ + private void wakeUp(long eventTime, String reason, String opPackageName, + final boolean checkProximity) { if (eventTime > SystemClock.uptimeMillis()) { throw new IllegalArgumentException("event time must not be in the future"); } @@ -4390,11 +4448,21 @@ public void wakeUp(long eventTime, String reason, String opPackageName) { android.Manifest.permission.DEVICE_POWER, null); final int uid = Binder.getCallingUid(); - final long ident = Binder.clearCallingIdentity(); - try { - wakeUpInternal(eventTime, reason, uid, opPackageName, uid); - } finally { - Binder.restoreCallingIdentity(ident); + final Runnable r = new Runnable() { + @Override + public void run() { + final long ident = Binder.clearCallingIdentity(); + try { + wakeUpInternal(eventTime, reason, uid, opPackageName, uid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + }; + if (checkProximity) { + runWithProximityCheck(r); + } else { + r.run(); } } @@ -4849,4 +4917,76 @@ public void powerHint(int hintId, int data) { powerHintInternal(hintId, data); } } + + private void cleanupProximity() { + synchronized (mProximityWakeLock) { + cleanupProximityLocked(); + } + } + + private void cleanupProximityLocked() { + if (mProximityWakeLock.isHeld()) { + mProximityWakeLock.release(); + } + if (mProximityListener != null) { + mSensorManager.unregisterListener(mProximityListener); + mProximityListener = null; + } + } + + private void runWithProximityCheck(final Runnable r) { + if (mHandler.hasMessages(MSG_WAKE_UP)) { + // A message is already queued + return; + } + + final TelephonyManager tm = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + final boolean hasIncomingCall = tm.getCallState() == TelephonyManager.CALL_STATE_RINGING; + + if (mProximityWakeSupported && mProximityWakeEnabled + && mProximitySensor != null && !hasIncomingCall) { + final Message msg = mHandler.obtainMessage(MSG_WAKE_UP); + msg.obj = r; + mHandler.sendMessageDelayed(msg, mProximityTimeOut); + runPostProximityCheck(r); + } else { + r.run(); + } + } + + private void runPostProximityCheck(final Runnable r) { + if (mSensorManager == null) { + r.run(); + return; + } + synchronized (mProximityWakeLock) { + mProximityWakeLock.acquire(); + mProximityListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + cleanupProximityLocked(); + if (!mHandler.hasMessages(MSG_WAKE_UP)) { + Slog.w(TAG, "Proximity sensor took too long, wake event already triggered!"); + return; + } + mHandler.removeMessages(MSG_WAKE_UP); + final float distance = event.values[0]; + if (distance >= PROXIMITY_NEAR_THRESHOLD || + distance >= mProximitySensor.getMaximumRange()) { + r.run(); + } else { + Slog.w(TAG, "Not waking up. Proximity sensor is blocked."); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Do nothing + } + }; + mSensorManager.registerListener(mProximityListener, + mProximitySensor, SensorManager.SENSOR_DELAY_FASTEST); + } + } }