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.provider.Settings; 37 import android.util.MutableBoolean; 38 import android.util.Slog; 39 import android.view.KeyEvent; 40 41 import com.android.internal.logging.MetricsLogger; 42 import com.android.internal.logging.MetricsProto.MetricsEvent; 43 import 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 */ 52 public 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 GestureLauncherService(Context context)109 public GestureLauncherService(Context context) { 110 super(context); 111 mContext = context; 112 } 113 onStart()114 public void onStart() { 115 LocalServices.addService(GestureLauncherService.class, this); 116 } 117 onBootPhase(int phase)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 registerContentObservers()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 updateCameraRegistered()148 private void updateCameraRegistered() { 149 Resources resources = mContext.getResources(); 150 if (isCameraLaunchSettingEnabled(mContext, mUserId)) { 151 registerCameraLaunchGesture(resources); 152 } else { 153 unregisterCameraLaunchGesture(); 154 } 155 } 156 updateCameraDoubleTapPowerEnabled()157 private void updateCameraDoubleTapPowerEnabled() { 158 boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId); 159 synchronized (this) { 160 mCameraDoubleTapPowerEnabled = enabled; 161 } 162 } 163 unregisterCameraLaunchGesture()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 */ registerCameraLaunchGesture(Resources resources)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 isCameraLaunchSettingEnabled(Context context, int userId)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 isCameraDoubleTapPowerSettingEnabled(Context context, int userId)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 */ isCameraLaunchEnabled(Resources resources)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 isCameraDoubleTapPowerEnabled(Resources resources)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 */ isGestureLauncherEnabled(Resources resources)250 public static boolean isGestureLauncherEnabled(Resources resources) { 251 return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources); 252 } 253 interceptPowerKeyDown(KeyEvent event, boolean interactive, MutableBoolean outLaunched)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 */ handleCameraLaunchGesture(boolean useWakelock, int source)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 onSensorChanged(SensorEvent event)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 onAccuracyChanged(Sensor sensor, int accuracy)354 public void onAccuracyChanged(Sensor sensor, int accuracy) { 355 // Ignored. 356 } 357 trackCameraLaunchEvent(SensorEvent event)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