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