diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 6fee69bf04727..1d4943fc1b034 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -54,6 +54,7 @@ public final class SingleKeyGestureDetector { private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; private volatile boolean mHandledByLongPress = false; private final Handler mHandler; + private long mLastDownTime = 0; private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); /** Supported gesture flags */ @@ -83,7 +84,6 @@ public final class SingleKeyGestureDetector { * */ abstract static class SingleKeyRule { - private final int mKeyCode; private final int mSupportedGestures; private final long mDefaultLongPressTimeout; @@ -205,7 +205,10 @@ public final class SingleKeyGestureDetector { mHandledByLongPress = true; mHandler.removeMessages(MSG_KEY_LONG_PRESS); mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); - mActiveRule.onLongPress(event.getEventTime()); + final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, mActiveRule.mKeyCode, + 0, mActiveRule); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); } return; } @@ -219,6 +222,7 @@ public final class SingleKeyGestureDetector { reset(); } mDownKeyCode = keyCode; + mLastDownTime = event.getDownTime(); // Picks a new rule, return if no rule picked. if (mActiveRule == null) { @@ -238,18 +242,17 @@ public final class SingleKeyGestureDetector { return; } - final long eventTime = event.getEventTime(); if (mKeyPressCounter == 0) { if (mActiveRule.supportLongPress()) { final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0, - eventTime); + mActiveRule); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs()); } if (mActiveRule.supportVeryLongPress()) { final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0, - eventTime); + mActiveRule); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs()); } @@ -262,9 +265,12 @@ public final class SingleKeyGestureDetector { if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) { if (DEBUG) { Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it" - + " reach the max count " + mKeyPressCounter); + + " reached the max count " + (mKeyPressCounter + 1)); } - mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1); + final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, keyCode, + mKeyPressCounter + 1, mActiveRule); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); mKeyPressCounter = 0; } } @@ -284,7 +290,6 @@ public final class SingleKeyGestureDetector { 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) { @@ -292,16 +297,17 @@ public final class SingleKeyGestureDetector { Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode())); } Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, - 1, downTime); + 1, mActiveRule); msg.setAsynchronous(true); mHandler.sendMessage(msg); + reset(); 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); + mKeyPressCounter, mActiveRule); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); return true; @@ -358,18 +364,24 @@ public final class SingleKeyGestureDetector { @Override public void handleMessage(Message msg) { - if (mActiveRule == null) { + final SingleKeyRule rule = (SingleKeyRule) msg.obj; + if (rule == null) { + Log.wtf(TAG, "No active rule."); return; } + // We count the press count when interceptKeyUp. Reset the counter here to prevent if + // the multi-press or press happened but the count is less than max multi-press count. + mKeyPressCounter = 0; + final int keyCode = msg.arg1; - final long eventTime = (long) msg.obj; + final int pressCount = msg.arg2; switch(msg.what) { case MSG_KEY_LONG_PRESS: if (DEBUG) { Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode)); } mHandledByLongPress = true; - mActiveRule.onLongPress(eventTime); + rule.onLongPress(mLastDownTime); break; case MSG_KEY_VERY_LONG_PRESS: if (DEBUG) { @@ -377,19 +389,18 @@ public final class SingleKeyGestureDetector { + KeyEvent.keyCodeToString(keyCode)); } mHandledByLongPress = true; - mActiveRule.onVeryLongPress(eventTime); + rule.onVeryLongPress(mLastDownTime); break; case MSG_KEY_DELAYED_PRESS: if (DEBUG) { Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode) - + ", count " + mKeyPressCounter); + + ", count " + pressCount); } - if (mKeyPressCounter == 1) { - mActiveRule.onPress(eventTime); + if (pressCount == 1) { + rule.onPress(mLastDownTime); } else { - mActiveRule.onMultiPress(eventTime, mKeyPressCounter); + rule.onMultiPress(mLastDownTime, pressCount); } - reset(); break; } } diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index 6b36fe808a2e9..1ff672404254e 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -18,6 +18,7 @@ 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_BACK; import static android.view.KeyEvent.KEYCODE_POWER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -31,6 +32,9 @@ import static org.junit.Assert.assertTrue; import android.app.Instrumentation; import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; import android.os.SystemClock; import android.view.KeyEvent; import android.view.ViewConfiguration; @@ -50,7 +54,8 @@ import java.util.concurrent.TimeUnit; public class SingleKeyGestureTests { private SingleKeyGestureDetector mDetector; - private int mMaxMultiPressPowerCount = 2; + private int mMaxMultiPressCount = 3; + private int mExpectedMultiPressCount = 2; private CountDownLatch mShortPressed = new CountDownLatch(1); private CountDownLatch mLongPressed = new CountDownLatch(1); @@ -69,8 +74,11 @@ public class SingleKeyGestureTests { @Before public void setUp() { - mDetector = new SingleKeyGestureDetector(); - initSingleKeyGestureRules(); + mInstrumentation.runOnMainSync(() -> { + mDetector = new SingleKeyGestureDetector(); + initSingleKeyGestureRules(); + }); + mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50; mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50; mVeryLongPressTime = mContext.getResources().getInteger( @@ -82,7 +90,7 @@ public class SingleKeyGestureTests { KEY_LONGPRESS | KEY_VERYLONGPRESS) { @Override int getMaxMultiPressCount() { - return mMaxMultiPressPowerCount; + return mMaxMultiPressCount; } @Override public void onPress(long downTime) { @@ -111,9 +119,35 @@ public class SingleKeyGestureTests { return; } mMultiPressed.countDown(); - assertEquals(mMaxMultiPressPowerCount, count); + assertTrue(mMaxMultiPressCount >= count); + assertEquals(mExpectedMultiPressCount, count); } }); + + mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(mContext, KEYCODE_BACK, 0) { + @Override + int getMaxMultiPressCount() { + return mMaxMultiPressCount; + } + @Override + public void onPress(long downTime) { + if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { + return; + } + mShortPressed.countDown(); + } + + @Override + void onMultiPress(long downTime, int count) { + if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { + return; + } + mMultiPressed.countDown(); + assertTrue(mMaxMultiPressCount >= count); + assertEquals(mExpectedMultiPressCount, count); + } + }); + } private void pressKey(long eventTime, int keyCode, long pressTime) { @@ -163,6 +197,16 @@ public class SingleKeyGestureTests { @Test public void testMultiPress() throws InterruptedException { final long eventTime = SystemClock.uptimeMillis(); + // Double presses. + mExpectedMultiPressCount = 2; + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + + // Triple presses. + mExpectedMultiPressCount = 3; + mMultiPressed = new CountDownLatch(1); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); @@ -181,4 +225,56 @@ public class SingleKeyGestureTests { pressKey(eventTime, KEYCODE_POWER, mLongPressTime, false /* interactive */); assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); } + + @Test + public void testShortPress_Pressure() throws InterruptedException { + final HandlerThread handlerThread = + new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY); + handlerThread.start(); + Handler newHandler = new Handler(handlerThread.getLooper()); + mMaxMultiPressCount = 1; // Will trigger short press when event up. + try { + // To make sure we won't get any crash while panic pressing keys. + for (int i = 0; i < 100; i++) { + mShortPressed = new CountDownLatch(2); + newHandler.runWithScissors(() -> { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + pressKey(eventTime, KEYCODE_BACK, 0 /* pressTime */); + }, mWaitTimeout); + assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + } finally { + handlerThread.quitSafely(); + } + } + + @Test + public void testMultiPress_Pressure() throws InterruptedException { + final HandlerThread handlerThread = + new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY); + handlerThread.start(); + Handler newHandler = new Handler(handlerThread.getLooper()); + try { + // To make sure we won't get any unexpected multi-press count. + for (int i = 0; i < 5; i++) { + mMultiPressed = new CountDownLatch(1); + mShortPressed = new CountDownLatch(1); + newHandler.runWithScissors(() -> { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + }, mWaitTimeout); + assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + + newHandler.runWithScissors(() -> { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + }, mWaitTimeout); + assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + } finally { + handlerThread.quitSafely(); + } + } }