Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.StatusBarManager;
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.res.Resources;
     26 import android.database.ContentObserver;
     27 import android.hardware.Sensor;
     28 import android.hardware.SensorEvent;
     29 import android.hardware.SensorEventListener;
     30 import android.hardware.SensorManager;
     31 import android.os.Handler;
     32 import android.os.PowerManager;
     33 import android.os.PowerManager.WakeLock;
     34 import android.os.SystemClock;
     35 import android.os.SystemProperties;
     36 import android.os.UserHandle;
     37 import android.provider.Settings;
     38 import android.util.MutableBoolean;
     39 import android.util.Slog;
     40 import android.view.KeyEvent;
     41 
     42 import com.android.internal.logging.MetricsLogger;
     43 import com.android.internal.logging.MetricsProto.MetricsEvent;
     44 import com.android.server.statusbar.StatusBarManagerInternal;
     45 
     46 /**
     47  * The service that listens for gestures detected in sensor firmware and starts the intent
     48  * accordingly.
     49  * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be
     50  * added.</p>
     51  * @hide
     52  */
     53 public class GestureLauncherService extends SystemService {
     54     private static final boolean DBG = false;
     55     private static final String TAG = "GestureLauncherService";
     56 
     57     /**
     58      * Time in milliseconds in which the power button must be pressed twice so it will be considered
     59      * as a camera launch.
     60      */
     61     private static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
     62 
     63     /** The listener that receives the gesture event. */
     64     private final GestureEventListener mGestureListener = new GestureEventListener();
     65 
     66     private Sensor mCameraLaunchSensor;
     67     private Context mContext;
     68 
     69     /** The wake lock held when a gesture is detected. */
     70     private WakeLock mWakeLock;
     71     private boolean mRegistered;
     72     private int mUserId;
     73 
     74     // Below are fields used for event logging only.
     75     /** Elapsed real time when the camera gesture is turned on. */
     76     private long mCameraGestureOnTimeMs = 0L;
     77 
     78     /** Elapsed real time when the last camera gesture was detected. */
     79     private long mCameraGestureLastEventTime = 0L;
     80 
     81     /**
     82      * How long the sensor 1 has been turned on since camera launch sensor was
     83      * subscribed to and when the last camera launch gesture was detected.
     84      * <p>Sensor 1 is the main sensor used to detect camera launch gesture.</p>
     85      */
     86     private long mCameraGestureSensor1LastOnTimeMs = 0L;
     87 
     88     /**
     89      * If applicable, how long the sensor 2 has been turned on since camera
     90      * launch sensor was subscribed to and when the last camera launch
     91      * gesture was detected.
     92      * <p>Sensor 2 is the secondary sensor used to detect camera launch gesture.
     93      * This is optional and if only sensor 1 is used for detect camera launch
     94      * gesture, this value would always be 0.</p>
     95      */
     96     private long mCameraGestureSensor2LastOnTimeMs = 0L;
     97 
     98     /**
     99      * Extra information about the event when the last camera launch gesture
    100      * was detected.
    101      */
    102     private int mCameraLaunchLastEventExtra = 0;
    103 
    104     /**
    105      * Whether camera double tap power button gesture is currently enabled;
    106      */
    107     private boolean mCameraDoubleTapPowerEnabled;
    108     private long mLastPowerDown;
    109 
    110     public GestureLauncherService(Context context) {
    111         super(context);
    112         mContext = context;
    113     }
    114 
    115     public void onStart() {
    116         LocalServices.addService(GestureLauncherService.class, this);
    117     }
    118 
    119     public void onBootPhase(int phase) {
    120         if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
    121             Resources resources = mContext.getResources();
    122             if (!isGestureLauncherEnabled(resources)) {
    123                 if (DBG) Slog.d(TAG, "Gesture launcher is disabled in system properties.");
    124                 return;
    125             }
    126 
    127             PowerManager powerManager = (PowerManager) mContext.getSystemService(
    128                     Context.POWER_SERVICE);
    129             mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    130                     "GestureLauncherService");
    131             updateCameraRegistered();
    132             updateCameraDoubleTapPowerEnabled();
    133 
    134             mUserId = ActivityManager.getCurrentUser();
    135             mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
    136             registerContentObservers();
    137         }
    138     }
    139 
    140     private void registerContentObservers() {
    141         mContext.getContentResolver().registerContentObserver(
    142                 Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
    143                 false, mSettingObserver, mUserId);
    144         mContext.getContentResolver().registerContentObserver(
    145                 Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
    146                 false, mSettingObserver, mUserId);
    147     }
    148 
    149     private void updateCameraRegistered() {
    150         Resources resources = mContext.getResources();
    151         if (isCameraLaunchSettingEnabled(mContext, mUserId)) {
    152             registerCameraLaunchGesture(resources);
    153         } else {
    154             unregisterCameraLaunchGesture();
    155         }
    156     }
    157 
    158     private void updateCameraDoubleTapPowerEnabled() {
    159         boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId);
    160         synchronized (this) {
    161             mCameraDoubleTapPowerEnabled = enabled;
    162         }
    163     }
    164 
    165     private void unregisterCameraLaunchGesture() {
    166         if (mRegistered) {
    167             mRegistered = false;
    168             mCameraGestureOnTimeMs = 0L;
    169             mCameraGestureLastEventTime = 0L;
    170             mCameraGestureSensor1LastOnTimeMs = 0;
    171             mCameraGestureSensor2LastOnTimeMs = 0;
    172             mCameraLaunchLastEventExtra = 0;
    173 
    174             SensorManager sensorManager = (SensorManager) mContext.getSystemService(
    175                     Context.SENSOR_SERVICE);
    176             sensorManager.unregisterListener(mGestureListener);
    177         }
    178     }
    179 
    180     /**
    181      * Registers for the camera launch gesture.
    182      */
    183     private void registerCameraLaunchGesture(Resources resources) {
    184         if (mRegistered) {
    185             return;
    186         }
    187         mCameraGestureOnTimeMs = SystemClock.elapsedRealtime();
    188         mCameraGestureLastEventTime = mCameraGestureOnTimeMs;
    189         SensorManager sensorManager = (SensorManager) mContext.getSystemService(
    190                 Context.SENSOR_SERVICE);
    191         int cameraLaunchGestureId = resources.getInteger(
    192                 com.android.internal.R.integer.config_cameraLaunchGestureSensorType);
    193         if (cameraLaunchGestureId != -1) {
    194             mRegistered = false;
    195             String sensorName = resources.getString(
    196                 com.android.internal.R.string.config_cameraLaunchGestureSensorStringType);
    197             mCameraLaunchSensor = sensorManager.getDefaultSensor(
    198                     cameraLaunchGestureId,
    199                     true /*wakeUp*/);
    200 
    201             // Compare the camera gesture string type to that in the resource file to make
    202             // sure we are registering the correct sensor. This is redundant check, it
    203             // makes the code more robust.
    204             if (mCameraLaunchSensor != null) {
    205                 if (sensorName.equals(mCameraLaunchSensor.getStringType())) {
    206                     mRegistered = sensorManager.registerListener(mGestureListener,
    207                             mCameraLaunchSensor, 0);
    208                 } else {
    209                     String message = String.format("Wrong configuration. Sensor type and sensor "
    210                             + "string type don't match: %s in resources, %s in the sensor.",
    211                             sensorName, mCameraLaunchSensor.getStringType());
    212                     throw new RuntimeException(message);
    213                 }
    214             }
    215             if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mRegistered);
    216         } else {
    217             if (DBG) Slog.d(TAG, "Camera launch sensor is not specified.");
    218         }
    219     }
    220 
    221     public static boolean isCameraLaunchSettingEnabled(Context context, int userId) {
    222         return isCameraLaunchEnabled(context.getResources())
    223                 && (Settings.Secure.getIntForUser(context.getContentResolver(),
    224                         Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
    225     }
    226 
    227     public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
    228         return isCameraDoubleTapPowerEnabled(context.getResources())
    229                 && (Settings.Secure.getIntForUser(context.getContentResolver(),
    230                         Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
    231     }
    232 
    233     /**
    234      * Whether to enable the camera launch gesture.
    235      */
    236     public static boolean isCameraLaunchEnabled(Resources resources) {
    237         boolean configSet = resources.getInteger(
    238                 com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1;
    239         return configSet &&
    240                 !SystemProperties.getBoolean("gesture.disable_camera_launch", false);
    241     }
    242 
    243     public static boolean isCameraDoubleTapPowerEnabled(Resources resources) {
    244         return resources.getBoolean(
    245                 com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled);
    246     }
    247 
    248     /**
    249      * Whether GestureLauncherService should be enabled according to system properties.
    250      */
    251     public static boolean isGestureLauncherEnabled(Resources resources) {
    252         return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources);
    253     }
    254 
    255     public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
    256             MutableBoolean outLaunched) {
    257         boolean launched = false;
    258         boolean intercept = false;
    259         long doubleTapInterval;
    260         synchronized (this) {
    261             doubleTapInterval = event.getEventTime() - mLastPowerDown;
    262             if (mCameraDoubleTapPowerEnabled
    263                     && doubleTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
    264                 launched = true;
    265                 intercept = interactive;
    266             }
    267             mLastPowerDown = event.getEventTime();
    268         }
    269         if (launched) {
    270             Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval="
    271                     + doubleTapInterval + "ms");
    272             launched = handleCameraLaunchGesture(false /* useWakelock */,
    273                     StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
    274             if (launched) {
    275                 MetricsLogger.action(mContext, MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
    276                         (int) doubleTapInterval);
    277             }
    278         }
    279         MetricsLogger.histogram(mContext, "power_double_tap_interval", (int) doubleTapInterval);
    280         outLaunched.value = launched;
    281         return intercept && launched;
    282     }
    283 
    284     /**
    285      * @return true if camera was launched, false otherwise.
    286      */
    287     private boolean handleCameraLaunchGesture(boolean useWakelock, int source) {
    288         boolean userSetupComplete = Settings.Secure.getIntForUser(mContext.getContentResolver(),
    289                 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
    290         if (!userSetupComplete) {
    291             if (DBG) Slog.d(TAG, String.format(
    292                     "userSetupComplete = %s, ignoring camera launch gesture.",
    293                     userSetupComplete));
    294             return false;
    295         }
    296         if (DBG) Slog.d(TAG, String.format(
    297                 "userSetupComplete = %s, performing camera launch gesture.",
    298                 userSetupComplete));
    299 
    300         if (useWakelock) {
    301             // Make sure we don't sleep too early
    302             mWakeLock.acquire(500L);
    303         }
    304         StatusBarManagerInternal service = LocalServices.getService(
    305                 StatusBarManagerInternal.class);
    306         service.onCameraLaunchGestureDetected(source);
    307         return true;
    308     }
    309 
    310     private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
    311         @Override
    312         public void onReceive(Context context, Intent intent) {
    313             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
    314                 mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
    315                 mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
    316                 registerContentObservers();
    317                 updateCameraRegistered();
    318                 updateCameraDoubleTapPowerEnabled();
    319             }
    320         }
    321     };
    322 
    323     private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) {
    324         public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
    325             if (userId == mUserId) {
    326                 updateCameraRegistered();
    327                 updateCameraDoubleTapPowerEnabled();
    328             }
    329         }
    330     };
    331 
    332     private final class GestureEventListener implements SensorEventListener {
    333         @Override
    334         public void onSensorChanged(SensorEvent event) {
    335             if (!mRegistered) {
    336               if (DBG) Slog.d(TAG, "Ignoring gesture event because it's unregistered.");
    337               return;
    338             }
    339             if (event.sensor == mCameraLaunchSensor) {
    340                 if (DBG) {
    341                     float[] values = event.values;
    342                     Slog.d(TAG, String.format("Received a camera launch event: " +
    343                             "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
    344                 }
    345                 if (handleCameraLaunchGesture(true /* useWakelock */,
    346                         StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
    347                     MetricsLogger.action(mContext, MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
    348                     trackCameraLaunchEvent(event);
    349                 }
    350                 return;
    351             }
    352         }
    353 
    354         @Override
    355         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    356             // Ignored.
    357         }
    358 
    359         private void trackCameraLaunchEvent(SensorEvent event) {
    360             long now = SystemClock.elapsedRealtime();
    361             long totalDuration = now - mCameraGestureOnTimeMs;
    362             // values[0]: ratio between total time duration when accel is turned on and time
    363             //            duration since camera launch gesture is subscribed.
    364             // values[1]: ratio between total time duration when gyro is turned on and time duration
    365             //            since camera launch gesture is subscribed.
    366             // values[2]: extra information
    367             float[] values = event.values;
    368 
    369             long sensor1OnTime = (long) (totalDuration * (double) values[0]);
    370             long sensor2OnTime = (long) (totalDuration * (double) values[1]);
    371             int extra = (int) values[2];
    372 
    373             // We only log the difference in the event log to make aggregation easier.
    374             long gestureOnTimeDiff = now - mCameraGestureLastEventTime;
    375             long sensor1OnTimeDiff = sensor1OnTime - mCameraGestureSensor1LastOnTimeMs;
    376             long sensor2OnTimeDiff = sensor2OnTime - mCameraGestureSensor2LastOnTimeMs;
    377             int extraDiff = extra - mCameraLaunchLastEventExtra;
    378 
    379             // Gating against negative time difference. This doesn't usually happen, but it may
    380             // happen because of numeric errors.
    381             if (gestureOnTimeDiff < 0 || sensor1OnTimeDiff < 0 || sensor2OnTimeDiff < 0) {
    382                 if (DBG) Slog.d(TAG, "Skipped event logging because negative numbers.");
    383                 return;
    384             }
    385 
    386             if (DBG) Slog.d(TAG, String.format("totalDuration: %d, sensor1OnTime: %s, " +
    387                     "sensor2OnTime: %d, extra: %d",
    388                     gestureOnTimeDiff,
    389                     sensor1OnTimeDiff,
    390                     sensor2OnTimeDiff,
    391                     extraDiff));
    392             EventLogTags.writeCameraGestureTriggered(
    393                     gestureOnTimeDiff,
    394                     sensor1OnTimeDiff,
    395                     sensor2OnTimeDiff,
    396                     extraDiff);
    397 
    398             mCameraGestureLastEventTime = now;
    399             mCameraGestureSensor1LastOnTimeMs = sensor1OnTime;
    400             mCameraGestureSensor2LastOnTimeMs = sensor2OnTime;
    401             mCameraLaunchLastEventExtra = extra;
    402         }
    403     }
    404 }
    405