Add camera lift trigger to GestureLauncherService

Add registration for the camera lift trigger sensor and handling for its
events to GestureLauncherService

Test: Confirmed camera launches on lift gesture if setting is enabled
(and other pieces are in place)
Change-Id: Ibfad2040409d86af2d019e2ce97ab546bef9ba78
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index e57a9b5..ab301bd 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -92,6 +92,7 @@
 
     public static final int CAMERA_LAUNCH_SOURCE_WIGGLE = 0;
     public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
+    public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
 
     private Context mContext;
     private IStatusBarService mService;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5abc47d..b94faf7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2933,4 +2933,10 @@
     <!-- Volume level of in-call notification tone playback,
          relative to the overall voice call stream volume [0..100] -->
     <integer name="config_inCallNotificationVolumeRelative">67</integer>
+
+    <!-- The OEM specified sensor type for the lift trigger to launch the camera app. -->
+    <integer name="config_cameraLiftTriggerSensorType">-1</integer>
+    <!-- The OEM specified sensor string type for the gesture to launch camera app, this value
+        must match the value of config_cameraLiftTriggerSensorType in OEM's HAL -->
+    <string translatable="false" name="config_cameraLiftTriggerSensorStringType"></string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c688414..cf10c90 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2518,6 +2518,8 @@
   <java-symbol type="integer" name="config_cameraLaunchGestureSensorType" />
   <java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
   <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
+  <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
+  <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
 
   <java-symbol type="drawable" name="platlogo_m" />
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 41a60e2..037e901 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -100,6 +100,7 @@
     public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
     public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
     public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
+    public static final String CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = "lift_to_launch_ml";
 
     public static final String EXTRA_CAMERA_LAUNCH_SOURCE
             = "com.android.systemui.camera_launch_source";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c52c11a..fc27483 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2363,6 +2363,8 @@
             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
         } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
         } else {
 
             // Default.
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index b755dd6..818f478 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3974,6 +3974,11 @@
     // OPEN: Settings > Battery > High Usage
     DIALOG_HANDLE_ANOMALY = 988;
 
+    // ACTION: Camera lift gesture
+    // CATEGORY: GLOBAL_SYSTEM_UI
+    // OS: O
+    ACTION_CAMERA_LIFT_TRIGGER = 989;
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 2e2ddba..6c44cc8 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -71,14 +71,18 @@
 
     /** The listener that receives the gesture event. */
     private final GestureEventListener mGestureListener = new GestureEventListener();
+    private final CameraLiftTriggerEventListener mCameraLiftTriggerListener =
+            new CameraLiftTriggerEventListener();
 
     private Sensor mCameraLaunchSensor;
+    private Sensor mCameraLiftTriggerSensor;
     private Context mContext;
     private final MetricsLogger mMetricsLogger;
 
     /** The wake lock held when a gesture is detected. */
     private WakeLock mWakeLock;
-    private boolean mRegistered;
+    private boolean mCameraLaunchRegistered;
+    private boolean mCameraLiftRegistered;
     private int mUserId;
 
     // Below are fields used for event logging only.
@@ -161,6 +165,9 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
                 false, mSettingObserver, mUserId);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
+                false, mSettingObserver, mUserId);
     }
 
     private void updateCameraRegistered() {
@@ -170,6 +177,12 @@
         } else {
             unregisterCameraLaunchGesture();
         }
+
+        if (isCameraLiftTriggerSettingEnabled(mContext, mUserId)) {
+            registerCameraLiftTrigger(resources);
+        } else {
+            unregisterCameraLiftTrigger();
+        }
     }
 
     @VisibleForTesting
