Fix the race condition in SingleKeyGestureDetector
Send the current active rule to handler message so we could prevent it from reset by another thread. Bug: 195097897 Test: atest SingleKeyGestureTests Change-Id: I5891540f19ab37bad8e9da424bdc8d4df2409460
This commit is contained in:
@@ -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 {
|
||||
* </pre>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user