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