@@ -181,8 +194,8 @@
     }
 
     private void unregisterCameraLaunchGesture() {
-        if (mRegistered) {
-            mRegistered = false;
+        if (mCameraLaunchRegistered) {
+            mCameraLaunchRegistered = false;
             mCameraGestureOnTimeMs = 0L;
             mCameraGestureLastEventTime = 0L;
             mCameraGestureSensor1LastOnTimeMs = 0;
@@ -199,7 +212,7 @@
      * Registers for the camera launch gesture.
      */
     private void registerCameraLaunchGesture(Resources resources) {
-        if (mRegistered) {
+        if (mCameraLaunchRegistered) {
             return;
         }
         mCameraGestureOnTimeMs = SystemClock.elapsedRealtime();
@@ -209,7 +222,7 @@
         int cameraLaunchGestureId = resources.getInteger(
                 com.android.internal.R.integer.config_cameraLaunchGestureSensorType);
         if (cameraLaunchGestureId != -1) {
-            mRegistered = false;
+            mCameraLaunchRegistered = false;
             String sensorName = resources.getString(
                 com.android.internal.R.string.config_cameraLaunchGestureSensorStringType);
             mCameraLaunchSensor = sensorManager.getDefaultSensor(
@@ -221,7 +234,7 @@
             // makes the code more robust.
             if (mCameraLaunchSensor != null) {
                 if (sensorName.equals(mCameraLaunchSensor.getStringType())) {
-                    mRegistered = sensorManager.registerListener(mGestureListener,
+                    mCameraLaunchRegistered = sensorManager.registerListener(mGestureListener,
                             mCameraLaunchSensor, 0);
                 } else {
                     String message = String.format("Wrong configuration. Sensor type and sensor "
@@ -230,12 +243,61 @@
                     throw new RuntimeException(message);
                 }
             }
-            if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mRegistered);
+            if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mCameraLaunchRegistered);
         } else {
             if (DBG) Slog.d(TAG, "Camera launch sensor is not specified.");
         }
     }
 
+    private void unregisterCameraLiftTrigger() {
+        if (mCameraLiftRegistered) {
+            mCameraLiftRegistered = false;
+
+            SensorManager sensorManager = (SensorManager) mContext.getSystemService(
+                    Context.SENSOR_SERVICE);
+            sensorManager.unregisterListener(mCameraLiftTriggerListener);
+        }
+    }
+
+    /**
+     * Registers for the camera lift trigger.
+     */
+    private void registerCameraLiftTrigger(Resources resources) {
+        if (mCameraLiftRegistered) {
+            return;
+        }
+        SensorManager sensorManager = (SensorManager) mContext.getSystemService(
+                Context.SENSOR_SERVICE);
+        int cameraLiftTriggerId = resources.getInteger(
+                com.android.internal.R.integer.config_cameraLiftTriggerSensorType);
+        if (cameraLiftTriggerId != -1) {
+            mCameraLiftRegistered = false;
+            String sensorName = resources.getString(
+                com.android.internal.R.string.config_cameraLiftTriggerSensorStringType);
+            mCameraLiftTriggerSensor = sensorManager.getDefaultSensor(
+                    cameraLiftTriggerId,
+                    false /*wakeUp*/);
+
+            // Compare the camera lift trigger string type to that in the resource file to make
+            // sure we are registering the correct sensor. This is redundant check, it
+            // makes the code more robust.
+            if (mCameraLiftTriggerSensor != null) {
+                if (sensorName.equals(mCameraLiftTriggerSensor.getStringType())) {
+                    mCameraLiftRegistered = sensorManager.registerListener(mCameraLiftTriggerListener,
+                            mCameraLiftTriggerSensor, 0);
+                } else {
+                    String message = String.format("Wrong configuration. Sensor type and sensor "
+                            + "string type don't match: %s in resources, %s in the sensor.",
+                            sensorName, mCameraLiftTriggerSensor.getStringType());
+                    throw new RuntimeException(message);
+                }
+            }
+            if (DBG) Slog.d(TAG, "Camera lift trigger sensor registered: " + mCameraLiftRegistered);
+        } else {
+            if (DBG) Slog.d(TAG, "Camera lift trigger sensor is not specified.");
+        }
+    }
+
     public static boolean isCameraLaunchSettingEnabled(Context context, int userId) {
         return isCameraLaunchEnabled(context.getResources())
                 && (Settings.Secure.getIntForUser(context.getContentResolver(),
@@ -248,6 +310,12 @@
                         Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
     }
 
+    public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
+        return isCameraLiftTriggerEnabled(context.getResources())
+                && (Settings.Secure.getIntForUser(context.getContentResolver(),
+                        Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, 0, userId) != 0);
+    }
+
     /**
      * Whether to enable the camera launch gesture.
      */
@@ -263,11 +331,18 @@
                 com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled);
     }
 
+    public static boolean isCameraLiftTriggerEnabled(Resources resources) {
+        boolean configSet = resources.getInteger(
+                com.android.internal.R.integer.config_cameraLiftTriggerSensorType) != -1;
+        return configSet;
+    }
+
     /**
      * Whether GestureLauncherService should be enabled according to system properties.
      */
     public static boolean isGestureLauncherEnabled(Resources resources) {
-        return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources);
+        return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources) ||
+                isCameraLiftTriggerEnabled(resources);
     }
 
     public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
@@ -296,7 +371,7 @@
         if (launched) {
             Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval="
                     + powerTapInterval + "ms");
-            launched = handleCameraLaunchGesture(false /* useWakelock */,
+            launched = handleCameraGesture(false /* useWakelock */,
                     StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
             if (launched) {
                 mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
@@ -313,17 +388,17 @@
      * @return true if camera was launched, false otherwise.
      */
     @VisibleForTesting
-    boolean handleCameraLaunchGesture(boolean useWakelock, int source) {
+    boolean handleCameraGesture(boolean useWakelock, int source) {
         boolean userSetupComplete = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
         if (!userSetupComplete) {
             if (DBG) Slog.d(TAG, String.format(
-                    "userSetupComplete = %s, ignoring camera launch gesture.",
+                    "userSetupComplete = %s, ignoring camera gesture.",
                     userSetupComplete));
             return false;
         }
         if (DBG) Slog.d(TAG, String.format(
-                "userSetupComplete = %s, performing camera launch gesture.",
+                "userSetupComplete = %s, performing camera gesture.",
                 userSetupComplete));
 
         if (useWakelock) {
@@ -361,7 +436,7 @@
     private final class GestureEventListener implements SensorEventListener {
         @Override
         public void onSensorChanged(SensorEvent event) {
-            if (!mRegistered) {
+            if (!mCameraLaunchRegistered) {
               if (DBG) Slog.d(TAG, "Ignoring gesture event because it's unregistered.");
               return;
             }
@@ -371,7 +446,7 @@
                     Slog.d(TAG, String.format("Received a camera launch event: " +
                             "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
                 }
-                if (handleCameraLaunchGesture(true /* useWakelock */,
+                if (handleCameraGesture(true /* useWakelock */,
                         StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
                     mMetricsLogger.action(MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
                     trackCameraLaunchEvent(event);
@@ -430,4 +505,31 @@
             mCameraLaunchLastEventExtra = extra;
         }
     }
+
+    private final class CameraLiftTriggerEventListener implements SensorEventListener {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (!mCameraLiftRegistered) {
+              if (DBG) Slog.d(TAG, "Ignoring camera lift event because it's unregistered.");
+              return;
+            }
+            if (event.sensor == mCameraLiftTriggerSensor) {
+                if (DBG) {
+                    float[] values = event.values;
+                    Slog.d(TAG, String.format("Received a camera lift trigger event: " +
+                            "values=[%.4f].", values[0]));
+                }
+                if (handleCameraGesture(true /* useWakelock */,
+                        StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) {
+                    MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER);
+                }
+                return;
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Ignored.
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index f281bd7..33e4165 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -154,7 +154,7 @@
         withUserSetupCompleteValue(true);
 
         boolean useWakeLock = false;
-        assertTrue(mGestureLauncherService.handleCameraLaunchGesture(useWakeLock, FAKE_SOURCE));
+        assertTrue(mGestureLauncherService.handleCameraGesture(useWakeLock, FAKE_SOURCE));
         verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(FAKE_SOURCE);
     }
 
@@ -163,7 +163,7 @@
         withUserSetupCompleteValue(false);
 
         boolean useWakeLock = false;
-        assertFalse(mGestureLauncherService.handleCameraLaunchGesture(useWakeLock, FAKE_SOURCE));
+        assertFalse(mGestureLauncherService.handleCameraGesture(useWakeLock, FAKE_SOURCE));
     }
 
     @Test