Merge "Introduce SingleKeyGestureDetector to PhoneWindowManager" into sc-dev
This commit is contained in:
@@ -102,9 +102,11 @@ public class KeyCombinationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the key event could be triggered by combine key rule before dispatching to a window.
|
* Check if the key event could be intercepted by combination key rule before it is dispatched
|
||||||
|
* to a window.
|
||||||
|
* Return true if any active rule could be triggered by the key event, otherwise false.
|
||||||
*/
|
*/
|
||||||
void interceptKey(KeyEvent event, boolean interactive) {
|
boolean interceptKey(KeyEvent event, boolean interactive) {
|
||||||
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
|
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
|
||||||
final int keyCode = event.getKeyCode();
|
final int keyCode = event.getKeyCode();
|
||||||
final int count = mActiveRules.size();
|
final int count = mActiveRules.size();
|
||||||
@@ -117,9 +119,9 @@ public class KeyCombinationManager {
|
|||||||
// exceed time from first key down.
|
// exceed time from first key down.
|
||||||
forAllRules(mActiveRules, (rule)-> rule.cancel());
|
forAllRules(mActiveRules, (rule)-> rule.cancel());
|
||||||
mActiveRules.clear();
|
mActiveRules.clear();
|
||||||
return;
|
return false;
|
||||||
} else if (count == 0) { // has some key down but no active rule exist.
|
} else if (count == 0) { // has some key down but no active rule exist.
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +129,7 @@ public class KeyCombinationManager {
|
|||||||
mDownTimes.put(keyCode, eventTime);
|
mDownTimes.put(keyCode, eventTime);
|
||||||
} else {
|
} else {
|
||||||
// ignore old key, maybe a repeat key.
|
// ignore old key, maybe a repeat key.
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDownTimes.size() == 1) {
|
if (mDownTimes.size() == 1) {
|
||||||
@@ -141,7 +143,7 @@ public class KeyCombinationManager {
|
|||||||
} else {
|
} else {
|
||||||
// Ignore if rule already triggered.
|
// Ignore if rule already triggered.
|
||||||
if (mTriggeredRule != null) {
|
if (mTriggeredRule != null) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if second key can trigger rule, or remove the non-match rule.
|
// check if second key can trigger rule, or remove the non-match rule.
|
||||||
@@ -156,6 +158,7 @@ public class KeyCombinationManager {
|
|||||||
mActiveRules.clear();
|
mActiveRules.clear();
|
||||||
if (mTriggeredRule != null) {
|
if (mTriggeredRule != null) {
|
||||||
mActiveRules.add(mTriggeredRule);
|
mActiveRules.add(mTriggeredRule);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -168,6 +171,7 @@ public class KeyCombinationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ import static android.view.WindowManagerGlobal.ADD_OKAY;
|
|||||||
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
|
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
|
||||||
|
|
||||||
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
|
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
|
||||||
|
import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS;
|
||||||
|
import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS;
|
||||||
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
|
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
|
||||||
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
|
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
|
||||||
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
|
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
|
||||||
@@ -456,7 +458,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
volatile boolean mPowerKeyHandled;
|
volatile boolean mPowerKeyHandled;
|
||||||
volatile boolean mBackKeyHandled;
|
volatile boolean mBackKeyHandled;
|
||||||
volatile boolean mBeganFromNonInteractive;
|
volatile boolean mBeganFromNonInteractive;
|
||||||
volatile int mPowerKeyPressCounter;
|
|
||||||
volatile boolean mEndCallKeyHandled;
|
volatile boolean mEndCallKeyHandled;
|
||||||
volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
|
volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
|
||||||
volatile boolean mGoingToSleep;
|
volatile boolean mGoingToSleep;
|
||||||
@@ -497,7 +498,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
boolean mHasSoftInput = false;
|
boolean mHasSoftInput = false;
|
||||||
boolean mHapticTextHandleEnabled;
|
boolean mHapticTextHandleEnabled;
|
||||||
boolean mUseTvRouting;
|
boolean mUseTvRouting;
|
||||||
int mVeryLongPressTimeout;
|
|
||||||
boolean mAllowStartActivityForLongPressOnPowerDuringSetup;
|
boolean mAllowStartActivityForLongPressOnPowerDuringSetup;
|
||||||
MetricsLogger mLogger;
|
MetricsLogger mLogger;
|
||||||
boolean mWakeOnDpadKeyPress;
|
boolean mWakeOnDpadKeyPress;
|
||||||
@@ -597,14 +597,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
|
private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
|
||||||
= new LogDecelerateInterpolator(100, 0);
|
= new LogDecelerateInterpolator(100, 0);
|
||||||
|
|
||||||
private final MutableBoolean mTmpBoolean = new MutableBoolean(false);
|
|
||||||
|
|
||||||
private boolean mPerDisplayFocusEnabled = false;
|
private boolean mPerDisplayFocusEnabled = false;
|
||||||
private volatile int mTopFocusedDisplayId = INVALID_DISPLAY;
|
private volatile int mTopFocusedDisplayId = INVALID_DISPLAY;
|
||||||
|
|
||||||
private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS;
|
private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS;
|
||||||
|
|
||||||
private KeyCombinationManager mKeyCombinationManager;
|
private KeyCombinationManager mKeyCombinationManager;
|
||||||
|
private SingleKeyGestureDetector mSingleKeyGestureDetector;
|
||||||
|
|
||||||
private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
|
private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
|
||||||
private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
|
private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
|
||||||
@@ -615,10 +614,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10;
|
private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10;
|
||||||
private static final int MSG_HIDE_BOOT_MESSAGE = 11;
|
private static final int MSG_HIDE_BOOT_MESSAGE = 11;
|
||||||
private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12;
|
private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12;
|
||||||
private static final int MSG_POWER_DELAYED_PRESS = 13;
|
|
||||||
private static final int MSG_POWER_LONG_PRESS = 14;
|
|
||||||
private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU = 15;
|
private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU = 15;
|
||||||
private static final int MSG_BACK_LONG_PRESS = 16;
|
|
||||||
private static final int MSG_ACCESSIBILITY_SHORTCUT = 17;
|
private static final int MSG_ACCESSIBILITY_SHORTCUT = 17;
|
||||||
private static final int MSG_BUGREPORT_TV = 18;
|
private static final int MSG_BUGREPORT_TV = 18;
|
||||||
private static final int MSG_ACCESSIBILITY_TV = 19;
|
private static final int MSG_ACCESSIBILITY_TV = 19;
|
||||||
@@ -626,8 +622,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
private static final int MSG_SYSTEM_KEY_PRESS = 21;
|
private static final int MSG_SYSTEM_KEY_PRESS = 21;
|
||||||
private static final int MSG_HANDLE_ALL_APPS = 22;
|
private static final int MSG_HANDLE_ALL_APPS = 22;
|
||||||
private static final int MSG_LAUNCH_ASSIST = 23;
|
private static final int MSG_LAUNCH_ASSIST = 23;
|
||||||
private static final int MSG_POWER_VERY_LONG_PRESS = 25;
|
private static final int MSG_RINGER_TOGGLE_CHORD = 24;
|
||||||
private static final int MSG_RINGER_TOGGLE_CHORD = 26;
|
|
||||||
|
|
||||||
private class PolicyHandler extends Handler {
|
private class PolicyHandler extends Handler {
|
||||||
@Override
|
@Override
|
||||||
@@ -668,22 +663,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
|
case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
|
||||||
launchVoiceAssistWithWakeLock();
|
launchVoiceAssistWithWakeLock();
|
||||||
break;
|
break;
|
||||||
case MSG_POWER_DELAYED_PRESS:
|
|
||||||
powerPress((Long) msg.obj, msg.arg1 != 0, msg.arg2);
|
|
||||||
finishPowerKeyPress();
|
|
||||||
break;
|
|
||||||
case MSG_POWER_LONG_PRESS:
|
|
||||||
powerLongPress((Long) msg.obj /* eventTime */);
|
|
||||||
break;
|
|
||||||
case MSG_POWER_VERY_LONG_PRESS:
|
|
||||||
powerVeryLongPress();
|
|
||||||
break;
|
|
||||||
case MSG_SHOW_PICTURE_IN_PICTURE_MENU:
|
case MSG_SHOW_PICTURE_IN_PICTURE_MENU:
|
||||||
showPictureInPictureMenuInternal();
|
showPictureInPictureMenuInternal();
|
||||||
break;
|
break;
|
||||||
case MSG_BACK_LONG_PRESS:
|
|
||||||
backLongPress();
|
|
||||||
break;
|
|
||||||
case MSG_ACCESSIBILITY_SHORTCUT:
|
case MSG_ACCESSIBILITY_SHORTCUT:
|
||||||
accessibilityShortcutActivated();
|
accessibilityShortcutActivated();
|
||||||
break;
|
break;
|
||||||
@@ -794,13 +776,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private Runnable mPossibleVeryLongPressReboot = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mActivityManagerInternal.prepareForPossibleShutdown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void handleRingerChordGesture() {
|
private void handleRingerChordGesture() {
|
||||||
if (mRingerToggleChord == VOLUME_HUSH_OFF) {
|
if (mRingerToggleChord == VOLUME_HUSH_OFF) {
|
||||||
return;
|
return;
|
||||||
@@ -840,28 +815,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void interceptBackKeyDown() {
|
|
||||||
mLogger.count("key_back_down", 1);
|
|
||||||
// Reset back key state for long press
|
|
||||||
mBackKeyHandled = false;
|
|
||||||
|
|
||||||
if (hasLongPressOnBackBehavior()) {
|
|
||||||
Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS);
|
|
||||||
msg.setAsynchronous(true);
|
|
||||||
mHandler.sendMessageDelayed(msg,
|
|
||||||
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns true if the key was handled and should not be passed to the user
|
// returns true if the key was handled and should not be passed to the user
|
||||||
private boolean interceptBackKeyUp(KeyEvent event) {
|
private boolean backKeyPress() {
|
||||||
mLogger.count("key_back_up", 1);
|
mLogger.count("key_back_press", 1);
|
||||||
// Cache handled state
|
// Cache handled state
|
||||||
boolean handled = mBackKeyHandled;
|
boolean handled = mBackKeyHandled;
|
||||||
|
|
||||||
// Reset back long press state
|
|
||||||
cancelPendingBackKeyAction();
|
|
||||||
|
|
||||||
if (mHasFeatureWatch) {
|
if (mHasFeatureWatch) {
|
||||||
TelecomManager telecomManager = getTelecommService();
|
TelecomManager telecomManager = getTelecommService();
|
||||||
|
|
||||||
@@ -883,10 +843,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mAutofillManagerInternal != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
if (mAutofillManagerInternal != null) {
|
||||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_BACK_KEY_TO_AUTOFILL));
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_BACK_KEY_TO_AUTOFILL));
|
||||||
}
|
}
|
||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,11 +855,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
mPowerKeyWakeLock.acquire();
|
mPowerKeyWakeLock.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel multi-press detection timeout.
|
|
||||||
if (mPowerKeyPressCounter != 0) {
|
|
||||||
mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
mWindowManagerFuncs.onPowerKeyDown(interactive);
|
mWindowManagerFuncs.onPowerKeyDown(interactive);
|
||||||
|
|
||||||
// Stop ringing or end call if configured to do so when power is pressed.
|
// Stop ringing or end call if configured to do so when power is pressed.
|
||||||
@@ -922,71 +876,20 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
|
|
||||||
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
|
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
|
||||||
|
|
||||||
GestureLauncherService gestureService = LocalServices.getService(
|
|
||||||
GestureLauncherService.class);
|
|
||||||
boolean gesturedServiceIntercepted = false;
|
|
||||||
if (gestureService != null) {
|
|
||||||
gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
|
|
||||||
mTmpBoolean);
|
|
||||||
if (mTmpBoolean.value && mRequestedOrGoingToSleep) {
|
|
||||||
mCameraGestureTriggeredDuringGoingToSleep = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inform the StatusBar; but do not allow it to consume the event.
|
// Inform the StatusBar; but do not allow it to consume the event.
|
||||||
sendSystemKeyToStatusBarAsync(event.getKeyCode());
|
sendSystemKeyToStatusBarAsync(event.getKeyCode());
|
||||||
|
|
||||||
schedulePossibleVeryLongPressReboot();
|
|
||||||
|
|
||||||
// If the power key has still not yet been handled, then detect short
|
// If the power key has still not yet been handled, then detect short
|
||||||
// press, long press, or multi press and decide what to do.
|
// press, long press, or multi press and decide what to do.
|
||||||
mPowerKeyHandled = hungUp || gesturedServiceIntercepted
|
mPowerKeyHandled = mPowerKeyHandled || hungUp
|
||||||
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
|
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
|
||||||
if (!mPowerKeyHandled) {
|
if (!mPowerKeyHandled) {
|
||||||
if (interactive) {
|
if (!interactive) {
|
||||||
// When interactive, we're already awake.
|
|
||||||
// Wait for a long press or for the button to be released to decide what to do.
|
|
||||||
if (hasLongPressOnPowerBehavior()) {
|
|
||||||
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
|
|
||||||
powerLongPress(event.getEventTime());
|
|
||||||
} else {
|
|
||||||
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS,
|
|
||||||
event.getEventTime());
|
|
||||||
msg.setAsynchronous(true);
|
|
||||||
mHandler.sendMessageDelayed(msg,
|
|
||||||
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
|
|
||||||
|
|
||||||
if (hasVeryLongPressOnPowerBehavior()) {
|
|
||||||
Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
|
|
||||||
longMsg.setAsynchronous(true);
|
|
||||||
mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wakeUpFromPowerKey(event.getDownTime());
|
wakeUpFromPowerKey(event.getDownTime());
|
||||||
|
|
||||||
if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {
|
if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {
|
||||||
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
|
|
||||||
powerLongPress(event.getEventTime());
|
|
||||||
} else {
|
|
||||||
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS,
|
|
||||||
event.getEventTime());
|
|
||||||
msg.setAsynchronous(true);
|
|
||||||
mHandler.sendMessageDelayed(msg,
|
|
||||||
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
|
|
||||||
|
|
||||||
if (hasVeryLongPressOnPowerBehavior()) {
|
|
||||||
Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
|
|
||||||
longMsg.setAsynchronous(true);
|
|
||||||
mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mBeganFromNonInteractive = true;
|
mBeganFromNonInteractive = true;
|
||||||
} else {
|
} else {
|
||||||
final int maxCount = getMaxMultiPressPowerCount();
|
final int maxCount = getMaxMultiPressPowerCount();
|
||||||
|
|
||||||
if (maxCount <= 1) {
|
if (maxCount <= 1) {
|
||||||
mPowerKeyHandled = true;
|
mPowerKeyHandled = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -994,68 +897,38 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// handled by another power key policy.
|
||||||
|
if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
|
||||||
|
mSingleKeyGestureDetector.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {
|
private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {
|
||||||
final boolean handled = canceled || mPowerKeyHandled;
|
final boolean handled = canceled || mPowerKeyHandled;
|
||||||
cancelPendingPowerKeyAction();
|
|
||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) {
|
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) {
|
||||||
// Abort possibly stuck animations only when power key up without long press case.
|
// Abort possibly stuck animations only when power key up without long press case.
|
||||||
mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe);
|
mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Figure out how to handle the key now that it has been released.
|
// handled by single key or another power key policy.
|
||||||
mPowerKeyPressCounter += 1;
|
mSingleKeyGestureDetector.reset();
|
||||||
|
finishPowerKeyPress();
|
||||||
final int maxCount = getMaxMultiPressPowerCount();
|
|
||||||
final long eventTime = event.getDownTime();
|
|
||||||
if (mPowerKeyPressCounter < maxCount) {
|
|
||||||
// This could be a multi-press. Wait a little bit longer to confirm.
|
|
||||||
// Continue holding the wake lock.
|
|
||||||
Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,
|
|
||||||
interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);
|
|
||||||
msg.setAsynchronous(true);
|
|
||||||
mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No other actions. Handle it immediately.
|
|
||||||
powerPress(eventTime, interactive, mPowerKeyPressCounter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done. Reset our state.
|
|
||||||
finishPowerKeyPress();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finishPowerKeyPress() {
|
private void finishPowerKeyPress() {
|
||||||
mBeganFromNonInteractive = false;
|
mBeganFromNonInteractive = false;
|
||||||
mPowerKeyPressCounter = 0;
|
mPowerKeyHandled = false;
|
||||||
if (mPowerKeyWakeLock.isHeld()) {
|
if (mPowerKeyWakeLock.isHeld()) {
|
||||||
mPowerKeyWakeLock.release();
|
mPowerKeyWakeLock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelPendingPowerKeyAction() {
|
|
||||||
if (!mPowerKeyHandled) {
|
|
||||||
mPowerKeyHandled = true;
|
|
||||||
mHandler.removeMessages(MSG_POWER_LONG_PRESS);
|
|
||||||
}
|
|
||||||
if (hasVeryLongPressOnPowerBehavior()) {
|
|
||||||
mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
|
|
||||||
}
|
|
||||||
cancelPossibleVeryLongPressReboot();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelPendingBackKeyAction() {
|
|
||||||
if (!mBackKeyHandled) {
|
|
||||||
mBackKeyHandled = true;
|
|
||||||
mHandler.removeMessages(MSG_BACK_LONG_PRESS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void powerPress(long eventTime, boolean interactive, int count) {
|
private void powerPress(long eventTime, boolean interactive, int count) {
|
||||||
if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
|
if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
|
||||||
Slog.i(TAG, "Suppressed redundant power key press while "
|
Slog.i(TAG, "Suppressed redundant power key press while "
|
||||||
@@ -1206,6 +1079,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
|
|
||||||
private void powerLongPress(long eventTime) {
|
private void powerLongPress(long eventTime) {
|
||||||
final int behavior = getResolvedLongPressOnPowerBehavior();
|
final int behavior = getResolvedLongPressOnPowerBehavior();
|
||||||
|
|
||||||
switch (behavior) {
|
switch (behavior) {
|
||||||
case LONG_PRESS_POWER_NOTHING:
|
case LONG_PRESS_POWER_NOTHING:
|
||||||
break;
|
break;
|
||||||
@@ -1844,8 +1718,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
|
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
|
||||||
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
|
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
|
||||||
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
|
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
|
||||||
mVeryLongPressTimeout = mContext.getResources().getInteger(
|
|
||||||
com.android.internal.R.integer.config_veryLongPressTimeout);
|
|
||||||
mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
|
mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
|
||||||
com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup);
|
com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup);
|
||||||
|
|
||||||
@@ -1939,6 +1811,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
initKeyCombinationRules();
|
initKeyCombinationRules();
|
||||||
|
initSingleKeyGestureRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initKeyCombinationRules() {
|
private void initKeyCombinationRules() {
|
||||||
@@ -1951,7 +1824,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
|
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
|
||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
cancelPendingPowerKeyAction();
|
mPowerKeyHandled = true;
|
||||||
interceptScreenshotChord();
|
interceptScreenshotChord();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -1986,7 +1859,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
cancelPendingPowerKeyAction();
|
mPowerKeyHandled = true;
|
||||||
interceptRingerToggleChord();
|
interceptRingerToggleChord();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -2000,7 +1873,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
new TwoKeysCombinationRule(KEYCODE_BACK, KEYCODE_DPAD_DOWN) {
|
new TwoKeysCombinationRule(KEYCODE_BACK, KEYCODE_DPAD_DOWN) {
|
||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
cancelPendingBackKeyAction();
|
mBackKeyHandled = true;
|
||||||
interceptAccessibilityGestureTv();
|
interceptAccessibilityGestureTv();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2014,7 +1887,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
new TwoKeysCombinationRule(KEYCODE_DPAD_CENTER, KEYCODE_BACK) {
|
new TwoKeysCombinationRule(KEYCODE_DPAD_CENTER, KEYCODE_BACK) {
|
||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
cancelPendingBackKeyAction();
|
mBackKeyHandled = true;
|
||||||
interceptBugreportGestureTv();
|
interceptBugreportGestureTv();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2026,6 +1899,84 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule for single power key gesture.
|
||||||
|
*/
|
||||||
|
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
|
||||||
|
PowerKeyRule(int gestures) {
|
||||||
|
super(KEYCODE_POWER, gestures);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int getMaxMultiPressCount() {
|
||||||
|
return getMaxMultiPressPowerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onPress(long downTime) {
|
||||||
|
powerPress(downTime, true, 1 /*count*/);
|
||||||
|
finishPowerKeyPress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onLongPress(long downTime) {
|
||||||
|
powerLongPress(downTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onVeryLongPress(long downTime) {
|
||||||
|
mActivityManagerInternal.prepareForPossibleShutdown();
|
||||||
|
powerVeryLongPress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onMultiPress(long downTime, int count) {
|
||||||
|
powerPress(downTime, true, count);
|
||||||
|
finishPowerKeyPress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule for single back key gesture.
|
||||||
|
*/
|
||||||
|
private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
|
||||||
|
BackKeyRule(int gestures) {
|
||||||
|
super(KEYCODE_BACK, gestures);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int getMaxMultiPressCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onPress(long downTime) {
|
||||||
|
mBackKeyHandled |= backKeyPress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onLongPress(long downTime) {
|
||||||
|
backLongPress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSingleKeyGestureRules() {
|
||||||
|
mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
|
||||||
|
|
||||||
|
int powerKeyGestures = 0;
|
||||||
|
if (hasVeryLongPressOnPowerBehavior()) {
|
||||||
|
powerKeyGestures |= KEY_VERYLONGPRESS;
|
||||||
|
}
|
||||||
|
if (hasLongPressOnPowerBehavior()) {
|
||||||
|
powerKeyGestures |= KEY_LONGPRESS;
|
||||||
|
}
|
||||||
|
mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures));
|
||||||
|
|
||||||
|
if (hasLongPressOnBackBehavior()) {
|
||||||
|
mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read values from config.xml that may be overridden depending on
|
* Read values from config.xml that may be overridden depending on
|
||||||
* the configuration of the device.
|
* the configuration of the device.
|
||||||
@@ -3550,8 +3501,21 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alternate TV power to power key for Android TV device.
|
||||||
|
final HdmiControlManager hdmiControlManager = getHdmiControlManager();
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_TV_POWER && mHasFeatureLeanback
|
||||||
|
&& (hdmiControlManager == null || !hdmiControlManager.shouldHandleTvPowerKey())) {
|
||||||
|
event = KeyEvent.obtain(
|
||||||
|
event.getDownTime(), event.getEventTime(),
|
||||||
|
event.getAction(), KeyEvent.KEYCODE_POWER,
|
||||||
|
event.getRepeatCount(), event.getMetaState(),
|
||||||
|
event.getDeviceId(), event.getScanCode(),
|
||||||
|
event.getFlags(), event.getSource(), event.getDisplayId(), null);
|
||||||
|
return interceptKeyBeforeQueueing(event, policyFlags);
|
||||||
|
}
|
||||||
|
|
||||||
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
|
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
|
||||||
mKeyCombinationManager.interceptKey(event, interactive);
|
handleKeyGesture(event, interactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable haptics if down and virtual key without multiple repetitions. If this is a hard
|
// Enable haptics if down and virtual key without multiple repetitions. If this is a hard
|
||||||
@@ -3566,12 +3530,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
switch (keyCode) {
|
switch (keyCode) {
|
||||||
case KeyEvent.KEYCODE_BACK: {
|
case KeyEvent.KEYCODE_BACK: {
|
||||||
if (down) {
|
if (down) {
|
||||||
interceptBackKeyDown();
|
mBackKeyHandled = false;
|
||||||
} else {
|
} else {
|
||||||
boolean handled = interceptBackKeyUp(event);
|
if (!hasLongPressOnBackBehavior()) {
|
||||||
|
mBackKeyHandled |= backKeyPress();
|
||||||
|
}
|
||||||
// Don't pass back press to app if we've already handled it via long press
|
// Don't pass back press to app if we've already handled it via long press
|
||||||
if (handled) {
|
if (mBackKeyHandled) {
|
||||||
result &= ~ACTION_PASS_TO_USER;
|
result &= ~ACTION_PASS_TO_USER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3683,33 +3648,17 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
case KeyEvent.KEYCODE_TV_POWER: {
|
case KeyEvent.KEYCODE_TV_POWER: {
|
||||||
result &= ~ACTION_PASS_TO_USER;
|
result &= ~ACTION_PASS_TO_USER;
|
||||||
isWakeKey = false; // wake-up will be handled separately
|
isWakeKey = false; // wake-up will be handled separately
|
||||||
HdmiControlManager hdmiControlManager = getHdmiControlManager();
|
if (down && hdmiControlManager != null) {
|
||||||
if (hdmiControlManager != null && hdmiControlManager.shouldHandleTvPowerKey()) {
|
hdmiControlManager.toggleAndFollowTvPower();
|
||||||
if (down) {
|
|
||||||
hdmiControlManager.toggleAndFollowTvPower();
|
|
||||||
}
|
|
||||||
} else if (mHasFeatureLeanback) {
|
|
||||||
KeyEvent fallbackEvent = KeyEvent.obtain(
|
|
||||||
event.getDownTime(), event.getEventTime(),
|
|
||||||
event.getAction(), KeyEvent.KEYCODE_POWER,
|
|
||||||
event.getRepeatCount(), event.getMetaState(),
|
|
||||||
event.getDeviceId(), event.getScanCode(),
|
|
||||||
event.getFlags(), event.getSource(), event.getDisplayId(), null);
|
|
||||||
if (down) {
|
|
||||||
interceptPowerKeyDown(fallbackEvent, interactive);
|
|
||||||
} else {
|
|
||||||
interceptPowerKeyUp(fallbackEvent, interactive, canceled);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Ignore this key for any device that is not connected to a TV via HDMI and
|
|
||||||
// not an Android TV device.
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_POWER: {
|
case KeyEvent.KEYCODE_POWER: {
|
||||||
EventLogTags.writeInterceptPower(
|
EventLogTags.writeInterceptPower(
|
||||||
KeyEvent.actionToString(event.getAction()),
|
KeyEvent.actionToString(event.getAction()),
|
||||||
mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter);
|
mPowerKeyHandled ? 1 : 0,
|
||||||
|
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
|
||||||
// Any activity on the power button stops the accessibility shortcut
|
// Any activity on the power button stops the accessibility shortcut
|
||||||
result &= ~ACTION_PASS_TO_USER;
|
result &= ~ACTION_PASS_TO_USER;
|
||||||
isWakeKey = false; // wake-up will be handled separately
|
isWakeKey = false; // wake-up will be handled separately
|
||||||
@@ -3890,6 +3839,43 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleKeyGesture(KeyEvent event, boolean interactive) {
|
||||||
|
if (mKeyCombinationManager.interceptKey(event, interactive)) {
|
||||||
|
// handled by combo keys manager.
|
||||||
|
mSingleKeyGestureDetector.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
mPowerKeyHandled = handleCameraGesture(event, interactive);
|
||||||
|
if (mPowerKeyHandled) {
|
||||||
|
// handled by camera gesture.
|
||||||
|
mSingleKeyGestureDetector.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mSingleKeyGestureDetector.interceptKey(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The camera gesture will be detected by GestureLauncherService.
|
||||||
|
private boolean handleCameraGesture(KeyEvent event, boolean interactive) {
|
||||||
|
// camera gesture.
|
||||||
|
GestureLauncherService gestureService = LocalServices.getService(
|
||||||
|
GestureLauncherService.class);
|
||||||
|
if (gestureService == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MutableBoolean outLaunched = new MutableBoolean(false);
|
||||||
|
final boolean gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event,
|
||||||
|
interactive, outLaunched);
|
||||||
|
if (outLaunched.value && mRequestedOrGoingToSleep) {
|
||||||
|
mCameraGestureTriggeredDuringGoingToSleep = true;
|
||||||
|
}
|
||||||
|
return gesturedServiceIntercepted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle statusbar expansion events.
|
* Handle statusbar expansion events.
|
||||||
* @param event
|
* @param event
|
||||||
@@ -4889,15 +4875,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void schedulePossibleVeryLongPressReboot() {
|
|
||||||
mHandler.removeCallbacks(mPossibleVeryLongPressReboot);
|
|
||||||
mHandler.postDelayed(mPossibleVeryLongPressReboot, mVeryLongPressTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelPossibleVeryLongPressReboot() {
|
|
||||||
mHandler.removeCallbacks(mPossibleVeryLongPressReboot);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
|
// TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
|
||||||
private void updateScreenOffSleepToken(boolean acquire) {
|
private void updateScreenOffSleepToken(boolean acquire) {
|
||||||
if (acquire) {
|
if (acquire) {
|
||||||
|
|||||||
@@ -0,0 +1,356 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.policy;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect single key gesture: press, long press, very long press and multi press.
|
||||||
|
*
|
||||||
|
* Call {@link #reset} if current {@link KeyEvent} has been handled by another policy
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final class SingleKeyGestureDetector {
|
||||||
|
private static final String TAG = "SingleKeyGesture";
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
private static final int MSG_KEY_LONG_PRESS = 0;
|
||||||
|
private static final int MSG_KEY_VERY_LONG_PRESS = 1;
|
||||||
|
private static final int MSG_KEY_DELAYED_PRESS = 2;
|
||||||
|
|
||||||
|
private final long mLongPressTimeout;
|
||||||
|
private final long mVeryLongPressTimeout;
|
||||||
|
|
||||||
|
private volatile int mKeyPressCounter;
|
||||||
|
|
||||||
|
private final ArrayList<SingleKeyRule> mRules = new ArrayList();
|
||||||
|
private SingleKeyRule mActiveRule = null;
|
||||||
|
|
||||||
|
// Key code of current key down event, reset when key up.
|
||||||
|
private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
|
||||||
|
private volatile boolean mHandledByLongPress = false;
|
||||||
|
private final Handler mHandler;
|
||||||
|
private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout();
|
||||||
|
|
||||||
|
|
||||||
|
/** Supported gesture flags */
|
||||||
|
public static final int KEY_LONGPRESS = 1 << 1;
|
||||||
|
public static final int KEY_VERYLONGPRESS = 1 << 2;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(prefix = { "KEY_" }, value = {
|
||||||
|
KEY_LONGPRESS,
|
||||||
|
KEY_VERYLONGPRESS,
|
||||||
|
})
|
||||||
|
public @interface KeyGestureFlag {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule definition for single keys gesture.
|
||||||
|
* E.g : define power key.
|
||||||
|
* <pre class="prettyprint">
|
||||||
|
* SingleKeyRule rule =
|
||||||
|
* new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) {
|
||||||
|
* int getMaxMultiPressCount() { // maximum multi press count. }
|
||||||
|
* void onPress(long downTime) { // short press behavior. }
|
||||||
|
* void onLongPress() { // long press behavior. }
|
||||||
|
* void onVeryLongPress() { // very long press behavior. }
|
||||||
|
* void onMultiPress(long downTime, int count) { // multi press behavior. }
|
||||||
|
* };
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
abstract static class SingleKeyRule {
|
||||||
|
private final int mKeyCode;
|
||||||
|
private final int mSupportedGestures;
|
||||||
|
|
||||||
|
SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) {
|
||||||
|
mKeyCode = keyCode;
|
||||||
|
mSupportedGestures = supportedGestures;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the rule could intercept the key.
|
||||||
|
*/
|
||||||
|
private boolean shouldInterceptKey(int keyCode) {
|
||||||
|
return keyCode == mKeyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the rule support long press.
|
||||||
|
*/
|
||||||
|
private boolean supportLongPress() {
|
||||||
|
return (mSupportedGestures & KEY_LONGPRESS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the rule support very long press.
|
||||||
|
*/
|
||||||
|
private boolean supportVeryLongPress() {
|
||||||
|
return (mSupportedGestures & KEY_VERYLONGPRESS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum count of multi presses.
|
||||||
|
* Return 1 will trigger onPress immediately when {@link KeyEvent.ACTION_UP}.
|
||||||
|
* Otherwise trigger onMultiPress immediately when reach max count when
|
||||||
|
* {@link KeyEvent.ACTION_DOWN}.
|
||||||
|
*/
|
||||||
|
int getMaxMultiPressCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when short press has been detected.
|
||||||
|
*/
|
||||||
|
abstract void onPress(long downTime);
|
||||||
|
/**
|
||||||
|
* Callback when multi press (>= 2) has been detected.
|
||||||
|
*/
|
||||||
|
void onMultiPress(long downTime, int count) {}
|
||||||
|
/**
|
||||||
|
* Callback when long press has been detected.
|
||||||
|
*/
|
||||||
|
void onLongPress(long downTime) {}
|
||||||
|
/**
|
||||||
|
* Callback when very long press has been detected.
|
||||||
|
*/
|
||||||
|
void onVeryLongPress(long downTime) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "KeyCode = " + KeyEvent.keyCodeToString(mKeyCode)
|
||||||
|
+ ", long press : " + supportLongPress()
|
||||||
|
+ ", very Long press : " + supportVeryLongPress()
|
||||||
|
+ ", max multi press count : " + getMaxMultiPressCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleKeyGestureDetector(Context context) {
|
||||||
|
mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
|
||||||
|
mVeryLongPressTimeout = context.getResources().getInteger(
|
||||||
|
com.android.internal.R.integer.config_veryLongPressTimeout);
|
||||||
|
mHandler = new KeyHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRule(SingleKeyRule rule) {
|
||||||
|
mRules.add(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
void interceptKey(KeyEvent event) {
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
interceptKeyDown(event);
|
||||||
|
} else {
|
||||||
|
interceptKeyUp(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void interceptKeyDown(KeyEvent event) {
|
||||||
|
final int keyCode = event.getKeyCode();
|
||||||
|
// same key down.
|
||||||
|
if (mDownKeyCode == keyCode) {
|
||||||
|
if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0
|
||||||
|
&& !mHandledByLongPress) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(TAG, "Long press Key " + KeyEvent.keyCodeToString(keyCode));
|
||||||
|
}
|
||||||
|
mHandledByLongPress = true;
|
||||||
|
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
|
||||||
|
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
|
||||||
|
mActiveRule.onLongPress(event.getEventTime());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a different key is pressed, stop processing gestures for the currently active key.
|
||||||
|
if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN
|
||||||
|
|| (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
mDownKeyCode = keyCode;
|
||||||
|
|
||||||
|
// Picks a new rule, return if no rule picked.
|
||||||
|
if (mActiveRule == null) {
|
||||||
|
final int count = mRules.size();
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
final SingleKeyRule rule = mRules.get(index);
|
||||||
|
if (rule.shouldInterceptKey(keyCode)) {
|
||||||
|
mActiveRule = rule;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mActiveRule == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long eventTime = event.getEventTime();
|
||||||
|
if (mKeyPressCounter == 0) {
|
||||||
|
if (mActiveRule.supportLongPress()) {
|
||||||
|
final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
|
||||||
|
eventTime);
|
||||||
|
msg.setAsynchronous(true);
|
||||||
|
mHandler.sendMessageDelayed(msg, mLongPressTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mActiveRule.supportVeryLongPress()) {
|
||||||
|
final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
|
||||||
|
eventTime);
|
||||||
|
msg.setAsynchronous(true);
|
||||||
|
mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
|
||||||
|
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
|
||||||
|
mHandler.removeMessages(MSG_KEY_DELAYED_PRESS);
|
||||||
|
|
||||||
|
// Trigger multi press immediately when reach max count.( > 1)
|
||||||
|
if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it"
|
||||||
|
+ " reach the max count " + mKeyPressCounter);
|
||||||
|
}
|
||||||
|
mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1);
|
||||||
|
mKeyPressCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean interceptKeyUp(KeyEvent event) {
|
||||||
|
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
|
||||||
|
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
|
||||||
|
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
|
||||||
|
if (mActiveRule == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mHandledByLongPress) {
|
||||||
|
mHandledByLongPress = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long downTime = event.getDownTime();
|
||||||
|
if (event.getKeyCode() == mActiveRule.mKeyCode) {
|
||||||
|
// Directly trigger short press when max count is 1.
|
||||||
|
if (mActiveRule.getMaxMultiPressCount() == 1) {
|
||||||
|
mActiveRule.onPress(downTime);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This could be a multi-press. Wait a little bit longer to confirm.
|
||||||
|
mKeyPressCounter++;
|
||||||
|
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
|
||||||
|
mKeyPressCounter, downTime);
|
||||||
|
msg.setAsynchronous(true);
|
||||||
|
mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getKeyPressCounter(int keyCode) {
|
||||||
|
if (mActiveRule != null && mActiveRule.mKeyCode == keyCode) {
|
||||||
|
return mKeyPressCounter;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
if (mActiveRule != null) {
|
||||||
|
if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
|
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
|
||||||
|
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mKeyPressCounter > 0) {
|
||||||
|
mHandler.removeMessages(MSG_KEY_DELAYED_PRESS);
|
||||||
|
mKeyPressCounter = 0;
|
||||||
|
}
|
||||||
|
mActiveRule = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mHandledByLongPress = false;
|
||||||
|
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isKeyIntercepted(int keyCode) {
|
||||||
|
if (mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode)) {
|
||||||
|
return mHandledByLongPress;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KeyHandler extends Handler {
|
||||||
|
KeyHandler() {
|
||||||
|
super(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
if (mActiveRule == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int keyCode = msg.arg1;
|
||||||
|
final long eventTime = (long) msg.obj;
|
||||||
|
switch(msg.what) {
|
||||||
|
case MSG_KEY_LONG_PRESS:
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
|
||||||
|
}
|
||||||
|
mHandledByLongPress = true;
|
||||||
|
mActiveRule.onLongPress(eventTime);
|
||||||
|
break;
|
||||||
|
case MSG_KEY_VERY_LONG_PRESS:
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(TAG, "Detect very long press "
|
||||||
|
+ KeyEvent.keyCodeToString(keyCode));
|
||||||
|
}
|
||||||
|
mHandledByLongPress = true;
|
||||||
|
mActiveRule.onVeryLongPress(eventTime);
|
||||||
|
break;
|
||||||
|
case MSG_KEY_DELAYED_PRESS:
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode)
|
||||||
|
+ ", count " + mKeyPressCounter);
|
||||||
|
}
|
||||||
|
if (mKeyPressCounter == 1) {
|
||||||
|
mActiveRule.onPress(eventTime);
|
||||||
|
} else {
|
||||||
|
mActiveRule.onMultiPress(eventTime, mKeyPressCounter);
|
||||||
|
}
|
||||||
|
mKeyPressCounter = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.policy;
|
||||||
|
|
||||||
|
import static android.view.KeyEvent.ACTION_DOWN;
|
||||||
|
import static android.view.KeyEvent.ACTION_UP;
|
||||||
|
import static android.view.KeyEvent.KEYCODE_POWER;
|
||||||
|
|
||||||
|
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||||
|
|
||||||
|
import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS;
|
||||||
|
import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for {@link SingleKeyGestureDetector}.
|
||||||
|
*
|
||||||
|
* Build/Install/Run:
|
||||||
|
* atest WmTests:SingleKeyGestureTests
|
||||||
|
*/
|
||||||
|
public class SingleKeyGestureTests {
|
||||||
|
private SingleKeyGestureDetector mDetector;
|
||||||
|
|
||||||
|
private int mMaxMultiPressPowerCount = 2;
|
||||||
|
|
||||||
|
private CountDownLatch mShortPressed = new CountDownLatch(1);
|
||||||
|
private CountDownLatch mLongPressed = new CountDownLatch(1);
|
||||||
|
private CountDownLatch mVeryLongPressed = new CountDownLatch(1);
|
||||||
|
private CountDownLatch mMultiPressed = new CountDownLatch(1);
|
||||||
|
|
||||||
|
private final Instrumentation mInstrumentation = getInstrumentation();
|
||||||
|
private final Context mContext = mInstrumentation.getTargetContext();
|
||||||
|
private long mWaitTimeout;
|
||||||
|
private long mLongPressTime;
|
||||||
|
private long mVeryLongPressTime;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mDetector = new SingleKeyGestureDetector(mContext);
|
||||||
|
initSingleKeyGestureRules();
|
||||||
|
mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50;
|
||||||
|
mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50;
|
||||||
|
mVeryLongPressTime = mContext.getResources().getInteger(
|
||||||
|
com.android.internal.R.integer.config_veryLongPressTimeout) + 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSingleKeyGestureRules() {
|
||||||
|
mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER,
|
||||||
|
KEY_LONGPRESS | KEY_VERYLONGPRESS) {
|
||||||
|
@Override
|
||||||
|
int getMaxMultiPressCount() {
|
||||||
|
return mMaxMultiPressPowerCount;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onPress(long downTime) {
|
||||||
|
mShortPressed.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onLongPress(long downTime) {
|
||||||
|
mLongPressed.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onVeryLongPress(long downTime) {
|
||||||
|
mVeryLongPressed.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onMultiPress(long downTime, int count) {
|
||||||
|
mMultiPressed.countDown();
|
||||||
|
assertEquals(mMaxMultiPressPowerCount, count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pressKey(long eventTime, int keyCode, long pressTime) {
|
||||||
|
final KeyEvent keyDown = new KeyEvent(eventTime, eventTime, ACTION_DOWN,
|
||||||
|
keyCode, 0 /* repeat */, 0 /* metaState */);
|
||||||
|
mDetector.interceptKey(keyDown);
|
||||||
|
|
||||||
|
// keep press down.
|
||||||
|
try {
|
||||||
|
Thread.sleep(pressTime);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
eventTime += pressTime;
|
||||||
|
final KeyEvent keyUp = new KeyEvent(eventTime, eventTime, ACTION_UP,
|
||||||
|
keyCode, 0 /* repeat */, 0 /* metaState */);
|
||||||
|
|
||||||
|
mDetector.interceptKey(keyUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShortPress() throws InterruptedException {
|
||||||
|
final long eventTime = SystemClock.uptimeMillis();
|
||||||
|
pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */);
|
||||||
|
assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLongPress() throws InterruptedException {
|
||||||
|
final long eventTime = SystemClock.uptimeMillis();
|
||||||
|
pressKey(eventTime, KEYCODE_POWER, mLongPressTime);
|
||||||
|
assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVeryLongPress() throws InterruptedException {
|
||||||
|
final long eventTime = SystemClock.uptimeMillis();
|
||||||
|
pressKey(eventTime, KEYCODE_POWER, mVeryLongPressTime);
|
||||||
|
assertTrue(mVeryLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiPress() throws InterruptedException {
|
||||||
|
final long eventTime = SystemClock.uptimeMillis();
|
||||||
|
pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */);
|
||||||
|
pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */);
|
||||||
|
assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user