提示:连续按10次power键恢复出厂设置功能
文章目录
- 前言
- 一、参考资料
- 二、修改文件
- 三、实现方案
- 四、源码分析
-
- 1、在线源码-GestureLauncherService
- 2、整体调用链
- 3、方法签名与返回值语义-interceptPowerKeyDown
- 4、完整执行流程
-
- [阶段 1:紧急手势冷却期检查](#阶段 1:紧急手势冷却期检查)
- [阶段 2:长按事件过滤](#阶段 2:长按事件过滤)
- [阶段 3:连续点击计数(核心,synchronized 块内)](#阶段 3:连续点击计数(核心,synchronized 块内))
- [阶段 4:紧急手势判定](#阶段 4:紧急手势判定)
- [阶段 5:双击相机 / 钱包判定](#阶段 5:双击相机 / 钱包判定)
- [阶段 6:退出 synchronized,执行动作](#阶段 6:退出 synchronized,执行动作)
- 五、方案思考
- 总结
前言
安卓短时间长按会进入相机界面,这个是最常见的现象,一般的产品都会做定制化开发 规避这个功能。
定制方案也会补充一个功能:连续按10次Power按键 恢复出厂设置。
一、参考资料
ndroid 10.0 系统去掉连续按两次电源键打开摄像头功能
mt6762 Android9 取消"开机键按两下打开系统相机
二、修改文件
java
frameworks/base/services/core/java/com/android/server/GestureLauncherService.java
三、实现方案
去掉-连续短按2次进入相机界面
可以参考资料,修改配置文件来实现,我这里直接屏蔽掉启动代码,如下 圈出来的直接屏蔽掉就可以了。

实现连续按10次恢复出厂设置功能
同理,根据源码自带的逻辑判断 然后调用恢复出厂设置代码即可:

自己写一下逻辑即可:
java
private void sendMasterClear(){
Intent intent = new Intent();
intent.setAction("com.fise.system.masterclear");
mContext.sendBroadcast(intent);
}
在其它类职工 监听广播:
java
if (action.equals("com.fise.system.masterclear")) {
Log.d("huanghb","com.fise.system.masterclear");
SlideUitls uitls = SlideUitls.getInstance(mContext);
uitls.sendMasterClearCustom();
}
public void sendMasterClearCustom(){
Log.d("huanghb", "sendMasterClearCustom");
Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
intent.setPackage("android");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
intent.putExtra(Intent.EXTRA_WIPE_ESIMS, true);
mContext.sendBroadcast(intent);
}
四、源码分析
开源代码最新版本:GestureLauncherService.java
1、在线源码-GestureLauncherService
源码如下:
java
package com.android.server;
import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
import static android.service.quickaccesswallet.Flags.launchWalletViaSysuiCallbacks;
import static com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.WindowManagerInternal;
/**
* The service that listens for gestures detected in sensor firmware and starts the intent
* accordingly.
*
* @hide
*/
public class GestureLauncherService extends SystemService {
private static final boolean DBG = false;
private static final boolean DBG_CAMERA_LIFT = false;
private static final String TAG = "GestureLauncherService";
/**
* Time in milliseconds in which the power button must be pressed twice so it will be considered
* as a camera launch.
*/
@VisibleForTesting static final long POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
/**
* Interval in milliseconds in which the power button must be depressed in succession to be
* considered part of an extended sequence of taps. Note that this is a looser threshold than
* the camera launch gesture, because the purpose of this threshold is to measure the
* frequency of consecutive taps, for evaluation for future gestures.
*/
@VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500;
/**
* Number of taps required to launch emergency gesture ui.
*/
private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5;
/**
* Default value of the power button "cooldown" period after the Emergency gesture is triggered.
* See {@link Settings.Global#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS}
*/
private static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT = 3000;
/**
* Maximum value of the power button "cooldown" period after the Emergency gesture is triggered.
* The value read from {@link Settings.Global#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS}
* is capped at this maximum.
*/
@VisibleForTesting
static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
/** Configuration value indicating double tap power gesture is disabled. */
@VisibleForTesting static final int DOUBLE_TAP_POWER_DISABLED_MODE = 0;
/** Configuration value indicating double tap power gesture should launch camera. */
@VisibleForTesting static final int DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE = 1;
/**
* Configuration value indicating double tap power gesture should launch one of many target
* actions.
*/
@VisibleForTesting static final int DOUBLE_TAP_POWER_MULTI_TARGET_MODE = 2;
/** Indicates camera launch is selected as target action for multi target double tap power. */
@VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0;
/** Indicates wallet launch is selected as target action for multi target double tap power. */
@VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1;
/** Number of taps required to launch the double tap shortcut (either camera or wallet). */
public static final int DOUBLE_POWER_TAP_COUNT_THRESHOLD = 2;
/** Bundle to send with PendingIntent to grant background activity start privileges. */
private static final Bundle GRANT_BACKGROUND_START_PRIVILEGES =
ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
.toBundle();
private final QuickAccessWalletClient mQuickAccessWalletClient;
/** The listener that receives the gesture event. */
private final GestureEventListener mGestureListener = new GestureEventListener();
private final CameraLiftTriggerEventListener mCameraLiftTriggerListener =
new CameraLiftTriggerEventListener();
private Sensor mCameraLaunchSensor;
private Sensor mCameraLiftTriggerSensor;
private Context mContext;
private final MetricsLogger mMetricsLogger;
private PowerManager mPowerManager;
private WindowManagerInternal mWindowManagerInternal;
/** The wake lock held when a gesture is detected. */
private WakeLock mWakeLock;
private boolean mCameraLaunchRegistered;
private boolean mCameraLiftRegistered;
private int mUserId;
// Below are fields used for event logging only.
/** Elapsed real time when the camera gesture is turned on. */
private long mCameraGestureOnTimeMs = 0L;
/** Elapsed real time when the last camera gesture was detected. */
private long mCameraGestureLastEventTime = 0L;
/**
* How long the sensor 1 has been turned on since camera launch sensor was
* subscribed to and when the last camera launch gesture was detected.
* <p>Sensor 1 is the main sensor used to detect camera launch gesture.</p>
*/
private long mCameraGestureSensor1LastOnTimeMs = 0L;
/**
* If applicable, how long the sensor 2 has been turned on since camera
* launch sensor was subscribed to and when the last camera launch
* gesture was detected.
* <p>Sensor 2 is the secondary sensor used to detect camera launch gesture.
* This is optional and if only sensor 1 is used for detect camera launch
* gesture, this value would always be 0.</p>
*/
private long mCameraGestureSensor2LastOnTimeMs = 0L;
/**
* Extra information about the event when the last camera launch gesture
* was detected.
*/
private int mCameraLaunchLastEventExtra = 0;
/**
* Whether camera double tap power button gesture is currently enabled;
*/
private boolean mCameraDoubleTapPowerEnabled;
/** Whether wallet double tap power button gesture is currently enabled. */
private boolean mWalletDoubleTapPowerEnabled;
/**
* Whether emergency gesture is currently enabled
*/
private boolean mEmergencyGestureEnabled;
/**
* Power button cooldown period in milliseconds, after emergency gesture is triggered. A zero
* value means the cooldown period is disabled.
*/
private int mEmergencyGesturePowerButtonCooldownPeriodMs;
private long mLastPowerDown;
private long mFirstPowerDown;
private long mLastEmergencyGestureTriggered;
private int mPowerButtonConsecutiveTaps;
private int mPowerButtonSlowConsecutiveTaps;
private final UiEventLogger mUiEventLogger;
private boolean mHasFeatureWatch;
@VisibleForTesting
public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "The user lifted the device just the right way to launch the camera.")
GESTURE_CAMERA_LIFT(658),
@UiEvent(doc = "The user wiggled the device just the right way to launch the camera.")
GESTURE_CAMERA_WIGGLE(659),
@UiEvent(doc = "The user double-tapped power quickly enough to launch the camera.")
GESTURE_CAMERA_DOUBLE_TAP_POWER(660),
@UiEvent(doc = "The user multi-tapped power quickly enough to signal an emergency.")
GESTURE_EMERGENCY_TAP_POWER(661),
@UiEvent(doc = "The user double-tapped power quickly enough to launch the wallet.")
GESTURE_WALLET_DOUBLE_TAP_POWER(2236);
private final int mId;
GestureLauncherEvent(int id) {
mId = id;
}
@Override
public int getId() {
return mId;
}
}
public GestureLauncherService(Context context) {
this(context, new MetricsLogger(),
QuickAccessWalletClient.create(context), new UiEventLoggerImpl());
}
@VisibleForTesting
public GestureLauncherService(Context context, MetricsLogger metricsLogger,
QuickAccessWalletClient quickAccessWalletClient, UiEventLogger uiEventLogger) {
super(context);
mContext = context;
mMetricsLogger = metricsLogger;
mQuickAccessWalletClient = quickAccessWalletClient;
mUiEventLogger = uiEventLogger;
}
@Override
public void onStart() {
LocalServices.addService(GestureLauncherService.class, this);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Resources resources = mContext.getResources();
if (!isGestureLauncherEnabled(resources)) {
if (DBG) Slog.d(TAG, "Gesture launcher is disabled in system properties.");
return;
}
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mPowerManager = (PowerManager) mContext.getSystemService(
Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
if (launchWalletOptionOnPowerDoubleTap()) {
updateWalletDoubleTapPowerEnabled();
}
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
mUserId = ActivityManager.getCurrentUser();
mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
registerContentObservers();
mHasFeatureWatch =
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
}
}
private void registerContentObservers() {
if (launchWalletOptionOnPowerDoubleTap()) {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE),
false, mSettingObserver, mUserId);
}
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(
Settings.Global.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS),
false, mSettingObserver, mUserId);
}
private void updateCameraRegistered() {
Resources resources = mContext.getResources();
if (isCameraLaunchSettingEnabled(mContext, mUserId)) {
registerCameraLaunchGesture(resources);
} else {
unregisterCameraLaunchGesture();
}
if (isCameraLiftTriggerSettingEnabled(mContext, mUserId)) {
registerCameraLiftTrigger(resources);
} else {
unregisterCameraLiftTrigger();
}
}
@VisibleForTesting
void updateCameraDoubleTapPowerEnabled() {
boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId);
synchronized (this) {
mCameraDoubleTapPowerEnabled = enabled;
}
}
@VisibleForTesting
void updateWalletDoubleTapPowerEnabled() {
boolean enabled = isWalletDoubleTapPowerSettingEnabled(mContext, mUserId);
synchronized (this) {
mWalletDoubleTapPowerEnabled = enabled;
}
}
@VisibleForTesting
void updateEmergencyGestureEnabled() {
boolean enabled = isEmergencyGestureSettingEnabled(mContext, mUserId);
synchronized (this) {
mEmergencyGestureEnabled = enabled;
}
}
@VisibleForTesting
void updateEmergencyGesturePowerButtonCooldownPeriodMs() {
int cooldownPeriodMs = getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, mUserId);
synchronized (this) {
mEmergencyGesturePowerButtonCooldownPeriodMs = cooldownPeriodMs;
}
}
private void unregisterCameraLaunchGesture() {
if (mCameraLaunchRegistered) {
mCameraLaunchRegistered = false;
mCameraGestureOnTimeMs = 0L;
mCameraGestureLastEventTime = 0L;
mCameraGestureSensor1LastOnTimeMs = 0;
mCameraGestureSensor2LastOnTimeMs = 0;
mCameraLaunchLastEventExtra = 0;
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
sensorManager.unregisterListener(mGestureListener);
}
}
/**
* Registers for the camera launch gesture.
*/
private void registerCameraLaunchGesture(Resources resources) {
if (mCameraLaunchRegistered) {
return;
}
mCameraGestureOnTimeMs = SystemClock.elapsedRealtime();
mCameraGestureLastEventTime = mCameraGestureOnTimeMs;
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
int cameraLaunchGestureId = resources.getInteger(
com.android.internal.R.integer.config_cameraLaunchGestureSensorType);
if (cameraLaunchGestureId != -1) {
mCameraLaunchRegistered = false;
String sensorName = resources.getString(
com.android.internal.R.string.config_cameraLaunchGestureSensorStringType);
mCameraLaunchSensor = sensorManager.getDefaultSensor(
cameraLaunchGestureId,
true /*wakeUp*/);
// Compare the camera gesture string type to that in the resource file to make
// sure we are registering the correct sensor. This is redundant check, it
// makes the code more robust.
if (mCameraLaunchSensor != null) {
if (sensorName.equals(mCameraLaunchSensor.getStringType())) {
mCameraLaunchRegistered = sensorManager.registerListener(mGestureListener,
mCameraLaunchSensor, 0);
} else {
String message = String.format("Wrong configuration. Sensor type and sensor "
+ "string type don't match: %s in resources, %s in the sensor.",
sensorName, mCameraLaunchSensor.getStringType());
throw new RuntimeException(message);
}
}
if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mCameraLaunchRegistered);
} else {
if (DBG) Slog.d(TAG, "Camera launch sensor is not specified.");
}
}
private void unregisterCameraLiftTrigger() {
if (mCameraLiftRegistered) {
mCameraLiftRegistered = false;
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
sensorManager.cancelTriggerSensor(mCameraLiftTriggerListener, mCameraLiftTriggerSensor);
}
}
/**
* Registers for the camera lift trigger.
*/
private void registerCameraLiftTrigger(Resources resources) {
if (mCameraLiftRegistered) {
return;
}
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
int cameraLiftTriggerId = resources.getInteger(
com.android.internal.R.integer.config_cameraLiftTriggerSensorType);
if (cameraLiftTriggerId != -1) {
mCameraLiftRegistered = false;
String sensorName = resources.getString(
com.android.internal.R.string.config_cameraLiftTriggerSensorStringType);
mCameraLiftTriggerSensor = sensorManager.getDefaultSensor(
cameraLiftTriggerId,
true /*wakeUp*/);
// Compare the camera lift trigger string type to that in the resource file to make
// sure we are registering the correct sensor. This is redundant check, it
// makes the code more robust.
if (mCameraLiftTriggerSensor != null) {
if (sensorName.equals(mCameraLiftTriggerSensor.getStringType())) {
mCameraLiftRegistered = sensorManager.requestTriggerSensor(mCameraLiftTriggerListener,
mCameraLiftTriggerSensor);
} else {
String message = String.format("Wrong configuration. Sensor type and sensor "
+ "string type don't match: %s in resources, %s in the sensor.",
sensorName, mCameraLiftTriggerSensor.getStringType());
throw new RuntimeException(message);
}
}
if (DBG) Slog.d(TAG, "Camera lift trigger sensor registered: " + mCameraLiftRegistered);
} else {
if (DBG) Slog.d(TAG, "Camera lift trigger sensor is not specified.");
}
}
public static boolean isCameraLaunchSettingEnabled(Context context, int userId) {
return isCameraLaunchEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
}
/** Checks if camera should be launched on double press of the power button. */
public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
if (!launchWalletOptionOnPowerDoubleTap()) {
return isCameraDoubleTapPowerEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
}
final int doubleTapPowerGestureSettingMode = getDoubleTapPowerGestureMode(
context.getResources());
return switch (doubleTapPowerGestureSettingMode) {
case DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE -> Settings.Secure.getIntForUser(
context.getContentResolver(),
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0;
case DOUBLE_TAP_POWER_MULTI_TARGET_MODE ->
isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId)
&& getDoubleTapPowerGestureAction(context, userId)
== LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
default -> false;
};
}
/** Checks if wallet should be launched on double tap of the power button. */
public static boolean isWalletDoubleTapPowerSettingEnabled(Context context, int userId) {
if (!launchWalletOptionOnPowerDoubleTap()) {
return false;
}
return getDoubleTapPowerGestureMode(context.getResources())
== DOUBLE_TAP_POWER_MULTI_TARGET_MODE
&& isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId)
&& getDoubleTapPowerGestureAction(context, userId)
== LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
}
public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
return isCameraLiftTriggerEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED,
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED_DEFAULT, userId) != 0);
}
/**
* Whether to enable emergency gesture.
*/
public static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
return isEmergencyGestureEnabled(context.getResources())
&& Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.EMERGENCY_GESTURE_ENABLED,
isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
/** Gets the double tap power gesture mode. */
private static int getDoubleTapPowerGestureMode(Resources resources) {
return resources.getInteger(R.integer.config_doubleTapPowerGestureMode);
}
/**
* Whether the setting for multi target double tap power gesture is enabled.
*
* <p>Multi target double tap power gesture allows the user to choose one of many target actions
* when double tapping the power button.
* </p>
*/
private static boolean isMultiTargetDoubleTapPowerGestureSettingEnabled(Context context,
int userId) {
return Settings.Secure.getIntForUser(
context.getContentResolver(),
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
getDoubleTapPowerGestureMode(context.getResources())
== DOUBLE_TAP_POWER_MULTI_TARGET_MODE ? 1 : 0,
userId)
== 1;
}
/** Gets the selected target action for the multi target double tap power gesture.
*
* <p>The target actions are defined in {@link Settings.Secure#DOUBLE_TAP_POWER_BUTTON_GESTURE}.
* </p>
*/
private static int getDoubleTapPowerGestureAction(Context context, int userId) {
return Settings.Secure.getIntForUser(
context.getContentResolver(),
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
context.getResources().getInteger(
R.integer.config_doubleTapPowerGestureMultiTargetDefaultAction),
userId);
}
/**
* Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
* value is capped at a maximum
* {@link GestureLauncherService#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX}. If the
* value is zero, it means the cooldown period is disabled.
*/
@VisibleForTesting
static int getEmergencyGesturePowerButtonCooldownPeriodMs(Context context, int userId) {
int cooldown = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT);
return Math.min(cooldown, EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX);
}
/**
* Whether to enable the camera launch gesture.
*/
private static boolean isCameraLaunchEnabled(Resources resources) {
boolean configSet = resources.getInteger(
com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1;
return configSet &&
!SystemProperties.getBoolean("gesture.disable_camera_launch", false);
}
@VisibleForTesting
static boolean isCameraDoubleTapPowerEnabled(Resources resources) {
return resources.getBoolean(
com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled);
}
private static boolean isCameraLiftTriggerEnabled(Resources resources) {
boolean configSet = resources.getInteger(
com.android.internal.R.integer.config_cameraLiftTriggerSensorType) != -1;
return configSet;
}
/**
* Whether or not the emergency gesture feature is enabled by platform
*/
private static boolean isEmergencyGestureEnabled(Resources resources) {
return resources.getBoolean(com.android.internal.R.bool.config_emergencyGestureEnabled);
}
private static boolean isDefaultEmergencyGestureEnabled(Resources resources) {
return resources.getBoolean(
com.android.internal.R.bool.config_defaultEmergencyGestureEnabled);
}
/**
* Whether GestureLauncherService should be enabled according to system properties.
*/
public static boolean isGestureLauncherEnabled(Resources resources) {
boolean res =
isCameraLaunchEnabled(resources)
|| isCameraLiftTriggerEnabled(resources)
|| isEmergencyGestureEnabled(resources);
if (launchWalletOptionOnPowerDoubleTap()) {
res |= getDoubleTapPowerGestureMode(resources) != DOUBLE_TAP_POWER_DISABLED_MODE;
} else {
res |= isCameraDoubleTapPowerEnabled(resources);
}
return res;
}
/**
* Attempts to intercept power key down event by detecting certain gesture patterns
*
* @param interactive true if the event's policy contains {@code FLAG_INTERACTIVE}
* @param outLaunched true if some action is taken as part of the key intercept (eg, app launch)
* @return true if the key down event is intercepted
*/
public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
MutableBoolean outLaunched) {
if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
&& event.getEventTime() - mLastEmergencyGestureTriggered
< mEmergencyGesturePowerButtonCooldownPeriodMs) {
Slog.i(TAG, String.format(
"Suppressing power button: within %dms cooldown period after Emergency "
+ "Gesture. Begin=%dms, end=%dms.",
mEmergencyGesturePowerButtonCooldownPeriodMs,
mLastEmergencyGestureTriggered,
mLastEmergencyGestureTriggered + mEmergencyGesturePowerButtonCooldownPeriodMs));
outLaunched.value = false;
return true;
}
if (event.isLongPress()) {
// Long presses are sent as a second key down. If the long press threshold is set lower
// than the double tap of sequence interval thresholds, this could cause false double
// taps or consecutive taps, so we want to ignore the long press event.
outLaunched.value = false;
return false;
}
boolean launchCamera = false;
boolean launchWallet = false;
boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
synchronized (this) {
powerTapInterval = event.getEventTime() - mLastPowerDown;
mLastPowerDown = event.getEventTime();
if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
// Tap too slow, reset consecutive tap counts.
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps = 1;
} else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
// Tap too slow for shortcuts
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps++;
} else {
// Fast consecutive tap
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
}
// Check if we need to launch camera or emergency gesture flows
if (mEmergencyGestureEnabled) {
// Commit to intercepting the powerkey event after the second "quick" tap to avoid
// lockscreen changes between launching camera and the emergency gesture flow.
// Since watch doesn't have camera gesture, only intercept power key event after
// emergency gesture tap count.
if (mPowerButtonConsecutiveTaps
> (mHasFeatureWatch ? EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD : 1)) {
intercept = interactive;
}
if (mPowerButtonConsecutiveTaps == EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD) {
long emergencyGestureSpentTime = event.getEventTime() - mFirstPowerDown;
long emergencyGestureTapDetectionMinTimeMs = Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS,
mContext.getResources().getInteger(
config_defaultMinEmergencyGestureTapDurationMillis));
if (emergencyGestureSpentTime <= emergencyGestureTapDetectionMinTimeMs) {
Slog.i(TAG, "Emergency gesture detected but it's too fast. Gesture time: "
+ emergencyGestureSpentTime + " ms");
// Reset consecutive tap counts.
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps = 1;
} else {
Slog.i(TAG, "Emergency gesture detected. Gesture time: "
+ emergencyGestureSpentTime + " ms");
launchEmergencyGesture = true;
mMetricsLogger.histogram("emergency_gesture_spent_time",
(int) emergencyGestureSpentTime);
}
}
}
if (mCameraDoubleTapPowerEnabled
&& powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
&& mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
launchCamera = true;
intercept = interactive;
} else if (launchWalletOptionOnPowerDoubleTap()
&& mWalletDoubleTapPowerEnabled
&& powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
&& mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
launchWallet = true;
intercept = interactive;
}
}
if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) {
Slog.i(TAG, Long.valueOf(mPowerButtonConsecutiveTaps)
+ " consecutive power button taps detected, "
+ Long.valueOf(mPowerButtonSlowConsecutiveTaps)
+ " consecutive slow power button taps detected");
}
if (launchCamera) {
Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval="
+ powerTapInterval + "ms");
launchCamera = handleCameraGesture(false /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
if (launchCamera) {
mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
(int) powerTapInterval);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
}
} else if (launchWallet) {
Slog.i(TAG, "Power button double tap gesture detected, launching wallet. Interval="
+ powerTapInterval + "ms");
launchWallet = launchWalletViaSysuiCallbacks() ?
handleWalletGesture() : sendGestureTargetActivityPendingIntent();
if (launchWallet) {
mUiEventLogger.log(GestureLauncherEvent.GESTURE_WALLET_DOUBLE_TAP_POWER);
}
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
launchEmergencyGesture = handleEmergencyGesture();
mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
// Record emergency trigger time if emergency UI was launched
if (launchEmergencyGesture) {
synchronized (this) {
mLastEmergencyGestureTriggered = event.getEventTime();
}
}
}
mMetricsLogger.histogram("power_consecutive_short_tap_count",
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
outLaunched.value = launchCamera || launchEmergencyGesture || launchWallet;
// Intercept power key event if the press is part of a gesture (camera, eGesture) and the
// user has completed setup.
return intercept && isUserSetupComplete();
}
/**
* Fetches and sends gestureTargetActivityPendingIntent from QuickAccessWallet, which is a
* specific activity that QuickAccessWalletService has defined to be launch on detection of the
* power button gesture.
*/
private boolean sendGestureTargetActivityPendingIntent() {
boolean userSetupComplete = isUserSetupComplete();
if (mQuickAccessWalletClient == null
|| !mQuickAccessWalletClient.isWalletServiceAvailable()) {
Slog.w(TAG, "QuickAccessWalletService is not available, ignoring wallet gesture.");
return false;
}
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, "userSetupComplete = false, ignoring wallet gesture.");
}
return false;
}
if (DBG) {
Slog.d(TAG, "userSetupComplete = true, performing wallet gesture.");
}
mQuickAccessWalletClient.getGestureTargetActivityPendingIntent(
getContext().getMainExecutor(),
gesturePendingIntent -> {
if (gesturePendingIntent == null) {
Slog.d(TAG, "getGestureTargetActivityPendingIntent is null.");
sendFallbackPendingIntent();
return;
}
sendPendingIntentWithBackgroundStartPrivileges(gesturePendingIntent);
});
return true;
}
/**
* If gestureTargetActivityPendingIntent is null, this method is invoked to start the activity
* that QuickAccessWalletService has defined to host the Wallet view, which is typically the
* home screen of the Wallet application.
*/
private void sendFallbackPendingIntent() {
mQuickAccessWalletClient.getWalletPendingIntent(
getContext().getMainExecutor(),
walletPendingIntent -> {
if (walletPendingIntent == null) {
Slog.w(TAG, "getWalletPendingIntent returns null. Not launching "
+ "anything for wallet.");
return;
}
sendPendingIntentWithBackgroundStartPrivileges(walletPendingIntent);
});
}
private void sendPendingIntentWithBackgroundStartPrivileges(PendingIntent pendingIntent) {
try {
pendingIntent.send(GRANT_BACKGROUND_START_PRIVILEGES);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "PendingIntent was canceled", e);
}
}
/**
* @return true if camera was launched, false otherwise.
*/
@VisibleForTesting
boolean handleCameraGesture(boolean useWakelock, int source) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "GestureLauncher:handleCameraGesture");
try {
boolean userSetupComplete = isUserSetupComplete();
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, ignoring camera gesture.",
userSetupComplete));
}
return false;
}
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, performing camera gesture.",
userSetupComplete));
}
if (useWakelock) {
// Make sure we don't sleep too early
mWakeLock.acquire(500L);
}
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
service.onCameraLaunchGestureDetected(source);
return true;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
@VisibleForTesting
boolean handleWalletGesture() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"GestureLauncher:handleWalletGesture");
try {
boolean userSetupComplete = isUserSetupComplete();
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, "userSetupComplete = false, ignoring wallet gesture.");
}
return false;
}
if (DBG) {
Slog.d(TAG, "userSetupComplete = true, performing wallet gesture.");
}
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
service.onWalletLaunchGestureDetected();
return true;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
/**
* @return true if emergency gesture UI was launched, false otherwise.
*/
@VisibleForTesting
boolean handleEmergencyGesture() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"GestureLauncher:handleEmergencyGesture");
try {
boolean userSetupComplete = isUserSetupComplete();
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, ignoring emergency gesture.",
userSetupComplete));
}
return false;
}
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, performing emergency gesture.",
userSetupComplete));
}
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
service.onEmergencyActionLaunchGestureDetected();
return true;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
private boolean isUserSetupComplete() {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
}
private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
registerContentObservers();
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
if (launchWalletOptionOnPowerDoubleTap()) {
updateWalletDoubleTapPowerEnabled();
}
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
}
}
};
private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) {
public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
if (userId == mUserId) {
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
if (launchWalletOptionOnPowerDoubleTap()) {
updateWalletDoubleTapPowerEnabled();
}
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
}
}
};
private final class GestureEventListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
if (!mCameraLaunchRegistered) {
if (DBG) Slog.d(TAG, "Ignoring gesture event because it's unregistered.");
return;
}
if (event.sensor == mCameraLaunchSensor) {
if (DBG) {
float[] values = event.values;
Slog.d(TAG, String.format("Received a camera launch event: " +
"values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
}
if (handleCameraGesture(true /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
mMetricsLogger.action(MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_WIGGLE);
trackCameraLaunchEvent(event);
}
return;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Ignored.
}
private void trackCameraLaunchEvent(SensorEvent event) {
long now = SystemClock.elapsedRealtime();
long totalDuration = now - mCameraGestureOnTimeMs;
// values[0]: ratio between total time duration when accel is turned on and time
// duration since camera launch gesture is subscribed.
// values[1]: ratio between total time duration when gyro is turned on and time duration
// since camera launch gesture is subscribed.
// values[2]: extra information
float[] values = event.values;
long sensor1OnTime = (long) (totalDuration * (double) values[0]);
long sensor2OnTime = (long) (totalDuration * (double) values[1]);
int extra = (int) values[2];
// We only log the difference in the event log to make aggregation easier.
long gestureOnTimeDiff = now - mCameraGestureLastEventTime;
long sensor1OnTimeDiff = sensor1OnTime - mCameraGestureSensor1LastOnTimeMs;
long sensor2OnTimeDiff = sensor2OnTime - mCameraGestureSensor2LastOnTimeMs;
int extraDiff = extra - mCameraLaunchLastEventExtra;
// Gating against negative time difference. This doesn't usually happen, but it may
// happen because of numeric errors.
if (gestureOnTimeDiff < 0 || sensor1OnTimeDiff < 0 || sensor2OnTimeDiff < 0) {
if (DBG) Slog.d(TAG, "Skipped event logging because negative numbers.");
return;
}
if (DBG) Slog.d(TAG, String.format("totalDuration: %d, sensor1OnTime: %s, " +
"sensor2OnTime: %d, extra: %d",
gestureOnTimeDiff,
sensor1OnTimeDiff,
sensor2OnTimeDiff,
extraDiff));
EventLogTags.writeCameraGestureTriggered(
gestureOnTimeDiff,
sensor1OnTimeDiff,
sensor2OnTimeDiff,
extraDiff);
mCameraGestureLastEventTime = now;
mCameraGestureSensor1LastOnTimeMs = sensor1OnTime;
mCameraGestureSensor2LastOnTimeMs = sensor2OnTime;
mCameraLaunchLastEventExtra = extra;
}
}
private final class CameraLiftTriggerEventListener extends TriggerEventListener {
@Override
public void onTrigger(TriggerEvent event) {
if (DBG_CAMERA_LIFT) Slog.d(TAG, String.format("onTrigger event - time: %d, name: %s",
event.timestamp, event.sensor.getName()));
if (!mCameraLiftRegistered) {
if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring camera lift event because it's " +
"unregistered.");
return;
}
if (event.sensor == mCameraLiftTriggerSensor) {
Resources resources = mContext.getResources();
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
boolean keyguardShowingAndNotOccluded =
mWindowManagerInternal.isKeyguardShowingAndNotOccluded();
boolean interactive = mPowerManager.isInteractive();
if (DBG_CAMERA_LIFT) {
float[] values = event.values;
Slog.d(TAG, String.format("Received a camera lift trigger " +
"event: values=[%.4f], keyguard showing: %b, interactive: %b", values[0],
keyguardShowingAndNotOccluded, interactive));
}
if (keyguardShowingAndNotOccluded || !interactive) {
if (handleCameraGesture(true /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_LIFT);
}
} else {
if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring lift event");
}
mCameraLiftRegistered = sensorManager.requestTriggerSensor(
mCameraLiftTriggerListener, mCameraLiftTriggerSensor);
if (DBG_CAMERA_LIFT) Slog.d(TAG, "Camera lift trigger sensor re-registered: " +
mCameraLiftRegistered);
return;
}
}
}
}
2、整体调用链
java
PhoneWindowManager.interceptKeyBeforeQueueing()
↓ (电源键 DOWN 事件)
GestureLauncherService.interceptPowerKeyDown(event, interactive, outLaunched)
↓
返回 true → 电源键事件被消费,不进入正常的亮灭屏流程
返回 false → 事件继续传递,按正常电源键逻辑处理
这个方法在每次电源键按下时都会被调用,是所有 Power 键手势的统一入口。
3、方法签名与返回值语义-interceptPowerKeyDown
java
public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive, MutableBoolean outLaunched)
| 参数 / 返回 | 含义 |
|---|---|
| event | 电源键 DOWN 事件,event.getEventTime() 是事件发生的时间戳 |
| interactive | 当前设备是否处于交互状态(亮屏),来自策略的 FLAG_INTERACTIVE |
| outLaunched | 输出参数,本次调用是否真正启动了某个动作(相机 / 钱包 / 紧急) |
| 返回值 | 是否拦截本次电源键事件(拦截 = 不往下传递) |
关键区别 :返回值=true 不一定代表启动了应用,它可能只是 "这次按键属于某个手势序列的一部分,先拦下来避免锁屏闪烁"。而 outLaunched=true 才代表真的启动了东西。
4、完整执行流程
阶段 1:紧急手势冷却期检查
java
if (mEmergencyGestureEnabled && cooldown >= 0
&& eventTime - mLastEmergencyGestureTriggered < cooldown) {
outLaunched.value = false;
return true; // 直接拦截,吞掉事件
}
- 紧急手势触发后,默认 3000ms 内(可配置,上限 5000ms)的所有电源键按下都被静默拦截
- 目的:紧急界面弹出后,防止用户紧张中继续按电源键导致灭屏或退出
cooldown = 0时此检查不生效(>= 0但差值< 0永远不成立)
阶段 2:长按事件过滤
java
if (event.isLongPress()) {
outLaunched.value = false;
return false;
}
长按电源键会产生第二个 key down 事件 (带 FLAG_LONG_PRESS)。如果长按阈值(默认 500ms)比双击窗口(300ms)大还好,但如果厂商把长按阈值调得比 300ms 小,这个长按的第二个 down 会被误判为 "第二次快速点击"。所以这里直接忽略。
阶段 3:连续点击计数(核心,synchronized 块内)
这是整个方法最精妙的部分。维护两个计数器:
java
mPowerButtonConsecutiveTaps // 快速连续点击计数(严格 < 300ms)
mPowerButtonSlowConsecutiveTaps // 慢速连续点击计数(宽松 < 500ms)
每次按下计算 powerTapInterval = 当前时间 - mLastPowerDown,分三档:
| 间隔 | consecutiveTaps | slowConsecutiveTaps | mFirstPowerDown |
|---|---|---|---|
| ≥ 500ms(太慢,新序列) | 重置为 1 | 重置为 1 | 更新为当前时间 |
| 300~500ms(够连续但不够快) | 重置为 1 | +1 | 更新为当前时间 |
| < 300ms(快速点击) | +1 | +1 | 不变 |
设计意图:
consecutiveTaps用于判定双击相机 / 钱包 和5 连紧急手势,要求严格快速slowConsecutiveTaps只用于埋点统计 (power_consecutive_short_tap_count直方图),分析用户按键习惯,为未来新手势评估数据mFirstPowerDown记录当前快速序列的起点时间,用于计算紧急手势总耗时
阶段 4:紧急手势判定
java
if (mEmergencyGestureEnabled) {
// 拦截时机
if (consecutiveTaps > (watch ? 5 : 1)) {
intercept = interactive;
}
// 触发判定
if (consecutiveTaps == 5) {
long spentTime = eventTime - mFirstPowerDown;
long minTime = Settings.Global.getInt(EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS, 默认值);
if (spentTime <= minTime) {
// 太快了,疑似误触,重置计数
mFirstPowerDown = eventTime;
consecutiveTaps = 1;
slowConsecutiveTaps = 1;
} else {
launchEmergencyGesture = true;
}
}
}
两个关键点:
(1) 拦截时机的设备差异
- 手机 / 平板 :
consecutiveTaps > 1,也就是从第 2 次快速按下就开始拦截 - 手表 :
consecutiveTaps > 5,也就是第 6 次才开始拦截
原因:手机有双击相机手势,第 2 次按下时可能要启动相机,必须提前拦截防止锁屏状态变化;手表没有相机手势,没必要提前拦截,让前 5 次按键正常走(但实际上第 5 次就触发紧急手势了,所以手表的这个条件基本不会在 >5 时命中,这里更多是防御性编码)。
(2) 最小耗时保护
5 次点击的总耗时必须 > EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS 。如果 5 次按得太快(比如机械故障、口袋里挤压),认为是误触,重置计数器而不是触发紧急手势。这是防误触的第二道保险。
阶段 5:双击相机 / 钱包判定
java
if (mCameraDoubleTapPowerEnabled
&& interval < 300 && consecutiveTaps == 2) {
launchCamera = true;
intercept = interactive;
} else if (walletFlag && mWalletDoubleTapPowerEnabled
&& interval < 300 && consecutiveTaps == 2) {
launchWallet = true;
intercept = interactive;
}
- 必须同时满足:功能已启用 + 间隔 < 300ms + 正好第 2 次快速点击
- 相机优先于钱包 (
if...else if),但实际上二者不会同时启用(设置互斥) intercept = interactive:亮屏时才拦截,灭屏时不拦截(让电源键正常亮屏)
阶段 6:退出 synchronized,执行动作
java
if (launchCamera) {
launchCamera = handleCameraGesture(false, POWER_DOUBLE_TAP);
// 成功则埋点
} else if (launchWallet) {
launchWallet = flag ? handleWalletGesture() : sendGestureTargetActivityPendingIntent();
// 成功则埋点
} else if (launchEmergencyGesture) {
launchEmergencyGesture = handleEmergencyGesture();
mUiEventLogger.log(GESTURE_EMERGENCY_TAP_POWER); // 无论成功都上报
if (launchEmergencyGesture) {
mLastEmergencyGestureTriggered = eventTime; // 记录触发时间,开启冷却期
}
}
三者互斥 :else if 链保证一次按键最多触发一个动作。优先级:相机 > 钱包 > 紧急。但实际上:
- 第 2 次按下时触发相机 / 钱包
- 第 5 次按下时触发紧急
- 不会在同一次调用中冲突
紧急手势的特殊处理:
mUiEventLogger.log()在if外面,只要手势被识别就上报(即使 UI 没启动成功)- 只有
handleEmergencyGesture()返回true(用户已完成 setup)才记录mLastEmergencyGestureTriggered,否则不开启冷却期
五、方案思考
实际项目实现
经过上面执行流程分析,其实紧急情况触发后直接进行恢复出厂设置的思路来实现需求。
java
private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 10;

