Implement e2e test for desktop windowing
Code changes:
- TaskView, GroupedTaskView and DesktopTaskView now uses different resId, so it can be differentiated by TAPL
- Extracted result handling of icon loading, so we can override in DesktopTaskView
- DesktopTaskView now load icons, so titleDescription can be loaded. When icons are loaded, contentDescription are applied to respective snapshotViews; icon unloading is ignored
- Track launchDesktopFromRecents and composeRecentsLaunchAnimator in TAPL events so we can test if the TaskView launch path is correct
Test changes:
- Added TaplTestsOverviewDesktop that move 2 TestActivities into Desktop, and launch the DesktopTaskView as static and live tile
- TaplTestsOverviewDesktop is limited to Tangor/cf_tablet only, and added LimitDeviceRule to AbstractLauncherUiTest to enable @AllowedDevices and @IgnoreLimit
TAPL changes (2 APIs added/modified):
- Changed TaskView matcher to use id/task_view_* to match all TaskView types
- When Overview is launcehd from Background, mark the currentTask after the launch as liveTile. When an OverviewTask has the same accessibility node as the liveTile, it'll expect different event when launching.
- [API change] BaseOverview.getTestActivityTask can now matches mutiple test activiites, useful for matching GroupedTaskView and DesktopTaskView; Fix a bug that getTestActivityTask wrongly use `getParent()` which is RecentsView to match activityName.
- In OverviewTask.open, we'll expect different events based on TaskView types and whether it's a live tile. Launching DesktopTaskView will in additional verify Desktop header is present on screen.
- [API change] In OverviewTaskMenu, support tapping Desktop menu and verify Desktop header is present on screen.
- Removed unused OverviewTaskMenuItem
Fix: 320313527
Test: TaplTestsOverviewDesktop
Flag: com.android.window.flags.enable_desktop_windowing_mode
Change-Id: I89261c787364901320f3acb18f01ddad5f62d17c
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index ac1a50a..34193d3 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:id="@+id/task"
+ android:id="@+id/task_view_single"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 64aa7e1..8c7090e 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:id="@+id/task"
+ android:id="@+id/task_view_desktop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="true"
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index da2b29f..cb4b98f 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -24,7 +24,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:id="@+id/task"
+ android:id="@+id/task_view_grouped"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 9ce2277..384945b 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -30,6 +30,8 @@
import androidx.core.view.updateLayoutParams
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
+import com.android.launcher3.testing.TestLogging
+import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
@@ -213,7 +215,15 @@
}
override fun needsUpdate(dataChange: Int, flag: Int) =
- if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false
+ if (flag == FLAG_UPDATE_CORNER_RADIUS) false else super.needsUpdate(dataChange, flag)
+
+ override fun onIconLoaded(taskContainer: TaskContainer) {
+ // Update contentDescription of snapshotView only, individual task icon is unused.
+ taskContainer.snapshotView.contentDescription = taskContainer.task.titleDescription
+ }
+
+ // Ignoring [onIconUnloaded] as all tasks shares the same Desktop icon
+ override fun onIconUnloaded(taskContainer: TaskContainer) {}
// thumbnailView is laid out differently and is handled in onMeasure
override fun updateThumbnailSize() {}
@@ -228,6 +238,11 @@
override fun launchTaskAnimated(): RunnableList? {
val recentsView = recentsView ?: return null
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN,
+ "launchDesktopFromRecents",
+ taskIds.contentToString()
+ )
val endCallback = RunnableList()
val desktopController = recentsView.desktopRecentsController
checkNotNull(desktopController) { "recentsController is null" }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index e189d14..99574a6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -868,18 +868,11 @@
it.task.icon = icon
it.task.titleDescription = contentDescription
it.task.title = title
- setIcon(it.iconView, icon)
- if (enableOverviewIconMenu()) {
- setText(it.iconView, title)
- }
- it.digitalWellBeingToast?.initialize(it.task)
+ onIconLoaded(it)
}
?.also { request -> pendingIconLoadRequests.add(request) }
} else {
- setIcon(it.iconView, null)
- if (enableOverviewIconMenu()) {
- setText(it.iconView, null)
- }
+ onIconUnloaded(it)
}
}
}
@@ -898,6 +891,21 @@
pendingIconLoadRequests.clear()
}
+ protected open fun onIconLoaded(taskContainer: TaskContainer) {
+ setIcon(taskContainer.iconView, taskContainer.task.icon)
+ if (enableOverviewIconMenu()) {
+ setText(taskContainer.iconView, taskContainer.task.title)
+ }
+ taskContainer.digitalWellBeingToast?.initialize(taskContainer.task)
+ }
+
+ protected open fun onIconUnloaded(taskContainer: TaskContainer) {
+ setIcon(taskContainer.iconView, null)
+ if (enableOverviewIconMenu()) {
+ setText(taskContainer.iconView, null)
+ }
+ }
+
protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) {
with(iconView) {
if (icon != null) {
@@ -1119,6 +1127,11 @@
isClickableAsLiveTile = true
return runnableList
}
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN,
+ "composeRecentsLaunchAnimator",
+ taskIds.contentToString()
+ )
val runnableList = RunnableList()
with(AnimatorSet()) {
TaskViewUtils.composeRecentsLaunchAnimator(
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
new file mode 100644
index 0000000..694a382
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.quickstep
+
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.IgnoreLimit
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.ui.AbstractLauncherUiTest
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape
+import com.android.launcher3.uioverrides.QuickstepLauncher
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+
+/** Test Desktop windowing in Overview. */
+@AllowedDevices(allowed = [DeviceProduct.CF_TABLET, DeviceProduct.TANGORPRO])
+@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
+class TaplTestsOverviewDesktop : AbstractLauncherUiTest<QuickstepLauncher?>() {
+ @Before
+ fun setup() {
+ val overview = mLauncher.goHome().switchToOverview()
+ if (overview.hasTasks()) {
+ overview.dismissAllTasks()
+ }
+ startTestAppsWithCheck()
+ mLauncher.goHome()
+ }
+
+ @Test
+ @PortraitLandscape
+ fun enterDesktopViaOverviewMenu() {
+ // Move last launched TEST_ACTIVITY_2 into Desktop
+ mLauncher.workspace
+ .switchToOverview()
+ .getTestActivityTask(TEST_ACTIVITY_2)
+ .tapMenu()
+ .tapDesktopMenuItem()
+ assertTestAppLaunched(TEST_ACTIVITY_2)
+
+ // Scroll back to TEST_ACTIVITY_1, then move it into Desktop
+ mLauncher
+ .goHome()
+ .switchToOverview()
+ .apply { flingForward() }
+ .getTestActivityTask(TEST_ACTIVITY_1)
+ .tapMenu()
+ .tapDesktopMenuItem()
+ TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
+
+ // Launch static DesktopTaskView
+ val desktop =
+ mLauncher.goHome().switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open()
+ TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
+
+ // Launch live-tile DesktopTaskView
+ desktop.switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open()
+ TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
+ }
+
+ private fun startTestAppsWithCheck() {
+ TEST_ACTIVITIES.forEach {
+ startTestActivity(it)
+ executeOnLauncher { launcher ->
+ assertWithMessage(
+ "Launcher activity is the top activity; expecting TestActivity$it"
+ )
+ .that(isInLaunchedApp(launcher))
+ .isTrue()
+ }
+ }
+ }
+
+ private fun assertTestAppLaunched(index: Int) {
+ assertWithMessage("TestActivity$index not opened in Desktop")
+ .that(
+ mDevice.wait(
+ Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity$index")),
+ DEFAULT_UI_TIMEOUT
+ )
+ )
+ .isTrue()
+ }
+
+ companion object {
+ const val TEST_ACTIVITY_1 = 2
+ const val TEST_ACTIVITY_2 = 3
+ val TEST_ACTIVITIES = listOf(TEST_ACTIVITY_1, TEST_ACTIVITY_2)
+ }
+}