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

Commit 06a81cac authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

Revert "Revert "Refactor Single key detector to provide start, c..."

Revert submission 33530799-revert-33464207-support_gemini_power_key_animation-OWWMRETITV

Reason for revert: Tested locally but the reported tests pass (falsely reverted): Will test on ABTD to confirm

Tests are failing with or without CLs: Some other issue.
Without CL: https://android-build.corp.google.com/builds/abtd/run/L46300030012099882
With CL: https://android-build.corp.google.com/builds/abtd/run/L46300030012098202

Reverted changes: /q/submissionid:33530799-revert-33464207-support_gemini_power_key_animation-OWWMRETITV

Change-Id: I6c1ad554fe4b2f2847be7ca1111b77e6141048e4
parent 98c7ca2f
Loading
Loading
Loading
Loading
+80 −39
Original line number Diff line number Diff line
@@ -82,6 +82,10 @@ import static android.view.contentprotection.flags.Flags.createAccessibilityOver

import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.hidBluetoothWakeup;
import static com.android.server.policy.SingleKeyGestureEvent.ACTION_COMPLETE;
import static com.android.server.policy.SingleKeyGestureEvent.SINGLE_KEY_GESTURE_TYPE_LONG_PRESS;
import static com.android.server.policy.SingleKeyGestureEvent.SINGLE_KEY_GESTURE_TYPE_PRESS;
import static com.android.server.policy.SingleKeyGestureEvent.SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -105,6 +109,7 @@ import static com.android.systemui.shared.Flags.enableLppAssistInvocationHapticE
import static com.android.window.flags.Flags.delegateBackGestureToShell;

