PowerManager: Add proximity check on wake

Co-authored-by: Adnan Begovic <adnan@cyngn.com>
Co-authored-by: Christopher R. Palmer <crpalmer@gmail.com>
Co-authored-by: Danesh M <daneshm90@gmail.com>
Co-authored-by: dankoman <dankoman30@gmail.com>
Co-authored-by: Erica Chang <echang@cyngn.com>
Co-authored-by: Han Wang <416810799@qq.com>
Co-authored-by: LuK1337 <priv.luk@gmail.com>
Co-authored-by: Michael Bestas <mkbestas@lineageos.org>
Co-authored-by: Oliver Scott <olivercscott@gmail.com>
Co-authored-by: Pat Erley <perley@cyngn.com>
Co-authored-by: Roman Birg <roman@cyngn.com>
Co-authored-by: Schubi <schubi@erlangen.ccc.de>
Co-authored-by: Scott Mertz <scott@cyngn.com>
Co-authored-by: Steve Kondik <steve@cyngn.com>
Co-authored-by: vm03 <vasy@vasy.ru>
Change-Id: Ia5ff446d88435773acccc7467e66009000dc20cb
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index c95b278..69aecb1 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -182,4 +182,6 @@
     const int GO_TO_SLEEP_REASON_MAX = 10;
     const int GO_TO_SLEEP_FLAG_NO_DOZE = 1 << 0;
 
+    // Lineage custom API
+    void wakeUpWithProximityCheck(long time, int reason, String details, String opPackageName, int displayId);
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0b5e734..1845deb3 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1763,6 +1763,24 @@
     }
 
     /**
+     * Forces the display with the supplied displayId to turn on only if nothing is blocking the
+     * proximity sensor.
+     *
+     * @see #wakeUp
+     *
+     * @hide
+     */
+    public void wakeUpWithProximityCheck(long time, @WakeReason int reason, String details,
+            int displayId) {
+        try {
+            mService.wakeUpWithProximityCheck(time, reason, details, mContext.getOpPackageName(),
+                    displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Forces the device to start napping.
      * <p>
      * If the device is currently awake, starts dreaming, otherwise does nothing.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b1007466..93088ff 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4505,7 +4505,7 @@
             }
 
             if (isWakeKey) {
-                wakeUpFromWakeKey(event);
+                wakeUpFromWakeKey(event, true);
             }
             return result;
         }
@@ -4930,7 +4930,8 @@
         }
 
         if (isWakeKey) {
-            wakeUpFromWakeKey(event);
+            // Check proximity only on wake key
+            wakeUpFromWakeKey(event, event.getKeyCode() == KeyEvent.KEYCODE_WAKEUP);
         }
 
         // If the key event is targeted to a specific display, then the user is interacting with
@@ -5459,11 +5460,26 @@
         wakeUpFromWakeKey(
                 event.getEventTime(),
                 event.getKeyCode(),
-                event.getAction() == KeyEvent.ACTION_DOWN);
+                event.getAction() == KeyEvent.ACTION_DOWN,
+                false);
+    }
+
+    private void wakeUpFromWakeKey(KeyEvent event, boolean withProximityCheck) {
+        wakeUpFromWakeKey(
+                event.getEventTime(),
+                event.getKeyCode(),
+                event.getAction() == KeyEvent.ACTION_DOWN,
+                withProximityCheck);
     }
 
     private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
-        if (mWindowWakeUpPolicy.wakeUpFromKey(DEFAULT_DISPLAY, eventTime, keyCode, isDown)) {
+        wakeUpFromWakeKey(eventTime, keyCode, isDown, false);
+    }
+
+    private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown,
+            boolean withProximityCheck) {
+        if (mWindowWakeUpPolicy.wakeUpFromKey(DEFAULT_DISPLAY, eventTime, keyCode, isDown,
+                withProximityCheck)) {
             final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
             // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
             if (shouldWakeUpWithHomeIntent() &&  keyCanLaunchHome) {
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index 30325b9..0a77038 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -117,6 +117,22 @@
      *      executed; {@code false} otherwise.
      */
     boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown) {
+        return wakeUpFromKey(displayId, eventTime, keyCode, isDown, false);
+    }
+
+    /**
+     * Wakes up from a key event.
+     *
+     * @param displayId the id of the display to wake.
+     * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+     * @param keyCode the {@link android.view.KeyEvent} key code of the key event.
+     * @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
+     * @param withProximityCheck {@code true} if we should check proximity sensor before wakeup.
+     * @return {@code true} if the policy allows the requested wake up and the request has been
+     *      executed; {@code false} otherwise.
+     */
+    boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown,
+            boolean withProximityCheck) {
         final boolean wakeAllowedDuringTheaterMode =
                 keyCode == KEYCODE_POWER
                         ? mAllowTheaterModeWakeFromPowerKey
@@ -134,12 +150,14 @@
                     displayId,
                     eventTime,
                     keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
-                    keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+                    keyCode == KEYCODE_POWER ? "POWER" : "KEY",
+                    withProximityCheck);
         } else {
             wakeUp(
                     eventTime,
                     keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
-                    keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+                    keyCode == KEYCODE_POWER ? "POWER" : "KEY",
+                    withProximityCheck);
         }
         return true;
     }
