Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b47ea976 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix the race condition in SingleKeyGestureDetector"

parents 6f1f343c 374d6d62
Loading
Loading
Loading
Loading
+30 −19
Original line number Diff line number Diff line
@@ -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;
            }
        }
+101 −5
Original line number Diff line number Diff line
@@ -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() {
        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();
        }
    }
}