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.hardware.TriggerEvent; 32 import android.hardware.TriggerEventListener; 33 import android.os.Handler; 34 import android.os.PowerManager; 35 import android.os.PowerManager.WakeLock; 36 import android.os.SystemClock; 37 import android.os.SystemProperties; 38 import android.os.UserHandle; 39 import android.provider.Settings; 40 import android.util.MutableBoolean; 41 import android.util.Slog; 42 import android.view.KeyEvent; 43 import android.view.WindowManagerInternal; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.internal.logging.MetricsLogger; 47 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 48 import com.android.server.LocalServices; 49 import com.android.server.statusbar.StatusBarManagerInternal; 50 51 /** 52 * The service that listens for gestures detected in sensor firmware and starts the intent 53 * accordingly. 54 * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be 55 * added.</p> 56 * @hide 57 */ 58 public class GestureLauncherService extends SystemService { 59 private static final boolean DBG = false; 60 private static final boolean DBG_CAMERA_LIFT = false; 61 private static final String TAG = "GestureLauncherService"; 62 63 /** 64 * Time in milliseconds in which the power button must be pressed twice so it will be considered 65 * as a camera launch. 66 */ 67 @VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300; 68 69 /** 70 * Interval in milliseconds in which the power button must be depressed in succession to be 71 * considered part of an extended sequence of taps. Note that this is a looser threshold than 72 * the camera launch gesture, because the purpose of this threshold is to measure the 73 * frequency of consecutive taps, for evaluation for future gestures. 74 */ 75 @VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500; 76 77 /** The listener that receives the gesture event. */ 78 private final GestureEventListener mGestureListener = new GestureEventListener(); 79 private final CameraLiftTriggerEventListener mCameraLiftTriggerListener = 80 new CameraLiftTriggerEventListener(); 81 82 private Sensor mCameraLaunchSensor; 83 private Sensor mCameraLiftTriggerSensor; 84 private Context mContext; 85 private final MetricsLogger mMetricsLogger; 86 private PowerManager mPowerManager; 87 private WindowManagerInternal mWindowManagerInternal; 88 89 /** The wake lock held when a gesture is detected. */ 90 private WakeLock mWakeLock; 91 private boolean mCameraLaunchRegistered; 92 private boolean mCameraLiftRegistered; 93 private int mUserId; 94 95 // Below are fields used for event logging only. 96 /** Elapsed real time when the camera gesture is turned on. */ 97 private long mCameraGestureOnTimeMs = 0L; 98 99 /** Elapsed real time when the last camera gesture was detected. */ 100 private long mCameraGestureLastEventTime = 0L; 101 102 /** 103 * How long the sensor 1 has been turned on since camera launch sensor was 104 * subscribed to and when the last camera launch gesture was detected. 105 * <p>Sensor 1 is the main sensor used to detect camera launch gesture.</p> 106 */ 107 private long mCameraGestureSensor1LastOnTimeMs = 0L; 108 109 /** 110 * If applicable, how long the sensor 2 has been turned on since camera 111 * launch sensor was subscribed to and when the last camera launch 112 * gesture was detected. 113 * <p>Sensor 2 is the secondary sensor used to detect camera launch gesture. 114 * This is optional and if only sensor 1 is used for detect camera launch 115 * gesture, this value would always be 0.</p> 116 */ 117 private long mCameraGestureSensor2LastOnTimeMs = 0L; 118 119 /** 120 * Extra information about the event when the last camera launch gesture 121 * was detected. 122 */ 123 private int mCameraLaunchLastEventExtra = 0; 124 125 /** 126 * Whether camera double tap power button gesture is currently enabled; 127 */ 128 private boolean mCameraDoubleTapPowerEnabled; 129 private long mLastPowerDown; 130 private int mPowerButtonConsecutiveTaps; 131 132 public GestureLauncherService(Context context) { 133 this(context, new MetricsLogger()); 134 } 135 136 @VisibleForTesting 137 GestureLauncherService(Context context, MetricsLogger metricsLogger) { 138 super(context); 139 mContext = context; 140 mMetricsLogger = metricsLogger; 141 } 142 143 public void onStart() { 144 LocalServices.addService(GestureLauncherService.class, this); 145 } 146 147 public void onBootPhase(int phase) { 148 if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 149 Resources resources = mContext.getResources(); 150 if (!isGestureLauncherEnabled(resources)) { 151 if (DBG) Slog.d(TAG, "Gesture launcher is disabled in system properties."); 152 return; 153 } 154 155 mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); 156 mPowerManager = (PowerManager) mContext.getSystemService( 157 Context.POWER_SERVICE); 158 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 159 "GestureLauncherService"); 160 updateCameraRegistered(); 161 updateCameraDoubleTapPowerEnabled(); 162 163 mUserId = ActivityManager.getCurrentUser(); 164 mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED)); 165 registerContentObservers(); 166 } 167 } 168 169 private void registerContentObservers() { 170 mContext.getContentResolver().registerContentObserver( 171 Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED), 172 false, mSettingObserver, mUserId); 173 mContext.getContentResolver().registerContentObserver( 174 Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED), 175 false, mSettingObserver, mUserId); 176 mContext.getContentResolver().registerContentObserver( 177 Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED), 178 false, mSettingObserver, mUserId); 179 } 180 181 private void updateCameraRegistered() { 182 Resources resources = mContext.getResources(); 183 if (isCameraLaunchSettingEnabled(mContext, mUserId)) { 184 registerCameraLaunchGesture(resources); 185 } else { 186 unregisterCameraLaunchGesture(); 187 } 188 189 if (isCameraLiftTriggerSettingEnabled(mContext, mUserId)) { 190 registerCameraLiftTrigger(resources); 191 } else { 192 unregisterCameraLiftTrigger(); 193 } 194 } 195 196 @VisibleForTesting 197 void updateCameraDoubleTapPowerEnabled() { 198 boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId); 199 synchronized (this) { 200 mCameraDoubleTapPowerEnabled = enabled; 201 } 202 } 203 204 private void unregisterCameraLaunchGesture() { 205 if (mCameraLaunchRegistered) { 206 mCameraLaunchRegistered = false; 207 mCameraGestureOnTimeMs = 0L; 208 mCameraGestureLastEventTime = 0L; 209 mCameraGestureSensor1LastOnTimeMs = 0; 210 mCameraGestureSensor2LastOnTimeMs = 0; 211 mCameraLaunchLastEventExtra = 0; 212 213 SensorManager sensorManager = (SensorManager) mContext.getSystemService( 214 Context.SENSOR_SERVICE); 215 sensorManager.unregisterListener(mGestureListener); 216 } 217 } 218 219 /** 220 * Registers for the camera launch gesture. 221 */ 222 private void registerCameraLaunchGesture(Resources resources) { 223 if (mCameraLaunchRegistered) { 224 return; 225 } 226 mCameraGestureOnTimeMs = SystemClock.elapsedRealtime(); 227 mCameraGestureLastEventTime = mCameraGestureOnTimeMs; 228 SensorManager sensorManager = (SensorManager) mContext.getSystemService( 229 Context.SENSOR_SERVICE); 230 int cameraLaunchGestureId = resources.getInteger( 231 com.android.internal.R.integer.config_cameraLaunchGestureSensorType); 232 if (cameraLaunchGestureId != -1) { 233 mCameraLaunchRegistered = false; 234 String sensorName = resources.getString( 235 com.android.internal.R.string.config_cameraLaunchGestureSensorStringType); 236 mCameraLaunchSensor = sensorManager.getDefaultSensor( 237 cameraLaunchGestureId, 238 true /*wakeUp*/); 239 240 // Compare the camera gesture string type to that in the resource file to make 241 // sure we are registering the correct sensor. This is redundant check, it 242 // makes the code more robust. 243 if (mCameraLaunchSensor != null) { 244 if (sensorName.equals(mCameraLaunchSensor.getStringType())) { 245 mCameraLaunchRegistered = sensorManager.registerListener(mGestureListener, 246 mCameraLaunchSensor, 0); 247 } else { 248 String message = String.format("Wrong configuration. Sensor type and sensor " 249 + "string type don't match: %s in resources, %s in the sensor.", 250 sensorName, mCameraLaunchSensor.getStringType()); 251 throw new RuntimeException(message); 252 } 253 } 254 if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mCameraLaunchRegistered); 255 } else { 256 if (DBG) Slog.d(TAG, "Camera launch sensor is not specified."); 257 } 258 } 259 260 private void unregisterCameraLiftTrigger() { 261 if (mCameraLiftRegistered) { 262 mCameraLiftRegistered = false; 263 264 SensorManager sensorManager = (SensorManager) mContext.getSystemService( 265 Context.SENSOR_SERVICE); 266 sensorManager.cancelTriggerSensor(mCameraLiftTriggerListener, mCameraLiftTriggerSensor); 267 } 268 } 269 270 /** 271 * Registers for the camera lift trigger. 272 */ 273 private void registerCameraLiftTrigger(Resources resources) { 274 if (mCameraLiftRegistered) { 275 return; 276 } 277 SensorManager sensorManager = (SensorManager) mContext.getSystemService( 278 Context.SENSOR_SERVICE); 279 int cameraLiftTriggerId = resources.getInteger( 280 com.android.internal.R.integer.config_cameraLiftTriggerSensorType); 281 if (cameraLiftTriggerId != -1) { 282 mCameraLiftRegistered = false; 283 String sensorName = resources.getString( 284 com.android.internal.R.string.config_cameraLiftTriggerSensorStringType); 285 mCameraLiftTriggerSensor = sensorManager.getDefaultSensor( 286 cameraLiftTriggerId, 287 true /*wakeUp*/); 288 289 // Compare the camera lift trigger string type to that in the resource file to make 290 // sure we are registering the correct sensor. This is redundant check, it 291 // makes the code more robust. 292 if (mCameraLiftTriggerSensor != null) { 293 if (sensorName.equals(mCameraLiftTriggerSensor.getStringType())) { 294 mCameraLiftRegistered = sensorManager.requestTriggerSensor(mCameraLiftTriggerListener, 295 mCameraLiftTriggerSensor); 296 } else { 297 String message = String.format("Wrong configuration. Sensor type and sensor " 298 + "string type don't match: %s in resources, %s in the sensor.", 299 sensorName, mCameraLiftTriggerSensor.getStringType()); 300 throw new RuntimeException(message); 301 } 302 } 303 if (DBG) Slog.d(TAG, "Camera lift trigger sensor registered: " + mCameraLiftRegistered); 304 } else { 305 if (DBG) Slog.d(TAG, "Camera lift trigger sensor is not specified."); 306 } 307 } 308 309 public static boolean isCameraLaunchSettingEnabled(Context context, int userId) { 310 return isCameraLaunchEnabled(context.getResources()) 311 && (Settings.Secure.getIntForUser(context.getContentResolver(), 312 Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0); 313 } 314 315 public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) { 316 return isCameraDoubleTapPowerEnabled(context.getResources()) 317 && (Settings.Secure.getIntForUser(context.getContentResolver(), 318 Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0); 319 } 320 321 public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) { 322 return isCameraLiftTriggerEnabled(context.getResources()) 323 && (Settings.Secure.getIntForUser(context.getContentResolver(), 324 Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, 325 Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED_DEFAULT, userId) != 0); 326 } 327 328 /** 329 * Whether to enable the camera launch gesture. 330 */ 331 public static boolean isCameraLaunchEnabled(Resources resources) { 332 boolean configSet = resources.getInteger( 333 com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1; 334 return configSet && 335 !SystemProperties.getBoolean("gesture.disable_camera_launch", false); 336 } 337 338 public static boolean isCameraDoubleTapPowerEnabled(Resources resources) { 339 return resources.getBoolean( 340 com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled); 341 } 342 343 public static boolean isCameraLiftTriggerEnabled(Resources resources) { 344 boolean configSet = resources.getInteger( 345 com.android.internal.R.integer.config_cameraLiftTriggerSensorType) != -1; 346 return configSet; 347 } 348 349 /** 350 * Whether GestureLauncherService should be enabled according to system properties. 351 */ 352 public static boolean isGestureLauncherEnabled(Resources resources) { 353 return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources) || 354 isCameraLiftTriggerEnabled(resources); 355 } 356 357 public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive, 358 MutableBoolean outLaunched) { 359 boolean launched = false; 360 boolean intercept = false; 361 long powerTapInterval; 362 synchronized (this) { 363 powerTapInterval = event.getEventTime() - mLastPowerDown; 364 if (mCameraDoubleTapPowerEnabled 365 && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) { 366 launched = true; 367 intercept = interactive; 368 mPowerButtonConsecutiveTaps++; 369 } else if (powerTapInterval < POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) { 370 mPowerButtonConsecutiveTaps++; 371 } else { 372 mPowerButtonConsecutiveTaps = 1; 373 } 374 mLastPowerDown = event.getEventTime(); 375 } 376 if (DBG && mPowerButtonConsecutiveTaps > 1) { 377 Slog.i(TAG, Long.valueOf(mPowerButtonConsecutiveTaps) + 378 " consecutive power button taps detected"); 379 } 380 if (launched) { 381 Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval=" 382 + powerTapInterval + "ms"); 383 launched = handleCameraGesture(false /* useWakelock */, 384 StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP); 385 if (launched) { 386 mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, 387 (int) powerTapInterval); 388 } 389 } 390 mMetricsLogger.histogram("power_consecutive_short_tap_count", mPowerButtonConsecutiveTaps); 391 mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval); 392 outLaunched.value = launched; 393 return intercept && launched; 394 } 395 396 /** 397 * @return true if camera was launched, false otherwise. 398 */ 399 @VisibleForTesting 400 boolean handleCameraGesture(boolean useWakelock, int source) { 401 boolean userSetupComplete = Settings.Secure.getIntForUser(mContext.getContentResolver(), 402 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; 403 if (!userSetupComplete) { 404 if (DBG) Slog.d(TAG, String.format( 405 "userSetupComplete = %s, ignoring camera gesture.", 406 userSetupComplete)); 407 return false; 408 } 409 if (DBG) Slog.d(TAG, String.format( 410 "userSetupComplete = %s, performing camera gesture.", 411 userSetupComplete)); 412 413 if (useWakelock) { 414 // Make sure we don't sleep too early 415 mWakeLock.acquire(500L); 416 } 417 StatusBarManagerInternal service = LocalServices.getService( 418 StatusBarManagerInternal.class); 419 service.onCameraLaunchGestureDetected(source); 420 return true; 421 } 422 423 private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { 424 @Override 425 public void onReceive(Context context, Intent intent) { 426 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { 427 mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); 428 mContext.getContentResolver().unregisterContentObserver(mSettingObserver); 429 registerContentObservers(); 430 updateCameraRegistered(); 431 updateCameraDoubleTapPowerEnabled(); 432 } 433 } 434 }; 435 436 private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) { 437 public void onChange(boolean selfChange, android.net.Uri uri, int userId) { 438 if (userId == mUserId) { 439 updateCameraRegistered(); 440 updateCameraDoubleTapPowerEnabled(); 441 } 442 } 443 }; 444 445 private final class GestureEventListener implements SensorEventListener { 446 @Override 447 public void onSensorChanged(SensorEvent event) { 448 if (!mCameraLaunchRegistered) { 449 if (DBG) Slog.d(TAG, "Ignoring gesture event because it's unregistered."); 450 return; 451 } 452 if (event.sensor == mCameraLaunchSensor) { 453 if (DBG) { 454 float[] values = event.values; 455 Slog.d(TAG, String.format("Received a camera launch event: " + 456 "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2])); 457 } 458 if (handleCameraGesture(true /* useWakelock */, 459 StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) { 460 mMetricsLogger.action(MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE); 461 trackCameraLaunchEvent(event); 462 } 463 return; 464 } 465 } 466 467 @Override 468 public void onAccuracyChanged(Sensor sensor, int accuracy) { 469 // Ignored. 470 } 471 472 private void trackCameraLaunchEvent(SensorEvent event) { 473 long now = SystemClock.elapsedRealtime(); 474 long totalDuration = now - mCameraGestureOnTimeMs; 475 // values[0]: ratio between total time duration when accel is turned on and time 476 // duration since camera launch gesture is subscribed. 477 // values[1]: ratio between total time duration when gyro is turned on and time duration 478 // since camera launch gesture is subscribed. 479 // values[2]: extra information 480 float[] values = event.values; 481 482 long sensor1OnTime = (long) (totalDuration * (double) values[0]); 483 long sensor2OnTime = (long) (totalDuration * (double) values[1]); 484 int extra = (int) values[2]; 485 486 // We only log the difference in the event log to make aggregation easier. 487 long gestureOnTimeDiff = now - mCameraGestureLastEventTime; 488 long sensor1OnTimeDiff = sensor1OnTime - mCameraGestureSensor1LastOnTimeMs; 489 long sensor2OnTimeDiff = sensor2OnTime - mCameraGestureSensor2LastOnTimeMs; 490 int extraDiff = extra - mCameraLaunchLastEventExtra; 491 492 // Gating against negative time difference. This doesn't usually happen, but it may 493 // happen because of numeric errors. 494 if (gestureOnTimeDiff < 0 || sensor1OnTimeDiff < 0 || sensor2OnTimeDiff < 0) { 495 if (DBG) Slog.d(TAG, "Skipped event logging because negative numbers."); 496 return; 497 } 498 499 if (DBG) Slog.d(TAG, String.format("totalDuration: %d, sensor1OnTime: %s, " + 500 "sensor2OnTime: %d, extra: %d", 501 gestureOnTimeDiff, 502 sensor1OnTimeDiff, 503 sensor2OnTimeDiff, 504 extraDiff)); 505 EventLogTags.writeCameraGestureTriggered( 506 gestureOnTimeDiff, 507 sensor1OnTimeDiff, 508 sensor2OnTimeDiff, 509 extraDiff); 510 511 mCameraGestureLastEventTime = now; 512 mCameraGestureSensor1LastOnTimeMs = sensor1OnTime; 513 mCameraGestureSensor2LastOnTimeMs = sensor2OnTime; 514 mCameraLaunchLastEventExtra = extra; 515 } 516 } 517 518 private final class CameraLiftTriggerEventListener extends TriggerEventListener { 519 @Override 520 public void onTrigger(TriggerEvent event) { 521 if (DBG_CAMERA_LIFT) Slog.d(TAG, String.format("onTrigger event - time: %d, name: %s", 522 event.timestamp, event.sensor.getName())); 523 if (!mCameraLiftRegistered) { 524 if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring camera lift event because it's " + 525 "unregistered."); 526 return; 527 } 528 if (event.sensor == mCameraLiftTriggerSensor) { 529 Resources resources = mContext.getResources(); 530 SensorManager sensorManager = (SensorManager) mContext.getSystemService( 531 Context.SENSOR_SERVICE); 532 boolean keyguardShowingAndNotOccluded = 533 mWindowManagerInternal.isKeyguardShowingAndNotOccluded(); 534 boolean interactive = mPowerManager.isInteractive(); 535 if (DBG_CAMERA_LIFT) { 536 float[] values = event.values; 537 Slog.d(TAG, String.format("Received a camera lift trigger " + 538 "event: values=[%.4f], keyguard showing: %b, interactive: %b", values[0], 539 keyguardShowingAndNotOccluded, interactive)); 540 } 541 if (keyguardShowingAndNotOccluded || !interactive) { 542 if (handleCameraGesture(true /* useWakelock */, 543 StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) { 544 MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER); 545 } 546 } else { 547 if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring lift event"); 548 } 549 550 mCameraLiftRegistered = sensorManager.requestTriggerSensor( 551 mCameraLiftTriggerListener, mCameraLiftTriggerSensor); 552 553 if (DBG_CAMERA_LIFT) Slog.d(TAG, "Camera lift trigger sensor re-registered: " + 554 mCameraLiftRegistered); 555 return; 556 } 557 } 558 } 559 } 560