Loading services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +30 −19 Original line number Diff line number Diff line Loading @@ -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 */ Loading Loading @@ -83,7 +84,6 @@ public final class SingleKeyGestureDetector { * </pre> */ abstract static class SingleKeyRule { private final int mKeyCode; private final int mSupportedGestures; private final long mDefaultLongPressTimeout; Loading Loading @@ -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; } Loading @@ -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) { Loading @@ -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()); } Loading @@ -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; } } Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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; } } Loading services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +101 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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); Loading @@ -69,8 +74,11 @@ public class SingleKeyGestureTests { @Before public void setUp() { mInstrumentation.runOnMainSync(() -> { mDetector = new SingleKeyGestureDetector(); initSingleKeyGestureRules(); }); mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50; mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50; mVeryLongPressTime = mContext.getResources().getInteger( Loading @@ -82,7 +90,7 @@ public class SingleKeyGestureTests { KEY_LONGPRESS | KEY_VERYLONGPRESS) { @Override int getMaxMultiPressCount() { return mMaxMultiPressPowerCount; return mMaxMultiPressCount; } @Override public void onPress(long downTime) { Loading Loading @@ -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) { Loading Loading @@ -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)); Loading @@ -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(); } } } Loading
services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +30 −19 Original line number Diff line number Diff line Loading @@ -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 */ Loading Loading @@ -83,7 +84,6 @@ public final class SingleKeyGestureDetector { * </pre> */ abstract static class SingleKeyRule { private final int mKeyCode; private final int mSupportedGestures; private final long mDefaultLongPressTimeout; Loading Loading @@ -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; } Loading @@ -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) { Loading @@ -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()); } Loading @@ -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; } } Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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; } } Loading
services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +101 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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); Loading @@ -69,8 +74,11 @@ public class SingleKeyGestureTests { @Before public void setUp() { mInstrumentation.runOnMainSync(() -> { mDetector = new SingleKeyGestureDetector(); initSingleKeyGestureRules(); }); mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50; mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50; mVeryLongPressTime = mContext.getResources().getInteger( Loading @@ -82,7 +90,7 @@ public class SingleKeyGestureTests { KEY_LONGPRESS | KEY_VERYLONGPRESS) { @Override int getMaxMultiPressCount() { return mMaxMultiPressPowerCount; return mMaxMultiPressCount; } @Override public void onPress(long downTime) { Loading Loading @@ -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) { Loading Loading @@ -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)); Loading @@ -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(); } } }