import android.accessibilityservice.AccessibilityService;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -2496,7 +2501,31 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        @Override
        void onPress(long downTime, int displayId) {
        void onKeyGesture(@NonNull SingleKeyGestureEvent event) {
            final long startTime = event.getStartTime();
            final int displayId = event.getDisplayId();
            final int pressCount = event.getPressCount();
            if (event.getAction() != ACTION_COMPLETE) {
                return;
            }
            switch (event.getType()) {
                case SINGLE_KEY_GESTURE_TYPE_PRESS:
                    if (event.getPressCount() > 1) {
                        onMultiPress(startTime, pressCount, displayId);
                    } else {
                        onPress(startTime, displayId);
                    }
                    break;
                case SINGLE_KEY_GESTURE_TYPE_LONG_PRESS:
                    onLongPress(startTime);
                    break;
                case SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS:
                    onVeryLongPress();
                    break;
            }
        }

        private void onPress(long downTime, int displayId) {
            if (mShouldEarlyShortPressOnPower) {
                return;
            }
@@ -2512,32 +2541,29 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            }
        }

        @Override
        void onLongPress(long eventTime) {
        private void onLongPress(long downTime) {
            if (mSingleKeyGestureDetector.beganFromNonInteractive()
                    && !mSupportLongPressPowerWhenNonInteractive) {
                Slog.v(TAG, "Not support long press power when device is not interactive.");
                return;
            }

            powerLongPress(eventTime);
            powerLongPress(downTime);
        }

        @Override
        void onVeryLongPress(long eventTime) {
        private void onVeryLongPress() {
            mActivityManagerInternal.prepareForPossibleShutdown();
            powerVeryLongPress();
        }

        @Override
        void onMultiPress(long downTime, int count, int displayId) {
        private void onMultiPress(long downTime, int count, int displayId) {
            powerPress(downTime, count, displayId);
        }

        @Override
        void onKeyUp(long eventTime, int count, int displayId, int deviceId, int metaState) {
        void onKeyUp(int count, KeyEvent event) {
            if (mShouldEarlyShortPressOnPower && count == 1) {
                powerPress(eventTime, 1 /*pressCount*/, displayId);
                powerPress(event.getDownTime(), 1 /*pressCount*/, event.getDisplayId());
            }
        }
    }
@@ -2556,18 +2582,20 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        @Override
        int getMaxMultiPressCount() {
            return 1;
        void onKeyGesture(@NonNull SingleKeyGestureEvent event) {
            if (event.getAction() != ACTION_COMPLETE) {
                return;
            }

        @Override
        void onPress(long downTime, int unusedDisplayId) {
            switch (event.getType()) {
                case SINGLE_KEY_GESTURE_TYPE_PRESS:
                    if (event.getPressCount() == 1) {
                        mBackKeyHandled |= backKeyPress();
                    }

        @Override
        void onLongPress(long downTime) {
                    break;
                case SINGLE_KEY_GESTURE_TYPE_LONG_PRESS:
                    backLongPress();
                    break;
            }
        }
    }

@@ -2590,7 +2618,27 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        @Override
        void onPress(long downTime, int unusedDisplayId) {
        void onKeyGesture(@NonNull SingleKeyGestureEvent event) {
            final long startTime = event.getStartTime();
            final int pressCount = event.getPressCount();
            if (event.getAction() != ACTION_COMPLETE) {
                return;
            }
            switch (event.getType()) {
                case SINGLE_KEY_GESTURE_TYPE_PRESS:
                    if (event.getPressCount() > 1) {
                        onMultiPress(startTime, pressCount);
                    } else {
                        onPress(startTime);
                    }
                    break;
                case SINGLE_KEY_GESTURE_TYPE_LONG_PRESS:
                    onLongPress(startTime);
                    break;
            }
        }

        private void onPress(long downTime) {
            if (shouldHandleStemPrimaryEarlyShortPress()) {
                return;
            }
@@ -2599,22 +2647,20 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    KeyEvent.KEYCODE_STEM_PRIMARY, downTime, () -> stemPrimaryPress(1 /*count*/));
        }

        @Override
        void onLongPress(long eventTime) {
        private void onLongPress(long downTime) {
            if (mLongPressOnStemPrimaryBehavior == LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT) {
                // Long-press to assistant gesture is not overridable by apps.
                stemPrimaryLongPress(eventTime);
                stemPrimaryLongPress(downTime);
            } else {
                // Other long-press actions should be triggered only if app doesn't handle it.
                mDeferredKeyActionExecutor.queueKeyAction(
                        KeyEvent.KEYCODE_STEM_PRIMARY,
                        eventTime,
                        () -> stemPrimaryLongPress(eventTime));
                        downTime,
                        () -> stemPrimaryLongPress(downTime));
            }
        }

        @Override
        void onMultiPress(long downTime, int count, int unusedDisplayId) {
        private void onMultiPress(long downTime, int count) {
            // Triple-press stem to toggle accessibility gesture should always be triggered
            // regardless of if app handles it.
            if (count == 3
@@ -2655,7 +2701,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        @Override
        void onKeyUp(long eventTime, int count, int displayId, int deviceId, int metaState) {
        void onKeyUp(int count, KeyEvent event) {
            if (count == 1) {
                // Save info about the most recent task on the first press of the stem key. This
                // may be used later to switch to the most recent app using double press gesture.
@@ -2672,7 +2718,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    // Key-up gesture should be triggered only if app doesn't handle it.
                    mDeferredKeyActionExecutor.queueKeyAction(
                            KeyEvent.KEYCODE_STEM_PRIMARY,
                            eventTime,
                            event.getDownTime(),
                            () -> {
                                Slog.d(TAG, "StemPrimaryKeyRule: executing deferred onKeyUp");
                                // Save the info of the focused task on screen. This may be used
@@ -2720,18 +2766,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        @Override
        void onPress(long downTime, int displayId) {

        }

        @Override
        void onKeyUp(long eventTime, int pressCount, int displayId, int deviceId, int metaState) {
        void onKeyUp(int pressCount, KeyEvent event) {
            if (pressCount != 1) {
                return;
            }
            // Single press on tail button triggers the open notes gesture.
            handleKeyGestureInKeyGestureController(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
                    deviceId, KEYCODE_STYLUS_BUTTON_TAIL, metaState);
                    event.getDeviceId(), KEYCODE_STYLUS_BUTTON_TAIL, event.getMetaState());
        }
    }

+164 −101

File changed.

Preview size limit exceeded, changes collapsed.

+585 −0

File added.

Preview size limit exceeded, changes collapsed.

+285 −19
Original line number Diff line number Diff line
@@ -18,12 +18,19 @@ 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_A;
import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.KeyEvent.KEYCODE_POWER;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.android.hardware.input.Flags.FLAG_ABORT_SLOW_MULTI_PRESS;
import static com.android.server.policy.SingleKeyGestureEvent.ACTION_CANCEL;
import static com.android.server.policy.SingleKeyGestureEvent.ACTION_COMPLETE;
import static com.android.server.policy.SingleKeyGestureEvent.ACTION_START;
import static com.android.server.policy.SingleKeyGestureEvent.SINGLE_KEY_GESTURE_TYPE_LONG_PRESS;
import static com.android.server.policy.SingleKeyGestureEvent.SINGLE_KEY_GESTURE_TYPE_PRESS;
import static com.android.server.policy.SingleKeyGestureEvent.SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -43,6 +50,9 @@ import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.KeyEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -72,7 +82,7 @@ public class SingleKeyGestureTests {
    private CountDownLatch mVeryLongPressed = new CountDownLatch(1);
    private CountDownLatch mMultiPressed = new CountDownLatch(1);
    private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>();

    private RandomKeyRule mRandomGestureRule = new RandomKeyRule(KEYCODE_A);
    private final Instrumentation mInstrumentation = getInstrumentation();
    private final Context mContext = mInstrumentation.getTargetContext();
    private long mWaitTimeout;
@@ -101,6 +111,7 @@ public class SingleKeyGestureTests {
    }

    private void initSingleKeyGestureRules() {
        // Similar to current POWER key rules defined in PhoneWindowManager
        mDetector.addRule(
                new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) {
                    @Override
@@ -119,15 +130,36 @@ public class SingleKeyGestureTests {
                    }

                    @Override
                    public void onPress(long downTime, int displayId) {
                    void onKeyGesture(@NonNull SingleKeyGestureEvent event) {
                        final int pressCount = event.getPressCount();
                        if (event.getAction() != ACTION_COMPLETE) {
                            return;
                        }
                        switch (event.getType()) {
                            case SINGLE_KEY_GESTURE_TYPE_PRESS:
                                if (event.getPressCount() > 1) {
                                    onMultiPress(pressCount);
                                } else {
                                    onPress();
                                }
                                break;
                            case SINGLE_KEY_GESTURE_TYPE_LONG_PRESS:
                                onLongPress();
                                break;
                            case SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS:
                                onVeryLongPress();
                                break;
                        }
                    }

                    private void onPress() {
                        if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                            return;
                        }
                        mShortPressed.countDown();
                    }

                    @Override
                    void onLongPress(long downTime) {
                    private void onLongPress() {
                        if (mDetector.beganFromNonInteractive()
                                && !mAllowNonInteractiveForLongPress) {
                            return;
@@ -135,13 +167,11 @@ public class SingleKeyGestureTests {
                        mLongPressed.countDown();
                    }

                    @Override
                    void onVeryLongPress(long downTime) {
                    private void onVeryLongPress() {
                        mVeryLongPressed.countDown();
                    }

                    @Override
                    void onMultiPress(long downTime, int count, int displayId) {
                    private void onMultiPress(int count) {
                        if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                            return;
                        }
@@ -151,12 +181,12 @@ public class SingleKeyGestureTests {
                    }

                    @Override
                    void onKeyUp(long eventTime, int multiPressCount, int displayId, int deviceId,
                            int metaState) {
                    void onKeyUp(int multiPressCount, KeyEvent event) {
                        mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount));
                    }
                });

        // Similar to current POWER key rules defined in PhoneWindowManager
        mDetector.addRule(
                new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_BACK) {
                    @Override
@@ -170,15 +200,35 @@ public class SingleKeyGestureTests {
                    }

                    @Override
                    public void onPress(long downTime, int displayId) {
                    void onKeyGesture(@NonNull SingleKeyGestureEvent event) {
                        final long eventTime = event.getEventTime();
                        final int displayId = event.getDisplayId();
                        final int pressCount = event.getPressCount();
                        if (event.getAction() != ACTION_COMPLETE) {
                            return;
                        }
                        switch (event.getType()) {
                            case SINGLE_KEY_GESTURE_TYPE_PRESS:
                                if (event.getPressCount() > 1) {
                                    onMultiPress(pressCount);
                                } else {
                                    onPress();
                                }
                                break;
                            case SINGLE_KEY_GESTURE_TYPE_LONG_PRESS:
                                onLongPress();
                                break;
                        }
                    }

                    private void onPress() {
                        if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                            return;
                        }
                        mShortPressed.countDown();
                    }

                    @Override
                    void onMultiPress(long downTime, int count, int displayId) {
                    private void onMultiPress(int count) {
                        if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                            return;
                        }
@@ -188,16 +238,16 @@ public class SingleKeyGestureTests {
                    }

                    @Override
                    void onKeyUp(long eventTime, int multiPressCount, int displayId, int deviceId,
                            int metaState) {
                    void onKeyUp(int multiPressCount, KeyEvent event) {
                        mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount));
                    }

                    @Override
                    void onLongPress(long downTime) {
                    private void onLongPress() {
                        mLongPressed.countDown();
                    }
                });

        mDetector.addRule(mRandomGestureRule);
    }

    private static class KeyUpData {
@@ -448,9 +498,12 @@ public class SingleKeyGestureTests {
        final SingleKeyGestureDetector.SingleKeyRule rule =
                new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) {
                    @Override
                    void onPress(long downTime, int displayId) {
                    void onKeyGesture(@NonNull SingleKeyGestureEvent event) {
                        if (event.getType() == SINGLE_KEY_GESTURE_TYPE_PRESS
                                && event.getPressCount() == 1) {
                            mShortPressed.countDown();
                        }
                    }
                };

        mDetector.removeRule(rule);
@@ -473,4 +526,217 @@ public class SingleKeyGestureTests {
        assertEquals(mVeryLongPressed.getCount(), 1);
        assertEquals(mShortPressed.getCount(), 1);
    }

    @Test
    public void testRandomRuleLongPress() throws InterruptedException {
        // The current flow of events based on implementation is:
        // - long-press(START)
        // - very long press (START)
        // - long press(COMPLETE)
        // - very long press(CANCEL)
        pressKey(KEYCODE_A, mLongPressTime + 50);

        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_COMPLETE);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_CANCEL);
    }

    @Test
    public void testRandomRuleVeryLongPress() throws InterruptedException {
        // The current flow of events based on implementation is:
        // - long-press(START)
        // - very long press (START)
        // - long press(COMPLETE)
        // - very long press(COMPLETE)
        pressKey(KEYCODE_A, mVeryLongPressTime + 50);

        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_COMPLETE);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_COMPLETE);
    }

    @Test
    public void testRandomRuleShortPress() throws InterruptedException {
        // The current flow of events based on implementation is:
        // - long-press(START)
        // - very long press (START)
        // - long press(CANCEL)
        // - very long press(CANCEL)
        // - 1-press(START)
        // - 1-press(COMPLETE)
        pressKey(KEYCODE_A, 0);

        // Long press and very long press gestures are started on key down
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_START);

        // Long press and very long press gestures are cancelled on key up (duration < thresholds)
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_CANCEL);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_CANCEL);

        // On key up start single press
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_START, /* pressCount = */1);
        // After waiting for multi-press timeout single press gesture is completed
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_COMPLETE, /* pressCount = */1);
    }

    @Test
    public void testRandomRuleDoublePress() throws InterruptedException {
        // The current flow of events based on implementation is:
        // - long-press(START)
        // - very long press (START)
        // - long press(CANCEL)
        // - very long press(CANCEL)
        // - 1-press(START)
        // - 1-press(CANCEL)
        // - 2-press(START)
        // - 2-press(COMPLETE)
        pressKey(KEYCODE_A, 0);
        pressKey(KEYCODE_A, 0);

        // Long press and very long press gestures are started on key down and cancelled on key up
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_CANCEL);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_CANCEL);

        // First press will start single press gesture
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_START, /* pressCount = */1);

        // Single press is cancelled on second key down
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_CANCEL, /* pressCount = */1);

        // On second key up start double press
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_START, /* pressCount = */2);
        // After waiting for multi-press timeout double press gesture is completed
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_COMPLETE, /* pressCount = */2);
    }

    @Test
    public void testRandomRuleTriplePress() throws InterruptedException {
        // The current flow of events based on implementation is:
        // - long-press(START)
        // - very long press (START)
        // - long press(CANCEL)
        // - very long press(CANCEL)
        // - 1-press(START)
        // - 1-press(CANCEL)
        // - 2-press(START)
        // - 2-press(CANCEL)
        // - 3-press(COMPLETE)
        pressKey(KEYCODE_A, 0);
        pressKey(KEYCODE_A, 0);
        pressKey(KEYCODE_A, 0);

        // Long press and very long press gestures are started on key down and cancelled on key up
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_START);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_LONG_PRESS, ACTION_CANCEL);
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_VERY_LONG_PRESS,
                ACTION_CANCEL);

        // First press will start single press gesture
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_START, /* pressCount = */1);

        // Single press is cancelled on second key down
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_CANCEL, /* pressCount = */1);

        // On second key up start double press
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_START, /* pressCount = */2);
        // Double press is cancelled on third key down
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_CANCEL, /* pressCount = */2);

        // Triple press is completed on third key down (since max press count is 3 no need to wait)
        mRandomGestureRule.assertEventReceived(SINGLE_KEY_GESTURE_TYPE_PRESS,
                ACTION_COMPLETE, /* pressCount = */3);
    }

    private class RandomKeyRule extends SingleKeyGestureDetector.SingleKeyRule {

        private final BlockingQueue<SingleKeyGestureEvent> mEvents = new LinkedBlockingQueue<>();

        private final int mKeyCode;

        RandomKeyRule(int keyCode) {
            super(keyCode);
            mKeyCode = keyCode;
        }

        @Override
        boolean supportLongPress() {
            return true;
        }

        @Override
        boolean supportVeryLongPress() {
            return true;
        }

        @Override
        int getMaxMultiPressCount() {
            return mMaxMultiPressCount;
        }

        @Override
        long getLongPressTimeoutMs() {
            return mLongPressTime;
        }

        @Override
        long getVeryLongPressTimeoutMs() {
            return mVeryLongPressTime;
        }

        @Override
        void onKeyGesture(@NonNull SingleKeyGestureEvent event) {
            if (event.getKeyCode() != mKeyCode) {
                throw new IllegalArgumentException(
                        "Rule generated a gesture for " + KeyEvent.keyCodeToString(
                                event.getKeyCode()) + " but the rule was made for "
                                + KeyEvent.keyCodeToString(mKeyCode));
            }
            mEvents.add(event);
        }

        @Nullable
        SingleKeyGestureEvent getEvent() throws InterruptedException {
            return mEvents.poll(500, TimeUnit.MILLISECONDS);
        }

        void assertEventReceived(int type, int action) throws InterruptedException {
            SingleKeyGestureEvent event = getEvent();
            assertNotNull(event);
            assertEquals("Type mismatch", type, event.getType());
            assertEquals("Action mismatch", action, event.getAction());
        }

        void assertEventReceived(int type, int action, int pressCount) throws InterruptedException {
            SingleKeyGestureEvent event = getEvent();
            assertNotNull(event);
            assertEquals("Type mismatch", type, event.getType());
            assertEquals("Action mismatch", action, event.getAction());
            assertEquals("Count mismatch", pressCount, event.getPressCount());
        }
    }
}