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