Loading quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +5 −0 Original line number Diff line number Diff line Loading @@ -1605,4 +1605,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext { boolean canToggleHomeAllApps() { return mControllers.uiController.canToggleHomeAllApps(); } @VisibleForTesting public TaskbarControllers getControllers() { return mControllers; } } quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +4 −2 Original line number Diff line number Diff line Loading @@ -611,7 +611,8 @@ public class TaskbarManager { } } private void addTaskbarRootViewToWindow() { @VisibleForTesting void addTaskbarRootViewToWindow() { if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) { mWindowManager.addView(mTaskbarRootLayout, mTaskbarActivityContext.getWindowLayoutParams()); Loading @@ -619,7 +620,8 @@ public class TaskbarManager { } } private void removeTaskbarRootViewFromWindow() { @VisibleForTesting void removeTaskbarRootViewFromWindow() { if (enableTaskbarNoRecreate() && mAddedWindow) { mWindowManager.removeViewImmediate(mTaskbarRootLayout); mAddedWindow = false; Loading quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java +19 −9 Original line number Diff line number Diff line Loading @@ -133,16 +133,19 @@ public final class TaskbarOverlayController { * <p> * This method should be called after an exit animation finishes, if applicable. */ @SuppressLint("WrongConstant") void maybeCloseWindow() { if (mOverlayContext != null && (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL) || mOverlayContext.getDragController().isSystemDragInProgress())) { return; } if (!canCloseWindow()) return; mProxyView.close(false); onDestroy(); } @SuppressLint("WrongConstant") private boolean canCloseWindow() { if (mOverlayContext == null) return true; if (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)) return false; return !mOverlayContext.getDragController().isSystemDragInProgress(); } /** Destroys the controller and any overlay window if present. */ public void onDestroy() { TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); Loading Loading @@ -212,10 +215,17 @@ public final class TaskbarOverlayController { @Override protected void handleClose(boolean animate) { if (mIsOpen) { if (!mIsOpen) return; mTaskbarContext.getDragLayer().removeView(this); Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate)); Optional.ofNullable(mOverlayContext).ifPresent(c -> { if (canCloseWindow()) { onDestroy(); // Window is already ready to be destroyed. } else { // Close window's AFVs before destroying it. Its drag layer will attempt to // close the proxy view again once its children are removed. closeAllOpenViews(c, animate); } }); } @Override Loading quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.launcher3.taskbar import android.app.PendingIntent import android.content.IIntentSender import android.content.Intent import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ServiceTestRule import com.android.launcher3.LauncherAppState 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.quickstep.AllAppsActionManager import com.android.quickstep.TouchInteractionService import com.android.quickstep.TouchInteractionService.TISBinder import org.junit.Assume.assumeTrue import org.junit.rules.MethodRule import org.junit.runners.model.FrameworkMethod import org.junit.runners.model.Statement /** * Manages the Taskbar lifecycle for unit tests. * * See [InjectController] for grabbing controller(s) under test with minimal boilerplate. */ class TaskbarUnitTestRule : MethodRule { private val instrumentation = InstrumentationRegistry.getInstrumentation() private val serviceTestRule = ServiceTestRule() private lateinit var taskbarManager: TaskbarManager private lateinit var target: Any val activityContext: TaskbarActivityContext get() { return taskbarManager.currentActivityContext ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.") } override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement { return object : Statement() { override fun evaluate() { this@TaskbarUnitTestRule.target = target val context = instrumentation.targetContext instrumentation.runOnMainSync { assumeTrue( LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent ) } // Check for existing Taskbar instance from Launcher process. val launcherTaskbarManager: TaskbarManager? = if (!isRunningInRobolectric) { try { val tisBinder = serviceTestRule.bindService( Intent(context, TouchInteractionService::class.java) ) as? TISBinder tisBinder?.taskbarManager } catch (_: Exception) { null } } else { null } instrumentation.runOnMainSync { taskbarManager = TaskbarManager( context, AllAppsActionManager(context, UI_HELPER_EXECUTOR) { PendingIntent(IIntentSender.Default()) }, object : TaskbarNavButtonCallbacks {}, ) } try { // Replace Launcher Taskbar window with test instance. instrumentation.runOnMainSync { launcherTaskbarManager?.removeTaskbarRootViewFromWindow() taskbarManager.onUserUnlocked() // Required to complete initialization. } injectControllers() base.evaluate() } finally { // Revert Taskbar window. instrumentation.runOnMainSync { taskbarManager.destroy() launcherTaskbarManager?.addTaskbarRootViewToWindow() } } } } } /** Simulates Taskbar recreation lifecycle. */ fun recreateTaskbar() { taskbarManager.recreateTaskbar() injectControllers() } private fun injectControllers() { val controllers = activityContext.controllers val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type } target.javaClass.fields .filter { it.isAnnotationPresent(InjectController::class.java) } .forEach { it.set( target, controllerFieldsByType[it.type]?.get(controllers) ?: throw NoSuchElementException("Failed to find controller for ${it.type}"), ) } } /** * Annotates test controller fields to inject the corresponding controllers from the current * [TaskbarControllers] instance. * * Controllers are injected during test setup and upon calling [recreateTaskbar]. * * Multiple controllers can be injected if needed. */ @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FIELD) annotation class InjectController } quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt 0 → 100644 +215 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.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 import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY import com.android.launcher3.AbstractFloatingView.hasOpenView import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.taskbar.TaskbarUnitTestRule import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.systemui.shared.system.TaskStackChangeListeners import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(LauncherMultivalentJUnit::class) @EmulatedDevices(["pixelFoldable2023"]) class TaskbarOverlayControllerTest { @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule() @InjectController lateinit var overlayController: TaskbarOverlayController private val taskbarContext: TaskbarActivityContext get() = taskbarUnitTestRule.activityContext @Test @UiThreadTest fun testRequestWindow_twice_reusesWindow() { val context1 = overlayController.requestWindow() val context2 = overlayController.requestWindow() assertThat(context1).isSameInstanceAs(context2) } @Test @UiThreadTest fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() { val context1 = overlayController.requestWindow() overlayController.hideWindow() val context2 = overlayController.requestWindow() assertThat(context1).isNotSameInstanceAs(context2) } @Test @UiThreadTest fun testRequestWindow_addsProxyView() { 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) assertThat(overlay.isOpen).isFalse() } @Test @UiThreadTest fun testHideWindow_closesOverlay() { val overlay = TestOverlayView.show(overlayController.requestWindow()) overlayController.hideWindow() assertThat(overlay.isOpen).isFalse() } @Test @UiThreadTest fun testTwoOverlays_closeOne_windowStaysOpen() { val context = overlayController.requestWindow() val overlay1 = TestOverlayView.show(context) val overlay2 = TestOverlayView.show(context) 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) overlay1.close(false) overlay2.close(false) assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @Test @UiThreadTest fun testRecreateTaskbar_closesWindow() { TestOverlayView.show(overlayController.requestWindow()) taskbarUnitTestRule.recreateTaskbar() assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @Test fun testTaskMovedToFront_closesOverlay() { lateinit var overlay: TestOverlayView getInstrumentation().runOnMainSync { overlay = 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() } } @Test fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() { lateinit var overlay: TestOverlayView getInstrumentation().runOnMainSync { overlay = TestOverlayView.show(overlayController.requestWindow()) taskbarContext.controllers.sharedState?.allAppsVisible = false } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() } } @Test fun testTaskStackChanged_allAppsOpen_closesOverlay() { lateinit var overlay: TestOverlayView getInstrumentation().runOnMainSync { overlay = TestOverlayView.show(overlayController.requestWindow()) taskbarContext.controllers.sharedState?.allAppsVisible = true } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() } } @Test @UiThreadTest fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() { val overlayContext = overlayController.requestWindow() val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP } overlayController.updateLauncherDeviceProfile( overlayController.launcherDeviceProfile .toBuilder(overlayContext) .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 } overlayController.updateLauncherDeviceProfile( overlayController.launcherDeviceProfile .toBuilder(overlayContext) .setGestureMode(false) .build() ) assertThat(overlay.isOpen).isTrue() } private class TestOverlayView private constructor( private val overlayContext: TaskbarOverlayContext, ) : AbstractFloatingView(overlayContext, null) { var type = TYPE_OPTIONS_POPUP private fun show() { mIsOpen = true overlayContext.dragLayer.addView(this) } override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean = false override fun handleClose(animate: Boolean) = overlayContext.dragLayer.removeView(this) override fun isOfType(type: Int): Boolean = (type and this.type) != 0 companion object { /** Adds a generic View to the Overlay window for testing. */ fun show(context: TaskbarOverlayContext): TestOverlayView { return TestOverlayView(context).apply { show() } } } } } Loading
quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +5 −0 Original line number Diff line number Diff line Loading @@ -1605,4 +1605,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext { boolean canToggleHomeAllApps() { return mControllers.uiController.canToggleHomeAllApps(); } @VisibleForTesting public TaskbarControllers getControllers() { return mControllers; } }
quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +4 −2 Original line number Diff line number Diff line Loading @@ -611,7 +611,8 @@ public class TaskbarManager { } } private void addTaskbarRootViewToWindow() { @VisibleForTesting void addTaskbarRootViewToWindow() { if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) { mWindowManager.addView(mTaskbarRootLayout, mTaskbarActivityContext.getWindowLayoutParams()); Loading @@ -619,7 +620,8 @@ public class TaskbarManager { } } private void removeTaskbarRootViewFromWindow() { @VisibleForTesting void removeTaskbarRootViewFromWindow() { if (enableTaskbarNoRecreate() && mAddedWindow) { mWindowManager.removeViewImmediate(mTaskbarRootLayout); mAddedWindow = false; Loading
quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java +19 −9 Original line number Diff line number Diff line Loading @@ -133,16 +133,19 @@ public final class TaskbarOverlayController { * <p> * This method should be called after an exit animation finishes, if applicable. */ @SuppressLint("WrongConstant") void maybeCloseWindow() { if (mOverlayContext != null && (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL) || mOverlayContext.getDragController().isSystemDragInProgress())) { return; } if (!canCloseWindow()) return; mProxyView.close(false); onDestroy(); } @SuppressLint("WrongConstant") private boolean canCloseWindow() { if (mOverlayContext == null) return true; if (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)) return false; return !mOverlayContext.getDragController().isSystemDragInProgress(); } /** Destroys the controller and any overlay window if present. */ public void onDestroy() { TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); Loading Loading @@ -212,10 +215,17 @@ public final class TaskbarOverlayController { @Override protected void handleClose(boolean animate) { if (mIsOpen) { if (!mIsOpen) return; mTaskbarContext.getDragLayer().removeView(this); Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate)); Optional.ofNullable(mOverlayContext).ifPresent(c -> { if (canCloseWindow()) { onDestroy(); // Window is already ready to be destroyed. } else { // Close window's AFVs before destroying it. Its drag layer will attempt to // close the proxy view again once its children are removed. closeAllOpenViews(c, animate); } }); } @Override Loading
quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.launcher3.taskbar import android.app.PendingIntent import android.content.IIntentSender import android.content.Intent import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ServiceTestRule import com.android.launcher3.LauncherAppState 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.quickstep.AllAppsActionManager import com.android.quickstep.TouchInteractionService import com.android.quickstep.TouchInteractionService.TISBinder import org.junit.Assume.assumeTrue import org.junit.rules.MethodRule import org.junit.runners.model.FrameworkMethod import org.junit.runners.model.Statement /** * Manages the Taskbar lifecycle for unit tests. * * See [InjectController] for grabbing controller(s) under test with minimal boilerplate. */ class TaskbarUnitTestRule : MethodRule { private val instrumentation = InstrumentationRegistry.getInstrumentation() private val serviceTestRule = ServiceTestRule() private lateinit var taskbarManager: TaskbarManager private lateinit var target: Any val activityContext: TaskbarActivityContext get() { return taskbarManager.currentActivityContext ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.") } override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement { return object : Statement() { override fun evaluate() { this@TaskbarUnitTestRule.target = target val context = instrumentation.targetContext instrumentation.runOnMainSync { assumeTrue( LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent ) } // Check for existing Taskbar instance from Launcher process. val launcherTaskbarManager: TaskbarManager? = if (!isRunningInRobolectric) { try { val tisBinder = serviceTestRule.bindService( Intent(context, TouchInteractionService::class.java) ) as? TISBinder tisBinder?.taskbarManager } catch (_: Exception) { null } } else { null } instrumentation.runOnMainSync { taskbarManager = TaskbarManager( context, AllAppsActionManager(context, UI_HELPER_EXECUTOR) { PendingIntent(IIntentSender.Default()) }, object : TaskbarNavButtonCallbacks {}, ) } try { // Replace Launcher Taskbar window with test instance. instrumentation.runOnMainSync { launcherTaskbarManager?.removeTaskbarRootViewFromWindow() taskbarManager.onUserUnlocked() // Required to complete initialization. } injectControllers() base.evaluate() } finally { // Revert Taskbar window. instrumentation.runOnMainSync { taskbarManager.destroy() launcherTaskbarManager?.addTaskbarRootViewToWindow() } } } } } /** Simulates Taskbar recreation lifecycle. */ fun recreateTaskbar() { taskbarManager.recreateTaskbar() injectControllers() } private fun injectControllers() { val controllers = activityContext.controllers val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type } target.javaClass.fields .filter { it.isAnnotationPresent(InjectController::class.java) } .forEach { it.set( target, controllerFieldsByType[it.type]?.get(controllers) ?: throw NoSuchElementException("Failed to find controller for ${it.type}"), ) } } /** * Annotates test controller fields to inject the corresponding controllers from the current * [TaskbarControllers] instance. * * Controllers are injected during test setup and upon calling [recreateTaskbar]. * * Multiple controllers can be injected if needed. */ @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FIELD) annotation class InjectController }
quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt 0 → 100644 +215 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.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 import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY import com.android.launcher3.AbstractFloatingView.hasOpenView import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.taskbar.TaskbarUnitTestRule import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.systemui.shared.system.TaskStackChangeListeners import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(LauncherMultivalentJUnit::class) @EmulatedDevices(["pixelFoldable2023"]) class TaskbarOverlayControllerTest { @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule() @InjectController lateinit var overlayController: TaskbarOverlayController private val taskbarContext: TaskbarActivityContext get() = taskbarUnitTestRule.activityContext @Test @UiThreadTest fun testRequestWindow_twice_reusesWindow() { val context1 = overlayController.requestWindow() val context2 = overlayController.requestWindow() assertThat(context1).isSameInstanceAs(context2) } @Test @UiThreadTest fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() { val context1 = overlayController.requestWindow() overlayController.hideWindow() val context2 = overlayController.requestWindow() assertThat(context1).isNotSameInstanceAs(context2) } @Test @UiThreadTest fun testRequestWindow_addsProxyView() { 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) assertThat(overlay.isOpen).isFalse() } @Test @UiThreadTest fun testHideWindow_closesOverlay() { val overlay = TestOverlayView.show(overlayController.requestWindow()) overlayController.hideWindow() assertThat(overlay.isOpen).isFalse() } @Test @UiThreadTest fun testTwoOverlays_closeOne_windowStaysOpen() { val context = overlayController.requestWindow() val overlay1 = TestOverlayView.show(context) val overlay2 = TestOverlayView.show(context) 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) overlay1.close(false) overlay2.close(false) assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @Test @UiThreadTest fun testRecreateTaskbar_closesWindow() { TestOverlayView.show(overlayController.requestWindow()) taskbarUnitTestRule.recreateTaskbar() assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @Test fun testTaskMovedToFront_closesOverlay() { lateinit var overlay: TestOverlayView getInstrumentation().runOnMainSync { overlay = 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() } } @Test fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() { lateinit var overlay: TestOverlayView getInstrumentation().runOnMainSync { overlay = TestOverlayView.show(overlayController.requestWindow()) taskbarContext.controllers.sharedState?.allAppsVisible = false } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() } } @Test fun testTaskStackChanged_allAppsOpen_closesOverlay() { lateinit var overlay: TestOverlayView getInstrumentation().runOnMainSync { overlay = TestOverlayView.show(overlayController.requestWindow()) taskbarContext.controllers.sharedState?.allAppsVisible = true } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() } } @Test @UiThreadTest fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() { val overlayContext = overlayController.requestWindow() val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP } overlayController.updateLauncherDeviceProfile( overlayController.launcherDeviceProfile .toBuilder(overlayContext) .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 } overlayController.updateLauncherDeviceProfile( overlayController.launcherDeviceProfile .toBuilder(overlayContext) .setGestureMode(false) .build() ) assertThat(overlay.isOpen).isTrue() } private class TestOverlayView private constructor( private val overlayContext: TaskbarOverlayContext, ) : AbstractFloatingView(overlayContext, null) { var type = TYPE_OPTIONS_POPUP private fun show() { mIsOpen = true overlayContext.dragLayer.addView(this) } override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean = false override fun handleClose(animate: Boolean) = overlayContext.dragLayer.removeView(this) override fun isOfType(type: Int): Boolean = (type and this.type) != 0 companion object { /** Adds a generic View to the Overlay window for testing. */ fun show(context: TaskbarOverlayContext): TestOverlayView { return TestOverlayView(context).apply { show() } } } } }