Add double-tap power button to open camera 1/2

Bug: 23787555
Change-Id: I052b64748f155c59fbb649b32265f559423a8845
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 78be3cd..2053dbe 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5719,6 +5719,15 @@
         public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled";
 
         /**
+         * Whether the camera launch gesture to double tap the power button when the screen is off
+         * should be disabled.
+         *
+         * @hide
+         */
+        public static final String CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED =
+                "camera_double_tap_power_gesture_disabled";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 6da0f63..b6240e4 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -44,6 +44,8 @@
     public static final int ACTION_FINGERPRINT_AUTH = 252;
     public static final int ACTION_FINGERPRINT_DELETE = 253;
     public static final int ACTION_FINGERPRINT_RENAME = 254;
+    public static final int ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE = 255;
+    public static final int ACTION_WIGGLE_CAMERA_GESTURE = 256;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d7658d1..f711c80 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2290,4 +2290,8 @@
     <!-- The OEM specified sensor string type for the gesture to launch camera app, this value
          must match the value of config_cameraLaunchGestureSensorType in OEM's HAL -->
     <string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string>
+
+    <!-- Allow the gesture to double tap the power button twice to start the camera while the device
+         is non-interactive. -->
+    <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4c8e55e..0e85f43 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2323,6 +2323,7 @@
   <!-- Gesture -->
   <java-symbol type="integer" name="config_cameraLaunchGestureSensorType" />
   <java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
+  <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
 
   <java-symbol type="drawable" name="platlogo_m" />
 </resources>
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 342a3ef..69f0cef 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -17,7 +17,6 @@
 package com.android.server;
 
 import android.app.ActivityManager;
-import android.app.KeyguardManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -33,10 +32,11 @@
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.Vibrator;
 import android.provider.Settings;
 import android.util.Slog;
+import android.view.KeyEvent;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 /**
@@ -46,10 +46,16 @@
  * added.</p>
  * @hide
  */
-class GestureLauncherService extends SystemService {
+public class GestureLauncherService extends SystemService {
     private static final boolean DBG = false;
     private static final String TAG = "GestureLauncherService";
 
+    /**
+     * Time in milliseconds in which the power button must be pressed twice so it will be considered
+     * as a camera launch.
+     */
+    private static final long CAMERA_POWER_DOUBLE_TAP_TIME_MS = 300;
+
     /** The listener that receives the gesture event. */
     private final GestureEventListener mGestureListener = new GestureEventListener();
 
@@ -91,13 +97,20 @@
      */
     private int mCameraLaunchLastEventExtra = 0;
 
+    /**
+     * Whether camera double tap power button gesture is currently enabled;
+     */
+    private boolean mCameraDoubleTapPowerEnabled;
+    private long mLastPowerDownWhileNonInteractive = 0;
+
+
     public GestureLauncherService(Context context) {
         super(context);
         mContext = context;
     }
 
     public void onStart() {
-        // Nothing to publish.
+        LocalServices.addService(GestureLauncherService.class, this);
     }
 
     public void onBootPhase(int phase) {
@@ -113,17 +126,21 @@
             mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                     "GestureLauncherService");
             updateCameraRegistered();
+            updateCameraDoubleTapPowerEnabled();
 
             mUserId = ActivityManager.getCurrentUser();
             mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
-            registerContentObserver();
+            registerContentObservers();
         }
     }
 
-    private void registerContentObserver() {
+    private void registerContentObservers() {
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
                 false, mSettingObserver, mUserId);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
+                false, mSettingObserver, mUserId);
     }
 
     private void updateCameraRegistered() {
@@ -135,6 +152,13 @@
         }
     }
 