@@ -276,14 +294,34 @@
 
     /** Wakes up {@link PowerManager}. */
     private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
-        mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
+        wakeUp(wakeTime, reason, details, false);
+    }
+
+    private void wakeUp(long wakeTime, @WakeReason int reason, String details,
+            boolean withProximityCheck) {
+        if (withProximityCheck) {
+            mPowerManager.wakeUpWithProximityCheck(wakeTime, reason, "android.policy:" + details,
+                    Display.DEFAULT_DISPLAY);
+        } else {
+            mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
+        }
     }
 
     /** Wakes up given display. */
     private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details) {
+        wakeUp(displayId, wakeTime, reason, details, false);
+    }
+
+    private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details,
+            boolean withProximityCheck) {
         // If we're given an invalid display id to wake, fall back to waking default display
         final int displayIdToWake =
                 displayId == Display.INVALID_DISPLAY ? Display.DEFAULT_DISPLAY : displayId;
-        mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayIdToWake);
+        if (withProximityCheck) {
+            mPowerManager.wakeUpWithProximityCheck(wakeTime, reason, "android.policy:" + details,
+                    displayIdToWake);
+        } else {
+            mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayIdToWake);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index fd98557..5c92a3a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -60,6 +60,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.devicestate.DeviceState;
@@ -108,6 +111,7 @@
 import android.provider.Settings.SettingNotFoundException;
 import android.service.dreams.DreamManagerInternal;
 import android.sysprop.PowerProperties;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.IntArray;
 import android.util.KeyValueListParser;
@@ -152,6 +156,8 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
+import lineageos.providers.LineageSettings;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -197,6 +203,9 @@
     // Message: Sent when the policy wants to force disable wakelocks.
     private static final int MSG_FORCE_DISABLE_WAKELOCKS = 8;
 
+    // Message: Sent when waking up with proximity check.
+    private static final int MSG_WAKE_UP = 9;
+
     // Dirty bit: mWakeLocks changed
     private static final int DIRTY_WAKE_LOCKS = 1 << 0;
     // Dirty bit: mWakefulness changed
@@ -251,6 +260,9 @@
     // This should perhaps be a setting.
     private static final int SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 5 * 1000;
 
+    // Threshold to consider proximity sensor covered
+    private static final float PROXIMITY_NEAR_THRESHOLD = 5.0f;
+
     // How long a partial wake lock must be held until we consider it a long wake lock.
     static final long MIN_LONG_WAKE_CHECK_INTERVAL = 60*1000;
 
@@ -1187,6 +1199,17 @@
     private static native boolean nativeSetPowerMode(int mode, boolean enabled);
     private static native boolean nativeForceSuspend();
 
+    // 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 PowerManager.WakeLock mProximityWakeLock;
+
     public PowerManagerService(Context context) {
         this(context, new Injector());
     }
@@ -1442,6 +1465,10 @@
                 // Shouldn't happen since in-process.
             }
 
+            // Initialize proximity sensor
+            mSensorManager = mContext.getSystemService(SensorManager.class);
+            mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+
             mLowPowerStandbyController.systemReady();
 
             // Go.
@@ -1516,6 +1543,11 @@
                 Settings.Global.DEVICE_DEMO_MODE),
                 false, mSettingsObserver, UserHandle.USER_SYSTEM);
 
