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

Commit c98777a7 authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

Move stylus tail button gesture detection to PhoneWindowManager

Instead of having custom gesture detection logic in SystemUI for the
stylus tail button, we add a single key gesture detector in
PhoneWindowManager for KEYCODE_STYLUS_BUTTON_TAIL.

We then use the KeyGestureEventHandler in SysUI to listen to
the two shortcuts for opening notes, which includes the stylus tail
button.

This will make it easy to add additional behavior to other gestures for
the stylus tail button, such as long press and double press.

Bug: 293591411
Bug: 358569822
Test: atest KeyGestureEventTests
Test: atest NoteTaskInitializerTest
Flag: com.android.hardware.input.use_key_gesture_event_handler
Change-Id: I753229a2c48bd3e6ac8652de378491f3fc66bfa8
parent 6ced56d5
Loading
Loading
Loading
Loading
+8 −2
Original line number Original line Diff line number Diff line
@@ -22,8 +22,6 @@ import android.annotation.Nullable;
import android.view.Display;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AnnotationValidations;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.FrameworkStatsLog;


import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
@@ -170,6 +168,14 @@ public final class KeyGestureEvent {
        this.mKeyGestureEvent = keyGestureEvent;
        this.mKeyGestureEvent = keyGestureEvent;
    }
    }


    /**
     * Tests whether this keyboard shortcut event has the given modifiers (i.e. all of the given
     * modifiers were pressed when this shortcut was triggered).
     */
    public boolean hasModifiers(int modifiers) {
        return (getModifierState() & modifiers) == modifiers;
    }

    /**
    /**
     * Key gesture event builder used to create a KeyGestureEvent for tests in Java.
     * Key gesture event builder used to create a KeyGestureEvent for tests in Java.
     *
     *
+24 −5
Original line number Original line Diff line number Diff line
@@ -75,8 +75,10 @@ constructor(
     * [NoteTaskController], ensure custom actions can be triggered (i.e., keyboard shortcut).
     * [NoteTaskController], ensure custom actions can be triggered (i.e., keyboard shortcut).
     */
     */
    private fun initializeHandleSystemKey() {
    private fun initializeHandleSystemKey() {
        if (!useKeyGestureEventHandler()) {
            commandQueue.addCallback(callbacks)
            commandQueue.addCallback(callbacks)
        }
        }
    }


    /**
    /**
     * Initializes a [InputManager.KeyGestureEventHandler] which will handle shortcuts for opening
     * Initializes a [InputManager.KeyGestureEventHandler] which will handle shortcuts for opening
@@ -130,6 +132,11 @@ constructor(
            InputManager.KeyGestureEventHandler {
            InputManager.KeyGestureEventHandler {


            override fun handleSystemKey(key: KeyEvent) {
            override fun handleSystemKey(key: KeyEvent) {
                if (useKeyGestureEventHandler()) {
                    throw IllegalStateException(
                        "handleSystemKey must not be used when KeyGestureEventHandler is used"
                    )
                }
                key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask)
                key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask)
            }
            }


@@ -151,13 +158,13 @@ constructor(


            override fun handleKeyGestureEvent(
            override fun handleKeyGestureEvent(
                event: KeyGestureEvent,
                event: KeyGestureEvent,
                focusedToken: IBinder?
                focusedToken: IBinder?,
            ): Boolean {
            ): Boolean {
                return this@NoteTaskInitializer.handleKeyGestureEvent(event)
                return this@NoteTaskInitializer.handleKeyGestureEvent(event)
            }
            }


            override fun isKeyGestureSupported(gestureType: Int): Boolean {
            override fun isKeyGestureSupported(gestureType: Int): Boolean {
                return this@NoteTaskInitializer.isKeyGestureSupported(gestureType);
                return this@NoteTaskInitializer.isKeyGestureSupported(gestureType)
            }
            }
        }
        }


@@ -209,9 +216,21 @@ constructor(
            "handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " +
            "handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " +
                event.keycodes.contentToString()
                event.keycodes.contentToString()
        }
        }
        if (
            event.keycodes.contains(KEYCODE_N) &&
                event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)
        ) {
            debugLog { "Note task triggered by keyboard shortcut" }
            backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
            backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
            return true
            return true
        }
        }
        if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) {
            debugLog { "Note task triggered by stylus tail button" }
            backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) }
            return true
        }
        return false
    }


    private fun isKeyGestureSupported(gestureType: Int): Boolean {
    private fun isKeyGestureSupported(gestureType: Int): Boolean {
        return gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
        return gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
+65 −20
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import android.hardware.input.InputManager
import android.hardware.input.KeyGestureEvent
import android.hardware.input.KeyGestureEvent
import android.os.UserHandle
import android.os.UserHandle
import android.os.UserManager
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.platform.test.flag.junit.SetFlagsRule
import android.view.KeyEvent
import android.view.KeyEvent
@@ -32,6 +33,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT
import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -62,8 +65,7 @@ import org.mockito.MockitoAnnotations.initMocks
@RunWith(AndroidJUnit4::class)
@RunWith(AndroidJUnit4::class)
internal class NoteTaskInitializerTest : SysuiTestCase() {
internal class NoteTaskInitializerTest : SysuiTestCase() {


    @get:Rule
    @get:Rule val setFlagsRule = SetFlagsRule()
    val setFlagsRule = SetFlagsRule()


    @Mock lateinit var commandQueue: CommandQueue
    @Mock lateinit var commandQueue: CommandQueue
    @Mock lateinit var inputManager: InputManager
    @Mock lateinit var inputManager: InputManager
@@ -83,10 +85,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
        whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true)
        whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true)
    }
    }


    private fun createUnderTest(
    private fun createUnderTest(isEnabled: Boolean, bubbles: Bubbles?): NoteTaskInitializer =
        isEnabled: Boolean,
        bubbles: Bubbles?,
    ): NoteTaskInitializer =
        NoteTaskInitializer(
        NoteTaskInitializer(
            controller = controller,
            controller = controller,
            commandQueue = commandQueue,
            commandQueue = commandQueue,
@@ -104,7 +103,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
        code: Int,
        code: Int,
        downTime: Long = 0L,
        downTime: Long = 0L,
        eventTime: Long = 0L,
        eventTime: Long = 0L,
        metaState: Int = 0
        metaState: Int = 0,
    ): KeyEvent = KeyEvent(downTime, eventTime, action, code, 0 /*repeat*/, metaState)
    ): KeyEvent = KeyEvent(downTime, eventTime, action, code, 0 /*repeat*/, metaState)


    @Test
    @Test