实际项目部分源码
实际项目源码和在线源码可以对比看看,实际项目是Android12 源码,在线项目源码目前是Android 16
java
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.WindowManagerInternal;
/**
* The service that listens for gestures detected in sensor firmware and starts the intent
* accordingly.
* <p>For now, only camera launch gesture is supported, and in the future, more gestures can be
* added.</p>
* @hide
*/
public class GestureLauncherService extends SystemService {
private static final boolean DBG = false;
private static final boolean DBG_CAMERA_LIFT = false;
private static final String TAG = "GestureLauncherService";
/**
* Time in milliseconds in which the power button must be pressed twice so it will be considered
* as a camera launch.
*/
@VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
/**
* Interval in milliseconds in which the power button must be depressed in succession to be
* considered part of an extended sequence of taps. Note that this is a looser threshold than
* the camera launch gesture, because the purpose of this threshold is to measure the
* frequency of consecutive taps, for evaluation for future gestures.
*/
@VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500;
/**
* Number of taps required to launch emergency gesture ui.
*/
private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 10;//huanghb
/**
* Number of taps required to launch camera shortcut.
*/
private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
/** The listener that receives the gesture event. */
private final GestureEventListener mGestureListener = new GestureEventListener();
private final CameraLiftTriggerEventListener mCameraLiftTriggerListener =
new CameraLiftTriggerEventListener();
private Sensor mCameraLaunchSensor;
private Sensor mCameraLiftTriggerSensor;
private Context mContext;
private final MetricsLogger mMetricsLogger;
private PowerManager mPowerManager;
private WindowManagerInternal mWindowManagerInternal;
/** The wake lock held when a gesture is detected. */
private WakeLock mWakeLock;
private boolean mCameraLaunchRegistered;
private boolean mCameraLiftRegistered;
private int mUserId;
// Below are fields used for event logging only.
/** Elapsed real time when the camera gesture is turned on. */
private long mCameraGestureOnTimeMs = 0L;
/** Elapsed real time when the last camera gesture was detected. */
private long mCameraGestureLastEventTime = 0L;
/**
* How long the sensor 1 has been turned on since camera launch sensor was
* subscribed to and when the last camera launch gesture was detected.
* <p>Sensor 1 is the main sensor used to detect camera launch gesture.</p>
*/
private long mCameraGestureSensor1LastOnTimeMs = 0L;
/**
* If applicable, how long the sensor 2 has been turned on since camera
* launch sensor was subscribed to and when the last camera launch
* gesture was detected.
* <p>Sensor 2 is the secondary sensor used to detect camera launch gesture.
* This is optional and if only sensor 1 is used for detect camera launch
* gesture, this value would always be 0.</p>
*/
private long mCameraGestureSensor2LastOnTimeMs = 0L;
/**
* Extra information about the event when the last camera launch gesture
* was detected.
*/
private int mCameraLaunchLastEventExtra = 0;
/**
* Whether camera double tap power button gesture is currently enabled;
*/
private boolean mCameraDoubleTapPowerEnabled;
/**
* Whether emergency gesture is currently enabled
*/
private boolean mEmergencyGestureEnabled;
private long mLastPowerDown;
private int mPowerButtonConsecutiveTaps;
private int mPowerButtonSlowConsecutiveTaps;
private final UiEventLogger mUiEventLogger;
@VisibleForTesting
public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "The user lifted the device just the right way to launch the camera.")
GESTURE_CAMERA_LIFT(658),
@UiEvent(doc = "The user wiggled the device just the right way to launch the camera.")
GESTURE_CAMERA_WIGGLE(659),
@UiEvent(doc = "The user double-tapped power quickly enough to launch the camera.")
GESTURE_CAMERA_DOUBLE_TAP_POWER(660),
@UiEvent(doc = "The user multi-tapped power quickly enough to signal an emergency.")
GESTURE_EMERGENCY_TAP_POWER(661);
private final int mId;
GestureLauncherEvent(int id) {
mId = id;
}
@Override
public int getId() {
return mId;
}
}
public GestureLauncherService(Context context) {
this(context, new MetricsLogger(), new UiEventLoggerImpl());
}
@VisibleForTesting
GestureLauncherService(Context context, MetricsLogger metricsLogger,
UiEventLogger uiEventLogger) {
super(context);
mContext = context;
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
}
@Override
public void onStart() {
LocalServices.addService(GestureLauncherService.class, this);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Resources resources = mContext.getResources();
if (!isGestureLauncherEnabled(resources)) {
if (DBG) Slog.d(TAG, "Gesture launcher is disabled in system properties.");
return;
}
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mPowerManager = (PowerManager) mContext.getSystemService(
Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
updateEmergencyGestureEnabled();
mUserId = ActivityManager.getCurrentUser();
mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
registerContentObservers();
}
}
private void registerContentObservers() {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED),
false, mSettingObserver, mUserId);
}
private void updateCameraRegistered() {
Resources resources = mContext.getResources();
if (isCameraLaunchSettingEnabled(mContext, mUserId)) {
registerCameraLaunchGesture(resources);
} else {
unregisterCameraLaunchGesture();
}
if (isCameraLiftTriggerSettingEnabled(mContext, mUserId)) {
registerCameraLiftTrigger(resources);
} else {
unregisterCameraLiftTrigger();
}
}
@VisibleForTesting
void updateCameraDoubleTapPowerEnabled() {
boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId);
synchronized (this) {
mCameraDoubleTapPowerEnabled = enabled;
}
}
@VisibleForTesting
void updateEmergencyGestureEnabled() {
boolean enabled = isEmergencyGestureSettingEnabled(mContext, mUserId);
synchronized (this) {
mEmergencyGestureEnabled = enabled;
}
}
private void unregisterCameraLaunchGesture() {
if (mCameraLaunchRegistered) {
mCameraLaunchRegistered = false;
mCameraGestureOnTimeMs = 0L;
mCameraGestureLastEventTime = 0L;
mCameraGestureSensor1LastOnTimeMs = 0;
mCameraGestureSensor2LastOnTimeMs = 0;
mCameraLaunchLastEventExtra = 0;
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
sensorManager.unregisterListener(mGestureListener);
}
}
/**
* Registers for the camera launch gesture.
*/
private void registerCameraLaunchGesture(Resources resources) {
if (mCameraLaunchRegistered) {
return;
}
mCameraGestureOnTimeMs = SystemClock.elapsedRealtime();
mCameraGestureLastEventTime = mCameraGestureOnTimeMs;
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
int cameraLaunchGestureId = resources.getInteger(
com.android.internal.R.integer.config_cameraLaunchGestureSensorType);
if (cameraLaunchGestureId != -1) {
mCameraLaunchRegistered = false;
String sensorName = resources.getString(
com.android.internal.R.string.config_cameraLaunchGestureSensorStringType);
mCameraLaunchSensor = sensorManager.getDefaultSensor(
cameraLaunchGestureId,
true /*wakeUp*/);
// Compare the camera gesture string type to that in the resource file to make
// sure we are registering the correct sensor. This is redundant check, it
// makes the code more robust.
if (mCameraLaunchSensor != null) {
if (sensorName.equals(mCameraLaunchSensor.getStringType())) {
mCameraLaunchRegistered = sensorManager.registerListener(mGestureListener,
mCameraLaunchSensor, 0);
} else {
String message = String.format("Wrong configuration. Sensor type and sensor "
+ "string type don't match: %s in resources, %s in the sensor.",
sensorName, mCameraLaunchSensor.getStringType());
throw new RuntimeException(message);
}
}
if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mCameraLaunchRegistered);
} else {
if (DBG) Slog.d(TAG, "Camera launch sensor is not specified.");
}
}
private void unregisterCameraLiftTrigger() {
if (mCameraLiftRegistered) {
mCameraLiftRegistered = false;
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
sensorManager.cancelTriggerSensor(mCameraLiftTriggerListener, mCameraLiftTriggerSensor);
}
}
/**
* Registers for the camera lift trigger.
*/
private void registerCameraLiftTrigger(Resources resources) {
if (mCameraLiftRegistered) {
return;
}
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
int cameraLiftTriggerId = resources.getInteger(
com.android.internal.R.integer.config_cameraLiftTriggerSensorType);
if (cameraLiftTriggerId != -1) {
mCameraLiftRegistered = false;
String sensorName = resources.getString(
com.android.internal.R.string.config_cameraLiftTriggerSensorStringType);
mCameraLiftTriggerSensor = sensorManager.getDefaultSensor(
cameraLiftTriggerId,
true /*wakeUp*/);
// Compare the camera lift trigger string type to that in the resource file to make
// sure we are registering the correct sensor. This is redundant check, it
// makes the code more robust.
if (mCameraLiftTriggerSensor != null) {
if (sensorName.equals(mCameraLiftTriggerSensor.getStringType())) {
mCameraLiftRegistered = sensorManager.requestTriggerSensor(mCameraLiftTriggerListener,
mCameraLiftTriggerSensor);
} else {
String message = String.format("Wrong configuration. Sensor type and sensor "
+ "string type don't match: %s in resources, %s in the sensor.",
sensorName, mCameraLiftTriggerSensor.getStringType());
throw new RuntimeException(message);
}
}
if (DBG) Slog.d(TAG, "Camera lift trigger sensor registered: " + mCameraLiftRegistered);
} else {
if (DBG) Slog.d(TAG, "Camera lift trigger sensor is not specified.");
}
}
public static boolean isCameraLaunchSettingEnabled(Context context, int userId) {
return isCameraLaunchEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
}
public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
return isCameraDoubleTapPowerEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
}
public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
return isCameraLiftTriggerEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED,
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED_DEFAULT, userId) != 0);
}
/**
* Whether to enable emergency gesture.
*/
@VisibleForTesting
static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
return isEmergencyGestureEnabled(context.getResources())
&& Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0;
}
/**
* Whether to enable the camera launch gesture.
*/
private static boolean isCameraLaunchEnabled(Resources resources) {
boolean configSet = resources.getInteger(
com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1;
return configSet &&
!SystemProperties.getBoolean("gesture.disable_camera_launch", false);
}
@VisibleForTesting
static boolean isCameraDoubleTapPowerEnabled(Resources resources) {
return resources.getBoolean(
com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled);
}
private static boolean isCameraLiftTriggerEnabled(Resources resources) {
boolean configSet = resources.getInteger(
com.android.internal.R.integer.config_cameraLiftTriggerSensorType) != -1;
return configSet;
}
/**
* Whether or not the emergency gesture feature is enabled by platform
*/
private static boolean isEmergencyGestureEnabled(Resources resources) {
return resources.getBoolean(com.android.internal.R.bool.config_emergencyGestureEnabled);
}
/**
* Whether GestureLauncherService should be enabled according to system properties.
*/
public static boolean isGestureLauncherEnabled(Resources resources) {
return isCameraLaunchEnabled(resources)
|| isCameraDoubleTapPowerEnabled(resources)
|| isCameraLiftTriggerEnabled(resources)
|| isEmergencyGestureEnabled(resources);
}
/**
* Attempts to intercept power key down event by detecting certain gesture patterns
*
* @param interactive true if the event's policy contains {@code FLAG_INTERACTIVE}
* @param outLaunched true if some action is taken as part of the key intercept (eg, app launch)
* @return true if the key down event is intercepted
*/
public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
MutableBoolean outLaunched) {
if (event.isLongPress()) {
// Long presses are sent as a second key down. If the long press threshold is set lower
// than the double tap of sequence interval thresholds, this could cause false double
// taps or consecutive taps, so we want to ignore the long press event.
return false;
}
boolean launchCamera = false;
boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
synchronized (this) {
powerTapInterval = event.getEventTime() - mLastPowerDown;
mLastPowerDown = event.getEventTime();
if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
// Tap too slow, reset consecutive tap counts.
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps = 1;
} else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
// Tap too slow for shortcuts
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps++;
} else {
// Fast consecutive tap
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
}
// Check if we need to launch camera or emergency gesture flows
if (mEmergencyGestureEnabled) {
// Commit to intercepting the powerkey event after the second "quick" tap to avoid
// lockscreen changes between launching camera and the emergency gesture flow.
if (mPowerButtonConsecutiveTaps > 1) {
intercept = interactive;
}
if (mPowerButtonConsecutiveTaps == EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD) {
launchEmergencyGesture = true;
}
}
if (mCameraDoubleTapPowerEnabled
&& powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS
&& mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD) {
launchCamera = true;
intercept = interactive;
}
}
if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) {
Slog.i(TAG, Long.valueOf(mPowerButtonConsecutiveTaps)
+ " consecutive power button taps detected, "
+ Long.valueOf(mPowerButtonSlowConsecutiveTaps)
+ " consecutive slow power button taps detected");
}
if (launchCamera) {
Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval="
+ powerTapInterval + "ms");
launchCamera = handleCameraGesture(false /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
if (launchCamera) {
mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
(int) powerTapInterval);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
}
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
//launchEmergencyGesture = handleEmergencyGesture();
// sendMasterClear(); // modify by fangchen
mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
}
mMetricsLogger.histogram("power_consecutive_short_tap_count",
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
outLaunched.value = launchCamera || launchEmergencyGesture;
// Intercept power key event if the press is part of a gesture (camera, eGesture) and the
// user has completed setup.
return intercept && isUserSetupComplete();
}
//huanghb add
private void sendMasterClear(){
Intent intent = new Intent();
intent.setAction("com.fise.system.masterclear");
mContext.sendBroadcast(intent);
}
/**
* @return true if camera was launched, false otherwise.
*/
@VisibleForTesting
boolean handleCameraGesture(boolean useWakelock, int source) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "GestureLauncher:handleCameraGesture");
try {
boolean userSetupComplete = isUserSetupComplete();
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, ignoring camera gesture.",
userSetupComplete));
}
return false;
}
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, performing camera gesture.",
userSetupComplete));
}
if (useWakelock) {
// Make sure we don't sleep too early
mWakeLock.acquire(500L);
}
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
service.onCameraLaunchGestureDetected(source);
return true;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
/**
* @return true if emergency gesture UI was launched, false otherwise.
*/
@VisibleForTesting
boolean handleEmergencyGesture() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"GestureLauncher:handleEmergencyGesture");
try {
boolean userSetupComplete = isUserSetupComplete();
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, ignoring emergency gesture.",
userSetupComplete));
}
return false;
}
if (DBG) {
Slog.d(TAG, String.format(
"userSetupComplete = %s, performing emergency gesture.",
userSetupComplete));
}
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
service.onEmergencyActionLaunchGestureDetected();
return true;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
private boolean isUserSetupComplete() {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
}
private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
registerContentObservers();
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
updateEmergencyGestureEnabled();
}
}
};
private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) {
public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
if (userId == mUserId) {
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
updateEmergencyGestureEnabled();
}
}
};
private final class GestureEventListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
if (!mCameraLaunchRegistered) {
if (DBG) Slog.d(TAG, "Ignoring gesture event because it's unregistered.");
return;
}
if (event.sensor == mCameraLaunchSensor) {
if (DBG) {
float[] values = event.values;
Slog.d(TAG, String.format("Received a camera launch event: " +
"values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
}
if (handleCameraGesture(true /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
mMetricsLogger.action(MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_WIGGLE);
trackCameraLaunchEvent(event);
}
return;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Ignored.
}
private void trackCameraLaunchEvent(SensorEvent event) {
long now = SystemClock.elapsedRealtime();
long totalDuration = now - mCameraGestureOnTimeMs;
// values[0]: ratio between total time duration when accel is turned on and time
// duration since camera launch gesture is subscribed.
// values[1]: ratio between total time duration when gyro is turned on and time duration
// since camera launch gesture is subscribed.
// values[2]: extra information
float[] values = event.values;
long sensor1OnTime = (long) (totalDuration * (double) values[0]);
long sensor2OnTime = (long) (totalDuration * (double) values[1]);
int extra = (int) values[2];
// We only log the difference in the event log to make aggregation easier.
long gestureOnTimeDiff = now - mCameraGestureLastEventTime;
long sensor1OnTimeDiff = sensor1OnTime - mCameraGestureSensor1LastOnTimeMs;
long sensor2OnTimeDiff = sensor2OnTime - mCameraGestureSensor2LastOnTimeMs;
int extraDiff = extra - mCameraLaunchLastEventExtra;
// Gating against negative time difference. This doesn't usually happen, but it may
// happen because of numeric errors.
if (gestureOnTimeDiff < 0 || sensor1OnTimeDiff < 0 || sensor2OnTimeDiff < 0) {
if (DBG) Slog.d(TAG, "Skipped event logging because negative numbers.");
return;
}
if (DBG) Slog.d(TAG, String.format("totalDuration: %d, sensor1OnTime: %s, " +
"sensor2OnTime: %d, extra: %d",
gestureOnTimeDiff,
sensor1OnTimeDiff,
sensor2OnTimeDiff,
extraDiff));
EventLogTags.writeCameraGestureTriggered(
gestureOnTimeDiff,
sensor1OnTimeDiff,
sensor2OnTimeDiff,
extraDiff);
mCameraGestureLastEventTime = now;
mCameraGestureSensor1LastOnTimeMs = sensor1OnTime;
mCameraGestureSensor2LastOnTimeMs = sensor2OnTime;
mCameraLaunchLastEventExtra = extra;
}
}
private final class CameraLiftTriggerEventListener extends TriggerEventListener {
@Override
public void onTrigger(TriggerEvent event) {
if (DBG_CAMERA_LIFT) Slog.d(TAG, String.format("onTrigger event - time: %d, name: %s",
event.timestamp, event.sensor.getName()));
if (!mCameraLiftRegistered) {
if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring camera lift event because it's " +
"unregistered.");
return;
}
if (event.sensor == mCameraLiftTriggerSensor) {
Resources resources = mContext.getResources();
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
boolean keyguardShowingAndNotOccluded =
mWindowManagerInternal.isKeyguardShowingAndNotOccluded();
boolean interactive = mPowerManager.isInteractive();
if (DBG_CAMERA_LIFT) {
float[] values = event.values;
Slog.d(TAG, String.format("Received a camera lift trigger " +
"event: values=[%.4f], keyguard showing: %b, interactive: %b", values[0],
keyguardShowingAndNotOccluded, interactive));
}
if (keyguardShowingAndNotOccluded || !interactive) {
if (handleCameraGesture(true /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_LIFT);
}
} else {
if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring lift event");
}
mCameraLiftRegistered = sensorManager.requestTriggerSensor(
mCameraLiftTriggerListener, mCameraLiftTriggerSensor);
if (DBG_CAMERA_LIFT) Slog.d(TAG, "Camera lift trigger sensor re-registered: " +
mCameraLiftRegistered);
return;
}
}
}
}
总结
- 核心就是系统把这个类拿出来了,判断几种手势 解耦操作
- 这里侧重于业务理解,当然也可以自己实现,但是强烈不建议这么搞,系统都解耦抽出来的功能 ,直接修改即可。