diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index e1c9384f7081b..0fb43e263d054 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -91,6 +91,26 @@ fun LayersAssertion.navBarLayerIsAlwaysVisible( } } +@JvmOverloads +fun LayersAssertion.appPairsDividerIsVisible( + bugId: Int = 0, + enabled: Boolean = bugId == 0 +) { + end("appPairsDividerIsVisible", bugId, enabled) { + this.showsLayer(FlickerTestBase.APP_PAIRS_DIVIDER) + } +} + +@JvmOverloads +fun LayersAssertion.appPairsDividerIsInvisible( + bugId: Int = 0, + enabled: Boolean = bugId == 0 +) { + end("appPairsDividerIsInVisible", bugId, enabled) { + this.hasNotLayer(FlickerTestBase.APP_PAIRS_DIVIDER) + } +} + @JvmOverloads fun LayersAssertion.dockedStackDividerIsVisible( bugId: Int = 0, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt index 2e6037d148a8c..54b8fdc83a1f5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt @@ -130,6 +130,7 @@ abstract class FlickerTestBase { const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar" const val STATUS_BAR_WINDOW_TITLE = "StatusBar" const val DOCKED_STACK_DIVIDER = "DockedStackDivider" + const val APP_PAIRS_DIVIDER = "AppPairDivider" const val IMAGE_WALLPAPER = "ImageWallpaper" } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt new file mode 100644 index 0000000000000..c33dbbc3f53a4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2020 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.wm.shell.flicker.apppairs + +import android.os.SystemClock +import android.util.Log +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.compatibility.common.util.SystemUtil +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.dsl.runWithFlicker +import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.wm.shell.flicker.helpers.AppPairsHelper +import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.TEST_REPETITIONS +import com.android.wm.shell.flicker.appPairsDividerIsInvisible +import com.android.wm.shell.flicker.appPairsDividerIsVisible +import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible +import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible +import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible +import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized +import java.io.IOException + +/** + * Test AppPairs launch. + * To run this test: `atest WMShellFlickerTests:AppPairsTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class AppPairsTest( + rotationName: String, + rotation: Int +) : AppPairsTestBase(rotationName, rotation) { + private val appPairsSetup: FlickerBuilder + get() = FlickerBuilder(instrumentation).apply { + val testLaunchActivity = "launch_appPairs_primary_secondary_activities" + withTestName { + testLaunchActivity + } + setup { + eachRun { + uiDevice.wakeUpAndGoToHomeScreen() + primaryApp.open() + uiDevice.pressHome() + secondaryApp.open() + uiDevice.pressHome() + updateTaskId() + } + } + teardown { + eachRun { + executeShellCommand(composePairsCommand( + primaryTaskId, secondaryTaskId, false /* pair */)) + primaryApp.forceStop() + secondaryApp.forceStop() + } + } + assertions { + layersTrace { + navBarLayerIsAlwaysVisible() + statusBarLayerIsAlwaysVisible() + } + windowManagerTrace { + navBarWindowIsAlwaysVisible() + statusBarWindowIsAlwaysVisible() + } + } + } + + @Test + fun testAppPairs_pairPrimaryAndSecondaryApps() { + val testTag = "testAppPaired_pairPrimaryAndSecondary" + runWithFlicker(appPairsSetup) { + withTestName { testTag } + repeat { + TEST_REPETITIONS + } + transitions { + // TODO pair apps through normal UX flow + executeShellCommand(composePairsCommand( + primaryTaskId, secondaryTaskId, true /* pair */)) + SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + } + assertions { + layersTrace { + appPairsDividerIsVisible() + end("appsEndingBounds", enabled = false) { + val entry = this.trace.entries.firstOrNull() + ?: throw IllegalStateException("Trace is empty") + val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER) + this.hasVisibleRegion(primaryApp.defaultWindowName, + appPairsHelper.getPrimaryBounds(dividerRegion)) + .and() + .hasVisibleRegion(secondaryApp.defaultWindowName, + appPairsHelper.getSecondaryBounds(dividerRegion)) + } + } + windowManagerTrace { + end { + showsAppWindow(primaryApp.defaultWindowName) + .and() + .showsAppWindow(secondaryApp.defaultWindowName) + } + } + } + } + } + + @Test + fun testAppPairs_unpairPrimaryAndSecondary() { + val testTag = "testAppPairs_unpairPrimaryAndSecondary" + runWithFlicker(appPairsSetup) { + withTestName { testTag } + repeat { + TEST_REPETITIONS + } + setup { + executeShellCommand(composePairsCommand( + primaryTaskId, secondaryTaskId, true /* pair */)) + SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + } + transitions { + // TODO pair apps through normal UX flow + executeShellCommand(composePairsCommand( + primaryTaskId, secondaryTaskId, false /* pair */)) + SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + } + assertions { + layersTrace { + appPairsDividerIsInvisible() + start("appsStartingBounds", enabled = false) { + val entry = this.trace.entries.firstOrNull() + ?: throw IllegalStateException("Trace is empty") + val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER) + this.hasVisibleRegion(primaryApp.defaultWindowName, + appPairsHelper.getPrimaryBounds(dividerRegion)) + .and() + .hasVisibleRegion(secondaryApp.defaultWindowName, + appPairsHelper.getSecondaryBounds(dividerRegion)) + } + end("appsEndingBounds", enabled = false) { + this.hasNotLayer(primaryApp.defaultWindowName) + .and() + .hasNotLayer(secondaryApp.defaultWindowName) + } + } + windowManagerTrace { + end { + hidesAppWindow(primaryApp.defaultWindowName) + .and() + .hidesAppWindow(secondaryApp.defaultWindowName) + } + } + } + } + } + + private fun composePairsCommand( + primaryApp: String, + secondaryApp: String, + pair: Boolean + ): String = buildString { + // dumpsys activity service SystemUIService WMShell {pair|unpair} ${TASK_ID_1} ${TASK_ID_2} + append("dumpsys activity service SystemUIService WMShell ") + if (pair) { + append("pair ") + } else { + append("unpair ") + } + append(primaryApp + " " + secondaryApp) + } + + private fun executeShellCommand(cmd: String) { + try { + SystemUtil.runShellCommand(instrumentation, cmd) + } catch (e: IOException) { + Log.d("AppPairsTest", "executeShellCommand error!" + e) + } + } + + private fun updateTaskId() { + val primaryAppComponent = primaryApp.openAppIntent.component + val secondaryAppComponent = secondaryApp.openAppIntent.component + if (primaryAppComponent != null) { + primaryTaskId = appPairsHelper.getTaskIdForActivity( + primaryAppComponent.packageName, primaryAppComponent.className).toString() + } + if (secondaryAppComponent != null) { + secondaryTaskId = appPairsHelper.getTaskIdForActivity( + secondaryAppComponent.packageName, secondaryAppComponent.className).toString() + } + } + + companion object { + var primaryTaskId = "" + var secondaryTaskId = "" + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection> { + val supportedRotations = intArrayOf(Surface.ROTATION_0) + return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) } + } + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt new file mode 100644 index 0000000000000..1a4de0a80bec3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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.wm.shell.flicker.apppairs + +import com.android.wm.shell.flicker.NonRotationTestBase +import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME +import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL +import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME +import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_LABEL +import com.android.wm.shell.flicker.helpers.AppPairsHelper +import com.android.wm.shell.flicker.helpers.SplitScreenHelper + +abstract class AppPairsTestBase( + rotationName: String, + rotation: Int +) : NonRotationTestBase(rotationName, rotation) { + protected val appPairsHelper = AppPairsHelper(instrumentation, + TEST_APP_SPLITSCREEN_PRIMARY_LABEL, + TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME) + protected val primaryApp = SplitScreenHelper(instrumentation, + TEST_APP_SPLITSCREEN_PRIMARY_LABEL, + TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME) + protected val secondaryApp = SplitScreenHelper(instrumentation, + TEST_APP_SPLITSCREEN_SECONDARY_LABEL, + TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME) +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt new file mode 100644 index 0000000000000..3b6fcdbee4be6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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.wm.shell.flicker.helpers + +import android.app.Instrumentation +import android.content.ComponentName +import android.graphics.Region +import android.system.helpers.ActivityHelper +import com.android.server.wm.flicker.helpers.WindowUtils + +class AppPairsHelper( + instrumentation: Instrumentation, + activityLabel: String, + componentName: ComponentName +) : BaseAppHelper( + instrumentation, + activityLabel, + componentName +) { + val activityHelper = ActivityHelper.getInstance() + + fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region { + val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right, + dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset) + return primaryAppBounds + } + + fun getSecondaryBounds(dividerBounds: Region): android.graphics.Region { + val displayBounds = WindowUtils.displayBounds + val secondaryAppBounds = Region(0, + dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset, + displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) + return secondaryAppBounds + } + + fun getTaskIdForActivity(pkgName: String, activityName: String): Int { + return activityHelper.getTaskIdForActivity(pkgName, activityName) + } + + companion object { + const val TEST_REPETITIONS = 1 + const val TIMEOUT_MS = 3_000L + } +}