+    private void updateCameraDoubleTapPowerEnabled() {
+        boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId);
+        synchronized (this) {
+            mCameraDoubleTapPowerEnabled = enabled;
+        }
+    }
+
     private void unregisterCameraLaunchGesture() {
         if (mRegistered) {
             mRegistered = false;
@@ -197,6 +221,12 @@
                         Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
     }
 
+    public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
+        return isCameraDoubleTapPowerEnabled(context.getResources())
+                && (Settings.Secure.getIntForUser(context.getContentResolver(),
+                        Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
+    }
+
     /**
      * Whether to enable the camera launch gesture.
      */
@@ -207,13 +237,64 @@
                 !SystemProperties.getBoolean("gesture.disable_camera_launch", false);
     }
 
+    public static boolean isCameraDoubleTapPowerEnabled(Resources resources) {
+        return resources.getBoolean(
+                com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled);
+    }
+
     /**
      * Whether GestureLauncherService should be enabled according to system properties.
      */
     public static boolean isGestureLauncherEnabled(Resources resources) {
-        // For now, the only supported gesture is camera launch gesture, so whether to enable this
-        // service equals to isCameraLaunchEnabled();
-        return isCameraLaunchEnabled(resources);
+        return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources);
+    }
+
+    public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive) {
+        boolean launched = false;
+        synchronized (this) {
+            if (!mCameraDoubleTapPowerEnabled) {
+                mLastPowerDownWhileNonInteractive = 0;
+                return false;
+            }
+            if (event.getEventTime() - mLastPowerDownWhileNonInteractive
+                    < CAMERA_POWER_DOUBLE_TAP_TIME_MS) {
+                launched = true;
+            }
+            mLastPowerDownWhileNonInteractive = interactive ? 0 : event.getEventTime();
+        }
+        if (launched) {
+            Slog.i(TAG, "Power button double tap gesture detected, launching camera.");
+            launched = handleCameraLaunchGesture(false /* useWakelock */,
+                    MetricsLogger.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE);
+        }
+        return launched;
+    }
+
+    /**
+     * @return true if camera was launched, false otherwise.
+     */
+    private boolean handleCameraLaunchGesture(boolean useWakelock, int logCategory) {
+        boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
+        if (!userSetupComplete) {
+            if (DBG) Slog.d(TAG, String.format(
+                    "userSetupComplete = %s, ignoring camera launch gesture.",
+                    userSetupComplete));
+            return false;
+        }
+        if (DBG) Slog.d(TAG, String.format(
+                "userSetupComplete = %s, performing camera launch gesture.",
+                userSetupComplete));
+
+        if (useWakelock) {
+            // Make sure we don't sleep too early
+            mWakeLock.acquire(500L);
+        }
+        StatusBarManagerInternal service = LocalServices.getService(
+                StatusBarManagerInternal.class);
+        service.onCameraLaunchGestureDetected();
+        MetricsLogger.action(mContext, logCategory);
+        return true;
     }
 
     private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
@@ -222,8 +303,9 @@
             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
                 mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                 mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
-                registerContentObserver();
+                registerContentObservers();
                 updateCameraRegistered();
+                updateCameraDoubleTapPowerEnabled();
             }
         }
     };
@@ -232,6 +314,7 @@
         public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
             if (userId == mUserId) {
                 updateCameraRegistered();
+                updateCameraDoubleTapPowerEnabled();
             }
         }
     };
@@ -244,38 +327,19 @@
               return;
             }
             if (event.sensor == mCameraLaunchSensor) {
-                handleCameraLaunchGesture(event);
+                if (DBG) {
+                    float[] values = event.values;
+                    Slog.d(TAG, String.format("Received a camera launch event: " +
+                            "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
+                }
+                if (handleCameraLaunchGesture(true /* useWakelock */,
+                        MetricsLogger.ACTION_WIGGLE_CAMERA_GESTURE)) {
+                    trackCameraLaunchEvent(event);
+                }
                 return;
             }
         }
 
-        private void handleCameraLaunchGesture(SensorEvent event) {
-            if (DBG) {
-                float[] values = event.values;
-                Slog.d(TAG, String.format("Received a camera launch event: " +
-                      "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
-            }
-            boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
-            if (!userSetupComplete) {
-                if (DBG) Slog.d(TAG, String.format(
-                        "userSetupComplete = %s, ignoring camera launch gesture.",
-                        userSetupComplete));
-                return;
-            }
-            if (DBG) Slog.d(TAG, String.format(
-                    "userSetupComplete = %s, performing camera launch gesture.",
-                    userSetupComplete));
-
-            // Make sure we don't sleep too early
-            mWakeLock.acquire(500L);
-            StatusBarManagerInternal service = LocalServices.getService(
-                    StatusBarManagerInternal.class);
-            service.onCameraLaunchGestureDetected();
-            trackCameraLaunchEvent(event);
-            mWakeLock.release();
-        }
-
         @Override
         public void onAccuracyChanged(Sensor sensor, int accuracy) {
             // Ignored.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 34737c1..d7e3c54 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -117,6 +117,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ScreenShapeHelper;
 import com.android.internal.widget.PointerLocationView;
+import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener;
@@ -125,7 +126,6 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 
@@ -931,10 +931,17 @@
             }
         }
 
+        GestureLauncherService gestureService = LocalServices.getService(
+                GestureLauncherService.class);
+        boolean gesturedServiceIntercepted = false;
+        if (gestureService != null) {
+            gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive);
+        }
+
         // If the power key has still not yet been handled, then detect short
         // press, long press, or multi press and decide what to do.
         mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
-                || mScreenshotChordVolumeUpKeyTriggered;
+                || mScreenshotChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
         if (!mPowerKeyHandled) {
             if (interactive) {
                 // When interactive, we're already awake.