Loading services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +21 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ public final class SingleKeyGestureDetector { 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 static final int MSG_KEY_UP = 3; private int mKeyPressCounter; private boolean mBeganFromNonInteractive = false; Loading Loading @@ -144,6 +145,13 @@ public final class SingleKeyGestureDetector { * Callback when very long press has been detected. */ void onVeryLongPress(long eventTime) {} /** * Callback executed upon each key up event that hasn't been processed by long press. * * @param eventTime the timestamp of this event. * @param pressCount the number of presses detected leading up to this key up event. */ void onKeyUp(long eventTime, int pressCount) {} @Override public String toString() { Loading Loading @@ -330,6 +338,13 @@ public final class SingleKeyGestureDetector { } if (event.getKeyCode() == mActiveRule.mKeyCode) { // key-up action should always be triggered if not processed by long press. Message msgKeyUp = mHandler.obtainMessage( MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule); msgKeyUp.setAsynchronous(true); mHandler.sendMessage(msgKeyUp); // Directly trigger short press when max count is 1. if (mActiveRule.getMaxMultiPressCount() == 1) { if (DEBUG) { Loading Loading @@ -417,6 +432,12 @@ public final class SingleKeyGestureDetector { final int keyCode = msg.arg1; final int pressCount = msg.arg2; switch(msg.what) { case MSG_KEY_UP: if (DEBUG) { Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode)); } rule.onKeyUp(mLastDownTime, pressCount); break; case MSG_KEY_LONG_PRESS: if (DEBUG) { Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode)); Loading services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +66 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.app.Instrumentation; Loading @@ -38,7 +39,9 @@ import android.view.KeyEvent; import org.junit.Before; import org.junit.Test; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** Loading @@ -57,6 +60,7 @@ public class SingleKeyGestureTests { private CountDownLatch mLongPressed = new CountDownLatch(1); private CountDownLatch mVeryLongPressed = new CountDownLatch(1); private CountDownLatch mMultiPressed = new CountDownLatch(1); private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>(); private final Instrumentation mInstrumentation = getInstrumentation(); private final Context mContext = mInstrumentation.getTargetContext(); Loading Loading @@ -134,6 +138,11 @@ public class SingleKeyGestureTests { assertTrue(mMaxMultiPressCount >= count); assertEquals(mExpectedMultiPressCount, count); } @Override void onKeyUp(long eventTime, int multiPressCount) { mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount)); } }); mDetector.addRule( Loading Loading @@ -166,6 +175,11 @@ public class SingleKeyGestureTests { assertEquals(mExpectedMultiPressCount, count); } @Override void onKeyUp(long eventTime, int multiPressCount) { mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount)); } @Override void onLongPress(long downTime) { mLongPressed.countDown(); Loading @@ -173,6 +187,16 @@ public class SingleKeyGestureTests { }); } private static class KeyUpData { public final int keyCode; public final int pressCount; KeyUpData(int keyCode, int pressCount) { this.keyCode = keyCode; this.pressCount = pressCount; } } private void pressKey(int keyCode, long pressTime) { pressKey(keyCode, pressTime, true /* interactive */); } Loading Loading @@ -249,6 +273,21 @@ public class SingleKeyGestureTests { assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); } @Test public void testOnKeyUp() throws InterruptedException { pressKey(KEYCODE_POWER, 0 /* pressTime */); verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */); } private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount) throws InterruptedException { KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS); assertNotNull(keyUpData); assertEquals(expectedKeyCode, keyUpData.keyCode); assertEquals(expectedMultiPressCount, keyUpData.pressCount); } @Test public void testNonInteractive() throws InterruptedException { // Disallow short press behavior from non interactive. Loading Loading @@ -313,6 +352,33 @@ public class SingleKeyGestureTests { } } @Test public void testOnKeyUp_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++) { newHandler.runWithScissors( () -> { pressKey(KEYCODE_POWER, 0 /* pressTime */); pressKey(KEYCODE_POWER, 0 /* pressTime */); }, mWaitTimeout); newHandler.runWithScissors( () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout); verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */); verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */); verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */); } } finally { handlerThread.quitSafely(); } } @Test public void testUpdateRule() throws InterruptedException { // Power key rule doesn't allow the long press gesture. Loading Loading
services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +21 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ public final class SingleKeyGestureDetector { 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 static final int MSG_KEY_UP = 3; private int mKeyPressCounter; private boolean mBeganFromNonInteractive = false; Loading Loading @@ -144,6 +145,13 @@ public final class SingleKeyGestureDetector { * Callback when very long press has been detected. */ void onVeryLongPress(long eventTime) {} /** * Callback executed upon each key up event that hasn't been processed by long press. * * @param eventTime the timestamp of this event. * @param pressCount the number of presses detected leading up to this key up event. */ void onKeyUp(long eventTime, int pressCount) {} @Override public String toString() { Loading Loading @@ -330,6 +338,13 @@ public final class SingleKeyGestureDetector { } if (event.getKeyCode() == mActiveRule.mKeyCode) { // key-up action should always be triggered if not processed by long press. Message msgKeyUp = mHandler.obtainMessage( MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule); msgKeyUp.setAsynchronous(true); mHandler.sendMessage(msgKeyUp); // Directly trigger short press when max count is 1. if (mActiveRule.getMaxMultiPressCount() == 1) { if (DEBUG) { Loading Loading @@ -417,6 +432,12 @@ public final class SingleKeyGestureDetector { final int keyCode = msg.arg1; final int pressCount = msg.arg2; switch(msg.what) { case MSG_KEY_UP: if (DEBUG) { Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode)); } rule.onKeyUp(mLastDownTime, pressCount); break; case MSG_KEY_LONG_PRESS: if (DEBUG) { Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode)); Loading
services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +66 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.app.Instrumentation; Loading @@ -38,7 +39,9 @@ import android.view.KeyEvent; import org.junit.Before; import org.junit.Test; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** Loading @@ -57,6 +60,7 @@ public class SingleKeyGestureTests { private CountDownLatch mLongPressed = new CountDownLatch(1); private CountDownLatch mVeryLongPressed = new CountDownLatch(1); private CountDownLatch mMultiPressed = new CountDownLatch(1); private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>(); private final Instrumentation mInstrumentation = getInstrumentation(); private final Context mContext = mInstrumentation.getTargetContext(); Loading Loading @@ -134,6 +138,11 @@ public class SingleKeyGestureTests { assertTrue(mMaxMultiPressCount >= count); assertEquals(mExpectedMultiPressCount, count); } @Override void onKeyUp(long eventTime, int multiPressCount) { mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount)); } }); mDetector.addRule( Loading Loading @@ -166,6 +175,11 @@ public class SingleKeyGestureTests { assertEquals(mExpectedMultiPressCount, count); } @Override void onKeyUp(long eventTime, int multiPressCount) { mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount)); } @Override void onLongPress(long downTime) { mLongPressed.countDown(); Loading @@ -173,6 +187,16 @@ public class SingleKeyGestureTests { }); } private static class KeyUpData { public final int keyCode; public final int pressCount; KeyUpData(int keyCode, int pressCount) { this.keyCode = keyCode; this.pressCount = pressCount; } } private void pressKey(int keyCode, long pressTime) { pressKey(keyCode, pressTime, true /* interactive */); } Loading Loading @@ -249,6 +273,21 @@ public class SingleKeyGestureTests { assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); } @Test public void testOnKeyUp() throws InterruptedException { pressKey(KEYCODE_POWER, 0 /* pressTime */); verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */); } private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount) throws InterruptedException { KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS); assertNotNull(keyUpData); assertEquals(expectedKeyCode, keyUpData.keyCode); assertEquals(expectedMultiPressCount, keyUpData.pressCount); } @Test public void testNonInteractive() throws InterruptedException { // Disallow short press behavior from non interactive. Loading Loading @@ -313,6 +352,33 @@ public class SingleKeyGestureTests { } } @Test public void testOnKeyUp_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++) { newHandler.runWithScissors( () -> { pressKey(KEYCODE_POWER, 0 /* pressTime */); pressKey(KEYCODE_POWER, 0 /* pressTime */); }, mWaitTimeout); newHandler.runWithScissors( () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout); verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */); verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */); verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */); } } finally { handlerThread.quitSafely(); } } @Test public void testUpdateRule() throws InterruptedException { // Power key rule doesn't allow the long press gesture. Loading