@@ -113,7 +112,6 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {


        createUnderTest(isEnabled = true, bubbles = bubbles).initialize()
        createUnderTest(isEnabled = true, bubbles = bubbles).initialize()


        verify(commandQueue).addCallback(any())
        verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
        verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
        verify(keyguardMonitor).registerCallback(any())
        verify(keyguardMonitor).registerCallback(any())
@@ -125,7 +123,6 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {


        createUnderTest(isEnabled = true, bubbles = bubbles).initialize()
        createUnderTest(isEnabled = true, bubbles = bubbles).initialize()


        verify(commandQueue).addCallback(any())
        verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
        verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
        verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
        verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
        verify(keyguardMonitor).registerCallback(any())
        verify(keyguardMonitor).registerCallback(any())
@@ -165,12 +162,13 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    fun initialize_handleSystemKey() {
    fun initialize_handleSystemKey() {
        val expectedKeyEvent =
        val expectedKeyEvent =
            createKeyEvent(
            createKeyEvent(
                ACTION_DOWN,
                ACTION_DOWN,
                KEYCODE_N,
                KEYCODE_N,
                metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
                metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
            )
            )
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        underTest.initialize()
@@ -183,8 +181,9 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {


    @Test
    @Test
    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    fun initialize_handleKeyGestureEvent() {
    fun handlesShortcut_metaCtrlN() {
        val gestureEvent = KeyGestureEvent.Builder()
        val gestureEvent =
            KeyGestureEvent.Builder()
                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N))
                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N))
                .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
                .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
                .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
                .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
@@ -192,13 +191,56 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
                .build()
                .build()
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        underTest.initialize()
        val callback =
        val callback = withArgCaptor {
            withArgCaptor { verify(inputManager).registerKeyGestureEventHandler(capture()) }
            verify(inputManager).registerKeyGestureEventHandler(capture())
        }


        assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue()
        assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue()


        executor.runAllReady()
        executor.runAllReady()
        verify(controller).showNoteTask(any())
        verify(controller).showNoteTask(eq(KEYBOARD_SHORTCUT))
    }

    @Test
    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    fun handlesShortcut_stylusTailButton() {
        val gestureEvent =
            KeyGestureEvent.Builder()
                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
                .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
                .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
                .build()
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        val callback = withArgCaptor {
            verify(inputManager).registerKeyGestureEventHandler(capture())
        }

        assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue()

        executor.runAllReady()
        verify(controller).showNoteTask(eq(TAIL_BUTTON))
    }

    @Test
    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    fun ignoresUnrelatedShortcuts() {
        val gestureEvent =
            KeyGestureEvent.Builder()
                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
                .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
                .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
                .build()
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        val callback = withArgCaptor {
            verify(inputManager).registerKeyGestureEventHandler(capture())
        }

        assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isFalse()

        executor.runAllReady()
        verify(controller, never()).showNoteTask(any())
    }
    }


    @Test
    @Test
@@ -249,6 +291,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    fun tailButtonGestureDetection_singlePress_shouldShowNoteTaskOnUp() {
    fun tailButtonGestureDetection_singlePress_shouldShowNoteTaskOnUp() {
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        underTest.initialize()
@@ -267,6 +310,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    fun tailButtonGestureDetection_doublePress_shouldNotShowNoteTaskTwice() {
    fun tailButtonGestureDetection_doublePress_shouldNotShowNoteTaskTwice() {
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        underTest.initialize()
@@ -289,6 +333,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
    fun tailButtonGestureDetection_longPress_shouldNotShowNoteTask() {
    fun tailButtonGestureDetection_longPress_shouldNotShowNoteTask() {
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        underTest.initialize()
+19 −0
Original line number Original line Diff line number Diff line
@@ -232,6 +232,9 @@ public abstract class InputManagerInternal {
    /**
    /**
     * Notify key gesture was completed by the user.
     * Notify key gesture was completed by the user.
     *
     *
     * NOTE: This is a temporary API added to assist in a long-term refactor, and is not meant for
     * general use by system services.
     *
     * @param deviceId the device ID of the keyboard using which the event was completed
     * @param deviceId the device ID of the keyboard using which the event was completed
     * @param keycodes the keys pressed for the event
     * @param keycodes the keys pressed for the event
     * @param modifierState the modifier state
     * @param modifierState the modifier state
@@ -240,4 +243,20 @@ public abstract class InputManagerInternal {
     */
     */
    public abstract void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState,
    public abstract void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState,
            @KeyGestureEvent.KeyGestureType int event);
            @KeyGestureEvent.KeyGestureType int event);

    /**
     * Notify that a key gesture was detected by another system component, and it should be handled
     * appropriately by KeyGestureController.
     *
     * NOTE: This is a temporary API added to assist in a long-term refactor, and is not meant for
     * general use by system services.
     *
     * @param deviceId the device ID of the keyboard using which the event was completed
     * @param keycodes the keys pressed for the event
     * @param modifierState the modifier state
     * @param event the gesture event that was completed
     *
     */
    public abstract void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes,
            int modifierState, @KeyGestureEvent.KeyGestureType int event);
}
}
+6 −0
Original line number Original line Diff line number Diff line
@@ -3408,6 +3408,12 @@ public class InputManagerService extends IInputManager.Stub
            mKeyGestureController.notifyKeyGestureCompleted(deviceId, keycodes, modifierState,
            mKeyGestureController.notifyKeyGestureCompleted(deviceId, keycodes, modifierState,
                    gestureType);
                    gestureType);
        }
        }

        @Override
        public void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes,
                int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) {
            mKeyGestureController.handleKeyGesture(deviceId, keycodes, modifierState, gestureType);
        }
    }
    }


    @Override
    @Override
Loading