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);
+ }
+ }
}