diff options
author | 2025-03-07 19:05:55 +0000 | |
---|---|---|
committer | 2025-03-14 04:01:42 -0700 | |
commit | b1d338f1074bf7137ad03e94ade1b167c3ca97ae (patch) | |
tree | b76f897633686bdb1b8cfdc134f5ddf39fed0193 | |
parent | 20380db5b2dc017041c522539a5b6facdd8ae4c4 (diff) |
(4/n) Shift to oneway binder call for register key handler
handleKeyGestureEvent() is synchronous API that makes system server
dependent on handler and can cause ANR if handlers misbehave.
As a replacement, handlers should provide list of gesture they want
to listen to. For consistency, we only allow single handler per
gesture.
Test: atest InputTests
Test: atest WmTests
Test: Presubmit
Bug: 358569822
Bug: 383602794
Flag: EXEMPT refactor
Change-Id: Iea0f6ba3360ed241e19f0265d6ab28fd7889aafa
20 files changed, 635 insertions, 544 deletions
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 1c2150f3c09f..5537135f7bfa 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -273,7 +273,7 @@ interface IInputManager { @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") - void registerKeyGestureHandler(IKeyGestureHandler handler); + void registerKeyGestureHandler(in int[] keyGesturesToHandle, IKeyGestureHandler handler); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " diff --git a/core/java/android/hardware/input/IKeyGestureHandler.aidl b/core/java/android/hardware/input/IKeyGestureHandler.aidl index 4da991ee85b1..08b015892710 100644 --- a/core/java/android/hardware/input/IKeyGestureHandler.aidl +++ b/core/java/android/hardware/input/IKeyGestureHandler.aidl @@ -20,12 +20,12 @@ import android.hardware.input.AidlKeyGestureEvent; import android.os.IBinder; /** @hide */ -interface IKeyGestureHandler { +oneway interface IKeyGestureHandler { /** - * Called when a key gesture starts, ends, or is cancelled. If a handler returns {@code true}, - * it means they intend to handle the full gesture and should handle all the events pertaining - * to that gesture. + * Called when a key gesture starts, ends, or is cancelled. It is only sent to the handler that + * registered the callback for that particular gesture type. + * {@see IInputManager#registerKeyGestureHandler(int[], IKeyGestureHandler)} */ - boolean handleKeyGesture(in AidlKeyGestureEvent event, in IBinder focusedToken); + void handleKeyGesture(in AidlKeyGestureEvent event, in IBinder focusedToken); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index d6419afb2a5a..a66ac76d7597 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1446,16 +1446,18 @@ public final class InputManager { /** * Registers a key gesture event handler for {@link KeyGestureEvent} handling. * + * @param keyGesturesToHandle list of KeyGestureTypes to listen to * @param handler the {@link KeyGestureEventHandler} - * @throws IllegalArgumentException if {@code handler} has already been registered previously. + * @throws IllegalArgumentException if {@code handler} has already been registered previously + * or key gestures provided are already registered by some other gesture handler. * @throws NullPointerException if {@code handler} or {@code executor} is null. * @hide * @see #unregisterKeyGestureEventHandler(KeyGestureEventHandler) */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) - public void registerKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler) - throws IllegalArgumentException { - mGlobal.registerKeyGestureEventHandler(handler); + public void registerKeyGestureEventHandler(List<Integer> keyGesturesToHandle, + @NonNull KeyGestureEventHandler handler) throws IllegalArgumentException { + mGlobal.registerKeyGestureEventHandler(keyGesturesToHandle, handler); } /** @@ -1463,7 +1465,7 @@ public final class InputManager { * * @param handler the {@link KeyGestureEventHandler} * @hide - * @see #registerKeyGestureEventHandler(KeyGestureEventHandler) + * @see #registerKeyGestureEventHandler(List, KeyGestureEventHandler) */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) public void unregisterKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler) { @@ -1741,7 +1743,7 @@ public final class InputManager { * {@see KeyGestureEventListener} which is to listen to successfully handled key gestures, this * interface allows system components to register handler for handling key gestures. * - * @see #registerKeyGestureEventHandler(KeyGestureEventHandler) + * @see #registerKeyGestureEventHandler(List, KeyGestureEventHandler) * @see #unregisterKeyGestureEventHandler(KeyGestureEventHandler) * * <p> NOTE: All callbacks will occur on system main and input threads, so the caller needs @@ -1750,14 +1752,11 @@ public final class InputManager { */ public interface KeyGestureEventHandler { /** - * Called when a key gesture event starts, is completed, or is cancelled. If a handler - * returns {@code true}, it implies that the handler intends to handle the key gesture and - * only this handler will receive the future events for this key gesture. + * Called when a key gesture event starts, is completed, or is cancelled. * * @param event the gesture event */ - boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, - @Nullable IBinder focusedToken); + void handleKeyGestureEvent(@NonNull KeyGestureEvent event, @Nullable IBinder focusedToken); } /** @hide */ diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index c4b4831ba76e..754182ce3d11 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -25,8 +25,8 @@ import android.hardware.BatteryState; import android.hardware.SensorManager; import android.hardware.input.InputManager.InputDeviceBatteryListener; import android.hardware.input.InputManager.InputDeviceListener; -import android.hardware.input.InputManager.KeyGestureEventHandler; import android.hardware.input.InputManager.KeyEventActivityListener; +import android.hardware.input.InputManager.KeyGestureEventHandler; import android.hardware.input.InputManager.KeyGestureEventListener; import android.hardware.input.InputManager.KeyboardBacklightListener; import android.hardware.input.InputManager.OnTabletModeChangedListener; @@ -49,6 +49,7 @@ import android.os.ServiceManager; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorManager; +import android.util.IntArray; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -132,13 +133,13 @@ public final class InputManagerGlobal { @Nullable private IKeyEventActivityListener mKeyEventActivityListener; - private final Object mKeyGestureEventHandlerLock = new Object(); - @GuardedBy("mKeyGestureEventHandlerLock") - @Nullable - private ArrayList<KeyGestureEventHandler> mKeyGestureEventHandlers; - @GuardedBy("mKeyGestureEventHandlerLock") + @GuardedBy("mKeyGesturesToHandlerMap") @Nullable private IKeyGestureHandler mKeyGestureHandler; + @GuardedBy("mKeyGesturesToHandlerMap") + private final SparseArray<KeyGestureEventHandler> mKeyGesturesToHandlerMap = + new SparseArray<>(); + // InputDeviceSensorManager gets notified synchronously from the binder thread when input // devices change, so it must be synchronized with the input device listeners. @@ -1177,50 +1178,69 @@ public final class InputManagerGlobal { private class LocalKeyGestureHandler extends IKeyGestureHandler.Stub { @Override - public boolean handleKeyGesture(@NonNull AidlKeyGestureEvent ev, IBinder focusedToken) { - synchronized (mKeyGestureEventHandlerLock) { - if (mKeyGestureEventHandlers == null) { - return false; - } - final int numHandlers = mKeyGestureEventHandlers.size(); - final KeyGestureEvent event = new KeyGestureEvent(ev); - for (int i = 0; i < numHandlers; i++) { - KeyGestureEventHandler handler = mKeyGestureEventHandlers.get(i); - if (handler.handleKeyGestureEvent(event, focusedToken)) { - return true; - } + public void handleKeyGesture(@NonNull AidlKeyGestureEvent ev, IBinder focusedToken) { + synchronized (mKeyGesturesToHandlerMap) { + KeyGestureEventHandler handler = mKeyGesturesToHandlerMap.get(ev.gestureType); + if (handler == null) { + Log.w(TAG, "Key gesture event " + ev.gestureType + + " occurred without a registered handler!"); + return; } + handler.handleKeyGestureEvent(new KeyGestureEvent(ev), focusedToken); } - return false; } } /** - * @see InputManager#registerKeyGestureEventHandler(KeyGestureEventHandler) + * @see InputManager#registerKeyGestureEventHandler(List, KeyGestureEventHandler) */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) - void registerKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler) - throws IllegalArgumentException { + void registerKeyGestureEventHandler(List<Integer> keyGesturesToHandle, + @NonNull KeyGestureEventHandler handler) throws IllegalArgumentException { + Objects.requireNonNull(keyGesturesToHandle, "List of gestures should not be null"); Objects.requireNonNull(handler, "handler should not be null"); - synchronized (mKeyGestureEventHandlerLock) { - if (mKeyGestureHandler == null) { - mKeyGestureEventHandlers = new ArrayList<>(); - mKeyGestureHandler = new LocalKeyGestureHandler(); + if (keyGesturesToHandle.isEmpty()) { + throw new IllegalArgumentException("No key gestures provided!"); + } - try { - mIm.registerKeyGestureHandler(mKeyGestureHandler); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + synchronized (mKeyGesturesToHandlerMap) { + IntArray newKeyGestures = new IntArray( + keyGesturesToHandle.size() + mKeyGesturesToHandlerMap.size()); + + // Check if the handler already exists + for (int i = 0; i < mKeyGesturesToHandlerMap.size(); i++) { + KeyGestureEventHandler h = mKeyGesturesToHandlerMap.valueAt(i); + if (h == handler) { + throw new IllegalArgumentException("Handler has already been registered!"); } + newKeyGestures.add(mKeyGesturesToHandlerMap.keyAt(i)); } - final int numHandlers = mKeyGestureEventHandlers.size(); - for (int i = 0; i < numHandlers; i++) { - if (mKeyGestureEventHandlers.get(i) == handler) { - throw new IllegalArgumentException("Handler has already been registered!"); + + // Check if any of the key gestures are already handled by existing handlers + for (int gesture : keyGesturesToHandle) { + if (mKeyGesturesToHandlerMap.contains(gesture)) { + throw new IllegalArgumentException("Key gesture " + gesture + + " is already registered by another handler!"); + } + newKeyGestures.add(gesture); + } + + try { + // If handler was already registered for this process, we need to unregister and + // re-register it for the new set of gestures + if (mKeyGestureHandler != null) { + mIm.unregisterKeyGestureHandler(mKeyGestureHandler); + } else { + mKeyGestureHandler = new LocalKeyGestureHandler(); + } + mIm.registerKeyGestureHandler(newKeyGestures.toArray(), mKeyGestureHandler); + for (int gesture : keyGesturesToHandle) { + mKeyGesturesToHandlerMap.put(gesture, handler); } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - mKeyGestureEventHandlers.add(handler); } } @@ -1231,18 +1251,21 @@ public final class InputManagerGlobal { void unregisterKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler) { Objects.requireNonNull(handler, "handler should not be null"); - synchronized (mKeyGestureEventHandlerLock) { - if (mKeyGestureEventHandlers == null) { + synchronized (mKeyGesturesToHandlerMap) { + if (mKeyGestureHandler == null) { return; } - mKeyGestureEventHandlers.removeIf(existingHandler -> existingHandler == handler); - if (mKeyGestureEventHandlers.isEmpty()) { + for (int i = mKeyGesturesToHandlerMap.size() - 1; i >= 0; i--) { + if (mKeyGesturesToHandlerMap.valueAt(i) == handler) { + mKeyGesturesToHandlerMap.removeAt(i); + } + } + if (mKeyGesturesToHandlerMap.size() == 0) { try { mIm.unregisterKeyGestureHandler(mKeyGestureHandler); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - mKeyGestureEventHandlers = null; mKeyGestureHandler = null; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt index 1ea545f3ab67..19507c17bc95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt @@ -23,10 +23,7 @@ import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventHandler import android.hardware.input.KeyGestureEvent import android.os.IBinder -import android.window.DesktopModeFlags -import com.android.hardware.input.Flags.manageKeyGestures import com.android.internal.protolog.ProtoLog -import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor @@ -51,16 +48,20 @@ class DesktopModeKeyGestureHandler( ) : KeyGestureEventHandler { init { - inputManager.registerKeyGestureEventHandler(this) + if (desktopTasksController.isPresent && desktopModeWindowDecorViewModel.isPresent) { + val supportedGestures = + listOf( + KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, + KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW, + ) + inputManager.registerKeyGestureEventHandler(supportedGestures, this) + } } - override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean { - if ( - !desktopTasksController.isPresent || - !desktopModeWindowDecorViewModel.isPresent - ) { - return false - } + override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?) { when (event.keyGestureType) { KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> { logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled") @@ -69,7 +70,6 @@ class DesktopModeKeyGestureHandler( desktopTasksController.get().moveToNextDisplay(it.taskId) } } - return true } KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW -> { logV("Key gesture SNAP_LEFT_FREEFORM_WINDOW is handled") @@ -85,7 +85,6 @@ class DesktopModeKeyGestureHandler( ) } } - return true } KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW -> { logV("Key gesture SNAP_RIGHT_FREEFORM_WINDOW is handled") @@ -101,7 +100,6 @@ class DesktopModeKeyGestureHandler( ) } } - return true } KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> { logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled") @@ -120,7 +118,6 @@ class DesktopModeKeyGestureHandler( ) } } - return true } KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> { logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled") @@ -129,9 +126,7 @@ class DesktopModeKeyGestureHandler( desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE) } } - return true } - else -> return false } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt index d510570e8839..e40da5e8498d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt @@ -50,7 +50,6 @@ import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel -import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -120,11 +119,11 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) doAnswer { - keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler) + keyGestureEventHandler = (it.arguments[1] as KeyGestureEventHandler) null } .whenever(inputManager) - .registerKeyGestureEventHandler(any()) + .registerKeyGestureEventHandler(any(), any()) shellInit.init() desktopModeKeyGestureHandler = @@ -176,10 +175,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D)) .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON) .build() - val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() - assertThat(result).isTrue() verify(desktopTasksController).moveToNextDisplay(task.taskId) } @@ -197,10 +195,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setKeycodes(intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET)) .setModifierState(KeyEvent.META_META_ON) .build() - val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() - assertThat(result).isTrue() verify(desktopModeWindowDecorViewModel) .onSnapResize( task.taskId, @@ -224,10 +221,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setKeycodes(intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET)) .setModifierState(KeyEvent.META_META_ON) .build() - val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() - assertThat(result).isTrue() verify(desktopModeWindowDecorViewModel) .onSnapResize( task.taskId, @@ -251,10 +247,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setKeycodes(intArrayOf(KeyEvent.KEYCODE_EQUALS)) .setModifierState(KeyEvent.META_META_ON) .build() - val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() - assertThat(result).isTrue() verify(desktopTasksController) .toggleDesktopTaskSize( task, @@ -280,10 +275,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setKeycodes(intArrayOf(KeyEvent.KEYCODE_MINUS)) .setModifierState(KeyEvent.META_META_ON) .build() - val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() - assertThat(result).isTrue() verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE) } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 8dc27bf4ac3e..080940169e46 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -86,7 +86,10 @@ constructor( */ private fun initializeKeyGestureEventHandler() { if (useKeyGestureEventHandler()) { - inputManager.registerKeyGestureEventHandler(callbacks) + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES), + callbacks, + ) } } @@ -156,11 +159,8 @@ constructor( controller.updateNoteTaskForCurrentUserAndManagedProfiles() } - override fun handleKeyGestureEvent( - event: KeyGestureEvent, - focusedToken: IBinder?, - ): Boolean { - return this@NoteTaskInitializer.handleKeyGestureEvent(event) + override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?) { + this@NoteTaskInitializer.handleKeyGestureEvent(event) } } @@ -202,23 +202,19 @@ constructor( return !isMultiPress && !isLongPress } - private fun handleKeyGestureEvent(event: KeyGestureEvent): Boolean { - // This method is on input hot path and should be kept lightweight. Shift all complex - // processing onto background executor wherever possible. + private fun handleKeyGestureEvent(event: KeyGestureEvent) { if (event.keyGestureType != KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) { - return false + return } debugLog { "handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " + event.keycodes.contentToString() } 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 + } else { + backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) } } - backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) } - return true } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 8a4f1ad13b78..5596cc7ee7da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -38,8 +38,6 @@ import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor @@ -52,11 +50,14 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyList import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations.initMocks +import org.mockito.kotlin.any +import org.mockito.kotlin.eq /** atest SystemUITests:NoteTaskInitializerTest */ @OptIn(InternalNoteTaskApi::class) @@ -180,6 +181,18 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { @Test @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun initialize_keyGestureTypeOpenNotes_isRegistered() { + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + verify(inputManager) + .registerKeyGestureEventHandler( + eq(listOf(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)), + any(), + ) + } + + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun handlesShortcut_keyGestureTypeOpenNotes() { val gestureEvent = KeyGestureEvent.Builder() @@ -189,12 +202,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() val callback = withArgCaptor { - verify(inputManager).registerKeyGestureEventHandler(capture()) + verify(inputManager).registerKeyGestureEventHandler(anyList(), capture()) } - assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue() - + callback.handleKeyGestureEvent(gestureEvent, null) executor.runAllReady() + verify(controller).showNoteTask(eq(KEYBOARD_SHORTCUT)) } @@ -203,19 +216,19 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { fun handlesShortcut_stylusTailButton() { val gestureEvent = KeyGestureEvent.Builder() - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)) + .setKeycodes(intArrayOf(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()) + verify(inputManager).registerKeyGestureEventHandler(anyList(), capture()) } - assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue() - + callback.handleKeyGestureEvent(gestureEvent, null) executor.runAllReady() + verify(controller).showNoteTask(eq(TAIL_BUTTON)) } @@ -224,19 +237,19 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { fun ignoresUnrelatedShortcuts() { val gestureEvent = KeyGestureEvent.Builder() - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)) + .setKeycodes(intArrayOf(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()) + verify(inputManager).registerKeyGestureEventHandler(anyList(), capture()) } - assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isFalse() - + callback.handleKeyGestureEvent(gestureEvent, null) executor.runAllReady() + verify(controller, never()).showNoteTask(any()) } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 703e37fad5ad..45f83c500d48 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -514,14 +514,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } private InputManager.KeyGestureEventHandler mKeyGestureEventHandler = - new InputManager.KeyGestureEventHandler() { - @Override - public boolean handleKeyGestureEvent( - @NonNull KeyGestureEvent event, - @Nullable IBinder focusedToken) { - return AccessibilityManagerService.this.handleKeyGestureEvent(event); - } - }; + (event, focusedToken) -> AccessibilityManagerService.this.handleKeyGestureEvent(event); @VisibleForTesting AccessibilityManagerService( @@ -637,7 +630,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub new AccessibilityContentObserver(mMainHandler).register( mContext.getContentResolver()); if (enableTalkbackAndMagnifierKeyGestures()) { - mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler); + List<Integer> supportedGestures = List.of( + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, + KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK); + mInputManager.registerKeyGestureEventHandler(supportedGestures, + mKeyGestureEventHandler); } if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) { if (mHearingDeviceNotificationController != null) { @@ -686,13 +683,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @VisibleForTesting - boolean handleKeyGestureEvent(KeyGestureEvent event) { + void handleKeyGestureEvent(KeyGestureEvent event) { final boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE && !event.isCancelled(); final int gestureType = event.getKeyGestureType(); if (!complete) { - return false; + return; } String targetName; @@ -703,7 +700,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub case KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK: targetName = mContext.getString(R.string.config_defaultSelectToSpeakService); if (targetName.isEmpty()) { - return false; + return; } final ComponentName targetServiceComponent = TextUtils.isEmpty(targetName) @@ -715,7 +712,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.getInstalledServiceInfoLocked(targetServiceComponent); } if (accessibilityServiceInfo == null) { - return false; + return; } // Skip enabling if a warning dialog is required for the feature. @@ -725,11 +722,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Slog.w(LOG_TAG, "Accessibility warning is required before this service can be " + "activated automatically via KEY_GESTURE shortcut."); - return false; + return; } break; default: - return false; + Slog.w(LOG_TAG, "Received a key gesture " + event + + " that was not registered by this handler"); + return; } List<String> shortcutTargets = getAccessibilityShortcutTargets( @@ -748,14 +747,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // this will be a separate dialog that appears that requires the user to confirm // which will resolve this race condition. For now, just require two presses the // first time it is activated. - return true; + return; } final int displayId = event.getDisplayId() != INVALID_DISPLAY ? event.getDisplayId() : getLastNonProxyTopFocusedDisplayId(); performAccessibilityShortcutInternal(displayId, KEY_GESTURE, targetName); - - return true; } @Override diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java index 8c028bc92841..eb102294ac32 100644 --- a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java +++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java @@ -111,7 +111,7 @@ final class AppLaunchShortcutManager { mContext = context; } - public void systemRunning() { + public void init() { loadShortcuts(); } diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 67e1ccc6a850..e6d71900f106 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -94,9 +94,9 @@ final class InputGestureManager { mContext = context; } - public void systemRunning() { + public void init(List<InputGestureData> bookmarks) { initSystemShortcuts(); - blockListBookmarkedTriggers(); + blockListBookmarkedTriggers(bookmarks); } private void initSystemShortcuts() { @@ -263,10 +263,9 @@ final class InputGestureManager { } } - private void blockListBookmarkedTriggers() { + private void blockListBookmarkedTriggers(List<InputGestureData> bookmarks) { synchronized (mGestureLock) { - InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); - for (InputGestureData bookmark : im.getAppLaunchBookmarks()) { + for (InputGestureData bookmark : bookmarks) { mBlockListedTriggers.add(bookmark.getTrigger()); } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 6e6d00d62819..29e04e744759 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -2751,18 +2751,23 @@ public class InputManagerService extends IInputManager.Stub @SuppressLint("MissingPermission") private void initKeyGestures() { InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); - im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() { - @Override - public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, - @Nullable IBinder focussedToken) { - return InputManagerService.this.handleKeyGestureEvent(event); - } - }); + List<Integer> supportedGestures = List.of( + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS + ); + im.registerKeyGestureEventHandler(supportedGestures, + (event, focusedToken) -> InputManagerService.this.handleKeyGestureEvent(event)); } @SuppressLint("MissingPermission") @VisibleForTesting - boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event) { + void handleKeyGestureEvent(@NonNull KeyGestureEvent event) { int deviceId = event.getDeviceId(); boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE && !event.isCancelled(); @@ -2771,20 +2776,20 @@ public class InputManagerService extends IInputManager.Stub if (complete) { mKeyboardBacklightController.incrementKeyboardBacklight(deviceId); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: if (complete) { mKeyboardBacklightController.decrementKeyboardBacklight(deviceId); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: // TODO(b/367748270): Add functionality to turn keyboard backlight on/off. - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: if (complete) { mNative.toggleCapsLock(deviceId); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS: if (complete) { final boolean bounceKeysEnabled = @@ -2792,7 +2797,6 @@ public class InputManagerService extends IInputManager.Stub InputSettings.setAccessibilityBounceKeysThreshold(mContext, bounceKeysEnabled ? 0 : InputSettings.DEFAULT_BOUNCE_KEYS_THRESHOLD_MILLIS); - return true; } break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS: @@ -2800,7 +2804,6 @@ public class InputManagerService extends IInputManager.Stub final boolean mouseKeysEnabled = InputSettings.isAccessibilityMouseKeysEnabled( mContext); InputSettings.setAccessibilityMouseKeysEnabled(mContext, !mouseKeysEnabled); - return true; } break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS: @@ -2808,7 +2811,6 @@ public class InputManagerService extends IInputManager.Stub final boolean stickyKeysEnabled = InputSettings.isAccessibilityStickyKeysEnabled(mContext); InputSettings.setAccessibilityStickyKeysEnabled(mContext, !stickyKeysEnabled); - return true; } break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS: @@ -2817,14 +2819,13 @@ public class InputManagerService extends IInputManager.Stub InputSettings.isAccessibilitySlowKeysEnabled(mContext); InputSettings.setAccessibilitySlowKeysThreshold(mContext, slowKeysEnabled ? 0 : InputSettings.DEFAULT_SLOW_KEYS_THRESHOLD_MILLIS); - return true; } break; default: - return false; - + Log.w(TAG, "Received a key gesture " + event + + " that was not registered by this handler"); + break; } - return false; } // Native callback. @@ -3147,11 +3148,14 @@ public class InputManagerService extends IInputManager.Stub @Override @PermissionManuallyEnforced - public void registerKeyGestureHandler(@NonNull IKeyGestureHandler handler) { + public void registerKeyGestureHandler(int[] keyGesturesToHandle, + @NonNull IKeyGestureHandler handler) { enforceManageKeyGesturePermission(); Objects.requireNonNull(handler); - mKeyGestureController.registerKeyGestureHandler(handler, Binder.getCallingPid()); + Objects.requireNonNull(keyGesturesToHandle); + mKeyGestureController.registerKeyGestureHandler(keyGesturesToHandle, handler, + Binder.getCallingPid()); } @Override diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index 395c77322c04..5de432e5849b 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -58,6 +58,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.Display; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -79,11 +80,11 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.TreeMap; /** * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a @@ -166,11 +167,14 @@ final class KeyGestureController { private final SparseArray<KeyGestureEventListenerRecord> mKeyGestureEventListenerRecords = new SparseArray<>(); - // List of currently registered key gesture event handler keyed by process pid. The map sorts - // in the order of preference of the handlers, and we prioritize handlers in system server - // over external handlers.. + // Map of currently registered key gesture event handlers keyed by pid. @GuardedBy("mKeyGestureHandlerRecords") - private final TreeMap<Integer, KeyGestureHandlerRecord> mKeyGestureHandlerRecords; + private final SparseArray<KeyGestureHandlerRecord> mKeyGestureHandlerRecords = + new SparseArray<>(); + + // Currently supported key gestures mapped to pid that registered the corresponding handler. + @GuardedBy("mKeyGestureHandlerRecords") + private final SparseIntArray mSupportedKeyGestureToPidMap = new SparseIntArray(); private final ArrayDeque<KeyGestureEvent> mLastHandledEvents = new ArrayDeque<>(); @@ -193,18 +197,6 @@ final class KeyGestureController { mHandler = new Handler(looper, this::handleMessage); mIoHandler = new Handler(ioLooper, this::handleIoMessage); mSystemPid = Process.myPid(); - mKeyGestureHandlerRecords = new TreeMap<>((p1, p2) -> { - if (Objects.equals(p1, p2)) { - return 0; - } - if (p1 == mSystemPid) { - return -1; - } else if (p2 == mSystemPid) { - return 1; - } else { - return Integer.compare(p1, p2); - } - }); mKeyCombinationManager = new KeyCombinationManager(mHandler); mSettingsObserver = new SettingsObserver(mHandler); mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext); @@ -450,8 +442,8 @@ final class KeyGestureController { public void systemRunning() { mSettingsObserver.observe(); - mAppLaunchShortcutManager.systemRunning(); - mInputGestureManager.systemRunning(); + mAppLaunchShortcutManager.init(); + mInputGestureManager.init(mAppLaunchShortcutManager.getBookmarks()); initKeyGestures(); int userId; @@ -465,22 +457,24 @@ final class KeyGestureController { @SuppressLint("MissingPermission") private void initKeyGestures() { InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); - im.registerKeyGestureEventHandler((event, focusedToken) -> { - switch (event.getKeyGestureType()) { - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: - if (event.getAction() == KeyGestureEvent.ACTION_GESTURE_START) { - mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT), - getAccessibilityShortcutTimeout()); + im.registerKeyGestureEventHandler( + List.of(KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD), + (event, focusedToken) -> { + if (event.getKeyGestureType() + == KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD) { + if (event.getAction() == KeyGestureEvent.ACTION_GESTURE_START) { + mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT), + getAccessibilityShortcutTimeout()); + } else { + mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); + } } else { - mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); + Log.w(TAG, "Received a key gesture " + event + + " that was not registered by this handler"); } - return true; - default: - return false; - } - }); + }); } public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { @@ -590,10 +584,11 @@ final class KeyGestureController { return true; } if (result.appLaunchData() != null) { - return handleKeyGesture(deviceId, new int[]{keyCode}, metaState, + handleKeyGesture(deviceId, new int[]{keyCode}, metaState, KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, result.appLaunchData()); + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */ + 0, result.appLaunchData()); + return true; } // Handle system shortcuts @@ -601,11 +596,11 @@ final class KeyGestureController { InputGestureData systemShortcut = mInputGestureManager.getSystemShortcutForKeyEvent( event); if (systemShortcut != null) { - return handleKeyGesture(deviceId, new int[]{keyCode}, metaState, + handleKeyGesture(deviceId, new int[]{keyCode}, metaState, systemShortcut.getAction().keyGestureType(), - KeyGestureEvent.ACTION_GESTURE_COMPLETE, - displayId, focusedToken, /* flags = */0, - systemShortcut.getAction().appLaunchData()); + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0, systemShortcut.getAction().appLaunchData()); + return true; } } @@ -687,11 +682,11 @@ final class KeyGestureController { return true; case KeyEvent.KEYCODE_SEARCH: if (firstDown && mSearchKeyBehavior == SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY) { - return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); - + return true; } break; case KeyEvent.KEYCODE_SETTINGS: @@ -782,11 +777,12 @@ final class KeyGestureController { if (KeyEvent.metaStateHasModifiers( shiftlessModifiers, KeyEvent.META_ALT_ON)) { mPendingHideRecentSwitcher = true; - return handleKeyGesture(deviceId, new int[]{keyCode}, + handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, KeyGestureEvent.ACTION_GESTURE_START, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); + return true; } } } @@ -803,21 +799,23 @@ final class KeyGestureController { } else { if (mPendingHideRecentSwitcher) { mPendingHideRecentSwitcher = false; - return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_TAB}, + handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_TAB}, KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); + return true; } // Toggle Caps Lock on META-ALT. if (mPendingCapsLockToggle) { mPendingCapsLockToggle = false; - return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT, + handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); + return true; } } break; @@ -885,11 +883,11 @@ final class KeyGestureController { if (customGesture == null) { return false; } - return handleKeyGesture(deviceId, new int[]{keyCode}, metaState, + handleKeyGesture(deviceId, new int[]{keyCode}, metaState, customGesture.getAction().keyGestureType(), - KeyGestureEvent.ACTION_GESTURE_COMPLETE, - displayId, focusedToken, /* flags = */0, - customGesture.getAction().appLaunchData()); + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, + /* flags = */0, customGesture.getAction().appLaunchData()); + return true; } return false; } @@ -908,7 +906,7 @@ final class KeyGestureController { // Handle keyboard layout switching. (CTRL + SPACE) if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) { - return handleKeyGesture(deviceId, new int[]{keyCode}, + handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_CTRL_ON | (event.isShiftPressed() ? KeyEvent.META_SHIFT_ON : 0), KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, @@ -921,7 +919,7 @@ final class KeyGestureController { if (down && KeyEvent.metaStateHasModifiers(metaState, KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON)) { // Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users. - return handleKeyGesture(deviceId, new int[]{keyCode}, + handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, @@ -930,7 +928,7 @@ final class KeyGestureController { break; case KeyEvent.KEYCODE_SYSRQ: if (down && repeatCount == 0) { - return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); @@ -938,7 +936,7 @@ final class KeyGestureController { break; case KeyEvent.KEYCODE_ESCAPE: if (down && KeyEvent.metaStateHasNoModifiers(metaState) && repeatCount == 0) { - return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); @@ -964,29 +962,31 @@ final class KeyGestureController { } @VisibleForTesting - boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState, + void handleKeyGesture(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId, @Nullable IBinder focusedToken, int flags, @Nullable AppLaunchData appLaunchData) { - return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes, - modifierState, gestureType, action, displayId, flags, appLaunchData), focusedToken); + handleKeyGesture( + createKeyGestureEvent(deviceId, keycodes, modifierState, gestureType, action, + displayId, flags, appLaunchData), focusedToken); } - private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) { + private void handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) { if (mVisibleBackgroundUsersEnabled && event.displayId != DEFAULT_DISPLAY && shouldIgnoreGestureEventForVisibleBackgroundUser(event.gestureType, event.displayId)) { - return false; + return; } synchronized (mKeyGestureHandlerRecords) { - for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) { - if (handler.handleKeyGesture(event, focusedToken)) { - Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT, event); - mHandler.sendMessage(msg); - return true; - } + int index = mSupportedKeyGestureToPidMap.indexOfKey(event.gestureType); + if (index < 0) { + Log.i(TAG, "Key gesture: " + event.gestureType + " is not supported"); + return; } + int pid = mSupportedKeyGestureToPidMap.valueAt(index); + mKeyGestureHandlerRecords.get(pid).handleKeyGesture(event, focusedToken); + Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT, event); + mHandler.sendMessage(msg); } - return false; } private boolean shouldIgnoreGestureEventForVisibleBackgroundUser( @@ -1285,12 +1285,23 @@ final class KeyGestureController { /** Register the key gesture event handler for a process. */ @BinderThread - public void registerKeyGestureHandler(IKeyGestureHandler handler, int pid) { + public void registerKeyGestureHandler(int[] keyGesturesToHandle, IKeyGestureHandler handler, + int pid) { synchronized (mKeyGestureHandlerRecords) { if (mKeyGestureHandlerRecords.get(pid) != null) { throw new IllegalStateException("The calling process has already registered " + "a KeyGestureHandler."); } + if (keyGesturesToHandle.length == 0) { + throw new IllegalArgumentException("No key gestures provided for pid = " + pid); + } + for (int gestureType : keyGesturesToHandle) { + if (mSupportedKeyGestureToPidMap.indexOfKey(gestureType) >= 0) { + throw new IllegalArgumentException( + "Key gesture " + gestureType + " is already registered by pid = " + + mSupportedKeyGestureToPidMap.get(gestureType)); + } + } KeyGestureHandlerRecord record = new KeyGestureHandlerRecord(pid, handler); try { handler.asBinder().linkToDeath(record, 0); @@ -1298,6 +1309,9 @@ final class KeyGestureController { throw new RuntimeException(ex); } mKeyGestureHandlerRecords.put(pid, record); + for (int gestureType : keyGesturesToHandle) { + mSupportedKeyGestureToPidMap.put(gestureType, pid); + } } } @@ -1315,7 +1329,7 @@ final class KeyGestureController { + "KeyGestureHandler."); } record.mKeyGestureHandler.asBinder().unlinkToDeath(record, 0); - mKeyGestureHandlerRecords.remove(pid); + onKeyGestureHandlerRemoved(pid); } } @@ -1328,9 +1342,14 @@ final class KeyGestureController { return mAppLaunchShortcutManager.getBookmarks(); } - private void onKeyGestureHandlerDied(int pid) { + private void onKeyGestureHandlerRemoved(int pid) { synchronized (mKeyGestureHandlerRecords) { mKeyGestureHandlerRecords.remove(pid); + for (int i = mSupportedKeyGestureToPidMap.size() - 1; i >= 0; i--) { + if (mSupportedKeyGestureToPidMap.valueAt(i) == pid) { + mSupportedKeyGestureToPidMap.removeAt(i); + } + } } } @@ -1369,18 +1388,17 @@ final class KeyGestureController { if (DEBUG) { Slog.d(TAG, "Key gesture event handler for pid " + mPid + " died."); } - onKeyGestureHandlerDied(mPid); + onKeyGestureHandlerRemoved(mPid); } - public boolean handleKeyGesture(AidlKeyGestureEvent event, IBinder focusedToken) { + public void handleKeyGesture(AidlKeyGestureEvent event, IBinder focusedToken) { try { - return mKeyGestureHandler.handleKeyGesture(event, focusedToken); + mKeyGestureHandler.handleKeyGesture(event, focusedToken); } catch (RemoteException ex) { Slog.w(TAG, "Failed to send key gesture to process " + mPid + ", assuming it died.", ex); binderDied(); } - return false; } } @@ -1479,18 +1497,21 @@ final class KeyGestureController { } } ipw.println("}"); - ipw.print("mKeyGestureHandlerRecords = {"); synchronized (mKeyGestureHandlerRecords) { - int i = mKeyGestureHandlerRecords.size() - 1; - for (int processId : mKeyGestureHandlerRecords.keySet()) { - ipw.print(processId); - if (i > 0) { + ipw.print("mKeyGestureHandlerRecords = {"); + int size = mKeyGestureHandlerRecords.size(); + for (int i = 0; i < size; i++) { + int pid = mKeyGestureHandlerRecords.keyAt(i); + ipw.print(pid); + if (i < size - 1) { ipw.print(", "); } - i--; } + ipw.println("}"); + ipw.println("mSupportedKeyGestures = " + Arrays.toString( + mSupportedKeyGestureToPidMap.copyKeys())); } - ipw.println("}"); + ipw.decreaseIndent(); ipw.println("Last handled KeyGestureEvents: "); ipw.increaseIndent(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 38d458767015..fcd1452b8702 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -115,7 +115,6 @@ import static com.android.server.wm.WindowManagerPolicyProto.SCREEN_ON_FULLY; import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE; import android.accessibilityservice.AccessibilityService; -import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; @@ -268,6 +267,7 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -4240,19 +4240,51 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!useKeyGestureEventHandler()) { return; } - mInputManager.registerKeyGestureEventHandler((event, focusedToken) -> { - boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event, - focusedToken); - if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( - (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { - mPowerKeyHandled = true; - } - return handled; - }); + List<Integer> supportedGestures = new ArrayList<>(List.of( + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, + KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, + KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP, + KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, + KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB, + KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD, + KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD, + KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS, + KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT + )); + if (enableTalkbackAndMagnifierKeyGestures()) { + supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK); + } + if (enableVoiceAccessKeyGestures()) { + supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS); + } + mInputManager.registerKeyGestureEventHandler(supportedGestures, + PhoneWindowManager.this::handleKeyGestureEvent); } @VisibleForTesting - boolean handleKeyGestureEvent(KeyGestureEvent event, IBinder focusedToken) { + void handleKeyGestureEvent(KeyGestureEvent event, IBinder focusedToken) { boolean start = event.getAction() == KeyGestureEvent.ACTION_GESTURE_START; boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE && !event.isCancelled(); @@ -4262,12 +4294,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { int modifierState = event.getModifierState(); boolean keyguardOn = keyguardOn(); boolean canLaunchApp = isUserSetupComplete() && !keyguardOn; + if (!event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( + (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { + mPowerKeyHandled = true; + } switch (gestureType) { case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS: if (complete) { showRecentApps(false); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH: if (!keyguardOn) { if (start) { @@ -4276,7 +4312,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { toggleRecentApps(); } } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT: if (complete && canLaunchApp) { @@ -4284,33 +4320,33 @@ public class PhoneWindowManager implements WindowManagerPolicy { deviceId, SystemClock.uptimeMillis(), AssistUtils.INVOCATION_TYPE_UNKNOWN); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_HOME: if (complete) { // Post to main thread to avoid blocking input pipeline. mHandler.post(() -> handleShortPressOnHome(displayId)); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS: if (complete && canLaunchApp) { showSystemSettings(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN: if (complete) { lockNow(null /* options */); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL: if (complete) { toggleNotificationPanel(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT: if (complete) { interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: if (complete && mEnableBugReportKeyboardShortcut) { try { @@ -4321,12 +4357,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { Slog.d(TAG, "Error taking bugreport", e); } } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_BACK: if (complete) { injectBackGesture(SystemClock.uptimeMillis()); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: if (complete) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); @@ -4335,7 +4371,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { getTargetDisplayIdForKeyGestureEvent(event)); } } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE: if (complete) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); @@ -4344,24 +4380,24 @@ public class PhoneWindowManager implements WindowManagerPolicy { getTargetDisplayIdForKeyGestureEvent(event)); } } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: if (complete) { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event), true /* leftOrTop */); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: if (complete) { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event), false /* leftOrTop */); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: if (complete) { toggleKeyboardShortcutsMenu(deviceId); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP: case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: if (complete) { @@ -4369,32 +4405,32 @@ public class PhoneWindowManager implements WindowManagerPolicy { gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP ? 1 : -1; changeDisplayBrightnessValue(displayId, direction); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: if (start) { showRecentApps(true); } else { hideRecentApps(true, false); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS: case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS: if (complete && isKeyEventForCurrentUser(event.getDisplayId(), event.getKeycodes()[0], "launchAllAppsViaA11y")) { launchAllAppsAction(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH: if (complete && canLaunchApp) { launchTargetSearchActivity(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH: if (complete) { int direction = (modifierState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(displayId, focusedToken, direction); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD: if (start) { // Screenshot chord is pressed: Wait for long press delay before taking @@ -4404,14 +4440,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { cancelPendingScreenshotChordAction(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD: if (start) { interceptRingerToggleChord(); } else { cancelPendingRingerToggleChordAction(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS: if (start) { performHapticFeedback( @@ -4421,40 +4457,34 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { cancelGlobalActionsAction(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT: if (start) { interceptBugreportGestureTv(); } else { cancelBugreportGestureTv(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT: if (complete && mAccessibilityShortcutController.isAccessibilityShortcutAvailable( isKeyguardLocked())) { mHandler.sendMessage(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT)); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS: if (complete) { mContext.closeSystemDialogs(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK: - if (enableTalkbackAndMagnifierKeyGestures()) { - if (complete) { - mTalkbackShortcutController.toggleTalkback(mCurrentUserId, - TalkbackShortcutController.ShortcutSource.KEYBOARD); - } - return true; + if (complete) { + mTalkbackShortcutController.toggleTalkback(mCurrentUserId, + TalkbackShortcutController.ShortcutSource.KEYBOARD); } break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS: - if (enableVoiceAccessKeyGestures()) { - if (complete) { - mVoiceAccessShortcutController.toggleVoiceAccess(mCurrentUserId); - } - return true; + if (complete) { + mVoiceAccessShortcutController.toggleVoiceAccess(mCurrentUserId); } break; case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION: @@ -4463,7 +4493,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { && mModifierShortcutManager.launchApplication(data)) { dismissKeyboardShortcutsMenu(); } - return true; + break; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB: NotificationManager nm = getNotificationService(); if (nm != null) { @@ -4472,9 +4502,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { : Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, "Key gesture DND", true); } - return true; + break; + default: + Log.w(TAG, "Received a key gesture " + event + + " that was not registered by this handler"); + break; } - return false; } private void changeDisplayBrightnessValue(int displayId, int direction) { diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index fcdf88f16550..0495e967c0e3 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -39,8 +39,6 @@ import androidx.test.filters.MediumTest; import com.android.hardware.input.Flags; import com.android.internal.annotations.Keep; -import junit.framework.Assert; - import junitparams.JUnitParamsRunner; import junitparams.Parameters; @@ -433,112 +431,94 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { @Test public void testKeyGestureRecentApps() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS); mPhoneWindowManager.assertShowRecentApps(); } @Test public void testKeyGestureAppSwitch() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH); mPhoneWindowManager.assertToggleRecentApps(); } @Test public void testKeyGestureLaunchAssistant() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT); mPhoneWindowManager.assertSearchManagerLaunchAssist(); } @Test public void testKeyGestureLaunchVoiceAssistant() { - Assert.assertTrue( - sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT); mPhoneWindowManager.assertSearchManagerLaunchAssist(); } @Test public void testKeyGestureGoHome() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME); mPhoneWindowManager.assertGoToHomescreen(); } @Test public void testKeyGestureLaunchSystemSettings() { - Assert.assertTrue( - sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS); mPhoneWindowManager.assertLaunchSystemSettings(); } @Test public void testKeyGestureLock() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN); mPhoneWindowManager.assertLockedAfterAppTransitionFinished(); } @Test public void testKeyGestureToggleNotificationPanel() throws RemoteException { - Assert.assertTrue( - sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL); mPhoneWindowManager.assertTogglePanel(); } @Test public void testKeyGestureScreenshot() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT); mPhoneWindowManager.assertTakeScreenshotCalled(); } @Test public void testKeyGestureTriggerBugReport() throws RemoteException { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT); mPhoneWindowManager.assertTakeBugreport(true); } @Test public void testKeyGestureBack() { - Assert.assertTrue(sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK); mPhoneWindowManager.assertBackEventInjected(); } @Test public void testKeyGestureMultiWindowNavigation() { - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION); mPhoneWindowManager.assertMoveFocusedTaskToFullscreen(); } @Test public void testKeyGestureDesktopMode() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE); mPhoneWindowManager.assertMoveFocusedTaskToDesktop(); } @Test public void testKeyGestureSplitscreenNavigation() { - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT); mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(true); - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT); mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(false); } @Test public void testKeyGestureShortcutHelper() { - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER); mPhoneWindowManager.assertToggleShortcutsMenu(); } @@ -549,173 +529,139 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { for (int i = 0; i < currentBrightness.length; i++) { mPhoneWindowManager.prepareBrightnessDecrease(currentBrightness[i]); - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN); mPhoneWindowManager.verifyNewBrightness(newBrightness[i]); } } @Test public void testKeyGestureRecentAppSwitcher() { - Assert.assertTrue(sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER); mPhoneWindowManager.assertShowRecentApps(); - - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER); mPhoneWindowManager.assertHideRecentApps(); } @Test public void testKeyGestureLanguageSwitch() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH); mPhoneWindowManager.assertSwitchKeyboardLayout(1, DEFAULT_DISPLAY); - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, - KeyEvent.META_SHIFT_ON)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + KeyEvent.META_SHIFT_ON); mPhoneWindowManager.assertSwitchKeyboardLayout(-1, DEFAULT_DISPLAY); } @Test public void testKeyGestureLaunchSearch() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH); mPhoneWindowManager.assertLaunchSearch(); } @Test public void testKeyGestureScreenshotChord() { - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD); mPhoneWindowManager.moveTimeForward(500); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD); mPhoneWindowManager.assertTakeScreenshotCalled(); } @Test public void testKeyGestureScreenshotChordCancelled() { - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD); mPhoneWindowManager.assertTakeScreenshotNotCalled(); } @Test public void testKeyGestureRingerToggleChord() { mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE); - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD); mPhoneWindowManager.moveTimeForward(500); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD); mPhoneWindowManager.assertVolumeMute(); } @Test public void testKeyGestureRingerToggleChordCancelled() { mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE); - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD); mPhoneWindowManager.assertVolumeNotMuted(); } @Test public void testKeyGestureGlobalAction() { mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS); - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS); mPhoneWindowManager.moveTimeForward(500); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS); mPhoneWindowManager.assertShowGlobalActionsCalled(); } @Test public void testKeyGestureGlobalActionCancelled() { mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS); - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS); mPhoneWindowManager.assertShowGlobalActionsNotCalled(); } @Test public void testKeyGestureTvTriggerBugReport() { - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT); mPhoneWindowManager.moveTimeForward(1000); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT); mPhoneWindowManager.assertBugReportTakenForTv(); } @Test public void testKeyGestureTvTriggerBugReportCancelled() { - Assert.assertTrue( - sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); - Assert.assertTrue( - sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT); + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT); mPhoneWindowManager.assertBugReportNotTakenForTv(); } @Test public void testKeyGestureAccessibilityShortcut() { - Assert.assertTrue( - sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT); mPhoneWindowManager.assertAccessibilityKeychordCalled(); } @Test public void testKeyGestureCloseAllDialogs() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS); mPhoneWindowManager.assertCloseAllDialogs(); } @Test @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void testKeyGestureToggleTalkback() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK); mPhoneWindowManager.assertTalkBack(true); - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK); mPhoneWindowManager.assertTalkBack(false); } @Test @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES) public void testKeyGestureToggleVoiceAccess() { - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS); mPhoneWindowManager.assertVoiceAccess(true); - Assert.assertTrue( - sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS); mPhoneWindowManager.assertVoiceAccess(false); } @Test public void testKeyGestureToggleDoNotDisturb() { mPhoneWindowManager.overrideZenMode(Settings.Global.ZEN_MODE_OFF); - Assert.assertTrue( - sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB); mPhoneWindowManager.assertZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); mPhoneWindowManager.overrideZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); - Assert.assertTrue( - sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB)); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB); mPhoneWindowManager.assertZenMode(Settings.Global.ZEN_MODE_OFF); } diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index 32a3b7f2c9cc..68229688c238 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -43,6 +43,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -146,7 +147,7 @@ public class PhoneWindowManagerTests { mPhoneWindowManager.mKeyguardDelegate = mKeyguardServiceDelegate; final InputManager im = mock(InputManager.class); - doNothing().when(im).registerKeyGestureEventHandler(any()); + doNothing().when(im).registerKeyGestureEventHandler(anyList(), any()); doReturn(im).when(mContext).getSystemService(eq(Context.INPUT_SERVICE)); } diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index c57adfd69b06..f89c6f638384 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -238,33 +238,33 @@ class ShortcutKeyTestBase { sendKeyCombination(new int[]{keyCode}, durationMillis, false, DEFAULT_DISPLAY); } - boolean sendKeyGestureEventStart(int gestureType) { - return mPhoneWindowManager.sendKeyGestureEvent( + void sendKeyGestureEventStart(int gestureType) { + mPhoneWindowManager.sendKeyGestureEvent( new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( KeyGestureEvent.ACTION_GESTURE_START).build()); } - boolean sendKeyGestureEventComplete(int gestureType) { - return mPhoneWindowManager.sendKeyGestureEvent( + void sendKeyGestureEventComplete(int gestureType) { + mPhoneWindowManager.sendKeyGestureEvent( new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); } - boolean sendKeyGestureEventCancel(int gestureType) { - return mPhoneWindowManager.sendKeyGestureEvent( + void sendKeyGestureEventCancel(int gestureType) { + mPhoneWindowManager.sendKeyGestureEvent( new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( KeyGestureEvent.ACTION_GESTURE_COMPLETE).setFlags( KeyGestureEvent.FLAG_CANCELLED).build()); } - boolean sendKeyGestureEventComplete(int gestureType, int modifierState) { - return mPhoneWindowManager.sendKeyGestureEvent( + void sendKeyGestureEventComplete(int gestureType, int modifierState) { + mPhoneWindowManager.sendKeyGestureEvent( new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType( gestureType).setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); } - boolean sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType) { - return mPhoneWindowManager.sendKeyGestureEvent( + void sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType) { + mPhoneWindowManager.sendKeyGestureEvent( new KeyGestureEvent.Builder().setKeycodes(new int[]{keycode}).setModifierState( modifierState).setKeyGestureType(gestureType).setAction( KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index e56fd3c6272d..7b6d361c55d4 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -49,6 +49,7 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.after; @@ -353,7 +354,7 @@ class TestPhoneWindowManager { doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class)); doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class)); doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class)); - doNothing().when(mInputManager).registerKeyGestureEventHandler(any()); + doNothing().when(mInputManager).registerKeyGestureEventHandler(anyList(), any()); doNothing().when(mInputManager).unregisterKeyGestureEventHandler(any()); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mSensorPrivacyManager).when(mContext).getSystemService( @@ -476,8 +477,8 @@ class TestPhoneWindowManager { mPhoneWindowManager.interceptUnhandledKey(event, mInputToken); } - boolean sendKeyGestureEvent(KeyGestureEvent event) { - return mPhoneWindowManager.handleKeyGestureEvent(event, mInputToken); + void sendKeyGestureEvent(KeyGestureEvent event) { + mPhoneWindowManager.handleKeyGestureEvent(event, mInputToken); } /** diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt index 794fd0255726..c62bd0b72584 100644 --- a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt @@ -18,12 +18,10 @@ package android.hardware.input import android.content.Context import android.content.ContextWrapper -import android.os.IBinder import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider -import com.android.server.testutils.any import com.android.test.input.MockInputManagerRule import org.junit.Before import org.junit.Rule @@ -37,6 +35,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.fail +import org.junit.Assert.assertThrows /** * Tests for [InputManager.KeyGestureEventHandler]. @@ -82,7 +81,7 @@ class KeyGestureEventHandlerTest { // Handle key gesture handler registration. doAnswer { - val listener = it.getArgument(0) as IKeyGestureHandler + val listener = it.getArgument(1) as IKeyGestureHandler if (registeredListener != null && registeredListener!!.asBinder() != listener.asBinder()) { // There can only be one registered key gesture handler per process. @@ -90,7 +89,7 @@ class KeyGestureEventHandlerTest { } registeredListener = listener null - }.`when`(inputManagerRule.mock).registerKeyGestureHandler(any()) + }.`when`(inputManagerRule.mock).registerKeyGestureHandler(Mockito.any(), Mockito.any()) // Handle key gesture handler being unregistered. doAnswer { @@ -101,7 +100,7 @@ class KeyGestureEventHandlerTest { } registeredListener = null null - }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(any()) + }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(Mockito.any()) } private fun handleKeyGestureEvent(event: KeyGestureEvent) { @@ -121,11 +120,12 @@ class KeyGestureEventHandlerTest { var callbackCount = 0 // Add a key gesture event listener - inputManager.registerKeyGestureEventHandler(KeyGestureHandler { event, _ -> + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + ) { event, _ -> assertEquals(HOME_GESTURE_EVENT, event) callbackCount++ - true - }) + } // Request handling for key gesture event will notify the handler. handleKeyGestureEvent(HOME_GESTURE_EVENT) @@ -135,29 +135,41 @@ class KeyGestureEventHandlerTest { @Test fun testAddingHandlersRegistersInternalCallbackHandler() { // Set up two callbacks. - val callback1 = KeyGestureHandler { _, _ -> false } - val callback2 = KeyGestureHandler { _, _ -> false } + val callback1 = InputManager.KeyGestureEventHandler { _, _ -> } + val callback2 = InputManager.KeyGestureEventHandler { _, _ -> } assertNull(registeredListener) // Adding the handler should register the callback with InputManagerService. - inputManager.registerKeyGestureEventHandler(callback1) + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + callback1 + ) assertNotNull(registeredListener) // Adding another handler should not register new internal listener. val currListener = registeredListener - inputManager.registerKeyGestureEventHandler(callback2) + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK), + callback2 + ) assertEquals(currListener, registeredListener) } @Test fun testRemovingHandlersUnregistersInternalCallbackHandler() { // Set up two callbacks. - val callback1 = KeyGestureHandler { _, _ -> false } - val callback2 = KeyGestureHandler { _, _ -> false } + val callback1 = InputManager.KeyGestureEventHandler { _, _ -> } + val callback2 = InputManager.KeyGestureEventHandler { _, _ -> } - inputManager.registerKeyGestureEventHandler(callback1) - inputManager.registerKeyGestureEventHandler(callback2) + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + callback1 + ) + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK), + callback2 + ) // Only removing all handlers should remove the internal callback inputManager.unregisterKeyGestureEventHandler(callback1) @@ -172,47 +184,74 @@ class KeyGestureEventHandlerTest { var callbackCount1 = 0 var callbackCount2 = 0 // Handler 1 captures all home gestures - val callback1 = KeyGestureHandler { event, _ -> + val callback1 = InputManager.KeyGestureEventHandler { event, _ -> callbackCount1++ - event.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_HOME + assertEquals(KeyGestureEvent.KEY_GESTURE_TYPE_HOME, event.keyGestureType) } - // Handler 2 captures all gestures - val callback2 = KeyGestureHandler { _, _ -> + // Handler 2 captures all back gestures + val callback2 = InputManager.KeyGestureEventHandler { event, _ -> callbackCount2++ - true + assertEquals(KeyGestureEvent.KEY_GESTURE_TYPE_BACK, event.keyGestureType) } // Add both key gesture event handlers - inputManager.registerKeyGestureEventHandler(callback1) - inputManager.registerKeyGestureEventHandler(callback2) + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + callback1 + ) + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK), + callback2 + ) - // Request handling for key gesture event, should notify callbacks in order. So, only the - // first handler should receive a callback since it captures the event. + // Request handling for home key gesture event, should notify only callback1 handleKeyGestureEvent(HOME_GESTURE_EVENT) assertEquals(1, callbackCount1) assertEquals(0, callbackCount2) - // Second handler should receive the event since the first handler doesn't capture the event + // Request handling for back key gesture event, should notify only callback2 handleKeyGestureEvent(BACK_GESTURE_EVENT) - assertEquals(2, callbackCount1) + assertEquals(1, callbackCount1) assertEquals(1, callbackCount2) inputManager.unregisterKeyGestureEventHandler(callback1) - // Request handling for key gesture event, should still trigger callback2 but not callback1. + + // Request handling for home key gesture event, should not trigger callback2 handleKeyGestureEvent(HOME_GESTURE_EVENT) - assertEquals(2, callbackCount1) - assertEquals(2, callbackCount2) + assertEquals(1, callbackCount1) + assertEquals(1, callbackCount2) + } + + @Test + fun testUnableToRegisterSameHandlerTwice() { + val handler = InputManager.KeyGestureEventHandler { _, _ -> } + + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + handler + ) + + assertThrows(IllegalArgumentException::class.java) { + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK), handler + ) + } } - inner class KeyGestureHandler( - private var handler: (event: KeyGestureEvent, token: IBinder?) -> Boolean - ) : InputManager.KeyGestureEventHandler { + @Test + fun testUnableToRegisterSameGestureTwice() { + val handler1 = InputManager.KeyGestureEventHandler { _, _ -> } + val handler2 = InputManager.KeyGestureEventHandler { _, _ -> } + + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + handler1 + ) - override fun handleKeyGestureEvent( - event: KeyGestureEvent, - focusedToken: IBinder? - ): Boolean { - return handler(event, focusedToken) + assertThrows(IllegalArgumentException::class.java) { + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), handler2 + ) } } } diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 4f1fb6487b19..163dda84a71c 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -63,6 +63,7 @@ import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -107,7 +108,10 @@ class KeyGestureControllerTests { const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0 const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1 const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2 + const val SYSTEM_PID = 0 const val TEST_PID = 10 + const val RANDOM_PID1 = 11 + const val RANDOM_PID2 = 12 } @JvmField @@ -170,6 +174,7 @@ class KeyGestureControllerTests { return atomicFile } }) + startNewInputGlobalTestSession() } @After @@ -199,17 +204,22 @@ class KeyGestureControllerTests { val correctIm = context.getSystemService(InputManager::class.java)!! val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!! val kcm = virtualDevice.keyCharacterMap!! - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) - val inputManager = InputManager(context) - Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) - .thenReturn(inputManager) - val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build() Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm) } + private fun startNewInputGlobalTestSession() { + if (this::inputManagerGlobalSession.isInitialized) { + inputManagerGlobalSession.close() + } + inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) + val inputManager = InputManager(context) + Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) + .thenReturn(inputManager) + } + private fun setupKeyGestureController() { keyGestureController = KeyGestureController( @@ -225,13 +235,14 @@ class KeyGestureControllerTests { return accessibilityShortcutController } }) - Mockito.`when`(iInputManager.registerKeyGestureHandler(Mockito.any())) + Mockito.`when`(iInputManager.registerKeyGestureHandler(Mockito.any(), Mockito.any())) .thenAnswer { val args = it.arguments if (args[0] != null) { keyGestureController.registerKeyGestureHandler( - args[0] as IKeyGestureHandler, - TEST_PID + args[0] as IntArray, + args[1] as IKeyGestureHandler, + SYSTEM_PID ) } } @@ -285,59 +296,6 @@ class KeyGestureControllerTests { ) } - @Test - fun testKeyGestureEvent_multipleGestureHandlers() { - setupKeyGestureController() - - // Set up two callbacks. - var callbackCount1 = 0 - var callbackCount2 = 0 - var selfCallback = 0 - val externalHandler1 = KeyGestureHandler { _, _ -> - callbackCount1++ - true - } - val externalHandler2 = KeyGestureHandler { _, _ -> - callbackCount2++ - true - } - val selfHandler = KeyGestureHandler { _, _ -> - selfCallback++ - false - } - - // Register key gesture handler: External process (last in priority) - keyGestureController.registerKeyGestureHandler(externalHandler1, currentPid + 1) - - // Register key gesture handler: External process (second in priority) - keyGestureController.registerKeyGestureHandler(externalHandler2, currentPid - 1) - - // Register key gesture handler: Self process (first in priority) - keyGestureController.registerKeyGestureHandler(selfHandler, currentPid) - - keyGestureController.handleKeyGesture(/* deviceId = */ 0, intArrayOf(KeyEvent.KEYCODE_HOME), - /* modifierState = */ 0, KeyGestureEvent.KEY_GESTURE_TYPE_HOME, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, /* displayId */ 0, - /* focusedToken = */ null, /* flags = */ 0, /* appLaunchData = */null - ) - - assertEquals( - "Self handler should get callbacks first", - 1, - selfCallback - ) - assertEquals( - "Higher priority handler should get callbacks first", - 1, - callbackCount2 - ) - assertEquals( - "Lower priority handler should not get callbacks if already handled", - 0, - callbackCount1 - ) - } - class TestData( val name: String, val keys: IntArray, @@ -789,10 +747,6 @@ class KeyGestureControllerTests { ) fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) { setupKeyGestureController() - // Need to re-init so that bookmarks are correctly blocklisted - Mockito.`when`(iInputManager.getAppLaunchBookmarks()) - .thenReturn(keyGestureController.appLaunchBookmarks) - keyGestureController.systemRunning() val builder = InputGestureData.Builder() .setKeyGestureType(test.expectedKeyGestureType) @@ -1163,9 +1117,6 @@ class KeyGestureControllerTests { KeyEvent.KEYCODE_FULLSCREEN ) - val handler = KeyGestureHandler { _, _ -> false } - keyGestureController.registerKeyGestureHandler(handler, 0) - for (key in testKeys) { sendKeys(intArrayOf(key), assertNotSentToApps = true) } @@ -1179,6 +1130,7 @@ class KeyGestureControllerTests { testKeyGestureNotProduced( "SEARCH -> Default Search", intArrayOf(KeyEvent.KEYCODE_SEARCH), + intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH) ) } @@ -1207,6 +1159,10 @@ class KeyGestureControllerTests { testKeyGestureNotProduced( "SETTINGS -> Do Nothing", intArrayOf(KeyEvent.KEYCODE_SETTINGS), + intArrayOf( + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL + ) ) } @@ -1290,28 +1246,6 @@ class KeyGestureControllerTests { ) ), TestData( - "VOLUME_DOWN + VOLUME_UP -> Accessibility Chord", - intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP), - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, - intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP), - 0, - intArrayOf( - KeyGestureEvent.ACTION_GESTURE_START, - KeyGestureEvent.ACTION_GESTURE_COMPLETE - ) - ), - TestData( - "BACK + DPAD_DOWN -> Accessibility Chord(for TV)", - intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, - intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), - 0, - intArrayOf( - KeyGestureEvent.ACTION_GESTURE_START, - KeyGestureEvent.ACTION_GESTURE_COMPLETE - ) - ), - TestData( "BACK + DPAD_CENTER -> TV Trigger Bug Report", intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER), KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT, @@ -1428,9 +1362,11 @@ class KeyGestureControllerTests { testLooper.dispatchAll() // Reinitialize the gesture controller simulating a login/logout for the user. + startNewInputGlobalTestSession() setupKeyGestureController() keyGestureController.setCurrentUserId(userId) testLooper.dispatchAll() + val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null) assertEquals( "Test: $test doesn't produce correct number of saved input gestures", @@ -1469,6 +1405,7 @@ class KeyGestureControllerTests { // Delete the old data and reinitialize the controller simulating a "fresh" install. tempFile.delete() + startNewInputGlobalTestSession() setupKeyGestureController() keyGestureController.setCurrentUserId(userId) testLooper.dispatchAll() @@ -1541,9 +1478,12 @@ class KeyGestureControllerTests { val handledEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> handledEvents.add(KeyGestureEvent(event)) - true } - keyGestureController.registerKeyGestureHandler(handler, 0) + keyGestureController.registerKeyGestureHandler( + intArrayOf(test.expectedKeyGestureType), + handler, + TEST_PID + ) handledEvents.clear() keyGestureController.handleTouchpadGesture(test.touchpadGestureType) @@ -1570,7 +1510,7 @@ class KeyGestureControllerTests { event.appLaunchData ) - keyGestureController.unregisterKeyGestureHandler(handler, 0) + keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) } @Test @@ -1591,9 +1531,11 @@ class KeyGestureControllerTests { testLooper.dispatchAll() // Reinitialize the gesture controller simulating a login/logout for the user. + startNewInputGlobalTestSession() setupKeyGestureController() keyGestureController.setCurrentUserId(userId) testLooper.dispatchAll() + val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null) assertEquals( "Test: $test doesn't produce correct number of saved input gestures", @@ -1627,6 +1569,7 @@ class KeyGestureControllerTests { // Delete the old data and reinitialize the controller simulating a "fresh" install. tempFile.delete() + startNewInputGlobalTestSession() setupKeyGestureController() keyGestureController.setCurrentUserId(userId) testLooper.dispatchAll() @@ -1699,13 +1642,97 @@ class KeyGestureControllerTests { Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut() } + @Test + fun testUnableToRegisterFromSamePidTwice() { + setupKeyGestureController() + + val handler1 = KeyGestureHandler { _, _ -> } + val handler2 = KeyGestureHandler { _, _ -> } + keyGestureController.registerKeyGestureHandler( + intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + handler1, + RANDOM_PID1 + ) + + assertThrows(IllegalStateException::class.java) { + keyGestureController.registerKeyGestureHandler( + intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK), + handler2, + RANDOM_PID1 + ) + } + } + + @Test + fun testUnableToRegisterSameGestureTwice() { + setupKeyGestureController() + + val handler1 = KeyGestureHandler { _, _ -> } + val handler2 = KeyGestureHandler { _, _ -> } + keyGestureController.registerKeyGestureHandler( + intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + handler1, + RANDOM_PID1 + ) + + assertThrows(IllegalArgumentException::class.java) { + keyGestureController.registerKeyGestureHandler( + intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + handler2, + RANDOM_PID2 + ) + } + } + + @Test + fun testUnableToRegisterEmptyListOfGestures() { + setupKeyGestureController() + + val handler = KeyGestureHandler { _, _ -> } + + assertThrows(IllegalArgumentException::class.java) { + keyGestureController.registerKeyGestureHandler( + intArrayOf(), + handler, + RANDOM_PID1 + ) + } + } + + @Test + fun testGestureHandlerNotCalledOnceUnregistered() { + setupKeyGestureController() + + var callbackCount = 0 + val handler1 = KeyGestureHandler { _, _ -> callbackCount++ } + keyGestureController.registerKeyGestureHandler( + intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS), + handler1, + TEST_PID + ) + sendKeys(intArrayOf(KeyEvent.KEYCODE_RECENT_APPS)) + assertEquals(1, callbackCount) + + keyGestureController.unregisterKeyGestureHandler( + handler1, + TEST_PID + ) + + // Callback should not be sent after unregister + sendKeys(intArrayOf(KeyEvent.KEYCODE_RECENT_APPS)) + assertEquals(1, callbackCount) + } + private fun testKeyGestureInternal(test: TestData) { val handledEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> handledEvents.add(KeyGestureEvent(event)) - true } - keyGestureController.registerKeyGestureHandler(handler, 0) + keyGestureController.registerKeyGestureHandler( + intArrayOf(test.expectedKeyGestureType), + handler, + TEST_PID + ) handledEvents.clear() sendKeys(test.keys) @@ -1744,16 +1771,19 @@ class KeyGestureControllerTests { ) } - keyGestureController.unregisterKeyGestureHandler(handler, 0) + keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) } - private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) { + private fun testKeyGestureNotProduced( + testName: String, + testKeys: IntArray, + possibleGestures: IntArray + ) { var handledEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> handledEvents.add(KeyGestureEvent(event)) - true } - keyGestureController.registerKeyGestureHandler(handler, 0) + keyGestureController.registerKeyGestureHandler(possibleGestures, handler, TEST_PID) handledEvents.clear() sendKeys(testKeys) @@ -1823,10 +1853,10 @@ class KeyGestureControllerTests { } inner class KeyGestureHandler( - private var handler: (event: AidlKeyGestureEvent, token: IBinder?) -> Boolean + private var handler: (event: AidlKeyGestureEvent, token: IBinder?) -> Unit ) : IKeyGestureHandler.Stub() { - override fun handleKeyGesture(event: AidlKeyGestureEvent, token: IBinder?): Boolean { - return handler(event, token) + override fun handleKeyGesture(event: AidlKeyGestureEvent, token: IBinder?) { + handler(event, token) } } } |