From 8f1a1c4b6a2b36eb32c2f31e16233537830a3f8c Mon Sep 17 00:00:00 2001 From: Brian Isganitis Date: Tue, 2 Jul 2024 19:15:59 -0400 Subject: [PATCH 1/4] Suspend Launcher taskbar while removed for tests. This approach is less destructive than completely destroying Taskbar while still ensuring it will not try to recreate itself. Test: Taskbar Unit Tests Bug: 230027385 Flag: TEST_ONLY Change-Id: Ida0f7cc0b9c5b1d53bbadc2bb9fd81689c7f3940 --- .../launcher3/taskbar/TaskbarManager.java | 22 ++++++++++++++++--- .../taskbar/rules/TaskbarUnitTestRule.kt | 4 ++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index ee79fbf201..b90e5fd33f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -115,6 +115,7 @@ public class TaskbarManager { private WindowManager mWindowManager; private FrameLayout mTaskbarRootLayout; private boolean mAddedWindow; + private boolean mIsSuspended; private final TaskbarNavButtonController mNavButtonController; private final ComponentCallbacks mComponentCallbacks; @@ -443,6 +444,8 @@ public class TaskbarManager { */ @VisibleForTesting public synchronized void recreateTaskbar() { + if (mIsSuspended) return; + Trace.beginSection("recreateTaskbar"); try { DeviceProfile dp = mUserUnlocked ? @@ -648,8 +651,22 @@ public class TaskbarManager { } } + /** + * Removes Taskbar from the window manager and prevents recreation if {@code true}. + *

+ * Suspending is for testing purposes only; avoid calling this method in production. + */ @VisibleForTesting - public void addTaskbarRootViewToWindow() { + public void setSuspended(boolean isSuspended) { + mIsSuspended = isSuspended; + if (mIsSuspended) { + removeTaskbarRootViewFromWindow(); + } else { + addTaskbarRootViewToWindow(); + } + } + + private void addTaskbarRootViewToWindow() { if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) { mWindowManager.addView(mTaskbarRootLayout, mTaskbarActivityContext.getWindowLayoutParams()); @@ -657,8 +674,7 @@ public class TaskbarManager { } } - @VisibleForTesting - public void removeTaskbarRootViewFromWindow() { + private void removeTaskbarRootViewFromWindow() { if (enableTaskbarNoRecreate() && mAddedWindow) { mWindowManager.removeViewImmediate(mTaskbarRootLayout); mAddedWindow = false; 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 12f946e730..39ac720333 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 @@ -119,7 +119,7 @@ class TaskbarUnitTestRule( try { // Replace Launcher Taskbar window with test instance. instrumentation.runOnMainSync { - launcherTaskbarManager?.removeTaskbarRootViewFromWindow() + launcherTaskbarManager?.setSuspended(true) taskbarManager.onUserUnlocked() // Required to complete initialization. } @@ -129,7 +129,7 @@ class TaskbarUnitTestRule( // Revert Taskbar window. instrumentation.runOnMainSync { taskbarManager.destroy() - launcherTaskbarManager?.addTaskbarRootViewToWindow() + launcherTaskbarManager?.setSuspended(false) } } } From 8e5de6577ba0c1541dadbb6e0d6096f1854e0fc6 Mon Sep 17 00:00:00 2001 From: Brian Isganitis Date: Tue, 2 Jul 2024 16:51:21 -0400 Subject: [PATCH 2/4] Don't use UiThreadTest for Taskbar Unit tests. This annotation also runs the rule on the main thread, which can lead to deadlocks with other threads (e.g. loading the model synchronously). Change-Id: Ib276e9dc322f6f65bd32658e774d6076efb94f2e Flag: TEST_ONLY Test: Taskbar Unit Tests Bug: 230027385 --- .../allapps/TaskbarAllAppsControllerTest.kt | 116 ++++++++------- .../overlay/TaskbarOverlayControllerTest.kt | 136 +++++++++--------- .../taskbar/rules/TaskbarUnitTestRule.kt | 16 +-- 3 files changed, 138 insertions(+), 130 deletions(-) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt index 9ecd9353bb..2f0b44604e 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt @@ -20,7 +20,6 @@ import android.animation.AnimatorTestRule import android.content.ComponentName import android.content.Intent import android.os.Process -import androidx.test.annotation.UiThreadTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.BubbleTextView import com.android.launcher3.appprediction.PredictionRowView @@ -34,6 +33,7 @@ import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.launcher3.util.PackageUserKey +import com.android.launcher3.util.TestUtil import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -55,17 +55,17 @@ class TaskbarAllAppsControllerTest { @InjectController lateinit var overlayController: TaskbarOverlayController @Test - @UiThreadTest fun testToggle_once_showsAllApps() { - allAppsController.toggle() + getInstrumentation().runOnMainSync { allAppsController.toggle() } assertThat(allAppsController.isOpen).isTrue() } @Test - @UiThreadTest fun testToggle_twice_closesAllApps() { - allAppsController.toggle() - allAppsController.toggle() + getInstrumentation().runOnMainSync { + allAppsController.toggle() + allAppsController.toggle() + } assertThat(allAppsController.isOpen).isFalse() } @@ -77,54 +77,62 @@ class TaskbarAllAppsControllerTest { } @Test - @UiThreadTest fun testSetApps_beforeOpened_cachesInfo() { - allAppsController.setApps(TEST_APPS, 0, emptyMap()) - allAppsController.toggle() + val overlayContext = + TestUtil.getOnUiThread { + allAppsController.setApps(TEST_APPS, 0, emptyMap()) + allAppsController.toggle() + overlayController.requestWindow() + } - val overlayContext = overlayController.requestWindow() assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS) } @Test - @UiThreadTest fun testSetApps_afterOpened_updatesStore() { - allAppsController.toggle() - allAppsController.setApps(TEST_APPS, 0, emptyMap()) + val overlayContext = + TestUtil.getOnUiThread { + allAppsController.toggle() + allAppsController.setApps(TEST_APPS, 0, emptyMap()) + overlayController.requestWindow() + } - val overlayContext = overlayController.requestWindow() assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS) } @Test - @UiThreadTest fun testSetPredictedApps_beforeOpened_cachesInfo() { - allAppsController.setPredictedApps(TEST_PREDICTED_APPS) - allAppsController.toggle() - val predictedApps = - overlayController - .requestWindow() - .appsView - .floatingHeaderView - .findFixedRowByType(PredictionRowView::class.java) - .predictedApps + TestUtil.getOnUiThread { + allAppsController.setPredictedApps(TEST_PREDICTED_APPS) + allAppsController.toggle() + + overlayController + .requestWindow() + .appsView + .floatingHeaderView + .findFixedRowByType(PredictionRowView::class.java) + .predictedApps + } + assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS) } @Test - @UiThreadTest fun testSetPredictedApps_afterOpened_cachesInfo() { - allAppsController.toggle() - allAppsController.setPredictedApps(TEST_PREDICTED_APPS) - val predictedApps = - overlayController - .requestWindow() - .appsView - .floatingHeaderView - .findFixedRowByType(PredictionRowView::class.java) - .predictedApps + TestUtil.getOnUiThread { + allAppsController.toggle() + allAppsController.setPredictedApps(TEST_PREDICTED_APPS) + + overlayController + .requestWindow() + .appsView + .floatingHeaderView + .findFixedRowByType(PredictionRowView::class.java) + .predictedApps + } + assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS) } @@ -140,36 +148,38 @@ class TaskbarAllAppsControllerTest { } // Ensure the recycler view fully inflates before trying to grab an icon. - getInstrumentation().runOnMainSync { - val btv = + val btv = + TestUtil.getOnUiThread { overlayController .requestWindow() .appsView .activeRecyclerView .findViewHolderForAdapterPosition(0) ?.itemView as? BubbleTextView - assertThat(btv?.hasDot()).isTrue() - } + } + assertThat(btv?.hasDot()).isTrue() } @Test - @UiThreadTest fun testUpdateNotificationDots_predictedApp_hasDot() { - allAppsController.setPredictedApps(TEST_PREDICTED_APPS) - allAppsController.toggle() + getInstrumentation().runOnMainSync { + allAppsController.setPredictedApps(TEST_PREDICTED_APPS) + allAppsController.toggle() + taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted( + PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]), + NotificationKeyData("key"), + ) + } - taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted( - PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]), - NotificationKeyData("key"), - ) - - val predictionRowView = - overlayController - .requestWindow() - .appsView - .floatingHeaderView - .findFixedRowByType(PredictionRowView::class.java) - val btv = predictionRowView.getChildAt(0) as BubbleTextView + val btv = + TestUtil.getOnUiThread { + overlayController + .requestWindow() + .appsView + .floatingHeaderView + .findFixedRowByType(PredictionRowView::class.java) + .getChildAt(0) as BubbleTextView + } assertThat(btv.hasDot()).isTrue() } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt index fae5562d55..f946d4d482 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt @@ -18,7 +18,6 @@ package com.android.launcher3.taskbar.overlay import android.app.ActivityManager.RunningTaskInfo import android.view.MotionEvent -import androidx.test.annotation.UiThreadTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.AbstractFloatingView import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP @@ -31,7 +30,7 @@ import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices -import com.android.launcher3.views.BaseDragLayer +import com.android.launcher3.util.TestUtil.getOnUiThread import com.android.systemui.shared.system.TaskStackChangeListeners import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -54,74 +53,69 @@ class TaskbarOverlayControllerTest { get() = taskbarUnitTestRule.activityContext @Test - @UiThreadTest fun testRequestWindow_twice_reusesWindow() { - val context1 = overlayController.requestWindow() - val context2 = overlayController.requestWindow() + val (context1, context2) = + getOnUiThread { + Pair(overlayController.requestWindow(), overlayController.requestWindow()) + } assertThat(context1).isSameInstanceAs(context2) } @Test - @UiThreadTest fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() { - val context1 = overlayController.requestWindow() - overlayController.hideWindow() + val context1 = getOnUiThread { overlayController.requestWindow() } + getInstrumentation().runOnMainSync { overlayController.hideWindow() } - val context2 = overlayController.requestWindow() + val context2 = getOnUiThread { overlayController.requestWindow() } assertThat(context1).isNotSameInstanceAs(context2) } @Test - @UiThreadTest fun testRequestWindow_afterHidingOverlay_createsNewWindow() { - val context1 = overlayController.requestWindow() - TestOverlayView.show(context1) - overlayController.hideWindow() + val context1 = getOnUiThread { overlayController.requestWindow() } + getInstrumentation().runOnMainSync { + TestOverlayView.show(context1) + overlayController.hideWindow() + } - val context2 = overlayController.requestWindow() + val context2 = getOnUiThread { overlayController.requestWindow() } assertThat(context1).isNotSameInstanceAs(context2) } @Test - @UiThreadTest fun testRequestWindow_addsProxyView() { - TestOverlayView.show(overlayController.requestWindow()) + getInstrumentation().runOnMainSync { + TestOverlayView.show(overlayController.requestWindow()) + } assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue() } @Test - @UiThreadTest fun testRequestWindow_closeProxyView_closesOverlay() { - val overlay = TestOverlayView.show(overlayController.requestWindow()) - AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY) + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } + getInstrumentation().runOnMainSync { + AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY) + } assertThat(overlay.isOpen).isFalse() } @Test fun testRequestWindow_attachesDragLayer() { - lateinit var dragLayer: BaseDragLayer<*> - getInstrumentation().runOnMainSync { - dragLayer = overlayController.requestWindow().dragLayer - } - + val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer } // Allow drag layer to attach before checking. getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() } } @Test - @UiThreadTest fun testHideWindow_closesOverlay() { - val overlay = TestOverlayView.show(overlayController.requestWindow()) - overlayController.hideWindow() + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } + getInstrumentation().runOnMainSync { overlayController.hideWindow() } assertThat(overlay.isOpen).isFalse() } @Test fun testHideWindow_detachesDragLayer() { - lateinit var dragLayer: BaseDragLayer<*> - getInstrumentation().runOnMainSync { - dragLayer = overlayController.requestWindow().dragLayer - } + val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer } // Wait for drag layer to be attached to window before hiding. getInstrumentation().runOnMainSync { @@ -131,26 +125,30 @@ class TaskbarOverlayControllerTest { } @Test - @UiThreadTest fun testTwoOverlays_closeOne_windowStaysOpen() { - val context = overlayController.requestWindow() - val overlay1 = TestOverlayView.show(context) - val overlay2 = TestOverlayView.show(context) + val (overlay1, overlay2) = + getOnUiThread { + val context = overlayController.requestWindow() + Pair(TestOverlayView.show(context), TestOverlayView.show(context)) + } - overlay1.close(false) + getInstrumentation().runOnMainSync { overlay1.close(false) } assertThat(overlay2.isOpen).isTrue() assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue() } @Test - @UiThreadTest fun testTwoOverlays_closeAll_closesWindow() { - val context = overlayController.requestWindow() - val overlay1 = TestOverlayView.show(context) - val overlay2 = TestOverlayView.show(context) + val (overlay1, overlay2) = + getOnUiThread { + val context = overlayController.requestWindow() + Pair(TestOverlayView.show(context), TestOverlayView.show(context)) + } - overlay1.close(false) - overlay2.close(false) + getInstrumentation().runOnMainSync { + overlay1.close(false) + overlay2.close(false) + } assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @@ -165,11 +163,7 @@ class TaskbarOverlayControllerTest { @Test fun testTaskMovedToFront_closesOverlay() { - lateinit var overlay: TestOverlayView - getInstrumentation().runOnMainSync { - overlay = TestOverlayView.show(overlayController.requestWindow()) - } - + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo()) // Make sure TaskStackChangeListeners' Handler posts the callback before checking state. getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() } @@ -177,9 +171,8 @@ class TaskbarOverlayControllerTest { @Test fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() { - lateinit var overlay: TestOverlayView + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } getInstrumentation().runOnMainSync { - overlay = TestOverlayView.show(overlayController.requestWindow()) taskbarContext.controllers.sharedState?.allAppsVisible = false } @@ -189,9 +182,8 @@ class TaskbarOverlayControllerTest { @Test fun testTaskStackChanged_allAppsOpen_closesOverlay() { - lateinit var overlay: TestOverlayView + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } getInstrumentation().runOnMainSync { - overlay = TestOverlayView.show(overlayController.requestWindow()) taskbarContext.controllers.sharedState?.allAppsVisible = true } @@ -200,33 +192,39 @@ class TaskbarOverlayControllerTest { } @Test - @UiThreadTest fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() { - val overlayContext = overlayController.requestWindow() - val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP } + val context = getOnUiThread { overlayController.requestWindow() } + val overlay = getOnUiThread { + TestOverlayView.show(context).apply { type = TYPE_OPTIONS_POPUP } + } - overlayController.updateLauncherDeviceProfile( - overlayController.launcherDeviceProfile - .toBuilder(overlayContext) - .setGestureMode(false) - .build() - ) + getInstrumentation().runOnMainSync { + overlayController.updateLauncherDeviceProfile( + overlayController.launcherDeviceProfile + .toBuilder(context) + .setGestureMode(false) + .build() + ) + } assertThat(overlay.isOpen).isFalse() } @Test - @UiThreadTest fun testUpdateLauncherDeviceProfile_overlayRebindSafe_overlayStaysOpen() { - val overlayContext = overlayController.requestWindow() - val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_TASKBAR_ALL_APPS } + val context = getOnUiThread { overlayController.requestWindow() } + val overlay = getOnUiThread { + TestOverlayView.show(context).apply { type = TYPE_TASKBAR_ALL_APPS } + } - overlayController.updateLauncherDeviceProfile( - overlayController.launcherDeviceProfile - .toBuilder(overlayContext) - .setGestureMode(false) - .build() - ) + getInstrumentation().runOnMainSync { + overlayController.updateLauncherDeviceProfile( + overlayController.launcherDeviceProfile + .toBuilder(context) + .setGestureMode(false) + .build() + ) + } assertThat(overlay.isOpen).isTrue() } 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 39ac720333..663a807a51 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 @@ -28,6 +28,7 @@ import com.android.launcher3.taskbar.TaskbarManager import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric +import com.android.launcher3.util.TestUtil import com.android.quickstep.AllAppsActionManager import com.android.quickstep.TouchInteractionService import com.android.quickstep.TouchInteractionService.TISBinder @@ -48,12 +49,11 @@ import org.junit.runners.model.Statement * that code that is executed on the main thread in production should also happen on that thread * when tested. * - * `@UiThreadTest` is a simple way to run an entire test body on the main thread. But if a test - * executes code that appends message(s) to the main thread's `MessageQueue`, the annotation will - * prevent those messages from being processed until after the test body finishes. + * `@UiThreadTest` is incompatible with this rule. The annotation causes this rule to run on the + * main thread, but it needs to be run on the test thread for it to work properly. Instead, only run + * code that requires the main thread using something like [Instrumentation.runOnMainSync] or + * [TestUtil.getOnUiThread]. * - * To test pending messages, instead use something like [Instrumentation.runOnMainSync] to perform - * only sections of the test body on the main thread synchronously: * ``` * @Test * fun example() { @@ -105,8 +105,8 @@ class TaskbarUnitTestRule( null } - instrumentation.runOnMainSync { - taskbarManager = + taskbarManager = + TestUtil.getOnUiThread { TaskbarManager( context, AllAppsActionManager(context, UI_HELPER_EXECUTOR) { @@ -114,7 +114,7 @@ class TaskbarUnitTestRule( }, object : TaskbarNavButtonCallbacks {}, ) - } + } try { // Replace Launcher Taskbar window with test instance. From 160ed1d31a8587c7183ae0c67caf27669a0cf0e0 Mon Sep 17 00:00:00 2001 From: Brian Isganitis Date: Wed, 26 Jun 2024 17:24:49 -0400 Subject: [PATCH 3/4] Add annotations for manipulating secure settings. By default, tests will run with user setup complete and kids mode disabled. Test: TaskbarUnitTestRuleTest Flag: TEST_ONLY Bug: 230027385 Change-Id: If6c74b3b2c07aa0eac5b6bda933b565351d65188 --- .../taskbar/TaskbarActivityContext.java | 5 +- .../taskbar/rules/TaskbarUnitTestRule.kt | 62 +++++++++++++++++++ .../taskbar/rules/TaskbarUnitTestRuleTest.kt | 37 ++++++++++- 3 files changed, 101 insertions(+), 3 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 21a826870e..27328cb078 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -43,6 +43,8 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; import static com.android.wm.shell.Flags.enableTinyTaskbar; +import static java.lang.invoke.MethodHandles.Lookup.PROTECTED; + import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.ActivityOptions; @@ -1515,7 +1517,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return mIsNavBarKidsMode && isThreeButtonNav(); } - protected boolean isNavBarForceVisible() { + @VisibleForTesting(otherwise = PROTECTED) + public boolean isNavBarForceVisible() { return mIsNavBarForceVisible; } 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 663a807a51..8a64949f7d 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 @@ -20,19 +20,25 @@ import android.app.Instrumentation import android.app.PendingIntent import android.content.IIntentSender import android.content.Intent +import android.provider.Settings +import android.provider.Settings.Secure.NAV_BAR_KIDS_MODE +import android.provider.Settings.Secure.USER_SETUP_COMPLETE import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ServiceTestRule import com.android.launcher3.LauncherAppState import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.taskbar.TaskbarManager import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric +import com.android.launcher3.util.ModelTestExtensions.loadModelSync import com.android.launcher3.util.TestUtil import com.android.quickstep.AllAppsActionManager import com.android.quickstep.TouchInteractionService import com.android.quickstep.TouchInteractionService.TISBinder import org.junit.Assume.assumeTrue +import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement @@ -71,6 +77,10 @@ class TaskbarUnitTestRule( private val instrumentation = InstrumentationRegistry.getInstrumentation() private val serviceTestRule = ServiceTestRule() + private val userSetupCompleteRule = TaskbarSecureSettingRule(USER_SETUP_COMPLETE) + private val kidsModeRule = TaskbarSecureSettingRule(NAV_BAR_KIDS_MODE) + private val settingRules = RuleChain.outerRule(userSetupCompleteRule).around(kidsModeRule) + private lateinit var taskbarManager: TaskbarManager val activityContext: TaskbarActivityContext @@ -80,15 +90,34 @@ class TaskbarUnitTestRule( } override fun apply(base: Statement, description: Description): Statement { + return settingRules.apply(createStatement(base, description), description) + } + + private fun createStatement(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { + // Only run test when Taskbar is enabled. instrumentation.runOnMainSync { assumeTrue( LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent ) } + // Process secure setting annotations. + instrumentation.runOnMainSync { + userSetupCompleteRule.putInt( + if (description.getAnnotation(UserSetupMode::class.java) != null) { + 0 + } else { + 1 + } + ) + kidsModeRule.putInt( + if (description.getAnnotation(NavBarKidsMode::class.java) != null) 1 else 0 + ) + } + // Check for existing Taskbar instance from Launcher process. val launcherTaskbarManager: TaskbarManager? = if (!isRunningInRobolectric) { @@ -117,6 +146,8 @@ class TaskbarUnitTestRule( } try { + LauncherAppState.getInstance(context).model.loadModelSync() + // Replace Launcher Taskbar window with test instance. instrumentation.runOnMainSync { launcherTaskbarManager?.setSuspended(true) @@ -167,4 +198,35 @@ class TaskbarUnitTestRule( @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FIELD) annotation class InjectController + + /** Overrides [USER_SETUP_COMPLETE] to be `false` for tests. */ + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) + annotation class UserSetupMode + + /** Overrides [NAV_BAR_KIDS_MODE] to be `true` for tests. */ + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) + annotation class NavBarKidsMode + + /** Rule for Taskbar integer-based secure settings. */ + private inner class TaskbarSecureSettingRule(private val settingName: String) : TestRule { + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + val originalValue = + Settings.Secure.getInt(context.contentResolver, settingName, /* def= */ 0) + try { + base.evaluate() + } finally { + instrumentation.runOnMainSync { putInt(originalValue) } + } + } + } + } + + /** Puts [value] into secure settings under [settingName]. */ + fun putInt(value: Int) = Settings.Secure.putInt(context.contentResolver, settingName, value) + } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt index 8262e0f23b..234e4991c6 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt @@ -22,6 +22,8 @@ import com.android.launcher3.taskbar.TaskbarKeyguardController import com.android.launcher3.taskbar.TaskbarManager import com.android.launcher3.taskbar.TaskbarStashController import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.NavBarKidsMode +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.google.common.truth.Truth.assertThat @@ -125,9 +127,40 @@ class TaskbarUnitTestRuleTest { } } - /** Executes [runTest] after the [testRule] setup phase completes. */ + @Test + fun testUserSetupMode_default_isComplete() { + onSetup { assertThat(activityContext.isUserSetupComplete).isTrue() } + } + + @Test + fun testUserSetupMode_withAnnotation_isIncomplete() { + @UserSetupMode class Mode + onSetup(description = Description.createSuiteDescription(Mode::class.java)) { + assertThat(activityContext.isUserSetupComplete).isFalse() + } + } + + @Test + fun testNavBarKidsMode_default_navBarNotForcedVisible() { + onSetup { assertThat(activityContext.isNavBarForceVisible).isFalse() } + } + + @Test + fun testNavBarKidsMode_withAnnotation_navBarForcedVisible() { + @NavBarKidsMode class Mode + onSetup(description = Description.createSuiteDescription(Mode::class.java)) { + assertThat(activityContext.isNavBarForceVisible).isTrue() + } + } + + /** + * Executes [runTest] after the [testRule] setup phase completes. + * + * A [description] can also be provided to mimic annotating a test or test class. + */ private fun onSetup( testRule: TaskbarUnitTestRule = TaskbarUnitTestRule(this, context), + description: Description = DESCRIPTION, runTest: TaskbarUnitTestRule.() -> Unit, ) { testRule @@ -135,7 +168,7 @@ class TaskbarUnitTestRuleTest { object : Statement() { override fun evaluate() = runTest(testRule) }, - DESCRIPTION, + description, ) .evaluate() } From bfa3b7c80ed2e337285069a0e2c08b8c9ce6d8c8 Mon Sep 17 00:00:00 2001 From: Brian Isganitis Date: Thu, 27 Jun 2024 19:23:50 -0400 Subject: [PATCH 4/4] Override DisplayController on main thread. Bug: 230027385 Flag: TEST_ONLY Test: TaskbarModeRuleTest Change-Id: I6e7c349d50c8372ed0d4e1e8d5bfe7f108b2c2e2 --- .../taskbar/rules/TaskbarModeRule.kt | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt index 6638736ff6..c48947e078 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar.rules +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode import com.android.launcher3.util.DisplayController @@ -59,23 +60,25 @@ class TaskbarModeRule(private val context: TaskbarWindowSandboxContext) : TestRu override fun evaluate() { val mode = taskbarMode.mode - context.applicationContext.putObject( - DisplayController.INSTANCE, - object : DisplayController(context) { - override fun getInfo(): Info { - return spy(super.getInfo()) { - on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT) - on { isPinnedTaskbar } doReturn (mode == Mode.PINNED) - on { navigationMode } doReturn - when (mode) { - Mode.TRANSIENT, - Mode.PINNED -> NavigationMode.NO_BUTTON - Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS - } + getInstrumentation().runOnMainSync { + context.applicationContext.putObject( + DisplayController.INSTANCE, + object : DisplayController(context) { + override fun getInfo(): Info { + return spy(super.getInfo()) { + on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT) + on { isPinnedTaskbar } doReturn (mode == Mode.PINNED) + on { navigationMode } doReturn + when (mode) { + Mode.TRANSIENT, + Mode.PINNED -> NavigationMode.NO_BUTTON + Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS + } + } } - } - }, - ) + }, + ) + } base.evaluate() }