From 28d37181ebd542c92c8c96b6ff33242cc18f9b23 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Fri, 28 Mar 2025 17:39:10 +0000 Subject: [PATCH] Register all apps key gesture event in quickstep launcher Bug: 406452076 Test: atest NexusLauncherTests:QuickstepKeyGestureEventsHandlerTest Test: atest NexusLauncherTests:AllAppsActionManagerTest Test: atest NexusLauncherOutOfProcTests:com.google.android.apps.nexuslauncher.TaplTestsNexus Test: Verfiy manually that meta key event triggered all apps if the display focused is the default display. Flag: com.android.window.flags.enable_key_gesture_handler_for_recents Change-Id: I654cdf527670fddc0bd6eb4d8cab18a9e1206ec1 --- quickstep/AndroidManifest.xml | 3 + .../launcher3/taskbar/TaskbarManager.java | 46 +++++- .../android/quickstep/AllAppsActionManager.kt | 9 +- .../quickstep/TouchInteractionService.java | 23 +-- .../input/QuickstepKeyGestureEventsManager.kt | 79 ++++++++++ .../taskbar/rules/TaskbarUnitTestRule.kt | 7 +- .../quickstep/AllAppsActionManagerTest.kt | 26 +++- .../QuickstepKeyGestureEventsHandlerTest.kt | 143 ++++++++++++++++++ 8 files changed, 314 insertions(+), 22 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/input/QuickstepKeyGestureEventsManager.kt create mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/input/QuickstepKeyGestureEventsHandlerTest.kt diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 5ca7143624..5590e5c3d8 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -49,6 +49,9 @@ --> + + + diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index 3bfe047ad6..e19fd5764b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -34,6 +34,7 @@ import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE; import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; import static com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR; import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR; @@ -44,12 +45,16 @@ import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.ComponentCallbacks; import android.content.Context; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Trace; import android.provider.Settings; import android.util.ArraySet; @@ -116,6 +121,12 @@ public class TaskbarManager implements DisplayDecorationListener { // TODO: b/397738606 - Remove all logs with this tag after the growth framework is integrated. public static final String GROWTH_FRAMEWORK_TAG = "Growth Framework"; + /** + * An integer extra specifying the ID of the display on which the All Apps UI should be shown + * or hidden. + */ + public static final String EXTRA_KEY_ALL_APPS_ACTION_DISPLAY_ID = + "com.android.quickstep.allapps.display_id"; /** * All the configurations which do not initiate taskbar recreation. @@ -575,10 +586,19 @@ public class TaskbarManager implements DisplayDecorationListener { } /** - * Toggles All Apps for Taskbar or Launcher depending on the current state. + * Shows or hides the All Apps view in the Taskbar or Launcher, based on its current + * visibility on the System UI tracked focused display. */ public void toggleAllAppsSearch() { - TaskbarActivityContext taskbar = getTaskbarForDisplay(getFocusedDisplayId()); + toggleAllAppsSearchForDisplay(getFocusedDisplayId()); + } + + /** + * Shows or hides the All Apps view in the Taskbar or Launcher, based on its current + * visibility on the given display, with ID {@code displayId}. + */ + public void toggleAllAppsSearchForDisplay(int displayId) { + TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId); if (taskbar == null) { // Home All Apps should be toggled from this class, because the controllers are not // initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null). @@ -1713,6 +1733,28 @@ public class TaskbarManager implements DisplayDecorationListener { debugTaskbarManager(debugReason, mPrimaryDisplayId, verbose); } + /** Creates a {@link PendingIntent} for showing / hiding the all apps UI. */ + public PendingIntent createAllAppsPendingIntent() { + return new PendingIntent(new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, + IBinder allowlistToken, IIntentReceiver finishedReceiver, + String requiredPermission, Bundle options) { + MAIN_EXECUTOR.execute(() -> { + int displayId = -1; + if (options != null) { + displayId = options.getInt(EXTRA_KEY_ALL_APPS_ACTION_DISPLAY_ID, -1); + } + if (displayId == -1) { + toggleAllAppsSearch(); + } else { + toggleAllAppsSearchForDisplay(displayId); + } + }); + } + }); + } + /** * Logs verbose debug information about the TaskbarManager for a specific display. */ diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt index b807a4bfe3..2d179f95df 100644 --- a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt +++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt @@ -27,6 +27,7 @@ import android.view.accessibility.AccessibilityManager import com.android.launcher3.R import com.android.launcher3.util.SettingsCache import com.android.launcher3.util.SettingsCache.OnChangeListener +import com.android.quickstep.input.QuickstepKeyGestureEventsManager import java.util.concurrent.Executor private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE) @@ -41,6 +42,7 @@ private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPL class AllAppsActionManager( private val context: Context, private val bgExecutor: Executor, + private val quickstepKeyGestureEventsManager: QuickstepKeyGestureEventsManager, private val createAllAppsPendingIntent: () -> PendingIntent, ) { @@ -92,17 +94,22 @@ class AllAppsActionManager( val accessibilityManager = context.getSystemService(AccessibilityManager::class.java) ?: return@execute if (shouldRegisterAction) { + val allAppsPendingIntent = createAllAppsPendingIntent() accessibilityManager.registerSystemAction( RemoteAction( Icon.createWithResource(context, R.drawable.ic_apps), context.getString(R.string.all_apps_label), context.getString(R.string.all_apps_label), - createAllAppsPendingIntent(), + allAppsPendingIntent, ), GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS, ) + quickstepKeyGestureEventsManager.registerAllAppsKeyGestureEvent( + allAppsPendingIntent + ) } else { accessibilityManager.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS) + quickstepKeyGestureEventsManager.unregisterAllAppsKeyGestureEvent() } } } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 2da039c636..00270d90fb 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -41,11 +41,8 @@ import static com.android.quickstep.InputConsumerUtils.newConsumer; import static com.android.quickstep.InputConsumerUtils.tryCreateAssistantInputConsumer; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; -import android.app.PendingIntent; import android.app.Service; import android.content.Context; -import android.content.IIntentReceiver; -import android.content.IIntentSender; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Region; @@ -100,6 +97,7 @@ import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener; import com.android.quickstep.fallback.window.RecentsDisplayModel; import com.android.quickstep.fallback.window.RecentsWindowFlags; import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler; +import com.android.quickstep.input.QuickstepKeyGestureEventsManager; import com.android.quickstep.inputconsumers.BubbleBarInputConsumer; import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer; import com.android.quickstep.util.ActiveGestureLog; @@ -633,6 +631,8 @@ public class TouchInteractionService extends Service { private DisplayRepository mDisplayRepository; + private QuickstepKeyGestureEventsManager mQuickstepKeyGestureEventsHandler; + @Override public void onCreate() { super.onCreate(); @@ -647,8 +647,10 @@ public class TouchInteractionService extends Service { mRotationTouchHelperRepository = RotationTouchHelper.REPOSITORY_INSTANCE.get(this); mRecentsDisplayModel = RecentsDisplayModel.getINSTANCE().get(this); mSystemDecorationChangeObserver = SystemDecorationChangeObserver.getINSTANCE().get(this); - mAllAppsActionManager = new AllAppsActionManager( - this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent); + mQuickstepKeyGestureEventsHandler = new QuickstepKeyGestureEventsManager(this); + mAllAppsActionManager = new AllAppsActionManager(this, UI_HELPER_EXECUTOR, + mQuickstepKeyGestureEventsHandler, + () -> mTaskbarManager.createAllAppsPendingIntent()); mTrackpadsConnected = new ActiveTrackpadList(this, () -> { if (mInputMonitorCompat != null && !mTrackpadsConnected.isEmpty()) { // Don't destroy and reinitialize input monitor due to trackpad @@ -806,17 +808,6 @@ public class TouchInteractionService extends Service { } } - private PendingIntent createAllAppsPendingIntent() { - return new PendingIntent(new IIntentSender.Stub() { - @Override - public void send(int code, Intent intent, String resolvedType, - IBinder allowlistToken, IIntentReceiver finishedReceiver, - String requiredPermission, Bundle options) { - MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllAppsSearch()); - } - }); - } - @UiThread private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags, int displayId) { if (LockedUserState.get(this).isUserUnlocked()) { diff --git a/quickstep/src/com/android/quickstep/input/QuickstepKeyGestureEventsManager.kt b/quickstep/src/com/android/quickstep/input/QuickstepKeyGestureEventsManager.kt new file mode 100644 index 0000000000..d6355e9a91 --- /dev/null +++ b/quickstep/src/com/android/quickstep/input/QuickstepKeyGestureEventsManager.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.input + +import android.app.PendingIntent +import android.content.Context +import android.hardware.input.InputManager +import android.hardware.input.InputManager.KeyGestureEventHandler +import android.hardware.input.KeyGestureEvent +import android.os.Bundle +import android.os.IBinder +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.launcher3.taskbar.TaskbarManager +import com.android.window.flags.Flags + +/** + * Manages subscription and unsubscription to launcher's key gesture events, e.g. all apps and + * recents (incl. alt + tab). + */ +class QuickstepKeyGestureEventsManager(context: Context) { + private val inputManager = requireNotNull(context.getSystemService(InputManager::class.java)) + private var allAppsPendingIntent: PendingIntent? = null + @VisibleForTesting + val allAppsKeyGestureEventHandler = + object : KeyGestureEventHandler { + override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?) { + if (!Flags.enableKeyGestureHandlerForRecents()) { + return + } + if (event.keyGestureType != KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) { + Log.e(TAG, "Ignore unsupported key gesture event type: ${event.keyGestureType}") + return + } + + allAppsPendingIntent?.send( + Bundle().apply { + putInt(TaskbarManager.EXTRA_KEY_ALL_APPS_ACTION_DISPLAY_ID, event.displayId) + } + ) + } + } + + /** Registers the all apps key gesture events. */ + fun registerAllAppsKeyGestureEvent(allAppsPendingIntent: PendingIntent) { + if (Flags.enableKeyGestureHandlerForRecents()) { + this.allAppsPendingIntent = allAppsPendingIntent + inputManager.registerKeyGestureEventHandler( + listOf(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS), + allAppsKeyGestureEventHandler, + ) + } + } + + /** Unregisters the all apps key gesture events. */ + fun unregisterAllAppsKeyGestureEvent() { + if (Flags.enableKeyGestureHandlerForRecents()) { + inputManager.unregisterKeyGestureEventHandler(allAppsKeyGestureEventHandler) + } + } + + private companion object { + const val TAG = "KeyGestureEventsHandler" + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt index 19c88240d9..6ccc063459 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt @@ -35,6 +35,7 @@ import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR import com.android.launcher3.util.TestUtil import com.android.quickstep.AllAppsActionManager import com.android.quickstep.fallback.window.RecentsDisplayModel +import com.android.quickstep.input.QuickstepKeyGestureEventsManager import java.lang.reflect.Field import java.lang.reflect.ParameterizedType import java.util.Locale @@ -108,7 +109,11 @@ class TaskbarUnitTestRule( object : TaskbarManager( context, - AllAppsActionManager(context, UI_HELPER_EXECUTOR) { + AllAppsActionManager( + context, + UI_HELPER_EXECUTOR, + QuickstepKeyGestureEventsManager(context), + ) { PendingIntent(IIntentSender.Default()) }, object : TaskbarNavButtonCallbacks {}, diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt index a1bd107caf..a37655186e 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt @@ -18,6 +18,7 @@ package com.android.quickstep import android.app.PendingIntent import android.content.IIntentSender +import android.hardware.input.InputManager import android.provider.Settings import android.provider.Settings.Secure.USER_SETUP_COMPLETE import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -29,6 +30,7 @@ import com.android.launcher3.util.SandboxApplication import com.android.launcher3.util.SettingsCache import com.android.launcher3.util.SettingsCacheSandbox import com.android.launcher3.util.TestUtil +import com.android.quickstep.input.QuickstepKeyGestureEventsManager import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component @@ -39,6 +41,11 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.whenever private const val TIMEOUT = 5L private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE) @@ -49,24 +56,29 @@ class AllAppsActionManagerTest { private val bgExecutor = UI_HELPER_EXECUTOR @get:Rule val context = SandboxApplication() + private val inputManager = context.spyService(InputManager::class.java) private val settingsCacheSandbox = SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 } + private val quickstepKeyGestureEventsManager = spy(QuickstepKeyGestureEventsManager(context)) private val allAppsActionManager by lazy(LazyThreadSafetyMode.NONE) { - AllAppsActionManager(context, bgExecutor) { + AllAppsActionManager(context, bgExecutor, quickstepKeyGestureEventsManager) { callbackSemaphore.release() PendingIntent(IIntentSender.Default()) } } @Before - fun initDaggerComponent() { + fun setUp() { context.initDaggerComponent( DaggerAllAppsActionManagerTestComponent.builder() .bindSettingsCache(settingsCacheSandbox.cache) ) + + doNothing().whenever(inputManager).registerKeyGestureEventHandler(any(), any()) + doNothing().whenever(inputManager).unregisterKeyGestureEventHandler(any()) } @After fun destroyManager() = allAppsActionManager.onDestroy() @@ -74,15 +86,19 @@ class AllAppsActionManagerTest { @Test fun taskbarPresent_actionRegistered() { allAppsActionManager.isTaskbarPresent = true + TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to register. assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() assertThat(allAppsActionManager.isActionRegistered).isTrue() + verify(quickstepKeyGestureEventsManager).registerAllAppsKeyGestureEvent(any()) } @Test fun homeAndOverviewSame_actionRegistered() { allAppsActionManager.isHomeAndOverviewSame = true + TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to register. assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() assertThat(allAppsActionManager.isActionRegistered).isTrue() + verify(quickstepKeyGestureEventsManager).registerAllAppsKeyGestureEvent(any()) } @Test @@ -93,6 +109,7 @@ class AllAppsActionManagerTest { allAppsActionManager.isTaskbarPresent = false TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to unregister. assertThat(allAppsActionManager.isActionRegistered).isFalse() + verify(quickstepKeyGestureEventsManager).unregisterAllAppsKeyGestureEvent() } @Test @@ -103,6 +120,7 @@ class AllAppsActionManagerTest { TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to unregister. assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() assertThat(allAppsActionManager.isActionRegistered).isFalse() + verify(quickstepKeyGestureEventsManager).unregisterAllAppsKeyGestureEvent() } @Test @@ -136,8 +154,10 @@ class AllAppsActionManagerTest { allAppsActionManager.isTaskbarPresent = true settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1 + TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to register. assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() assertThat(allAppsActionManager.isActionRegistered).isTrue() + verify(quickstepKeyGestureEventsManager).registerAllAppsKeyGestureEvent(any()) } @Test @@ -146,8 +166,10 @@ class AllAppsActionManagerTest { allAppsActionManager.isTaskbarPresent = true allAppsActionManager.isSetupUiVisible = false + TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to register. assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() assertThat(allAppsActionManager.isActionRegistered).isTrue() + verify(quickstepKeyGestureEventsManager).registerAllAppsKeyGestureEvent(any()) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/input/QuickstepKeyGestureEventsHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/input/QuickstepKeyGestureEventsHandlerTest.kt new file mode 100644 index 0000000000..e5b517ede9 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/input/QuickstepKeyGestureEventsHandlerTest.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.input + +import android.app.PendingIntent +import android.hardware.input.InputManager +import android.hardware.input.KeyGestureEvent +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS +import android.os.Bundle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.taskbar.TaskbarManager.EXTRA_KEY_ALL_APPS_ACTION_DISPLAY_ID +import com.android.launcher3.util.SandboxApplication +import com.android.window.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.KArgumentCaptor +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class QuickstepKeyGestureEventsHandlerTest { + @get:Rule val context = SandboxApplication() + + @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) + + private val inputManager = context.spyService(InputManager::class.java) + private val keyGestureEventsManager = QuickstepKeyGestureEventsManager(context) + private val allAppsPendingIntent: PendingIntent = mock() + private val keyGestureEventsCaptor: KArgumentCaptor> = argumentCaptor() + private val bundleCaptor: KArgumentCaptor = argumentCaptor() + + @Before + fun setup() { + doNothing().whenever(inputManager).registerKeyGestureEventHandler(any(), any()) + doNothing().whenever(inputManager).unregisterKeyGestureEventHandler(any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_RECENTS) + fun registerKeyGestureEventsHandler_flagEnabled_registerWithExpectedKeyGestureEvents() { + keyGestureEventsManager.registerAllAppsKeyGestureEvent(allAppsPendingIntent) + + verify(inputManager) + .registerKeyGestureEventHandler( + keyGestureEventsCaptor.capture(), + eq(keyGestureEventsManager.allAppsKeyGestureEventHandler), + ) + assertThat(keyGestureEventsCaptor.firstValue).containsExactly(KEY_GESTURE_TYPE_ALL_APPS) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_RECENTS) + fun registerKeyGestureEventsHandler_flagDisabled_noRegister() { + keyGestureEventsManager.registerAllAppsKeyGestureEvent(allAppsPendingIntent) + + verifyNoInteractions(inputManager) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_RECENTS) + fun unregisterKeyGestureEventsHandler_flagEnabled_unregisterHandler() { + keyGestureEventsManager.unregisterAllAppsKeyGestureEvent() + + verify(inputManager) + .unregisterKeyGestureEventHandler( + eq(keyGestureEventsManager.allAppsKeyGestureEventHandler) + ) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_RECENTS) + fun unregisterKeyGestureEventsHandler_flagDisabled_noUnregister() { + keyGestureEventsManager.unregisterAllAppsKeyGestureEvent() + + verifyNoInteractions(inputManager) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_RECENTS) + fun handleEvent_flagEnabled_allApps_toggleAllAppsSearchWithDisplayId() { + keyGestureEventsManager.registerAllAppsKeyGestureEvent(allAppsPendingIntent) + + keyGestureEventsManager.allAppsKeyGestureEventHandler.handleKeyGestureEvent( + KeyGestureEvent.Builder() + .setDisplayId(TEST_DISPLAY_ID) + .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS) + .build(), + /* focusedToken= */ null, + ) + + verify(allAppsPendingIntent).send(bundleCaptor.capture()) + assertThat(bundleCaptor.firstValue.getInt(EXTRA_KEY_ALL_APPS_ACTION_DISPLAY_ID)) + .isEqualTo(TEST_DISPLAY_ID) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_RECENTS) + fun handleEvent_flagDisabled_allApps_noInteractionWithTaskbar() { + keyGestureEventsManager.registerAllAppsKeyGestureEvent(allAppsPendingIntent) + + keyGestureEventsManager.allAppsKeyGestureEventHandler.handleKeyGestureEvent( + KeyGestureEvent.Builder() + .setDisplayId(TEST_DISPLAY_ID) + .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS) + .build(), + /* focusedToken= */ null, + ) + + verifyNoInteractions(allAppsPendingIntent) + } + + private companion object { + const val TEST_DISPLAY_ID = 6789 + } +}