+        // Register for Lineage settings changes.
+        resolver.registerContentObserver(LineageSettings.System.getUriFor(
+                LineageSettings.System.PROXIMITY_ON_WAKE),
+                false, mSettingsObserver, UserHandle.USER_ALL);
+
         // Register for broadcasts from other components of the system.
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
@@ -1582,6 +1614,17 @@
                 com.android.internal.R.bool.config_brightWhenDozing);
         mSupportsDoubleTapWakeConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_supportDoubleTapWake);
+
+        mProximityWakeSupported = resources.getBoolean(
+                org.lineageos.platform.internal.R.bool.config_proximityCheckOnWake);
+        mProximityWakeEnabledByDefaultConfig = resources.getBoolean(
+                org.lineageos.platform.internal.R.bool.config_proximityCheckOnWakeEnabledByDefault);
+        mProximityTimeOut = resources.getInteger(
+                org.lineageos.platform.internal.R.integer.config_proximityCheckTimeout);
+        if (mProximityWakeSupported) {
+            mProximityWakeLock = mContext.getSystemService(PowerManager.class)
+                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ProximityWakeLock");
+        }
     }
 
     @GuardedBy("mLock")
@@ -1631,6 +1674,10 @@
             mSystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
         }
 
+        mProximityWakeEnabled = LineageSettings.System.getIntForUser(resolver,
+                LineageSettings.System.PROXIMITY_ON_WAKE,
+                mProximityWakeEnabledByDefaultConfig ? 1 : 0, UserHandle.USER_CURRENT) == 1;
+
         mDirty |= DIRTY_SETTINGS;
     }
 
@@ -5543,6 +5590,10 @@
                         setForceDisableWakelocksInternal(false);
                     }
                     break;
+                case MSG_WAKE_UP:
+                    cleanupProximity();
+                    ((Runnable) msg.obj).run();
+                    break;
             }
 
             return true;
@@ -6298,12 +6349,24 @@
         @Override // Binder call
         public void wakeUp(long eventTime, @WakeReason int reason, String details,
                 String opPackageName) {
-            wakeUpWithDisplayId(eventTime, reason, details, opPackageName, Display.DEFAULT_DISPLAY);
+            wakeUpWithDisplayId(eventTime, reason, details, opPackageName, Display.DEFAULT_DISPLAY,
+                false);
+        }
+
+        @Override // Binder call
+        public void wakeUpWithProximityCheck(long eventTime, @WakeReason int reason,
+                String details, String opPackageName, int displayId) {
+            wakeUpWithDisplayId(eventTime, reason, details, opPackageName, displayId, true);
         }
 
         @Override // Binder call
         public void wakeUpWithDisplayId(long eventTime, @WakeReason int reason, String details,
                 String opPackageName, int displayId) {
+            wakeUpWithDisplayId(eventTime, reason, details, opPackageName, displayId, false);
+        }
+
+        public void wakeUpWithDisplayId(long eventTime, @WakeReason int reason, String details,
+                String opPackageName, int displayId, boolean checkProximity) {
             final long now = mClock.uptimeMillis();
             if (eventTime > now) {
                 Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now);
@@ -6314,20 +6377,27 @@
                     android.Manifest.permission.DEVICE_POWER, null);
 
             final int uid = Binder.getCallingUid();
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                int displayGroupId = getDisplayGroupId(displayId);
-                synchronized (mLock) {
-                    if (!mBootCompleted && sQuiescent) {
-                        mDirty |= DIRTY_QUIESCENT;
-                        updatePowerStateLocked();
-                        return;
+            final Runnable r = () -> {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    int displayGroupId = getDisplayGroupId(displayId);
+                    synchronized (mLock) {
+                        if (!mBootCompleted && sQuiescent) {
+                            mDirty |= DIRTY_QUIESCENT;
+                            updatePowerStateLocked();
+                            return;
+                        }
+                        wakePowerGroupLocked(mPowerGroups.get(displayGroupId), eventTime,
+                                reason, details, uid, opPackageName, uid);
                     }
-                    wakePowerGroupLocked(mPowerGroups.get(displayGroupId), eventTime,
-                            reason, details, uid, opPackageName, uid);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
                 }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
+            };
+            if (checkProximity) {
+                runWithProximityCheck(r);
+            } else {
+                r.run();
             }
         }
 
@@ -7692,4 +7762,76 @@
         }
         return displayInfo.displayGroupId;
     }
+
+    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 = mContext.getSystemService(TelephonyManager.class);
+        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);
+        }
+    }
 }