diff options
Diffstat (limited to 'libs/WindowManager/Shell/tests')
113 files changed, 5433 insertions, 1764 deletions
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 9dd25fe0e6fe..3ca5b9c38aff 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -25,11 +25,17 @@ package { android_test { name: "WMShellFlickerTests", - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", platform_apis: true, certificate: "platform", + optimize: { + enabled: false, + }, test_suites: ["device-tests"], libs: ["android.test.runner"], static_libs: [ diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml index e6d32ff1166f..06df9568e01a 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml @@ -42,6 +42,9 @@ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> <!-- ATM.removeRootTasksWithActivityTypes() --> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <!-- Enable bubble notification--> + <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <!-- Allow the test to write directly to /sdcard/ --> <application android:requestLegacyExternalStorage="true"> <uses-library android:name="android.test.runner"/> 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 c5b5b91d570b..c4be785cff19 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 @@ -14,100 +14,104 @@ * limitations under the License. */ +@file:JvmName("CommonAssertions") package com.android.wm.shell.flicker import android.graphics.Region import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.traces.layers.getVisibleBounds +import com.android.server.wm.traces.common.FlickerComponentName -fun FlickerTestParameter.appPairsDividerIsVisible() { +fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() { assertLayersEnd { - this.isVisible(APP_PAIR_SPLIT_DIVIDER) + this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.appPairsDividerIsInvisible() { +fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() { assertLayersEnd { - this.notContains(APP_PAIR_SPLIT_DIVIDER) + this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } fun FlickerTestParameter.appPairsDividerBecomesVisible() { assertLayers { - this.isInvisible(DOCKED_STACK_DIVIDER) + this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() - .isVisible(DOCKED_STACK_DIVIDER) + .isVisible(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.dockedStackDividerIsVisible() { +fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() { assertLayersEnd { - this.isVisible(DOCKED_STACK_DIVIDER) + this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) } } fun FlickerTestParameter.dockedStackDividerBecomesVisible() { assertLayers { - this.isInvisible(DOCKED_STACK_DIVIDER) + this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() - .isVisible(DOCKED_STACK_DIVIDER) + .isVisible(DOCKED_STACK_DIVIDER_COMPONENT) } } fun FlickerTestParameter.dockedStackDividerBecomesInvisible() { assertLayers { - this.isVisible(DOCKED_STACK_DIVIDER) + this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() - .isInvisible(DOCKED_STACK_DIVIDER) + .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.dockedStackDividerIsInvisible() { +fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() { assertLayersEnd { - this.notContains(DOCKED_STACK_DIVIDER) + this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.appPairsPrimaryBoundsIsVisible(rotation: Int, primaryLayerName: String) { +fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd( + rotation: Int, + primaryComponent: FlickerComponentName +) { assertLayersEnd { - val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - visibleRegion(primaryLayerName) - .coversExactly(getPrimaryRegion(dividerRegion, rotation)) + val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(primaryComponent) + .overlaps(getPrimaryRegion(dividerRegion, rotation)) } } -fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisible( +fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd( rotation: Int, - primaryLayerName: String + primaryComponent: FlickerComponentName ) { assertLayersEnd { - val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - visibleRegion(primaryLayerName) - .coversExactly(getPrimaryRegion(dividerRegion, rotation)) + val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(primaryComponent) + .overlaps(getPrimaryRegion(dividerRegion, rotation)) } } -fun FlickerTestParameter.appPairsSecondaryBoundsIsVisible( +fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd( rotation: Int, - secondaryLayerName: String + secondaryComponent: FlickerComponentName ) { assertLayersEnd { - val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - visibleRegion(secondaryLayerName) - .coversExactly(getSecondaryRegion(dividerRegion, rotation)) + val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(secondaryComponent) + .overlaps(getSecondaryRegion(dividerRegion, rotation)) } } -fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisible( +fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd( rotation: Int, - secondaryLayerName: String + secondaryComponent: FlickerComponentName ) { assertLayersEnd { - val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - visibleRegion(secondaryLayerName) - .coversExactly(getSecondaryRegion(dividerRegion, rotation)) + val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(secondaryComponent) + .overlaps(getSecondaryRegion(dividerRegion, rotation)) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index 03b93c74233c..40891f36a5da 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -14,9 +14,11 @@ * limitations under the License. */ +@file:JvmName("CommonConstants") package com.android.wm.shell.flicker -const val IME_WINDOW_NAME = "InputMethod" +import com.android.server.wm.traces.common.FlickerComponentName + const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" -const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider" -const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
\ No newline at end of file +val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#") +val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#")
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt index a6d67355f271..b63d9fffdb61 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:JvmName("WaitUtils") package com.android.wm.shell.flicker import android.os.SystemClock diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index ef9f7421fd60..038be9c190c2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -25,7 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.wm.shell.flicker.appPairsDividerIsInvisible +import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow @@ -61,7 +60,7 @@ class AppPairsTestCannotPairNonResizeableApps( // TODO pair apps through normal UX flow executeShellCommand( composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true)) - SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) } } } @@ -85,15 +84,13 @@ class AppPairsTestCannotPairNonResizeableApps( @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @FlakyTest + @Presubmit @Test - override fun navBarLayerIsAlwaysVisible() { - super.navBarLayerIsAlwaysVisible() - } + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() @Presubmit @Test - fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible() + fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() @Presubmit @Test @@ -103,8 +100,8 @@ class AppPairsTestCannotPairNonResizeableApps( "Non resizeable app not initialized" } testSpec.assertWmEnd { - isVisible(nonResizeableApp.defaultWindowName) - isInvisible(primaryApp.defaultWindowName) + isAppWindowVisible(nonResizeableApp.component) + isAppWindowInvisible(primaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index db63c4c43523..bbc6b2dbece8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -25,10 +24,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.traces.layers.getVisibleBounds -import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER -import com.android.wm.shell.flicker.appPairsDividerIsVisible +import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper +import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -54,10 +53,14 @@ class AppPairsTestPairPrimaryAndSecondaryApps( // TODO pair apps through normal UX flow executeShellCommand( composePairsCommand(primaryTaskId, secondaryTaskId, pair = true)) - SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + waitAppsShown(primaryApp, secondaryApp) } } + @Presubmit + @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() @@ -68,14 +71,14 @@ class AppPairsTestPairPrimaryAndSecondaryApps( @Presubmit @Test - fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() + fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() @Presubmit @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(primaryApp.defaultWindowName) - isVisible(secondaryApp.defaultWindowName) + isAppWindowVisible(primaryApp.component) + isAppWindowVisible(secondaryApp.component) } } @@ -83,10 +86,10 @@ class AppPairsTestPairPrimaryAndSecondaryApps( @Test fun appsEndingBounds() { testSpec.assertLayersEnd { - val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - visibleRegion(primaryApp.defaultWindowName) + val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(primaryApp.component) .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion)) - visibleRegion(secondaryApp.defaultWindowName) + visibleRegion(secondaryApp.component) .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion)) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt index c8d34237231c..bb784a809b7e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -25,7 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.wm.shell.flicker.appPairsDividerIsVisible +import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow @@ -61,7 +60,7 @@ class AppPairsTestSupportPairNonResizeableApps( // TODO pair apps through normal UX flow executeShellCommand( composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true)) - SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) } } } @@ -77,6 +76,10 @@ class AppPairsTestSupportPairNonResizeableApps( resetMultiWindowConfig(instrumentation) } + @Presubmit + @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() @@ -87,7 +90,7 @@ class AppPairsTestSupportPairNonResizeableApps( @Presubmit @Test - fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() + fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() @Presubmit @Test @@ -97,8 +100,8 @@ class AppPairsTestSupportPairNonResizeableApps( "Non resizeable app not initialized" } testSpec.assertWmEnd { - isVisible(nonResizeableApp.defaultWindowName) - isVisible(primaryApp.defaultWindowName) + isAppWindowVisible(nonResizeableApp.component) + isAppWindowVisible(primaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 83df83600d11..a1a4db112dfd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -25,10 +25,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.traces.layers.getVisibleBounds -import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER -import com.android.wm.shell.flicker.appPairsDividerIsInvisible +import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper +import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -51,9 +51,11 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( get() = { super.transition(this, it) setup { - executeShellCommand( - composePairsCommand(primaryTaskId, secondaryTaskId, pair = true)) - SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + eachRun { + executeShellCommand( + composePairsCommand(primaryTaskId, secondaryTaskId, pair = true)) + waitAppsShown(primaryApp, secondaryApp) + } } transitions { // TODO pair apps through normal UX flow @@ -73,14 +75,14 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( @Presubmit @Test - fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible() + fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() @Presubmit @Test fun bothAppWindowsInvisible() { testSpec.assertWmEnd { - isInvisible(primaryApp.defaultWindowName) - isInvisible(secondaryApp.defaultWindowName) + isAppWindowInvisible(primaryApp.component) + isAppWindowInvisible(secondaryApp.component) } } @@ -88,10 +90,10 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( @Test fun appsStartingBounds() { testSpec.assertLayersStart { - val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - visibleRegion(primaryApp.defaultWindowName) + val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(primaryApp.component) .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion)) - visibleRegion(secondaryApp.defaultWindowName) + visibleRegion(secondaryApp.component) .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion)) } } @@ -100,16 +102,14 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( @Test fun appsEndingBounds() { testSpec.assertLayersEnd { - notContains(primaryApp.defaultWindowName) - notContains(secondaryApp.defaultWindowName) + notContains(primaryApp.component) + notContains(secondaryApp.component) } } - @FlakyTest + @Presubmit @Test - override fun navBarLayerIsAlwaysVisible() { - super.navBarLayerIsAlwaysVisible() - } + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 1935bb97849c..9e20bbbc1a1b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -20,24 +20,23 @@ import android.app.Instrumentation import android.content.Context import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper -import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.isRotated import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.BaseAppHelper import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow @@ -55,7 +54,7 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) protected val activityHelper = ActivityHelper.getInstance() protected val appPairsHelper = AppPairsHelper(instrumentation, Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT) + Components.SplitScreenActivity.COMPONENT.toFlickerComponent()) protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation) protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) @@ -154,39 +153,33 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) @FlakyTest(bugId = 186510496) @Test - open fun navBarLayerIsAlwaysVisible() { - testSpec.navBarLayerIsAlwaysVisible() + open fun navBarLayerIsVisible() { + testSpec.navBarLayerIsVisible() } @Presubmit @Test - open fun statusBarLayerIsAlwaysVisible() { - testSpec.statusBarLayerIsAlwaysVisible() + open fun statusBarLayerIsVisible() { + testSpec.statusBarLayerIsVisible() } @Presubmit @Test - open fun navBarWindowIsAlwaysVisible() { - testSpec.navBarWindowIsAlwaysVisible() + open fun navBarWindowIsVisible() { + testSpec.navBarWindowIsVisible() } @Presubmit @Test - open fun statusBarWindowIsAlwaysVisible() { - testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarWindowIsVisible() { + testSpec.statusBarWindowIsVisible() } @Presubmit @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, - testSpec.config.endRotation) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - open fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, - testSpec.config.endRotation) - } + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index c875c0006703..56a2531a3fe1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -28,10 +27,10 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.setRotation -import com.android.wm.shell.flicker.appPairsDividerIsVisible -import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible -import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible -import com.android.wm.shell.flicker.helpers.AppPairsHelper +import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -57,41 +56,43 @@ class RotateTwoLaunchedAppsInAppPairsMode( transitions { executeShellCommand(composePairsCommand( primaryTaskId, secondaryTaskId, true /* pair */)) - SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + waitAppsShown(primaryApp, secondaryApp) setRotation(testSpec.config.endRotation) } } - @FlakyTest + @Presubmit @Test - override fun statusBarLayerIsAlwaysVisible() { - super.statusBarLayerIsAlwaysVisible() - } + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + + @Presubmit + @Test + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() @Presubmit @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(primaryApp.defaultWindowName) - .isVisible(secondaryApp.defaultWindowName) + isAppWindowVisible(primaryApp.component) + isAppWindowVisible(secondaryApp.component) } } @Presubmit @Test - fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() + fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @FlakyTest(bugId = 172776659) + @Presubmit @Test - fun appPairsPrimaryBoundsIsVisible() = - testSpec.appPairsPrimaryBoundsIsVisible(testSpec.config.endRotation, - primaryApp.defaultWindowName) + fun appPairsPrimaryBoundsIsVisibleAtEnd() = + testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation, + primaryApp.component) - @FlakyTest(bugId = 172776659) + @FlakyTest @Test - fun appPairsSecondaryBoundsIsVisible() = - testSpec.appPairsSecondaryBoundsIsVisible(testSpec.config.endRotation, - secondaryApp.defaultWindowName) + fun appPairsSecondaryBoundsIsVisibleAtEnd() = + testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation, + secondaryApp.component) companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index c3360ca0f7d3..0699a4fd0512 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -28,12 +27,10 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.appPairsDividerIsVisible -import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible -import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible -import com.android.wm.shell.flicker.helpers.AppPairsHelper +import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -60,48 +57,50 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( this.setRotation(testSpec.config.endRotation) executeShellCommand( composePairsCommand(primaryTaskId, secondaryTaskId, pair = true)) - SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + waitAppsShown(primaryApp, secondaryApp) } } @Presubmit @Test - fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() + fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() @Presubmit @Test - override fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() @Presubmit @Test - override fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + @Presubmit @Test - override fun statusBarLayerIsAlwaysVisible() { - super.statusBarLayerIsAlwaysVisible() - } + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + @Presubmit + @Test + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() @Presubmit @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(primaryApp.defaultWindowName) - isVisible(secondaryApp.defaultWindowName) + isAppWindowVisible(primaryApp.component) + isAppWindowVisible(secondaryApp.component) } } @FlakyTest(bugId = 172776659) @Test - fun appPairsPrimaryBoundsIsVisible() = - testSpec.appPairsPrimaryBoundsIsVisible(testSpec.config.endRotation, - primaryApp.defaultWindowName) + fun appPairsPrimaryBoundsIsVisibleAtEnd() = + testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation, + primaryApp.component) @FlakyTest(bugId = 172776659) @Test - fun appPairsSecondaryBoundsIsVisible() = - testSpec.appPairsSecondaryBoundsIsVisible(testSpec.config.endRotation, - secondaryApp.defaultWindowName) + fun appPairsSecondaryBoundsIsVisibleAtEnd() = + testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation, + secondaryApp.component) companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt index 512fd9a58ea8..b95193a17265 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt @@ -22,7 +22,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.Assume.assumeFalse +import org.junit.Before import org.junit.Test abstract class RotateTwoLaunchedAppsTransition( @@ -37,8 +40,8 @@ abstract class RotateTwoLaunchedAppsTransition( test { device.wakeUpAndGoToHomeScreen() this.setRotation(Surface.ROTATION_0) - primaryApp.launchViaIntent() - secondaryApp.launchViaIntent() + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) updateTasksId() } } @@ -52,10 +55,17 @@ abstract class RotateTwoLaunchedAppsTransition( } } + @Before + override fun setup() { + // AppPairs hasn't been updated to Shell Transition. There will be conflict on rotation. + assumeFalse(isShellTransitionsEnabled()) + super.setup() + } + @FlakyTest @Test - override fun navBarLayerIsAlwaysVisible() { - super.navBarLayerIsAlwaysVisible() + override fun navBarLayerIsVisible() { + super.navBarLayerIsVisible() } @FlakyTest diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt new file mode 100644 index 000000000000..322d8b5e4dac --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 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.bubble + +import android.app.INotificationManager +import android.app.Instrumentation +import android.app.NotificationManager +import android.content.Context +import android.os.ServiceManager +import android.view.Surface +import androidx.test.filters.FlakyTest +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE +import com.android.server.wm.flicker.repetitions +import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper +import org.junit.Test +import org.junit.runners.Parameterized + +/** + * Base configurations for Bubble flicker tests + */ +abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) { + + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val context: Context = instrumentation.context + protected val testApp = LaunchBubbleHelper(instrumentation) + + protected val notifyManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)) + + protected val packageManager = context.getPackageManager() + protected val uid = packageManager.getApplicationInfo( + testApp.component.packageName, 0).uid + + protected lateinit var addBubbleBtn: UiObject2 + protected lateinit var cancelAllBtn: UiObject2 + + protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + + @JvmOverloads + protected open fun buildTransition( + extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {} + ): FlickerBuilder.(Map<String, Any?>) -> Unit { + return { configuration -> + + setup { + test { + notifyManager.setBubblesAllowed(testApp.component.packageName, + uid, NotificationManager.BUBBLE_PREFERENCE_ALL) + testApp.launchViaIntent(wmHelper) + addBubbleBtn = device.wait(Until.findObject( + By.text("Add Bubble")), FIND_OBJECT_TIMEOUT) + cancelAllBtn = device.wait(Until.findObject( + By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT) + } + } + + teardown { + notifyManager.setBubblesAllowed(testApp.component.packageName, + uid, NotificationManager.BUBBLE_PREFERENCE_NONE) + testApp.exit() + } + + extraSpec(this, configuration) + } + } + + @FlakyTest + @Test + fun testAppIsAlwaysVisible() { + testSpec.assertLayers { + this.isVisible(testApp.component) + } + } + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + repeat { testSpec.config.repetitions } + transition(this, testSpec.config) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) + } + + const val FIND_OBJECT_TIMEOUT = 2000L + const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE + const val BUBBLE_RES_NAME = "bubble_view" + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt new file mode 100644 index 000000000000..bfdcb363a818 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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.bubble + +import android.content.Context +import android.graphics.Point +import android.util.DisplayMetrics +import android.view.WindowManager +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a new activity from bubble. + * + * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen` + * + * Actions: + * Dismiss a bubble notification + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + val displaySize = DisplayMetrics() + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition() { + setup { + eachRun { + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found") + } + } + transitions { + wm?.run { wm.getDefaultDisplay().getMetrics(displaySize) } ?: error("WM not found") + val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels) + val showBubble = device.wait(Until.findObject( + By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt new file mode 100644 index 000000000000..42eeadf3ddd9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 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.bubble + +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a new activity from bubble. + * + * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen` + * + * Actions: + * Launch an app and enable app's bubble notification + * Send a bubble notification + * The activity for the bubble is launched + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition() { + setup { + test { + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found") + } + } + transitions { + val showBubble = device.wait(Until.findObject( + By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT) + showBubble?.run { showBubble.click() } ?: error("Bubble notify not found") + device.pressBack() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt index cf84a2c696d0..47e8c0c047a8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 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. @@ -14,47 +14,35 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package com.android.wm.shell.flicker.bubble -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder -import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip launch. - * To run this test: `atest WMShellFlickerTests:PipCloseWithDismissButton` + * Test creating a bubble notification + * + * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen` + * + * Actions: + * Launch an app and enable app's bubble notification + * Send a bubble notification */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class PipCloseWithDismissButtonTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) { +@Group4 +class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit - get() = { - super.transition(this, it) + get() = buildTransition() { transitions { - pipApp.closePipWindow(wmHelper) + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found") } } - - @FlakyTest - @Test - override fun pipLayerBecomesInvisible() { - super.pipLayerBecomesInvisible() - } - - @FlakyTest - @Test - override fun pipWindowBecomesInvisible() { - super.pipWindowBecomesInvisible() - } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt new file mode 100644 index 000000000000..194e28fd6e8a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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.bubble + +import android.os.SystemClock +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a new activity from bubble. + * + * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen` + * + * Actions: + * Switch in different bubble notifications + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition() { + setup { + test { + for (i in 1..3) { + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found") + } + val showBubble = device.wait(Until.findObject( + By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + showBubble?.run { showBubble.click() } ?: error("Show bubble not found") + SystemClock.sleep(1000) + } + } + transitions { + val bubbles = device.wait(Until.findObjects( + By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + for (entry in bubbles) { + entry?.run { entry.click() } ?: error("Bubble not found") + SystemClock.sleep(1000) + } + } + } +} 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 index 5b8cfb81016a..623055f659b9 100644 --- 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 @@ -17,14 +17,15 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.graphics.Region +import com.android.server.wm.flicker.Flicker import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.traces.common.FlickerComponentName class AppPairsHelper( instrumentation: Instrumentation, activityLabel: String, - component: ComponentName + component: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, component) { fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region { val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right, @@ -43,5 +44,17 @@ class AppPairsHelper( companion object { const val TEST_REPETITIONS = 1 const val TIMEOUT_MS = 3_000L + + fun Flicker.waitAppsShown(app1: SplitScreenHelper?, app2: SplitScreenHelper?) { + wmHelper.waitFor("primaryAndSecondaryAppsVisible") { dump -> + val primaryAppVisible = app1?.let { + dump.wmState.isWindowSurfaceShown(app1.defaultWindowName) + } ?: false + val secondaryAppVisible = app2?.let { + dump.wmState.isWindowSurfaceShown(app2.defaultWindowName) + } ?: false + primaryAppVisible && secondaryAppVisible + } + } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt index 4fe69ad7fabe..57bc0d580d72 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt @@ -17,9 +17,9 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.content.pm.PackageManager.FEATURE_LEANBACK import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY +import android.os.SystemProperties import android.support.test.launcherhelper.LauncherStrategyFactory import android.util.Log import androidx.test.uiautomator.By @@ -27,13 +27,13 @@ import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.compatibility.common.util.SystemUtil import com.android.server.wm.flicker.helpers.StandardAppHelper -import com.android.server.wm.traces.parser.toWindowName +import com.android.server.wm.traces.common.FlickerComponentName import java.io.IOException abstract class BaseAppHelper( instrumentation: Instrumentation, launcherName: String, - component: ComponentName + component: FlickerComponentName ) : StandardAppHelper( instrumentation, launcherName, @@ -60,6 +60,9 @@ abstract class BaseAppHelper( companion object { private const val APP_CLOSE_WAIT_TIME_MS = 3_000L + fun isShellTransitionsEnabled() = + SystemProperties.getBoolean("persist.debug.shell_transit", false) + fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { try { SystemUtil.runShellCommand(instrumentation, cmd) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt index b4ae18749b34..471e010cf560 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt @@ -17,10 +17,11 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.testapp.Components class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.FixedActivity.LABEL, - Components.FixedActivity.COMPONENT + Components.FixedActivity.COMPONENT.toFlickerComponent() )
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt index cac46fe676b3..0f00edea136f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt @@ -21,13 +21,14 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.FIND_TIMEOUT +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.testapp.Components open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.ImeActivity.LABEL, - Components.ImeActivity.COMPONENT + Components.ImeActivity.COMPONENT.toFlickerComponent() ) { /** * Opens the IME and wait for it to be displayed @@ -61,7 +62,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( if (wmHelper == null) { device.waitForIdle() } else { - require(wmHelper.waitImeWindowShown()) { "IME did not appear" } + require(wmHelper.waitImeShown()) { "IME did not appear" } } } @@ -78,7 +79,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( if (wmHelper == null) { uiDevice.waitForIdle() } else { - require(wmHelper.waitImeWindowGone()) { "IME did did not close" } + require(wmHelper.waitImeGone()) { "IME did did not close" } } } else { // While pressing the back button should close the IME on TV as well, it may also lead diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt index 0037059e2c51..6695c17ed514 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt @@ -14,16 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package com.android.wm.shell.flicker.helpers -import android.content.ComponentName -import com.android.server.wm.traces.common.windowmanager.WindowManagerState -import com.android.server.wm.traces.parser.toWindowName +import android.app.Instrumentation +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.wm.shell.flicker.testapp.Components -/** - * Checks that an activity [activity] is in PIP mode - */ -fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean { - val windowName = activity.toWindowName() - return isInPipMode(windowName) +class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper( + instrumentation, + Components.LaunchBubbleActivity.LABEL, + Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent() +) { + + companion object { + const val TEST_REPETITIONS = 1 + const val TIMEOUT_MS = 3_000L + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt index 7f99e62b36b0..12ccbafce651 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt @@ -17,14 +17,14 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.content.Context import android.provider.Settings +import com.android.server.wm.traces.common.FlickerComponentName class MultiWindowHelper( instrumentation: Instrumentation, activityLabel: String, - componentsInfo: ComponentName + componentsInfo: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { companion object { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index f4dd7decb1b7..2357b0debb33 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -17,12 +17,16 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation +import android.graphics.Rect import android.media.session.MediaController import android.media.session.MediaSessionManager import android.os.SystemClock import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.FIND_TIMEOUT import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild @@ -31,7 +35,7 @@ import com.android.wm.shell.flicker.testapp.Components class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.PipActivity.LABEL, - Components.PipActivity.COMPONENT + Components.PipActivity.COMPONENT.toFlickerComponent() ) { private val mediaSessionManager: MediaSessionManager get() = context.getSystemService(MediaSessionManager::class.java) @@ -62,7 +66,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( stringExtras: Map<String, String> ) { super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras) - wmHelper.waitFor { it.wmState.hasPipWindow() } + wmHelper.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } } private fun focusOnObject(selector: BySelector): Boolean { @@ -84,7 +88,11 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( clickObject(ENTER_PIP_BUTTON_ID) // Wait on WMHelper or simply wait for 3 seconds - wmHelper?.waitFor { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000) + wmHelper?.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000) + // when entering pip, the dismiss button is visible at the start. to ensure the pip + // animation is complete, wait until the pip dismiss button is no longer visible. + // b/176822698: dismiss-only state will be removed in the future + uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT) } fun clickStartMediaSessionButton() { @@ -113,61 +121,61 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } } + private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect { + val windowRegion = wmHelper.getWindowRegion(component) + require(!windowRegion.isEmpty) { + "Unable to find a PIP window in the current state" + } + return windowRegion.bounds + } + /** - * Expands the pip window and dismisses it by clicking on the X button. - * - * Note, currently the View coordinates reported by the accessibility are relative to - * the window, so the correct coordinates need to be calculated - * - * For example, in a PIP window located at Rect(508, 1444 - 1036, 1741), the - * dismiss button coordinates are shown as Rect(650, 0 - 782, 132), with center in - * Point(716, 66), instead of Point(970, 1403) - * - * See b/179337864 + * Taps the pip window and dismisses it by clicking on the X button. */ fun closePipWindow(wmHelper: WindowManagerStateHelper) { if (isTelevision) { uiDevice.closeTvPipWindow() } else { - expandPipWindow(wmHelper) - val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss")) - requireNotNull(exitPipObject) { "PIP window dismiss button not found" } - val dismissButtonBounds = exitPipObject.visibleBounds + val windowRect = getWindowRect(wmHelper) + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + // search and interact with the dismiss button + val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") + uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) + val dismissPipObject = uiDevice.findObject(dismissSelector) + ?: error("PIP window dismiss button not found") + val dismissButtonBounds = dismissPipObject.visibleBounds uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) } // Wait for animation to complete. - wmHelper.waitFor { !it.wmState.hasPipWindow() } + wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } wmHelper.waitForHomeActivityVisible() } /** - * Click once on the PIP window to expand it + * Close the pip window by pressing the expand button */ - fun expandPipWindow(wmHelper: WindowManagerStateHelper) { - val windowRegion = wmHelper.getWindowRegion(component) - require(!windowRegion.isEmpty) { - "Unable to find a PIP window in the current state" - } - val windowRect = windowRegion.bounds + fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { + val windowRect = getWindowRect(wmHelper) uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // Ensure WindowManagerService wait until all animations have completed + // search and interact with the expand button + val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button") + uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT) + val expandPipObject = uiDevice.findObject(expandSelector) + ?: error("PIP window expand button not found") + val expandButtonBounds = expandPipObject.visibleBounds + uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) + wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } wmHelper.waitForAppTransitionIdle() - mInstrumentation.uiAutomation.syncInputTransactions() } /** - * Double click on the PIP window to reopen to app + * Double click on the PIP window to expand it */ - fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { - val windowRegion = wmHelper.getWindowRegion(component) - require(!windowRegion.isEmpty) { - "Unable to find a PIP window in the current state" - } - val windowRect = windowRegion.bounds + fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) { + val windowRect = getWindowRect(wmHelper) uiDevice.click(windowRect.centerX(), windowRect.centerY()) uiDevice.click(windowRect.centerX(), windowRect.centerY()) - wmHelper.waitFor { !it.wmState.hasPipWindow() } wmHelper.waitForAppTransitionIdle() } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt index ba13e38ae9e3..4d0fbc4a0e38 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt @@ -17,10 +17,11 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.testapp.Components class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.SimpleActivity.LABEL, - Components.SimpleActivity.COMPONENT + Components.SimpleActivity.COMPONENT.toFlickerComponent() )
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt index 901b7a393291..0ec9b2d869a8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt @@ -17,32 +17,39 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName +import android.content.res.Resources +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.testapp.Components class SplitScreenHelper( instrumentation: Instrumentation, activityLabel: String, - componentsInfo: ComponentName + componentsInfo: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { companion object { const val TEST_REPETITIONS = 1 const val TIMEOUT_MS = 3_000L + // TODO: remove all legacy split screen flicker tests when legacy split screen is fully + // deprecated. + fun isUsingLegacySplit(): Boolean = + Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useLegacySplit) + fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper = SplitScreenHelper(instrumentation, Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT) + Components.SplitScreenActivity.COMPONENT.toFlickerComponent()) fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper = SplitScreenHelper(instrumentation, Components.SplitScreenSecondaryActivity.LABEL, - Components.SplitScreenSecondaryActivity.COMPONENT) + Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()) fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper = SplitScreenHelper(instrumentation, Components.NonResizeableActivity.LABEL, - Components.NonResizeableActivity.COMPONENT) + Components.NonResizeableActivity.COMPONENT.toFlickerComponent()) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt index 4f12f2bb9f5f..bd44d082a1aa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt @@ -18,20 +18,21 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Presubmit import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.HOME_WINDOW_TITLE -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible +import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -48,7 +49,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class EnterSplitScreenDockActivity( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -60,16 +61,16 @@ class EnterSplitScreenDockActivity( } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, LIVE_WALLPAPER_PACKAGE_NAME, - splitScreenApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, *HOME_WINDOW_TITLE) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, LIVE_WALLPAPER_COMPONENT, + splitScreenApp.component, FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, LAUNCHER_COMPONENT) @Presubmit @Test - fun dockedStackPrimaryBoundsIsVisible() = - testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation, - splitScreenApp.defaultWindowName) + fun dockedStackPrimaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + splitScreenApp.component) @Presubmit @Test @@ -77,27 +78,39 @@ class EnterSplitScreenDockActivity( @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test fun appWindowIsVisible() { testSpec.assertWmEnd { - isVisible(splitScreenApp.defaultWindowName) + isAppWindowVisible(splitScreenApp.component) } } + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910 + supportedRotations = listOf(Surface.ROTATION_0), // bugId = 179116910 + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt index f91f634a00e5..625d48b8ab5a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt @@ -22,10 +22,11 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.dockedStackDividerIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -42,6 +43,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 class EnterSplitScreenFromDetachedRecentTask( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -61,24 +63,34 @@ class EnterSplitScreenFromDetachedRecentTask( } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, - splitScreenApp.defaultWindowName) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + splitScreenApp.component) @Presubmit @Test - fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test fun appWindowIsVisible() { testSpec.assertWmEnd { - isVisible(splitScreenApp.defaultWindowName) + isAppWindowVisible(splitScreenApp.component) } } + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt index 85ded8a45233..2ed2806af528 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt @@ -22,18 +22,17 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.appWindowBecomesVisible +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible -import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible +import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -49,7 +48,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class EnterSplitScreenLaunchToSide( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -62,22 +61,22 @@ class EnterSplitScreenLaunchToSide( } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, - secondaryApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component, + secondaryApp.component, FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Presubmit @Test - fun dockedStackPrimaryBoundsIsVisible() = - testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation, - splitScreenApp.defaultWindowName) + fun dockedStackPrimaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + splitScreenApp.component) @Presubmit @Test - fun dockedStackSecondaryBoundsIsVisible() = - testSpec.dockedStackSecondaryBoundsIsVisible(testSpec.config.startRotation, - secondaryApp.defaultWindowName) + fun dockedStackSecondaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + secondaryApp.component) @Presubmit @Test @@ -85,15 +84,35 @@ class EnterSplitScreenLaunchToSide( @Presubmit @Test - fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp.defaultWindowName) + fun appWindowBecomesVisible() { + testSpec.assertWm { + // when the app is launched, first the activity becomes visible, then the + // SnapshotStartingWindow appears and then the app window becomes visible. + // Because we log WM once per frame, sometimes the activity and the window + // become visible in the same entry, sometimes not, thus it is not possible to + // assert the visibility of the activity here + this.isAppWindowInvisible(secondaryApp.component) + .then() + // during re-parenting, the window may disappear and reappear from the + // trace, this occurs because we log only 1x per frame + .notContains(secondaryApp.component, isOptional = true) + .then() + .isAppWindowVisible(secondaryApp.component) + } + } + + @Presubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt index e958bf39930e..ee6cf341c9ff 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt @@ -22,11 +22,11 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.canSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.dockedStackDividerIsInvisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -50,7 +50,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group1 +@Group4 class EnterSplitScreenNotSupportNonResizable( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -70,12 +70,12 @@ class EnterSplitScreenNotSupportNonResizable( } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, - nonResizeableApp.defaultWindowName, - splitScreenApp.defaultWindowName) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + nonResizeableApp.component, + splitScreenApp.component) @Before override fun setup() { @@ -91,7 +91,12 @@ class EnterSplitScreenNotSupportNonResizable( @Presubmit @Test - fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible() + fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt index d3acc82121b0..163b6ffda6e2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt @@ -25,8 +25,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.dockedStackDividerIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -67,12 +67,12 @@ class EnterSplitScreenSupportNonResizable( } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, - nonResizeableApp.defaultWindowName, - splitScreenApp.defaultWindowName) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + nonResizeableApp.component, + splitScreenApp.component) @Before override fun setup() { @@ -88,16 +88,21 @@ class EnterSplitScreenSupportNonResizable( @Presubmit @Test - fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test fun appWindowIsVisible() { testSpec.assertWmEnd { - isVisible(nonResizeableApp.defaultWindowName) + isAppWindowVisible(nonResizeableApp.component) } } + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt index bad46836dcb7..2b629b0a7eb5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.Postsubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -24,15 +24,13 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesInVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.layerBecomesInvisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -67,31 +65,52 @@ class ExitLegacySplitScreenFromBottom( } } transitions { - device.exitSplitScreenFromBottom() + device.exitSplitScreenFromBottom(wmHelper) } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME, - splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, + splitScreenApp.component, secondaryApp.component, + FlickerComponentName.SNAPSHOT) - @Presubmit + @Postsubmit @Test - fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(DOCKED_STACK_DIVIDER) + fun layerBecomesInvisible() { + testSpec.assertLayers { + this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) + .then() + .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) + } + } @FlakyTest @Test - fun appWindowBecomesInVisible() = - testSpec.appWindowBecomesInVisible(secondaryApp.defaultWindowName) + fun appWindowBecomesInVisible() { + testSpec.assertWm { + this.isAppWindowVisible(secondaryApp.component) + .then() + .isAppWindowInvisible(secondaryApp.component) + } + } + + @Postsubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - @Presubmit + @Postsubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - @Presubmit + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @FlakyTest @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt index 76dcd8b89242..95fe3bef4852 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt @@ -24,15 +24,13 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesInVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.layerBecomesInvisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.dockedStackDividerIsInvisible +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -71,31 +69,52 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen( } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME, - splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, + splitScreenApp.component, secondaryApp.component, + FlickerComponentName.SNAPSHOT) - @FlakyTest(bugId = 175687842) + @Presubmit @Test - fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible() + fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() @FlakyTest @Test - fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) + fun layerBecomesInvisible() { + testSpec.assertLayers { + this.isVisible(splitScreenApp.component) + .then() + .isInvisible(splitScreenApp.component) + } + } @FlakyTest @Test - fun appWindowBecomesInVisible() = - testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName) + fun appWindowBecomesInVisible() { + testSpec.assertWm { + this.isAppWindowVisible(splitScreenApp.component) + .then() + .isAppWindowInvisible(splitScreenApp.component) + } + } + + @Presubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Presubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt index d0a64b3774c7..f7d628d48769 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt @@ -23,15 +23,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesInVisible -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.layerBecomesInvisible -import com.android.server.wm.flicker.layerBecomesVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER -import com.android.wm.shell.flicker.dockedStackDividerIsInvisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -72,11 +68,11 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( } } - override val ignoredWindows: List<String> - get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, - nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, + nonResizeableApp.component, splitScreenApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -92,44 +88,109 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( @Presubmit @Test - fun resizableAppLayerBecomesInvisible() = - testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) + fun resizableAppLayerBecomesInvisible() { + testSpec.assertLayers { + this.isVisible(splitScreenApp.component) + .then() + .isInvisible(splitScreenApp.component) + } + } + + @Presubmit + @Test + fun nonResizableAppLayerBecomesVisible() { + testSpec.assertLayers { + this.notContains(nonResizeableApp.component) + .then() + .isInvisible(nonResizeableApp.component) + .then() + .isVisible(nonResizeableApp.component) + } + } + /** + * Assets that [splitScreenApp] exists at the start of the trace and, once it becomes + * invisible, it remains invisible until the end of the trace. + */ @Presubmit @Test - fun nonResizableAppLayerBecomesVisible() = - testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + fun resizableAppWindowBecomesInvisible() { + testSpec.assertWm { + // when the activity gets PAUSED the window may still be marked as visible + // it will be updated in the next log entry. This occurs because we record 1x + // per frame, thus ignore activity check here + this.isAppWindowVisible(splitScreenApp.component) + .then() + // immediately after the window (after onResume and before perform relayout) + // the activity is invisible. This may or not be logged, since we record 1x + // per frame, thus ignore activity check here + .isAppWindowInvisible(splitScreenApp.component) + } + } + /** + * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then + * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes + * visible, it remains visible until the end of the trace. + */ @Presubmit @Test - fun resizableAppWindowBecomesInvisible() = - testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName) + fun nonResizableAppWindowBecomesVisible() { + testSpec.assertWm { + this.notContains(nonResizeableApp.component) + .then() + // we log once per frame, upon logging, window may be visible or not depending + // on what was processed until that moment. Both behaviors are correct + .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) + .then() + // immediately after the window (after onResume and before perform relayout) + // the activity is invisible. This may or not be logged, since we record 1x + // per frame, thus ignore activity check here + .isAppWindowVisible(nonResizeableApp.component) + } + } + /** + * Asserts that both the app window and the activity are visible at the end of the trace + */ @Presubmit @Test - fun nonResizableAppWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppWindowBecomesVisibleAtEnd() { + testSpec.assertWmEnd { + isAppWindowVisible(nonResizeableApp.component) + } + } @Presubmit @Test - fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible() + fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() @Presubmit @Test fun onlyNonResizableAppWindowIsVisibleAtEnd() { testSpec.assertWmEnd { - isInvisible(splitScreenApp.defaultWindowName) - isVisible(nonResizeableApp.defaultWindowName) + isAppWindowInvisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 + repetitions = SplitScreenHelper.TEST_REPETITIONS, + supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt index c26c05fa8db6..a5c6571f68de 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt @@ -23,13 +23,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.layerBecomesVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER -import com.android.wm.shell.flicker.dockedStackDividerIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -70,11 +68,11 @@ class LegacySplitScreenFromIntentSupportNonResizable( } } - override val ignoredWindows: List<String> - get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, - nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, + nonResizeableApp.component, splitScreenApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -90,27 +88,59 @@ class LegacySplitScreenFromIntentSupportNonResizable( @Presubmit @Test - fun nonResizableAppLayerBecomesVisible() = - testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppLayerBecomesVisible() { + testSpec.assertLayers { + this.isInvisible(nonResizeableApp.component) + .then() + .isVisible(nonResizeableApp.component) + } + } + /** + * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then + * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes + * visible, it remains visible until the end of the trace. + */ @Presubmit @Test - fun nonResizableAppWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppWindowBecomesVisible() { + testSpec.assertWm { + this.notContains(nonResizeableApp.component) + .then() + // we log once per frame, upon logging, window may be visible or not depending + // on what was processed until that moment. Both behaviors are correct + .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) + .then() + // immediately after the window (after onResume and before perform relayout) + // the activity is invisible. This may or not be logged, since we record 1x + // per frame, thus ignore activity check here + .isAppWindowVisible(nonResizeableApp.component) + } + } @Presubmit @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test fun bothAppsWindowsAreVisibleAtEnd() { testSpec.assertWmEnd { - isVisible(splitScreenApp.defaultWindowName) - isVisible(nonResizeableApp.defaultWindowName) + isAppWindowVisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt index fb1758975442..6f486b0ddfea 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.legacysplitscreen +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -23,16 +24,12 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesInVisible -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.layerBecomesInvisible -import com.android.server.wm.flicker.layerBecomesVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER -import com.android.wm.shell.flicker.dockedStackDividerIsInvisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -73,11 +70,11 @@ class LegacySplitScreenFromRecentNotSupportNonResizable( } } - override val ignoredWindows: List<String> - get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME, - splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, + TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -93,37 +90,73 @@ class LegacySplitScreenFromRecentNotSupportNonResizable( @Presubmit @Test - fun resizableAppLayerBecomesInvisible() = - testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) + fun resizableAppLayerBecomesInvisible() { + testSpec.assertLayers { + this.isVisible(splitScreenApp.component) + .then() + .isInvisible(splitScreenApp.component) + } + } @Presubmit @Test - fun nonResizableAppLayerBecomesVisible() = - testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppLayerBecomesVisible() { + testSpec.assertLayers { + this.isInvisible(nonResizeableApp.component) + .then() + .isVisible(nonResizeableApp.component) + } + } @Presubmit @Test - fun resizableAppWindowBecomesInvisible() = - testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName) + fun resizableAppWindowBecomesInvisible() { + testSpec.assertWm { + // when the activity gets PAUSED the window may still be marked as visible + // it will be updated in the next log entry. This occurs because we record 1x + // per frame, thus ignore activity check here + this.isAppWindowVisible(splitScreenApp.component) + .then() + // immediately after the window (after onResume and before perform relayout) + // the activity is invisible. This may or not be logged, since we record 1x + // per frame, thus ignore activity check here + .isAppWindowInvisible(splitScreenApp.component) + } + } - @Presubmit + @Postsubmit @Test - fun nonResizableAppWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppWindowBecomesVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(nonResizeableApp.component) + .then() + .isAppWindowVisible(nonResizeableApp.component) + } + } @Presubmit @Test - fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible() + fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() @Presubmit @Test fun onlyNonResizableAppWindowIsVisibleAtEnd() { testSpec.assertWmEnd { - isInvisible(splitScreenApp.defaultWindowName) - isVisible(nonResizeableApp.defaultWindowName) + isAppWindowInvisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt index a9c28efcdf44..f03c927b8d58 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt @@ -23,14 +23,12 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.layerBecomesVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER -import com.android.wm.shell.flicker.dockedStackDividerIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -71,11 +69,11 @@ class LegacySplitScreenFromRecentSupportNonResizable( } } - override val ignoredWindows: List<String> - get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME, - splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, + TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -91,27 +89,60 @@ class LegacySplitScreenFromRecentSupportNonResizable( @Presubmit @Test - fun nonResizableAppLayerBecomesVisible() = - testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppLayerBecomesVisible() { + testSpec.assertLayers { + this.isInvisible(nonResizeableApp.component) + .then() + .isVisible(nonResizeableApp.component) + } + } @Presubmit @Test - fun nonResizableAppWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppWindowBecomesVisible() { + testSpec.assertWm { + // when the app is launched, first the activity becomes visible, then the + // SnapshotStartingWindow appears and then the app window becomes visible. + // Because we log WM once per frame, sometimes the activity and the window + // become visible in the same entry, sometimes not, thus it is not possible to + // assert the visibility of the activity here + this.isAppWindowInvisible(nonResizeableApp.component) + .then() + // during re-parenting, the window may disappear and reappear from the + // trace, this occurs because we log only 1x per frame + .notContains(nonResizeableApp.component, isOptional = true) + .then() + // if the window reappears after re-parenting it will most likely not + // be visible in the first log entry (because we log only 1x per frame) + .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) + .then() + .isAppWindowVisible(nonResizeableApp.component) + } + } @Presubmit @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test fun bothAppsWindowsAreVisibleAtEnd() { testSpec.assertWmEnd { - isVisible(splitScreenApp.defaultWindowName) - isVisible(nonResizeableApp.defaultWindowName) + isAppWindowVisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index a4d2ab51e358..2ccd03bf1d6a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -16,10 +16,9 @@ package com.android.wm.shell.flicker.legacysplitscreen +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -27,21 +26,19 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation -import com.android.server.wm.flicker.focusDoesNotChange +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.exitSplitScreen import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.layerBecomesInvisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible import com.android.wm.shell.flicker.helpers.SimpleAppHelper import org.junit.FixMethodOrder @@ -62,8 +59,6 @@ import org.junit.runners.Parameterized class LegacySplitScreenToLauncher( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - private val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation) - .launcherStrategy.supportedLauncherPackage private val testApp = SimpleAppHelper(instrumentation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit @@ -90,51 +85,69 @@ class LegacySplitScreenToLauncher( } } - override val ignoredWindows: List<String> - get() = listOf(launcherPackageName, WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.endRotation) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - @Presubmit + @Postsubmit @Test fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible() + @Postsubmit + @Test + fun layerBecomesInvisible() { + testSpec.assertLayers { + this.isVisible(testApp.component) + .then() + .isInvisible(testApp.component) + } + } + + @Postsubmit + @Test + fun focusDoesNotChange() { + testSpec.assertEventLog { + this.focusDoesNotChange() + } + } + @Presubmit @Test - fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(testApp.getPackage()) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() - @FlakyTest(bugId = 151179149) + @Presubmit @Test - fun focusDoesNotChange() = testSpec.focusDoesNotChange() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt index e8d4d1e9ada2..661c8b69068e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt @@ -31,11 +31,14 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.After +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test @@ -46,12 +49,17 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation) protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation) - protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation) - .launcherStrategy.supportedLauncherPackage + protected val LAUNCHER_COMPONENT = FlickerComponentName("", + LauncherStrategyFactory.getInstance(instrumentation) + .launcherStrategy.supportedLauncherPackage) private var prevDevEnableNonResizableMultiWindow = 0 @Before open fun setup() { + // Only run legacy split tests when the system is using legacy split screen. + assumeTrue(SplitScreenHelper.isUsingLegacySplit()) + // Legacy split is having some issue with Shell transition, and will be deprecated soon. + assumeFalse(isShellTransitionsEnabled()) prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context) if (prevDevEnableNonResizableMultiWindow != 0) { // Turn off the development option @@ -70,8 +78,9 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa * * b/182720234 */ - open val ignoredWindows: List<String> = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + open val ignoredWindows: List<FlickerComponentName> = listOf( + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> @@ -138,9 +147,9 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa } companion object { - internal const val LIVE_WALLPAPER_PACKAGE_NAME = - "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2" - internal const val LETTERBOX_NAME = "Letterbox" - internal const val TOAST_NAME = "Toast" + internal val LIVE_WALLPAPER_COMPONENT = FlickerComponentName("", + "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2") + internal val LETTERBOX_COMPONENT = FlickerComponentName("", "Letterbox") + internal val TOAST_COMPONENT = FlickerComponentName("", "Toast") } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt index 05eb5f49a641..34eff80a04bc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt @@ -24,15 +24,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.focusChanges +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.layerBecomesVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.appPairsDividerBecomesVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -62,22 +58,28 @@ class OpenAppToLegacySplitScreen( } } - override val ignoredWindows: List<String> - get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @FlakyTest @Test - fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(splitScreenApp.getPackage()) + fun appWindowBecomesVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(splitScreenApp.component) + .then() + .isAppWindowVisible(splitScreenApp.component) + } + } - @FlakyTest + @Presubmit @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() @Presubmit @Test @@ -85,12 +87,27 @@ class OpenAppToLegacySplitScreen( @FlakyTest @Test - fun layerBecomesVisible() = testSpec.layerBecomesVisible(splitScreenApp.getPackage()) + fun layerBecomesVisible() { + testSpec.assertLayers { + this.isInvisible(splitScreenApp.component) + .then() + .isVisible(splitScreenApp.component) + } + } + + @Presubmit + @Test + fun focusChanges() { + testSpec.assertEventLog { + this.focusChanges(splitScreenApp.`package`, + "recents_animation_input_consumer", "NexusLauncherActivity") + } + } - @FlakyTest(bugId = 151179149) + @Presubmit @Test - fun focusChanges() = testSpec.focusChanges(splitScreenApp.`package`, - "recents_animation_input_consumer", "NexusLauncherActivity") + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index 3e83b6382939..58e1def6f37a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -27,24 +27,24 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.resizeSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.traces.layers.getVisibleBounds -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.helpers.SimpleAppHelper +import com.android.wm.shell.flicker.testapp.Components import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -101,16 +101,16 @@ class ResizeLegacySplitScreen( } @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @FlakyTest(bugId = 156223549) @Test fun topAppWindowIsAlwaysVisible() { testSpec.assertWm { - this.showsAppWindow(sSimpleActivity) + this.isAppWindowVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent()) } } @@ -118,45 +118,43 @@ class ResizeLegacySplitScreen( @Test fun bottomAppWindowIsAlwaysVisible() { testSpec.assertWm { - this.showsAppWindow(sImeActivity) + this.isAppWindowVisible(Components.ImeActivity.COMPONENT.toFlickerComponent()) } } @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.endRotation) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Test fun topAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.isVisible(sSimpleActivity) + this.isVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent()) } } @Test fun bottomAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.isVisible(sImeActivity) + this.isVisible(Components.ImeActivity.COMPONENT.toFlickerComponent()) } } @Test fun dividerLayerIsAlwaysVisible() { testSpec.assertLayers { - this.isVisible(DOCKED_STACK_DIVIDER) + this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) } } @@ -166,7 +164,7 @@ class ResizeLegacySplitScreen( testSpec.assertLayersStart { val displayBounds = WindowUtils.displayBounds val dividerBounds = - entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds + layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds val topAppBounds = Region(0, 0, dividerBounds.right, dividerBounds.top + WindowUtils.dockedStackDividerInset) @@ -174,8 +172,10 @@ class ResizeLegacySplitScreen( dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - visibleRegion("SimpleActivity").coversExactly(topAppBounds) - visibleRegion("ImeActivity").coversExactly(bottomAppBounds) + visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) + .coversExactly(topAppBounds) + visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent()) + .coversExactly(bottomAppBounds) } } @@ -185,7 +185,7 @@ class ResizeLegacySplitScreen( testSpec.assertLayersStart { val displayBounds = WindowUtils.displayBounds val dividerBounds = - entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds + layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds val topAppBounds = Region(0, 0, dividerBounds.right, dividerBounds.top + WindowUtils.dockedStackDividerInset) @@ -194,8 +194,10 @@ class ResizeLegacySplitScreen( displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - visibleRegion(sSimpleActivity).coversExactly(topAppBounds) - visibleRegion(sImeActivity).coversExactly(bottomAppBounds) + visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) + .coversExactly(topAppBounds) + visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent()) + .coversExactly(bottomAppBounds) } } @@ -207,8 +209,6 @@ class ResizeLegacySplitScreen( } companion object { - private const val sSimpleActivity = "SimpleActivity" - private const val sImeActivity = "ImeActivity" private val startRatio = Rational(1, 3) private val stopRatio = Rational(2, 3) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt index 58482eaae3f5..8a50bc0b20cf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt @@ -24,18 +24,16 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -66,38 +64,44 @@ class RotateOneLaunchedAppAndEnterSplitScreen( @Presubmit @Test - fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test - fun dockedStackPrimaryBoundsIsVisible() = - testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation, - splitScreenApp.defaultWindowName) + fun dockedStackPrimaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + splitScreenApp.component) - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @FlakyTest @Test - fun appWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(splitScreenApp.defaultWindowName) + fun appWindowBecomesVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(splitScreenApp.component) + .then() + .isAppWindowVisible(splitScreenApp.component) + } + } + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt index 06828d6adb26..84676a9186be 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt @@ -24,18 +24,16 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -66,35 +64,43 @@ class RotateOneLaunchedAppInSplitScreenMode( @Presubmit @Test - fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test - fun dockedStackPrimaryBoundsIsVisible() = testSpec.dockedStackPrimaryBoundsIsVisible( - testSpec.config.startRotation, splitScreenApp.defaultWindowName) + fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd( + testSpec.config.startRotation, splitScreenApp.component) - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales( - testSpec.config.startRotation, testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales( - testSpec.config.startRotation, testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @FlakyTest @Test - fun appWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(splitScreenApp.defaultWindowName) + fun appWindowBecomesVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(splitScreenApp.component) + .then() + .isAppWindowVisible(splitScreenApp.component) + } + } + + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt index f8e32bf171d8..2abdca9216f9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt @@ -18,26 +18,23 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible -import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -69,42 +66,63 @@ class RotateTwoLaunchedAppAndEnterSplitScreen( @Presubmit @Test - fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test - fun dockedStackPrimaryBoundsIsVisible() = - testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation, - splitScreenApp.defaultWindowName) + fun dockedStackPrimaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + splitScreenApp.component) @Presubmit @Test - fun dockedStackSecondaryBoundsIsVisible() = - testSpec.dockedStackSecondaryBoundsIsVisible(testSpec.config.startRotation, - secondaryApp.defaultWindowName) + fun dockedStackSecondaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + secondaryApp.component) - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales( - testSpec.config.startRotation, testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + + @Presubmit + @Test + fun appWindowBecomesVisible() { + testSpec.assertWm { + // when the app is launched, first the activity becomes visible, then the + // SnapshotStartingWindow appears and then the app window becomes visible. + // Because we log WM once per frame, sometimes the activity and the window + // become visible in the same entry, sometimes not, thus it is not possible to + // assert the visibility of the activity here + this.isAppWindowInvisible(secondaryApp.component) + .then() + // during re-parenting, the window may disappear and reappear from the + // trace, this occurs because we log only 1x per frame + .notContains(secondaryApp.component, isOptional = true) + .then() + // if the window reappears after re-parenting it will most likely not + // be visible in the first log entry (because we log only 1x per frame) + .isAppWindowInvisible(secondaryApp.component, isOptional = true) + .then() + .isAppWindowVisible(secondaryApp.component) + } + } @Presubmit @Test - fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp.defaultWindowName) + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt index cb246ca0b694..fe9b9f514015 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt @@ -24,20 +24,18 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible -import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -74,44 +72,55 @@ class RotateTwoLaunchedAppInSplitScreenMode( @Presubmit @Test - fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() @Presubmit @Test - fun dockedStackPrimaryBoundsIsVisible() = - testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation, - splitScreenApp.defaultWindowName) + fun dockedStackPrimaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + splitScreenApp.component) @Presubmit @Test - fun dockedStackSecondaryBoundsIsVisible() = - testSpec.dockedStackSecondaryBoundsIsVisible(testSpec.config.startRotation, - secondaryApp.defaultWindowName) + fun dockedStackSecondaryBoundsIsVisibleAtEnd() = + testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation, + secondaryApp.component) - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 169271943) + @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @FlakyTest @Test - fun appWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(secondaryApp.defaultWindowName) + fun appWindowBecomesVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(secondaryApp.component) + .then() + .isAppWindowVisible(secondaryApp.component) + } + } + + @Presubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Presubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt index 2a660747bc1d..f9b08000290f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 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. @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:JvmName("CommonAssertions") package com.android.wm.shell.flicker.pip -internal const val PIP_WINDOW_TITLE = "PipMenuActivity" +internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index b6af26060050..52a744f3897d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -23,6 +23,7 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder @@ -32,8 +33,21 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip launch. + * Test entering pip from an app by interacting with the app UI + * * To run this test: `atest WMShellFlickerTests:EnterPipTest` + * + * Actions: + * Launch an app in full screen + * Press an "enter pip" button to put [pipApp] in pip mode + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -41,49 +55,121 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { + /** + * Defines the transition used to run the test + */ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true, stringExtras = emptyMap()) { transitions { - pipApp.clickEnterPipButton() - pipApp.expandPipWindow(wmHelper) + pipApp.clickEnterPipButton(wmHelper) } } - @FlakyTest + /** + * Checks [pipApp] window remains visible throughout the animation + */ + @Presubmit @Test - override fun noUncoveredRegions() { - super.noUncoveredRegions() + fun pipAppWindowAlwaysVisible() { + testSpec.assertWm { + this.isAppWindowVisible(pipApp.component) + } } + /** + * Checks [pipApp] layer remains visible throughout the animation + */ @Presubmit @Test - fun pipAppWindowAlwaysVisible() { + fun pipAppLayerAlwaysVisible() { + testSpec.assertLayers { + this.isVisible(pipApp.component) + } + } + + /** + * Checks that the pip app window remains inside the display bounds throughout the whole + * animation + */ + @Presubmit + @Test + fun pipWindowRemainInsideVisibleBounds() { testSpec.assertWm { - this.showsAppWindow(pipApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.component) } } - @FlakyTest + /** + * Checks that the pip app layer remains inside the display bounds throughout the whole + * animation + */ + @Presubmit @Test - fun pipLayerBecomesVisible() { + fun pipLayerRemainInsideVisibleBounds() { testSpec.assertLayers { - this.isVisible(pipApp.windowName) + coversAtMost(displayBounds, pipApp.component) } } - @FlakyTest + /** + * Checks that the visible region of [pipApp] always reduces during the animation + */ + @Presubmit @Test - fun pipWindowBecomesVisible() { - testSpec.assertWm { - invoke("pipWindowIsNotVisible") { - verify("Has no pip window").that(it.wmState.hasPipWindow()).isTrue() - }.then().invoke("pipWindowIsVisible") { - verify("Has pip window").that(it.wmState.hasPipWindow()).isTrue() + fun pipLayerReduces() { + val layerName = pipApp.component.toLayerName() + testSpec.assertLayers { + val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + pipLayerList.zipWithNext { previous, current -> + current.visibleRegion.coversAtMost(previous.visibleRegion.region) } } } + /** + * Checks that [pipApp] window becomes pinned + */ + @Presubmit + @Test + fun pipWindowBecomesPinned() { + testSpec.assertWm { + invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.component) } + .then() + .invoke("pipWindowIsPinned") { it.isPinned(pipApp.component) } + } + } + + /** + * Checks [LAUNCHER_COMPONENT] layer remains visible throughout the animation + */ + @Presubmit + @Test + fun launcherLayerBecomesVisible() { + testSpec.assertLayers { + isInvisible(LAUNCHER_COMPONENT) + .then() + .isVisible(LAUNCHER_COMPONENT) + } + } + + /** + * Checks the focus doesn't change during the animation + */ + @FlakyTest + @Test + fun focusDoesNotChange() { + testSpec.assertEventLog { + this.focusDoesNotChange() + } + } + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<FlickerTestParameter> { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 3a1456e53f87..c8c3f4d64294 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -25,7 +25,11 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT @@ -38,8 +42,22 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip with orientation changes. - * To run this test: `atest WMShellFlickerTests:PipOrientationTest` + * Test entering pip while changing orientation (from app in landscape to pip window in portrait) + * + * To run this test: `atest EnterPipToOtherOrientationTest:EnterPipToOtherOrientationTest` + * + * Actions: + * Launch [testApp] on a fixed portrait orientation + * Launch [pipApp] on a fixed landscape orientation + * Broadcast action [ACTION_ENTER_PIP] to enter pip mode + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -53,6 +71,9 @@ class EnterPipToOtherOrientationTest( private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) + /** + * Defines the transition used to run the test + */ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setupAndTeardown(this, configuration) @@ -79,65 +100,125 @@ class EnterPipToOtherOrientationTest( broadcastActionTrigger.doAction(ACTION_ENTER_PIP) wmHelper.waitFor { it.wmState.hasPipWindow() } wmHelper.waitForAppTransitionIdle() + // during rotation the status bar becomes invisible and reappears at the end + wmHelper.waitForNavBarStatusBarVisible() } } + /** + * Checks that the [FlickerComponentName.NAV_BAR] has the correct position at + * the start and end of the transition + */ @FlakyTest @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @FlakyTest + /** + * Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at + * the start and end of the transition + */ + @Presubmit @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - @FlakyTest + /** + * Checks that all parts of the screen are covered at the start and end of the transition + * + * TODO b/197726599 Prevents all states from being checked + */ + @Presubmit @Test - override fun noUncoveredRegions() { - super.noUncoveredRegions() - } + override fun entireScreenCovered() = testSpec.entireScreenCovered(allStates = false) + /** + * Checks [pipApp] window remains visible and on top throughout the transition + */ @Presubmit @Test fun pipAppWindowIsAlwaysOnTop() { testSpec.assertWm { - showsAppWindowOnTop(pipApp.defaultWindowName) + isAppWindowOnTop(pipApp.component) } } + /** + * Checks that [testApp] window is not visible at the start + */ @Presubmit @Test - fun pipAppHidesTestApp() { + fun testAppWindowInvisibleOnStart() { testSpec.assertWmStart { - isInvisible(testApp.defaultWindowName) + isAppWindowInvisible(testApp.component) } } + /** + * Checks that [testApp] window is visible at the end + */ @Presubmit @Test - fun testAppWindowIsVisible() { + fun testAppWindowVisibleOnEnd() { testSpec.assertWmEnd { - isVisible(testApp.defaultWindowName) + isAppWindowVisible(testApp.component) + } + } + + /** + * Checks that [testApp] layer is not visible at the start + */ + @Presubmit + @Test + fun testAppLayerInvisibleOnStart() { + testSpec.assertLayersStart { + isInvisible(testApp.component) + } + } + + /** + * Checks that [testApp] layer is visible at the end + */ + @Presubmit + @Test + fun testAppLayerVisibleOnEnd() { + testSpec.assertLayersEnd { + isVisible(testApp.component) } } + /** + * Checks that the visible region of [pipApp] covers the full display area at the start of + * the transition + */ @Presubmit @Test - fun pipAppLayerHidesTestApp() { + fun pipAppLayerCoversFullScreenOnStart() { testSpec.assertLayersStart { - visibleRegion(pipApp.defaultWindowName).coversExactly(startingBounds) - isInvisible(testApp.defaultWindowName) + visibleRegion(pipApp.component).coversExactly(startingBounds) } } + /** + * Checks that the visible region of [testApp] plus the visible region of [pipApp] + * cover the full display area at the end of the transition + */ @Presubmit @Test - fun testAppLayerCoversFullScreen() { + fun testAppPlusPipLayerCoversFullScreenOnEnd() { testSpec.assertLayersEnd { - visibleRegion(testApp.defaultWindowName).coversExactly(endingBounds) + val pipRegion = visibleRegion(pipApp.component).region + visibleRegion(testApp.component) + .plus(pipRegion) + .coversExactly(endingBounds) } } companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt new file mode 100644 index 000000000000..64b7eb53bd6f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -0,0 +1,130 @@ +/* + * 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.pip + +import android.platform.test.annotations.Presubmit +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.wm.shell.flicker.helpers.FixedAppHelper +import org.junit.Test + +/** + * Base class for pip expand tests + */ +abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { + protected val testApp = FixedAppHelper(instrumentation) + + /** + * Checks that the pip app window remains inside the display bounds throughout the whole + * animation + */ + @Presubmit + @Test + open fun pipAppWindowRemainInsideVisibleBounds() { + testSpec.assertWm { + coversAtMost(displayBounds, pipApp.component) + } + } + + /** + * Checks that the pip app layer remains inside the display bounds throughout the whole + * animation + */ + @Presubmit + @Test + open fun pipAppLayerRemainInsideVisibleBounds() { + testSpec.assertLayers { + coversAtMost(displayBounds, pipApp.component) + } + } + + /** + * Checks both app windows are visible at the start of the transition (with [pipApp] on top). + * Then, during the transition, [testApp] becomes invisible and [pipApp] remains visible + */ + @Presubmit + @Test + open fun showBothAppWindowsThenHidePip() { + testSpec.assertWm { + // when the activity is STOPPING, sometimes it becomes invisible in an entry before + // the window, sometimes in the same entry. This occurs because we log 1x per frame + // thus we ignore activity here + isAppWindowVisible(testApp.component) + .isAppWindowOnTop(pipApp.component) + .then() + .isAppWindowInvisible(testApp.component) + .isAppWindowVisible(pipApp.component) + } + } + + /** + * Checks both app layers are visible at the start of the transition. Then, during the + * transition, [testApp] becomes invisible and [pipApp] remains visible + */ + @Presubmit + @Test + open fun showBothAppLayersThenHidePip() { + testSpec.assertLayers { + isVisible(testApp.component) + .isVisible(pipApp.component) + .then() + .isInvisible(testApp.component) + .isVisible(pipApp.component) + } + } + + /** + * Checks that the visible region of [testApp] plus the visible region of [pipApp] + * cover the full display area at the start of the transition + */ + @Presubmit + @Test + open fun testPlusPipAppsCoverFullScreenAtStart() { + testSpec.assertLayersStart { + val pipRegion = visibleRegion(pipApp.component).region + visibleRegion(testApp.component) + .plus(pipRegion) + .coversExactly(displayBounds) + } + } + + /** + * Checks that the visible region of [pipApp] covers the full display area at the end of + * the transition + */ + @Presubmit + @Test + open fun pipAppCoversFullScreenAtEnd() { + testSpec.assertLayersEnd { + visibleRegion(pipApp.component).coversExactly(displayBounds) + } + } + + /** + * Checks that the visible region of [pipApp] always expands during the animation + */ + @Presubmit + @Test + open fun pipLayerExpands() { + val layerName = pipApp.component.toLayerName() + testSpec.assertLayers { + val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + pipLayerList.zipWithNext { previous, current -> + current.visibleRegion.coversAtLeast(previous.visibleRegion.region) + } + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index eae7e973711c..5207fed59208 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -20,15 +20,16 @@ import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation import org.junit.Test -import org.junit.runners.Parameterized -abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +/** + * Base class for exiting pip (closing pip window) without returning to the app + */ +abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { configuration -> setup { @@ -43,37 +44,49 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio } } + /** + * Checks that [pipApp] window is pinned and visible at the start and then becomes + * unpinned and invisible at the same moment, and remains unpinned and invisible + * until the end of the transition + */ @Presubmit @Test open fun pipWindowBecomesInvisible() { testSpec.assertWm { - this.showsAppWindow(PIP_WINDOW_TITLE) - .then() - .hidesAppWindow(PIP_WINDOW_TITLE) + this.invoke("hasPipWindow") { + it.isPinned(pipApp.component).isAppWindowVisible(pipApp.component) + }.then().invoke("!hasPipWindow") { + it.isNotPinned(pipApp.component).isAppWindowInvisible(pipApp.component) + } } } + /** + * Checks that [pipApp] and [LAUNCHER_COMPONENT] layers are visible at the start + * of the transition. Then [pipApp] layer becomes invisible, and remains invisible + * until the end of the transition + */ @Presubmit @Test open fun pipLayerBecomesInvisible() { testSpec.assertLayers { - this.isVisible(PIP_WINDOW_TITLE) + this.isVisible(pipApp.component) + .isVisible(LAUNCHER_COMPONENT) .then() - .isInvisible(PIP_WINDOW_TITLE) + .isInvisible(pipApp.component) + .isVisible(LAUNCHER_COMPONENT) } } + /** + * Checks that the focus changes between the [pipApp] window and the launcher when + * closing the pip window + */ @FlakyTest(bugId = 151179149) @Test - open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity") - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + open fun focusChanges() { + testSpec.assertEventLog { + this.focusChanges(pipApp.launcherName, "NexusLauncherActivity") } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt new file mode 100644 index 000000000000..b53342d6f2f7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 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.pip + +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test expanding a pip window back to full screen via the expand button + * + * To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest` + * + * Actions: + * Launch an app in pip mode [pipApp], + * Launch another full screen mode [testApp] + * Expand [pipApp] app to full screen by clicking on the pip window and + * then on the expand button + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group3 +class ExitPipViaExpandButtonClickTest( + testSpec: FlickerTestParameter +) : ExitPipToAppTransition(testSpec) { + + /** + * Defines the transition used to run the test + */ + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition(eachRun = true) { + setup { + eachRun { + // launch an app behind the pip one + testApp.launchViaIntent(wmHelper) + } + } + transitions { + // This will bring PipApp to fullscreen + pipApp.expandPipWindowToApp(wmHelper) + // Wait until the other app is no longer visible + wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName()) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index 00e50e7fe3b5..1fec3cf85214 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -24,88 +23,62 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip launch and exit. - * To run this test: `atest WMShellFlickerTests:EnterExitPipTest` + * Test expanding a pip window back to full screen via an intent + * + * To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest` + * + * Actions: + * Launch an app in pip mode [pipApp], + * Launch another full screen mode [testApp] + * Expand [pipApp] app to full screen via an intent + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited from [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -class EnterExitPipTest( - testSpec: FlickerTestParameter -) : PipTransition(testSpec) { - private val testApp = FixedAppHelper(instrumentation) +class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) { + /** + * Defines the transition used to run the test + */ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { setup { eachRun { + // launch an app behind the pip one testApp.launchViaIntent(wmHelper) } } transitions { // This will bring PipApp to fullscreen pipApp.launchViaIntent(wmHelper) + // Wait until the other app is no longer visible + wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName()) } } - @Presubmit - @Test - fun pipAppRemainInsideVisibleBounds() { - testSpec.assertWm { - coversAtMost(displayBounds, pipApp.defaultWindowName) - } - } - - @Presubmit - @Test - fun showBothAppWindowsThenHidePip() { - testSpec.assertWm { - showsAppWindow(testApp.defaultWindowName) - .showsAppWindowOnTop(pipApp.defaultWindowName) - .then() - .hidesAppWindow(testApp.defaultWindowName) - } - } - - @Presubmit - @Test - fun showBothAppLayersThenHidePip() { - testSpec.assertLayers { - isVisible(testApp.defaultWindowName) - .isVisible(pipApp.defaultWindowName) - .then() - .isInvisible(testApp.defaultWindowName) - } - } - - @Presubmit - @Test - fun testAppCoversFullScreenWithPipOnDisplay() { - testSpec.assertLayersStart { - visibleRegion(testApp.defaultWindowName).coversExactly(displayBounds) - visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds) - } - } - - @Presubmit - @Test - fun pipAppCoversFullScreen() { - testSpec.assertLayersEnd { - visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds) - } - } - companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<FlickerTestParameter> { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt new file mode 100644 index 000000000000..73626c23065a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -0,0 +1,78 @@ +/* + * 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.pip + +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test closing a pip window via the dismiss button + * + * To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest` + * + * Actions: + * Launch an app in pip mode [pipApp], + * Click on the pip window + * Click on dismiss button and wait window disappear + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group3 +class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + transitions { + pipApp.closePipWindow(wmHelper) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 524a1b404591..9e43deef8d99 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -22,9 +22,9 @@ import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales import org.junit.FixMethodOrder import org.junit.Test @@ -33,42 +33,58 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip launch. - * To run this test: `atest WMShellFlickerTests:PipCloseWithSwipe` + * Test closing a pip window by swiping it to the bottom-center of the screen + * + * To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest` + * + * Actions: + * Launch an app in pip mode [pipApp], + * Swipe the pip window to the bottom-center of the screen and wait it disappear + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) { +class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) { override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit - get() = { - super.transition(this, it) + get() = { args -> + super.transition(this, args) transitions { val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds val pipCenterX = pipRegion.centerX() val pipCenterY = pipRegion.centerY() val displayCenterX = device.displayWidth / 2 - device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 5) + device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10) + wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } + wmHelper.waitForWindowSurfaceDisappeared(pipApp.component) + wmHelper.waitForAppTransitionIdle() } } @Presubmit @Test - override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() @Presubmit @Test - override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible() + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() @Presubmit @Test - override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() @Presubmit @Test - override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() @FlakyTest @Test @@ -80,14 +96,29 @@ class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition @Presubmit @Test - override fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test - override fun noUncoveredRegions() = super.noUncoveredRegions() + override fun entireScreenCovered() = super.entireScreenCovered() @Presubmit @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 20) + } + } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt new file mode 100644 index 000000000000..d0fee9a82093 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -0,0 +1,174 @@ +/* + * 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.pip + +import android.platform.test.annotations.Presubmit +import android.view.Surface +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test expanding a pip window by double clicking it + * + * To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest` + * + * Actions: + * Launch an app in pip mode [pipApp], + * Expand [pipApp] app to its maximum pip size by double clicking on it + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group3 +class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition(eachRun = true) { + transitions { + pipApp.doubleClickPipWindow(wmHelper) + } + } + + /** + * Checks that the pip app window remains inside the display bounds throughout the whole + * animation + */ + @Presubmit + @Test + fun pipWindowRemainInsideVisibleBounds() { + testSpec.assertWm { + coversAtMost(displayBounds, pipApp.component) + } + } + + /** + * Checks that the pip app layer remains inside the display bounds throughout the whole + * animation + */ + @Presubmit + @Test + fun pipLayerRemainInsideVisibleBounds() { + testSpec.assertLayers { + coversAtMost(displayBounds, pipApp.component) + } + } + + /** + * Checks [pipApp] window remains visible throughout the animation + */ + @Presubmit + @Test + fun pipWindowIsAlwaysVisible() { + testSpec.assertWm { + isAppWindowVisible(pipApp.component) + } + } + + /** + * Checks [pipApp] layer remains visible throughout the animation + */ + @Presubmit + @Test + fun pipLayerIsAlwaysVisible() { + testSpec.assertLayers { + isVisible(pipApp.component) + } + } + + /** + * Checks that the visible region of [pipApp] always expands during the animation + */ + @Presubmit + @Test + fun pipLayerExpands() { + val layerName = pipApp.component.toLayerName() + testSpec.assertLayers { + val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + pipLayerList.zipWithNext { previous, current -> + current.visibleRegion.coversAtLeast(previous.visibleRegion.region) + } + } + } + + /** + * Checks [pipApp] window remains pinned throughout the animation + */ + @Presubmit + @Test + fun windowIsAlwaysPinned() { + testSpec.assertWm { + this.invoke("hasPipWindow") { it.isPinned(pipApp.component) } + } + } + + /** + * Checks [pipApp] layer remains visible throughout the animation + */ + @Presubmit + @Test + fun launcherIsAlwaysVisible() { + testSpec.assertLayers { + isVisible(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that the focus doesn't change between windows during the transition + */ + @FlakyTest + @Test + fun focusDoesNotChange() { + testSpec.assertEventLog { + this.focusDoesNotChange() + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt new file mode 100644 index 000000000000..0ab857d755ee --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2021 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.pip + +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.traces.RegionSubject +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip movement with Launcher shelf height change (decrease). + * + * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest` + * + * Actions: + * Launch [pipApp] in pip mode + * Launch [testApp] + * Press home + * Check if pip window moves down (visually) + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group3 +class MovePipDownShelfHeightChangeTest( + testSpec: FlickerTestParameter +) : MovePipShelfHeightTransition(testSpec) { + /** + * Defines the transition used to run the test + */ + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition(eachRun = false) { + teardown { + eachRun { + testApp.launchViaIntent(wmHelper) + } + test { + testApp.exit(wmHelper) + } + } + transitions { + taplInstrumentation.pressHome() + } + } + + override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) { + current.isHigherOrEqual(previous.region) + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt new file mode 100644 index 000000000000..6e0324c17272 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 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.pip + +import android.platform.test.annotations.Presubmit +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.traces.RegionSubject +import com.android.wm.shell.flicker.helpers.FixedAppHelper +import org.junit.Test + +/** + * Base class for pip tests with Launcher shelf height change + */ +abstract class MovePipShelfHeightTransition( + testSpec: FlickerTestParameter +) : PipTransition(testSpec) { + protected val taplInstrumentation = LauncherInstrumentation() + protected val testApp = FixedAppHelper(instrumentation) + + /** + * Checks if the window movement direction is valid + */ + protected abstract fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) + + /** + * Checks [pipApp] window remains visible throughout the animation + */ + @Presubmit + @Test + open fun pipWindowIsAlwaysVisible() { + testSpec.assertWm { + isAppWindowVisible(pipApp.component) + } + } + + /** + * Checks [pipApp] layer remains visible throughout the animation + */ + @Presubmit + @Test + open fun pipLayerIsAlwaysVisible() { + testSpec.assertLayers { + isVisible(pipApp.component) + } + } + + /** + * Checks that the pip app window remains inside the display bounds throughout the whole + * animation + */ + @Presubmit + @Test + open fun pipWindowRemainInsideVisibleBounds() { + testSpec.assertWm { + coversAtMost(displayBounds, pipApp.component) + } + } + + /** + * Checks that the pip app layer remains inside the display bounds throughout the whole + * animation + */ + @Presubmit + @Test + open fun pipLayerRemainInsideVisibleBounds() { + testSpec.assertLayers { + coversAtMost(displayBounds, pipApp.component) + } + } + + /** + * Checks that the visible region of [pipApp] always moves in the correct direction + * during the animation. + */ + @Presubmit + @Test + open fun pipWindowMoves() { + val windowName = pipApp.component.toWindowName() + testSpec.assertWm { + val pipWindowList = this.windowStates { it.name.contains(windowName) && it.isVisible } + pipWindowList.zipWithNext { previous, current -> + assertRegionMovement(previous.frame, current.frame) + } + } + } + + /** + * Checks that the visible region of [pipApp] always moves up during the animation + */ + @Presubmit + @Test + open fun pipLayerMoves() { + val layerName = pipApp.component.toLayerName() + testSpec.assertLayers { + val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + pipLayerList.zipWithNext { previous, current -> + assertRegionMovement(previous.visibleRegion, current.visibleRegion) + } + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipShelfHeightTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index 1294ac93f647..e507edfda48c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipShelfHeightTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -16,36 +16,49 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice -import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.google.common.truth.Truth +import com.android.server.wm.flicker.traces.RegionSubject import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip movement with Launcher shelf height change. - * To run this test: `atest WMShellFlickerTests:PipShelfHeightTest` + * Test Pip movement with Launcher shelf height change (increase). + * + * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest` + * + * Actions: + * Launch [pipApp] in pip mode + * Press home + * Launch [testApp] + * Check if pip window moves up (visually) + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -class PipShelfHeightTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - private val taplInstrumentation = LauncherInstrumentation() - private val testApp = FixedAppHelper(instrumentation) - +class MovePipUpShelfHeightChangeTest( + testSpec: FlickerTestParameter +) : MovePipShelfHeightTransition(testSpec) { + /** + * Defines the transition used to run the test + */ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = false) { teardown { @@ -61,33 +74,17 @@ class PipShelfHeightTest(testSpec: FlickerTestParameter) : PipTransition(testSpe } } - @Presubmit - @Test - fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) } - - @Presubmit - @Test - fun pipLayerInsideDisplay() { - testSpec.assertLayersStart { - visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds) - } - } - - @Presubmit - @Test - fun pipWindowMovesUp() = testSpec.assertWmEnd { - val initialState = this.trace?.first()?.wmState - ?: error("Trace should not be empty") - val startPos = initialState.pinnedWindows.first().frame - val currPos = this.wmState.pinnedWindows.first().frame - val subject = Truth.assertWithMessage("Pip should have moved up") - subject.that(currPos.top).isGreaterThan(startPos.top) - subject.that(currPos.bottom).isGreaterThan(startPos.bottom) - subject.that(currPos.left).isEqualTo(startPos.left) - subject.that(currPos.right).isEqualTo(startPos.right) + override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) { + current.isLowerOrEqual(previous.region) } companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<FlickerTestParameter> { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index d88f94d5954a..aba8aced298f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -22,12 +22,12 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation -import com.android.wm.shell.flicker.IME_WINDOW_NAME +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.helpers.ImeAppHelper import org.junit.FixMethodOrder import org.junit.Test @@ -43,7 +43,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 +@Group4 class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) @@ -79,7 +79,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun pipInVisibleBounds() { testSpec.assertWm { val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - coversAtMost(displayBounds, pipApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.component) } } @@ -90,7 +90,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Test fun pipIsAboveAppWindow() { testSpec.assertWmTag(TAG_IME_VISIBLE) { - isAboveWindow(IME_WINDOW_NAME, pipApp.defaultWindowName) + isAboveWindow(FlickerComponentName.IME, pipApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index 6833b96a802b..9bea5c03dadb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -23,15 +23,20 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.wm.shell.flicker.helpers.ImeAppHelper -import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled +import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.wm.shell.flicker.helpers.ImeAppHelper +import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -46,12 +51,19 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@FlakyTest(bugId = 161435597) -@Group3 +@Group4 class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) private val testApp = FixedAppHelper(instrumentation) + @Before + open fun setup() { + // Only run legacy split tests when the system is using legacy split screen. + assumeTrue(SplitScreenHelper.isUsingLegacySplit()) + // Legacy split is having some issue with Shell transition, and will be deprecated soon. + assumeFalse(isShellTransitionsEnabled()) + } + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { withTestName { testSpec.name } @@ -80,11 +92,11 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t } } - @Presubmit + @FlakyTest(bugId = 161435597) @Test fun pipWindowInsideDisplayBounds() { testSpec.assertWm { - coversAtMost(displayBounds, pipApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.component) } } @@ -92,25 +104,17 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(testApp.defaultWindowName) - isVisible(imeApp.defaultWindowName) - noWindowsOverlap(testApp.defaultWindowName, imeApp.defaultWindowName) + isAppWindowVisible(testApp.component) + isAppWindowVisible(imeApp.component) + doNotOverlap(testApp.component, imeApp.component) } } - @Presubmit - @Test - override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() - - @Presubmit + @FlakyTest(bugId = 161435597) @Test fun pipLayerInsideDisplayBounds() { testSpec.assertLayers { - coversAtMost(displayBounds, pipApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.component) } } @@ -118,18 +122,14 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun bothAppLayersVisible() { testSpec.assertLayersEnd { - visibleRegion(testApp.defaultWindowName).coversAtMost(displayBounds) - visibleRegion(imeApp.defaultWindowName).coversAtMost(displayBounds) + visibleRegion(testApp.component).coversAtMost(displayBounds) + visibleRegion(imeApp.component).coversAtMost(displayBounds) } } - @Presubmit - @Test - override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() - - @Presubmit + @FlakyTest(bugId = 161435597) @Test - override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible() + override fun entireScreenCovered() = super.entireScreenCovered() companion object { const val TEST_REPETITIONS = 2 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index d531af28e2ad..669f37ad1e72 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -23,13 +23,13 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.wm.shell.flicker.helpers.FixedAppHelper @@ -41,17 +41,32 @@ import org.junit.runners.Parameterized /** * Test Pip Stack in bounds after rotations. + * * To run this test: `atest WMShellFlickerTests:PipRotationTest` + * + * Actions: + * Launch a [pipApp] in pip mode + * Launch another app [fixedApp] (appears below pip) + * Rotate the screen from [testSpec.config.startRotation] to [testSpec.config.endRotation] + * (usually, 0->90 and 90->0) + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited from [PipTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 +@Group4 class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val fixedApp = FixedAppHelper(instrumentation) - private val startingBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - private val endingBounds = WindowUtils.getDisplayBounds(testSpec.config.endRotation) + private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.config.endRotation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = false) { configuration -> @@ -66,49 +81,104 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) transitions { setRotation(configuration.endRotation) } - teardown { - eachRun { - setRotation(Surface.ROTATION_0) - } - } } - @FlakyTest(bugId = 185400889) + /** + * Checks that all parts of the screen are covered at the start and end of the transition + */ + @Presubmit @Test - override fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, - testSpec.config.endRotation, allStates = false) + override fun entireScreenCovered() = testSpec.entireScreenCovered() + /** + * Checks the position of the navigation bar at the start and end of the transition + */ @FlakyTest @Test - override fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + /** + * Checks the position of the status bar at the start and end of the transition + */ @Presubmit @Test - override fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, - testSpec.config.endRotation) + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - @FlakyTest(bugId = 185400889) + /** + * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition + */ + @Presubmit @Test fun appLayerRotates_StartingBounds() { testSpec.assertLayersStart { - visibleRegion(fixedApp.defaultWindowName).coversExactly(startingBounds) - visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds) + visibleRegion(fixedApp.component).coversExactly(screenBoundsStart) } } - @FlakyTest(bugId = 185400889) + /** + * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition + */ + @Presubmit @Test fun appLayerRotates_EndingBounds() { testSpec.assertLayersEnd { - visibleRegion(fixedApp.defaultWindowName).coversExactly(endingBounds) - visibleRegion(pipApp.defaultWindowName).coversAtMost(endingBounds) + visibleRegion(fixedApp.component).coversExactly(screenBoundsEnd) + } + } + + /** + * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition + */ + @Presubmit + @Test + fun pipLayerRotates_StartingBounds() { + testSpec.assertLayersStart { + visibleRegion(pipApp.component).coversAtMost(screenBoundsStart) + } + } + + /** + * Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition + */ + @Presubmit + @Test + fun pipLayerRotates_EndingBounds() { + testSpec.assertLayersEnd { + visibleRegion(pipApp.component).coversAtMost(screenBoundsEnd) + } + } + + /** + * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the + * transition + */ + @Presubmit + @Test + fun pipIsAboveFixedAppWindow_Start() { + testSpec.assertWmStart { + isAboveWindow(pipApp.component, fixedApp.component) + } + } + + /** + * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the + * transition + */ + @Presubmit + @Test + fun pipIsAboveFixedAppWindow_End() { + testSpec.assertWmEnd { + isAboveWindow(pipApp.component, fixedApp.component) } } companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt deleted file mode 100644 index 55e5c4128967..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.pip - -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.focusChanges -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.startRotation -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test Pip launch. - * To run this test: `atest WMShellFlickerTests:PipToAppTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit - get() = buildTransition(eachRun = true) { configuration -> - setup { - eachRun { - this.setRotation(configuration.startRotation) - } - } - teardown { - eachRun { - this.setRotation(Surface.ROTATION_0) - } - } - transitions { - pipApp.expandPipWindowToApp(wmHelper) - } - } - - @FlakyTest - @Test - fun appReplacesPipWindow() { - testSpec.assertWm { - this.showsAppWindow(PIP_WINDOW_TITLE) - .then() - .showsAppWindowOnTop(pipApp.launcherName) - } - } - - @FlakyTest - @Test - fun appReplacesPipLayer() { - testSpec.assertLayers { - this.isVisible(PIP_WINDOW_TITLE) - .then() - .isVisible(pipApp.launcherName) - } - } - - @FlakyTest - @Test - fun testAppCoversFullScreen() { - testSpec.assertLayersStart { - visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds) - } - } - - @FlakyTest(bugId = 151179149) - @Test - fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity", - pipApp.launcherName, "NexusLauncherActivity") - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index b4c75a6d1165..e8a61e8a1dae 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -20,25 +20,24 @@ import android.app.Instrumentation import android.content.Intent import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.isRotated import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.wm.shell.flicker.helpers.PipAppHelper import com.android.wm.shell.flicker.testapp.Components import org.junit.Test @@ -162,32 +161,29 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { @Presubmit @Test - open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + open fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - @FlakyTest + @Presubmit @Test - open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + open fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - @FlakyTest + @Presubmit @Test - open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + open fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() @Presubmit @Test - open fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - open fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test - open fun noUncoveredRegions() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) + open fun entireScreenCovered() = testSpec.entireScreenCovered() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 1f58bb2bf9db..d6dbc366aec0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -16,14 +16,13 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE @@ -44,7 +43,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 +@Group4 class SetRequestedOrientationWhilePinnedTest( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { @@ -83,55 +82,69 @@ class SetRequestedOrientationWhilePinnedTest( @FlakyTest @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + + @FlakyTest + @Test + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + + @FlakyTest + @Test + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() + + @FlakyTest + @Test + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + @FlakyTest + @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() @FlakyTest @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @FlakyTest @Test fun pipWindowInsideDisplay() { testSpec.assertWmStart { - frameRegion(pipApp.defaultWindowName).coversAtMost(startingBounds) + frameRegion(pipApp.component).coversAtMost(startingBounds) } } - @Presubmit + @FlakyTest @Test fun pipAppShowsOnTop() { testSpec.assertWmEnd { - showsAppWindowOnTop(pipApp.defaultWindowName) + isAppWindowOnTop(pipApp.component) } } - @Presubmit + @FlakyTest @Test fun pipLayerInsideDisplay() { testSpec.assertLayersStart { - visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds) + visibleRegion(pipApp.component).coversAtMost(startingBounds) } } - @Presubmit + @FlakyTest @Test fun pipAlwaysVisible() = testSpec.assertWm { - this.showsAppWindow(pipApp.windowName) + this.isAppWindowVisible(pipApp.component) } - @Presubmit + @FlakyTest @Test fun pipAppLayerCoversFullScreen() { testSpec.assertLayersEnd { - visibleRegion(pipApp.defaultWindowName).coversExactly(endingBounds) + visibleRegion(pipApp.component).coversExactly(endingBounds) } } @FlakyTest @Test - override fun noUncoveredRegions() { - super.noUncoveredRegions() - } + override fun entireScreenCovered() = super.entireScreenCovered() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt index 0110ba3f5b30..061218a015e4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt @@ -37,14 +37,17 @@ class TvPipMenuTests : TvPipTestBase() { private val systemUiResources = packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) private val pipBoundsWhileInMenu: Rect = systemUiResources.run { - val bounds = getString(getIdentifier("pip_menu_bounds", "string", SYSTEM_UI_PACKAGE_NAME)) + val bounds = getString(getIdentifier("pip_menu_bounds", "string", + SYSTEM_UI_PACKAGE_NAME)) Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds") } private val playButtonDescription = systemUiResources.run { - getString(getIdentifier("pip_play", "string", SYSTEM_UI_PACKAGE_NAME)) + getString(getIdentifier("pip_play", "string", + SYSTEM_UI_PACKAGE_NAME)) } private val pauseButtonDescription = systemUiResources.run { - getString(getIdentifier("pip_pause", "string", SYSTEM_UI_PACKAGE_NAME)) + getString(getIdentifier("pip_pause", "string", + SYSTEM_UI_PACKAGE_NAME)) } @Before diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt index 1b73920046dc..1c663409b913 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt @@ -70,7 +70,8 @@ fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? { // descendant and then retrieve the element from the menu and return to the caller of this // method. val elementSelector = By.desc(desc) - val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector) + val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR) + .hasDescendant(elementSelector) return wait(Until.findObject(menuContainingElementSelector), WAIT_TIME_MS) ?.findObject(elementSelector) @@ -94,7 +95,8 @@ fun UiDevice.clickTvPipMenuFullscreenButton() { } fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) { - focusOnAndClickTvPipMenuElement(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) || + focusOnAndClickTvPipMenuElement(By.desc(desc) + .pkg(SYSTEM_UI_PACKAGE_NAME)) || error("Could not focus on the Pip menu object with \"$desc\" description") // So apparently Accessibility framework on TV is not very reliable and sometimes the state of // the tree of accessibility nodes as seen by the accessibility clients kind of lags behind of diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml index 5549330df766..2cdbffa7589c 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml @@ -107,5 +107,20 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity + android:name=".LaunchBubbleActivity" + android:label="LaunchBubbleApp" + android:exported="true" + android:launchMode="singleTop"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <action android:name="android.intent.action.VIEW" /> + </intent-filter> + </activity> + <activity + android:name=".BubbleActivity" + android:label="BubbleApp" + android:exported="false" + android:resizeableActivity="true" /> </application> </manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png Binary files differnew file mode 100644 index 000000000000..d424a17b4157 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml new file mode 100644 index 000000000000..b43f31da748d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> + <path + android:fillColor="#FF000000" + android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> + <path + android:fillColor="#FF000000" + android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/> +</vector> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml new file mode 100644 index 000000000000..0e8c7a0fe64a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z" + android:fillColor="#000000" + android:fillType="evenOdd"/> +</vector> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml new file mode 100644 index 000000000000..f8b0ca3da26e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:id="@+id/button_finish" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_marginStart="8dp" + android:text="Finish" /> + <Button + android:id="@+id/button_new_task" + android:layout_width="wrap_content" + android:layout_height="46dp" + android:layout_marginStart="8dp" + android:text="New Task" /> + <Button + android:id="@+id/button_new_bubble" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:text="New Bubble" /> + + <Button + android:id="@+id/button_activity_for_result" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginStart="8dp" + android:text="Activity For Result" /> +</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml new file mode 100644 index 000000000000..f23c46455c63 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/black"> + + <Button + android:id="@+id/button_create" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Add Bubble" /> + + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/button_create" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:text="Cancel Bubble" /> + + <Button + android:id="@+id/button_cancel_all" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/button_cancel" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:text="Cancel All Bubble" /> +</RelativeLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java new file mode 100644 index 000000000000..bc3bc75ab903 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java @@ -0,0 +1,77 @@ +/* + * 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.testapp; + + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; + +public class BubbleActivity extends Activity { + private int mNotifId = 0; + + public BubbleActivity() { + super(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent != null) { + mNotifId = intent.getIntExtra(BubbleHelper.EXTRA_BUBBLE_NOTIF_ID, -1); + } else { + mNotifId = -1; + } + + setContentView(R.layout.activity_bubble); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + String result = resultCode == Activity.RESULT_OK ? "OK" : "CANCELLED"; + Toast.makeText(this, "Activity result: " + result, Toast.LENGTH_SHORT).show(); + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java new file mode 100644 index 000000000000..d743dffd3c9e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2021 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.testapp; + + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Person; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.graphics.drawable.Icon; +import android.os.SystemClock; +import android.service.notification.StatusBarNotification; +import android.view.WindowManager; + +import java.util.HashMap; + +public class BubbleHelper { + + static final String EXTRA_BUBBLE_NOTIF_ID = "EXTRA_BUBBLE_NOTIF_ID"; + static final String CHANNEL_ID = "bubbles"; + static final String CHANNEL_NAME = "Bubbles"; + static final int DEFAULT_HEIGHT_DP = 300; + + private static BubbleHelper sInstance; + + private final Context mContext; + private NotificationManager mNotificationManager; + private float mDisplayHeight; + + private HashMap<Integer, BubbleInfo> mBubbleMap = new HashMap<>(); + + private int mNextNotifyId = 0; + private int mColourIndex = 0; + + public static class BubbleInfo { + public int id; + public int height; + public Icon icon; + + public BubbleInfo(int id, int height, Icon icon) { + this.id = id; + this.height = height; + this.icon = icon; + } + } + + public static BubbleHelper getInstance(Context context) { + if (sInstance == null) { + sInstance = new BubbleHelper(context); + } + return sInstance; + } + + private BubbleHelper(Context context) { + mContext = context; + mNotificationManager = context.getSystemService(NotificationManager.class); + + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription("Channel that posts bubbles"); + channel.setAllowBubbles(true); + mNotificationManager.createNotificationChannel(channel); + + Point p = new Point(); + WindowManager wm = context.getSystemService(WindowManager.class); + wm.getDefaultDisplay().getRealSize(p); + mDisplayHeight = p.y; + + } + + private int getNextNotifyId() { + int id = mNextNotifyId; + mNextNotifyId++; + return id; + } + + private Icon getIcon() { + return Icon.createWithResource(mContext, R.drawable.bg); + } + + public int addNewBubble(boolean autoExpand, boolean suppressNotif) { + int id = getNextNotifyId(); + BubbleInfo info = new BubbleInfo(id, DEFAULT_HEIGHT_DP, getIcon()); + mBubbleMap.put(info.id, info); + + Notification.BubbleMetadata data = getBubbleBuilder(info) + .setSuppressNotification(suppressNotif) + .setAutoExpandBubble(false) + .build(); + Notification notification = getNotificationBuilder(info.id) + .setBubbleMetadata(data).build(); + + mNotificationManager.notify(info.id, notification); + return info.id; + } + + private Notification.Builder getNotificationBuilder(int id) { + Person chatBot = new Person.Builder() + .setBot(true) + .setName("BubbleBot") + .setImportant(true) + .build(); + + RemoteInput remoteInput = new RemoteInput.Builder("key") + .setLabel("Reply") + .build(); + + String shortcutId = "BubbleChat"; + return new Notification.Builder(mContext, CHANNEL_ID) + .setChannelId(CHANNEL_ID) + .setShortcutId(shortcutId) + .setContentIntent(PendingIntent.getActivity(mContext, 0, + new Intent(mContext, LaunchBubbleActivity.class), + PendingIntent.FLAG_UPDATE_CURRENT)) + .setStyle(new Notification.MessagingStyle(chatBot) + .setConversationTitle("Bubble Chat") + .addMessage("Hello? This is bubble: " + id, + SystemClock.currentThreadTimeMillis() - 300000, chatBot) + .addMessage("Is it me, " + id + ", you're looking for?", + SystemClock.currentThreadTimeMillis(), chatBot) + ) + .setSmallIcon(R.drawable.ic_bubble); + } + + private Notification.BubbleMetadata.Builder getBubbleBuilder(BubbleInfo info) { + Intent target = new Intent(mContext, BubbleActivity.class); + target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id); + PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target, + PendingIntent.FLAG_UPDATE_CURRENT); + + return new Notification.BubbleMetadata.Builder() + .setIntent(bubbleIntent) + .setIcon(info.icon) + .setDesiredHeight(info.height); + } + + public void cancel(int id) { + mNotificationManager.cancel(id); + } + + public void cancelAll() { + mNotificationManager.cancelAll(); + } + + public void cancelLast() { + StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); + if (activeNotifications.length > 0) { + mNotificationManager.cancel( + activeNotifications[activeNotifications.length - 1].getId()); + } + } + + public void cancelFirst() { + StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); + if (activeNotifications.length > 0) { + mNotificationManager.cancel(activeNotifications[0].getId()); + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java index 0ead91bb37de..0ed59bdafd1d 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java @@ -87,4 +87,16 @@ public class Components { public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".SplitScreenSecondaryActivity"); } + + public static class LaunchBubbleActivity { + public static final String LABEL = "LaunchBubbleApp"; + public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, + PACKAGE_NAME + ".LaunchBubbleActivity"); + } + + public static class BubbleActivity { + public static final String LABEL = "BubbleApp"; + public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, + PACKAGE_NAME + ".BubbleActivity"); + } } diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java new file mode 100644 index 000000000000..71fa66d8a61c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 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.testapp; + + +import android.app.Activity; +import android.app.Person; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.view.View; + +import java.util.Arrays; + +public class LaunchBubbleActivity extends Activity { + + private BubbleHelper mBubbleHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addInboxShortcut(getApplicationContext()); + mBubbleHelper = BubbleHelper.getInstance(this); + setContentView(R.layout.activity_main); + findViewById(R.id.button_create).setOnClickListener(this::add); + findViewById(R.id.button_cancel).setOnClickListener(this::cancel); + findViewById(R.id.button_cancel_all).setOnClickListener(this::cancelAll); + } + + private void add(View v) { + mBubbleHelper.addNewBubble(false /* autoExpand */, false /* suppressNotif */); + } + + private void cancel(View v) { + mBubbleHelper.cancelLast(); + } + + private void cancelAll(View v) { + mBubbleHelper.cancelAll(); + } + + private void addInboxShortcut(Context context) { + Icon icon = Icon.createWithResource(this, R.drawable.bg); + Person[] persons = new Person[4]; + for (int i = 0; i < persons.length; i++) { + persons[i] = new Person.Builder() + .setBot(false) + .setIcon(icon) + .setName("google" + i) + .setImportant(true) + .build(); + } + + ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "BubbleChat") + .setShortLabel("BubbleChat") + .setLongLived(true) + .setIntent(new Intent(Intent.ACTION_VIEW)) + .setIcon(Icon.createWithResource(context, R.drawable.ic_message)) + .setPersons(persons) + .build(); + ShortcutManager scmanager = context.getSystemService(ShortcutManager.class); + scmanager.addDynamicShortcuts(Arrays.asList(shortcut)); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 6b74b620dad7..a3b98a8fc880 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -56,7 +56,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.sizecompatui.SizeCompatUIController; +import com.android.wm.shell.compatui.CompatUIController; import org.junit.Before; import org.junit.Test; @@ -65,6 +65,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Optional; /** * Tests for the shell task organizer. @@ -81,7 +82,7 @@ public class ShellTaskOrganizerTests { @Mock private Context mContext; @Mock - private SizeCompatUIController mSizeCompatUI; + private CompatUIController mCompatUI; ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); @@ -131,7 +132,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mSizeCompatUI)); + mCompatUI, Optional.empty())); } @Test @@ -197,6 +198,43 @@ public class ShellTaskOrganizerTests { } @Test + public void testAddListenerForMultipleTypes() { + RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN); + mOrganizer.onTaskAppeared(taskInfo1, null); + RunningTaskInfo taskInfo2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW); + mOrganizer.onTaskAppeared(taskInfo2, null); + + TrackingTaskListener listener = new TrackingTaskListener(); + mOrganizer.addListenerForType(listener, + TASK_LISTENER_TYPE_MULTI_WINDOW, TASK_LISTENER_TYPE_FULLSCREEN); + + // onTaskAppeared event should be delivered once for each taskInfo. + assertTrue(listener.appeared.contains(taskInfo1)); + assertTrue(listener.appeared.contains(taskInfo2)); + assertEquals(2, listener.appeared.size()); + } + + @Test + public void testRemoveListenerForMultipleTypes() { + RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN); + mOrganizer.onTaskAppeared(taskInfo1, null); + RunningTaskInfo taskInfo2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW); + mOrganizer.onTaskAppeared(taskInfo2, null); + + TrackingTaskListener listener = new TrackingTaskListener(); + mOrganizer.addListenerForType(listener, + TASK_LISTENER_TYPE_MULTI_WINDOW, TASK_LISTENER_TYPE_FULLSCREEN); + + mOrganizer.removeListener(listener); + + // If listener is removed properly, onTaskInfoChanged event shouldn't be delivered. + mOrganizer.onTaskInfoChanged(taskInfo1); + assertTrue(listener.infoChanged.isEmpty()); + mOrganizer.onTaskInfoChanged(taskInfo2); + assertTrue(listener.infoChanged.isEmpty()); + } + + @Test public void testWindowingModeChange() { RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW); TrackingTaskListener mwListener = new TrackingTaskListener(); @@ -296,34 +334,34 @@ public class ShellTaskOrganizerTests { mOrganizer.onTaskAppeared(taskInfo1, null); // sizeCompatActivity is null if top activity is not in size compat. - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, null /* taskConfig */, null /* taskListener */); // sizeCompatActivity is non-null if top activity is in size compat. - clearInvocations(mSizeCompatUI); + clearInvocations(mCompatUI); final RunningTaskInfo taskInfo2 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo2.displayId = taskInfo1.displayId; taskInfo2.topActivityInSizeCompat = true; taskInfo2.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo2); - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, taskInfo1.configuration, taskListener); // Not show size compat UI if task is not visible. - clearInvocations(mSizeCompatUI); + clearInvocations(mCompatUI); final RunningTaskInfo taskInfo3 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo3.displayId = taskInfo1.displayId; taskInfo3.topActivityInSizeCompat = true; taskInfo3.isVisible = false; mOrganizer.onTaskInfoChanged(taskInfo3); - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, null /* taskConfig */, null /* taskListener */); - clearInvocations(mSizeCompatUI); + clearInvocations(mCompatUI); mOrganizer.onTaskVanished(taskInfo1); - verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, null /* taskConfig */, null /* taskListener */); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java index 5bdf831a81f4..6080f3ae78e8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java @@ -26,6 +26,7 @@ import androidx.test.InstrumentationRegistry; import org.junit.After; import org.junit.Before; +import org.mockito.MockitoAnnotations; /** * Base class that does shell test case setup. @@ -36,6 +37,7 @@ public abstract class ShellTestCase { @Before public void shellSetup() { + MockitoAnnotations.initMocks(this); final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final DisplayManager dm = context.getSystemService(DisplayManager.class); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java index 20ac5bf8fa84..1cbad155ba7b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java @@ -47,6 +47,8 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; import com.android.wm.shell.common.HandlerExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable; import org.junit.After; import org.junit.Before; @@ -71,6 +73,8 @@ public class TaskViewTest extends ShellTestCase { ShellTaskOrganizer mOrganizer; @Mock HandlerExecutor mExecutor; + @Mock + SyncTransactionQueue mSyncQueue; SurfaceSession mSession; SurfaceControl mLeash; @@ -99,7 +103,14 @@ public class TaskViewTest extends ShellTestCase { }).when(mExecutor).execute(any()); when(mOrganizer.getExecutor()).thenReturn(mExecutor); - mTaskView = new TaskView(mContext, mOrganizer); + + doAnswer((InvocationOnMock invocationOnMock) -> { + final TransactionRunnable r = invocationOnMock.getArgument(0); + r.runWithTransaction(new SurfaceControl.Transaction()); + return null; + }).when(mSyncQueue).runInSync(any()); + + mTaskView = new TaskView(mContext, mOrganizer, mSyncQueue); mTaskView.setListener(mExecutor, mViewListener); } @@ -112,7 +123,7 @@ public class TaskViewTest extends ShellTestCase { @Test public void testSetPendingListener_throwsException() { - TaskView taskView = new TaskView(mContext, mOrganizer); + TaskView taskView = new TaskView(mContext, mOrganizer, mSyncQueue); taskView.setListener(mExecutor, mViewListener); try { taskView.setListener(mExecutor, mViewListener); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java index 27c626170a4b..294bc1276291 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -30,7 +31,7 @@ public class TestAppPairsController extends AppPairsController { public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, DisplayController displayController) { super(organizer, syncQueue, displayController, mock(ShellExecutor.class), - mock(DisplayImeController.class)); + mock(DisplayImeController.class), mock(DisplayInsetsController.class)); mPool = new TestAppPairsPool(this); setPairsPool(mPool); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 3e3195fe8dc5..8bc1223cfd64 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -39,6 +39,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import android.util.Pair; import android.view.WindowManager; @@ -131,7 +132,7 @@ public class BubbleDataTest extends ShellTestCase { NotificationListenerService.Ranking ranking = mock(NotificationListenerService.Ranking.class); - when(ranking.visuallyInterruptive()).thenReturn(true); + when(ranking.isTextChanged()).thenReturn(true); mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking); mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null, mMainExecutor); @@ -793,7 +794,7 @@ public class BubbleDataTest extends ShellTestCase { } @Test - public void test_expanded_removeLastBubble_collapsesStack() { + public void test_expanded_removeLastBubble_showsOverflowIfNotEmpty() { // Setup sendUpdatedEntryAtTime(mEntryA1, 1000); changeExpandedStateAtTime(true, 2000); @@ -802,6 +803,21 @@ public class BubbleDataTest extends ShellTestCase { // Test mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE); verifyUpdateReceived(); + assertThat(mBubbleData.getOverflowBubbles().size()).isGreaterThan(0); + assertSelectionChangedTo(mBubbleData.getOverflow()); + } + + @Test + public void test_expanded_removeLastBubble_collapsesIfOverflowEmpty() { + // Setup + sendUpdatedEntryAtTime(mEntryA1, 1000); + changeExpandedStateAtTime(true, 2000); + mBubbleData.setListener(mListener); + + // Test + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NO_BUBBLE_UP); + verifyUpdateReceived(); + assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); assertExpandedChangedTo(false); } @@ -869,6 +885,60 @@ public class BubbleDataTest extends ShellTestCase { assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey())); } + /** + * Verifies that after the stack is collapsed with the overflow selected, it will select + * the top bubble upon next expansion. + */ + @Test + public void test_collapseWithOverflowSelected_nextExpansion() { + sendUpdatedEntryAtTime(mEntryA1, 1000); + sendUpdatedEntryAtTime(mEntryA2, 2000); + mBubbleData.setExpanded(true); + + mBubbleData.setListener(mListener); + + // Select the overflow + mBubbleData.setShowingOverflow(true); + mBubbleData.setSelectedBubble(mBubbleData.getOverflow()); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleData.getOverflow()); + + // Collapse + mBubbleData.setExpanded(false); + verifyUpdateReceived(); + assertSelectionNotChanged(); + + // Expand (here we should select the new bubble) + mBubbleData.setExpanded(true); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleA2); + } + + /** + * - have a maxed out bubble stack & all of the bubbles have been recently accessed + * - bubble a notification that was posted before any of those bubbles were accessed + * => that bubble should be added + * + */ + @Test + public void test_addOldNotifWithNewerBubbles() { + sendUpdatedEntryAtTime(mEntryA1, 2000); + sendUpdatedEntryAtTime(mEntryA2, 3000); + sendUpdatedEntryAtTime(mEntryA3, 4000); + sendUpdatedEntryAtTime(mEntryB1, 5000); + sendUpdatedEntryAtTime(mEntryB2, 6000); + + mBubbleData.setListener(mListener); + sendUpdatedEntryAtTime(mEntryB3, 1000 /* postTime */, 7000 /* currentTime */); + verifyUpdateReceived(); + + // B3 is in the stack + assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleB3.getKey())).isNotNull(); + // A1 is the oldest so it's in the overflow + assertThat(mBubbleData.getOverflowBubbleWithKey(mEntryA1.getKey())).isNotNull(); + assertOrderChangedTo(mBubbleB3, mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); @@ -902,7 +972,7 @@ public class BubbleDataTest extends ShellTestCase { assertWithMessage("selectionChanged").that(update.selectionChanged).isFalse(); } - private void assertSelectionChangedTo(Bubble bubble) { + private void assertSelectionChangedTo(BubbleViewProvider bubble) { BubbleData.Update update = mUpdateCaptor.getValue(); assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue(); assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble); @@ -925,7 +995,6 @@ public class BubbleDataTest extends ShellTestCase { assertThat(update.overflowBubbles).isEqualTo(bubbles); } - private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName, NotificationListenerService.Ranking ranking) { return createBubbleEntry(userId, notifKey, packageName, ranking, 1000); @@ -971,15 +1040,21 @@ public class BubbleDataTest extends ShellTestCase { } private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) { - sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */); + setCurrentTime(postTime); + sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */); + } + + private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, long currentTime) { + setCurrentTime(currentTime); + sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */); } private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, - boolean visuallyInterruptive) { + boolean textChanged) { setPostTime(entry, postTime); // BubbleController calls this: Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */); - b.setVisuallyInterruptiveForTest(visuallyInterruptive); + b.setTextChangedForTest(textChanged); // And then this mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/, true /* showInShade */); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java index 6644eaf28a62..5c1bcb9753a4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java @@ -63,7 +63,7 @@ public class BubbleFlyoutViewTest extends ShellTestCase { mFlyoutMessage.senderName = "Josh"; mFlyoutMessage.message = "Hello"; - mFlyout = new BubbleFlyoutView(getContext()); + mFlyout = new BubbleFlyoutView(getContext(), mPositioner); mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text); mSenderName = mFlyout.findViewById(R.id.bubble_flyout_name); @@ -75,9 +75,8 @@ public class BubbleFlyoutViewTest extends ShellTestCase { public void testShowFlyout_isVisible() { mFlyout.setupFlyoutStartingAsDot( mFlyoutMessage, - new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter, - false, - mPositioner); + new PointF(100, 100), true, Color.WHITE, null, null, mDotCenter, + false); mFlyout.setVisibility(View.VISIBLE); assertEquals("Hello", mFlyoutText.getText()); @@ -89,9 +88,8 @@ public class BubbleFlyoutViewTest extends ShellTestCase { public void testFlyoutHide_runsCallback() { Runnable after = mock(Runnable.class); mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage, - new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter, - false, - mPositioner); + new PointF(100, 100), true, Color.WHITE, null, after, mDotCenter, + false); mFlyout.hideFlyout(); verify(after).run(); @@ -100,9 +98,8 @@ public class BubbleFlyoutViewTest extends ShellTestCase { @Test public void testSetCollapsePercent() { mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage, - new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter, - false, - mPositioner); + new PointF(100, 100), true, Color.WHITE, null, null, mDotCenter, + false); mFlyout.setVisibility(View.VISIBLE); mFlyout.setCollapsePercent(1f); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java index 1eba3c266358..335222e98c6c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java @@ -16,9 +16,12 @@ package com.android.wm.shell.bubbles.animation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.annotation.SuppressLint; import android.content.res.Configuration; @@ -36,12 +39,12 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.BubbleStackView; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Spy; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -49,26 +52,32 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC private int mDisplayWidth = 500; private int mDisplayHeight = 1000; - private int mExpandedViewPadding = 10; private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class); - @Spy ExpandedAnimationController mExpandedController; private int mStackOffset; private PointF mExpansionPoint; + private BubblePositioner mPositioner; + private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState(); @SuppressLint("VisibleForTests") @Before public void setUp() throws Exception { super.setUp(); - BubblePositioner positioner = new BubblePositioner(getContext(), mock(WindowManager.class)); - positioner.updateInternal(Configuration.ORIENTATION_PORTRAIT, + mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class)); + mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT, Insets.of(0, 0, 0, 0), new Rect(0, 0, mDisplayWidth, mDisplayHeight)); - mExpandedController = new ExpandedAnimationController(positioner, mExpandedViewPadding, - mOnBubbleAnimatedOutAction); + + BubbleStackView stackView = mock(BubbleStackView.class); + when(stackView.getState()).thenReturn(getStackViewState()); + + mExpandedController = new ExpandedAnimationController(mPositioner, + mOnBubbleAnimatedOutAction, + stackView); + spyOn(mExpandedController); addOneMoreThanBubbleLimitBubbles(); mLayout.setActiveController(mExpandedController); @@ -78,6 +87,13 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC mExpansionPoint = new PointF(100, 100); } + public BubbleStackView.StackViewState getStackViewState() { + mStackViewState.numberOfBubbles = mLayout.getChildCount(); + mStackViewState.selectedIndex = 0; + mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint); + return mStackViewState; + } + @Test @Ignore public void testExpansionAndCollapse() throws InterruptedException { @@ -121,6 +137,12 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC testBubblesInCorrectExpandedPositions(); } + @Test + public void testDragBubbleOutDoesntNPE() throws InterruptedException { + mExpandedController.onGestureFinished(); + mExpandedController.dragBubbleOut(mViews.get(0), 1, 1); + } + /** Expand the stack and wait for animations to finish. */ private void expand() throws InterruptedException { mExpandedController.expandFromStack(mock(Runnable.class)); @@ -143,11 +165,12 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC private void testBubblesInCorrectExpandedPositions() { // Check all the visible bubbles to see if they're in the right place. for (int i = 0; i < mLayout.getChildCount(); i++) { - float expectedPosition = mExpandedController.getBubbleXOrYForOrientation(i); - assertEquals(expectedPosition, + PointF expectedPosition = mPositioner.getExpandedBubbleXY(i, + getStackViewState()); + assertEquals(expectedPosition.x, mLayout.getChildAt(i).getTranslationX(), 2f); - assertEquals(expectedPosition, + assertEquals(expectedPosition.y, mLayout.getChildAt(i).getTranslationY(), 2f); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index ef046d48e1cf..b88845044263 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -58,7 +58,7 @@ public class DisplayImeControllerTest { mT = mock(SurfaceControl.Transaction.class); mMock = mock(IInputMethodManager.class); mExecutor = spy(Runnable::run); - mPerDisplay = new DisplayImeController(null, null, mExecutor, new TransactionPool() { + mPerDisplay = new DisplayImeController(null, null, null, mExecutor, new TransactionPool() { @Override public SurfaceControl.Transaction acquire() { return mT; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java new file mode 100644 index 000000000000..b66c2b4aee9b --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2021 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.common; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.RemoteException; +import android.util.SparseArray; +import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; +import android.view.InsetsSourceControl; +import android.view.InsetsState; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.TestShellExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@SmallTest +public class DisplayInsetsControllerTest { + + private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 10; + + @Mock + private IWindowManager mWm; + @Mock + private DisplayController mDisplayController; + private DisplayInsetsController mController; + private SparseArray<IDisplayWindowInsetsController> mInsetsControllersByDisplayId; + private TestShellExecutor mExecutor; + + private ArgumentCaptor<Integer> mDisplayIdCaptor; + private ArgumentCaptor<IDisplayWindowInsetsController> mInsetsControllerCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mExecutor = new TestShellExecutor(); + mInsetsControllersByDisplayId = new SparseArray<>(); + mDisplayIdCaptor = ArgumentCaptor.forClass(Integer.class); + mInsetsControllerCaptor = ArgumentCaptor.forClass(IDisplayWindowInsetsController.class); + mController = new DisplayInsetsController(mWm, mDisplayController, mExecutor); + addDisplay(DEFAULT_DISPLAY); + } + + @Test + public void testOnDisplayAdded_setsDisplayWindowInsetsControllerOnWMService() + throws RemoteException { + addDisplay(SECOND_DISPLAY); + + verify(mWm).setDisplayWindowInsetsController(eq(SECOND_DISPLAY), notNull()); + } + + @Test + public void testOnDisplayRemoved_unsetsDisplayWindowInsetsControllerInWMService() + throws RemoteException { + addDisplay(SECOND_DISPLAY); + removeDisplay(SECOND_DISPLAY); + + verify(mWm).setDisplayWindowInsetsController(SECOND_DISPLAY, null); + } + + @Test + public void testPerDisplayListenerCallback() throws RemoteException { + TrackedListener defaultListener = new TrackedListener(); + TrackedListener secondListener = new TrackedListener(); + addDisplay(SECOND_DISPLAY); + mController.addInsetsChangedListener(DEFAULT_DISPLAY, defaultListener); + mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener); + + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false); + mExecutor.flushAll(); + + assertTrue(defaultListener.topFocusedWindowChangedCount == 1); + assertTrue(defaultListener.insetsChangedCount == 1); + assertTrue(defaultListener.insetsControlChangedCount == 1); + assertTrue(defaultListener.showInsetsCount == 1); + assertTrue(defaultListener.hideInsetsCount == 1); + + assertTrue(secondListener.topFocusedWindowChangedCount == 0); + assertTrue(secondListener.insetsChangedCount == 0); + assertTrue(secondListener.insetsControlChangedCount == 0); + assertTrue(secondListener.showInsetsCount == 0); + assertTrue(secondListener.hideInsetsCount == 0); + + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false); + mExecutor.flushAll(); + + assertTrue(defaultListener.topFocusedWindowChangedCount == 1); + assertTrue(defaultListener.insetsChangedCount == 1); + assertTrue(defaultListener.insetsControlChangedCount == 1); + assertTrue(defaultListener.showInsetsCount == 1); + assertTrue(defaultListener.hideInsetsCount == 1); + + assertTrue(secondListener.topFocusedWindowChangedCount == 1); + assertTrue(secondListener.insetsChangedCount == 1); + assertTrue(secondListener.insetsControlChangedCount == 1); + assertTrue(secondListener.showInsetsCount == 1); + assertTrue(secondListener.hideInsetsCount == 1); + } + + private void addDisplay(int displayId) throws RemoteException { + mController.onDisplayAdded(displayId); + verify(mWm, times(mInsetsControllersByDisplayId.size() + 1)) + .setDisplayWindowInsetsController(mDisplayIdCaptor.capture(), + mInsetsControllerCaptor.capture()); + List<Integer> displayIds = mDisplayIdCaptor.getAllValues(); + List<IDisplayWindowInsetsController> insetsControllers = + mInsetsControllerCaptor.getAllValues(); + for (int i = 0; i < displayIds.size(); i++) { + mInsetsControllersByDisplayId.put(displayIds.get(i), insetsControllers.get(i)); + } + } + + private void removeDisplay(int displayId) { + mController.onDisplayRemoved(displayId); + mInsetsControllersByDisplayId.remove(displayId); + } + + private static class TrackedListener implements + DisplayInsetsController.OnInsetsChangedListener { + int topFocusedWindowChangedCount = 0; + int insetsChangedCount = 0; + int insetsControlChangedCount = 0; + int showInsetsCount = 0; + int hideInsetsCount = 0; + + @Override + public void topFocusedWindowChanged(String packageName) { + topFocusedWindowChangedCount++; + } + + @Override + public void insetsChanged(InsetsState insetsState) { + insetsChangedCount++; + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + insetsControlChangedCount++; + } + + @Override + public void showInsets(int types, boolean fromIme) { + showInsetsCount++; + } + + @Override + public void hideInsets(int types, boolean fromIme) { + hideInsetsCount++; + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java index 88e754c58792..0ffa5b35331d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java @@ -21,9 +21,14 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.content.res.Resources; @@ -35,8 +40,12 @@ import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.policy.SystemBarUtils; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import org.mockito.MockitoSession; /** * Tests for {@link DisplayLayout}. @@ -46,29 +55,48 @@ import org.junit.Test; */ @SmallTest public class DisplayLayoutTest { + private MockitoSession mMockitoSession; + + @Before + public void setup() { + mMockitoSession = mockitoSession() + .initMocks(this) + .mockStatic(SystemBarUtils.class) + .startMocking(); + } + + @After + public void tearDown() { + mMockitoSession.finishMocking(); + } @Test public void testInsets() { - Resources res = createResources(40, 50, false, 30, 40); + Resources res = createResources(40, 50, false); // Test empty display, no bars or anything DisplayInfo info = createDisplayInfo(1000, 1500, 0, ROTATION_0); DisplayLayout dl = new DisplayLayout(info, res, false, false); + when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(40); + dl.recalcInsets(res); assertEquals(new Rect(0, 0, 0, 0), dl.stableInsets()); assertEquals(new Rect(0, 0, 0, 0), dl.nonDecorInsets()); // Test with bars dl = new DisplayLayout(info, res, true, true); + dl.recalcInsets(res); assertEquals(new Rect(0, 40, 0, 50), dl.stableInsets()); assertEquals(new Rect(0, 0, 0, 50), dl.nonDecorInsets()); // Test just cutout info = createDisplayInfo(1000, 1500, 60, ROTATION_0); dl = new DisplayLayout(info, res, false, false); + dl.recalcInsets(res); assertEquals(new Rect(0, 60, 0, 0), dl.stableInsets()); assertEquals(new Rect(0, 60, 0, 0), dl.nonDecorInsets()); // Test with bars and cutout dl = new DisplayLayout(info, res, true, true); + dl.recalcInsets(res); assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets()); assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets()); } @@ -76,27 +104,30 @@ public class DisplayLayoutTest { @Test public void testRotate() { // Basic rotate utility - Resources res = createResources(40, 50, false, 30, 40); + Resources res = createResources(40, 50, false); DisplayInfo info = createDisplayInfo(1000, 1500, 60, ROTATION_0); DisplayLayout dl = new DisplayLayout(info, res, true, true); + when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(40); + dl.recalcInsets(res); assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets()); assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets()); // Rotate to 90 + when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(30); dl.rotateTo(res, ROTATION_90); assertEquals(new Rect(60, 30, 0, 40), dl.stableInsets()); assertEquals(new Rect(60, 0, 0, 40), dl.nonDecorInsets()); // Rotate with moving navbar - res = createResources(40, 50, true, 30, 40); + res = createResources(40, 50, true); dl = new DisplayLayout(info, res, true, true); + when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(30); dl.rotateTo(res, ROTATION_270); assertEquals(new Rect(40, 30, 60, 0), dl.stableInsets()); assertEquals(new Rect(40, 0, 60, 0), dl.nonDecorInsets()); } - private Resources createResources( - int navLand, int navPort, boolean navMoves, int statusLand, int statusPort) { + private Resources createResources(int navLand, int navPort, boolean navMoves) { Configuration cfg = new Configuration(); cfg.uiMode = UI_MODE_TYPE_NORMAL; Resources res = mock(Resources.class); @@ -108,8 +139,6 @@ public class DisplayLayoutTest { doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height); doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width); doReturn(navMoves).when(res).getBoolean(R.bool.config_navBarCanMove); - doReturn(statusLand).when(res).getDimensionPixelSize(R.dimen.status_bar_height_landscape); - doReturn(statusPort).when(res).getDimensionPixelSize(R.dimen.status_bar_height_portrait); doReturn(cfg).when(res).getConfiguration(); return res; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 952dc31cdaee..453050fcfab4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -24,11 +24,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.graphics.Rect; -import android.view.SurfaceControl; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -42,6 +42,8 @@ import com.android.wm.shell.common.DisplayImeController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -50,42 +52,63 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class SplitLayoutTests extends ShellTestCase { @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler; - @Mock SurfaceControl mRootLeash; + @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; @Mock DisplayImeController mDisplayImeController; @Mock ShellTaskOrganizer mTaskOrganizer; + @Captor ArgumentCaptor<Runnable> mRunnableCaptor; private SplitLayout mSplitLayout; @Before public void setup() { MockitoAnnotations.initMocks(this); - mSplitLayout = new SplitLayout( + mSplitLayout = spy(new SplitLayout( "TestSplitLayout", mContext, - getConfiguration(false), + getConfiguration(), mSplitLayoutHandler, - b -> b.setParent(mRootLeash), + mCallbacks, mDisplayImeController, - mTaskOrganizer); + mTaskOrganizer, + false /* applyDismissingParallax */)); } @Test @UiThreadTest public void testUpdateConfiguration() { - mSplitLayout.init(); - assertThat(mSplitLayout.updateConfiguration(getConfiguration(false))).isFalse(); - assertThat(mSplitLayout.updateConfiguration(getConfiguration(true))).isTrue(); + final Configuration config = getConfiguration(); + + // Verify it returns true if new config won't affect split layout. + assertThat(mSplitLayout.updateConfiguration(config)).isFalse(); + + // Verify updateConfiguration returns true if the orientation changed. + config.orientation = ORIENTATION_LANDSCAPE; + assertThat(mSplitLayout.updateConfiguration(config)).isTrue(); + + // Verify updateConfiguration returns true if it rotated. + config.windowConfiguration.setRotation(1); + assertThat(mSplitLayout.updateConfiguration(config)).isTrue(); + + // Verify updateConfiguration returns true if the root bounds changed. + config.windowConfiguration.setBounds(new Rect(0, 0, 2160, 1080)); + assertThat(mSplitLayout.updateConfiguration(config)).isTrue(); } @Test public void testUpdateDivideBounds() { mSplitLayout.updateDivideBounds(anyInt()); - verify(mSplitLayoutHandler).onBoundsChanging(any(SplitLayout.class)); + verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class)); } @Test public void testSetDividePosition() { mSplitLayout.setDividePosition(anyInt()); - verify(mSplitLayoutHandler).onBoundsChanged(any(SplitLayout.class)); + verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); + } + + @Test + public void testSetDivideRatio() { + mSplitLayout.setDivideRatio(0.5f); + verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); } @Test @@ -96,24 +119,40 @@ public class SplitLayoutTests extends ShellTestCase { @Test @UiThreadTest - public void testSnapToDismissTarget() { + public void testSnapToDismissStart() { // verify it callbacks properly when the snap target indicates dismissing split. DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START); + mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); + waitDividerFlingFinished(); verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false)); - snapTarget = getSnapTarget(0 /* position */, + } + + @Test + @UiThreadTest + public void testSnapToDismissEnd() { + // verify it callbacks properly when the snap target indicates dismissing split. + DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END); + mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); + waitDividerFlingFinished(); verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true)); } - private static Configuration getConfiguration(boolean isLandscape) { + private void waitDividerFlingFinished() { + verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture()); + mRunnableCaptor.getValue().run(); + } + + private static Configuration getConfiguration() { final Configuration configuration = new Configuration(); configuration.unset(); - configuration.orientation = isLandscape ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; + configuration.orientation = ORIENTATION_PORTRAIT; + configuration.windowConfiguration.setRotation(0); configuration.windowConfiguration.setBounds( - new Rect(0, 0, isLandscape ? 2160 : 1080, isLandscape ? 1080 : 2160)); + new Rect(0, 0, 1080, 2160)); return configuration; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java index 698315a77d8e..9bb54a18063f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.graphics.Rect; -import android.view.SurfaceControl; +import android.view.InsetsState; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -40,8 +40,8 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class SplitWindowManagerTests extends ShellTestCase { - @Mock SurfaceControl mSurfaceControl; @Mock SplitLayout mSplitLayout; + @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; private SplitWindowManager mSplitWindowManager; @Before @@ -50,7 +50,7 @@ public class SplitWindowManagerTests extends ShellTestCase { final Configuration configuration = new Configuration(); configuration.setToDefaults(); mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration, - b -> b.setParent(mSurfaceControl)); + mCallbacks); when(mSplitLayout.getDividerBounds()).thenReturn( new Rect(0, 0, configuration.windowConfiguration.getBounds().width(), configuration.windowConfiguration.getBounds().height())); @@ -59,7 +59,7 @@ public class SplitWindowManagerTests extends ShellTestCase { @Test @UiThreadTest public void testInitRelease() { - mSplitWindowManager.init(mSplitLayout); + mSplitWindowManager.init(mSplitLayout, new InsetsState()); assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull(); mSplitWindowManager.release(); assertThat(mSplitWindowManager.getSurfaceControl()).isNull(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java new file mode 100644 index 000000000000..f622edb7f134 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2021 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.compatui; + +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.res.Configuration; +import android.testing.AndroidTestingRunner; +import android.view.InsetsSource; +import android.view.InsetsState; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link CompatUIController}. + * + * Build/Install/Run: + * atest WMShellUnitTests:CompatUIControllerTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class CompatUIControllerTest extends ShellTestCase { + private static final int DISPLAY_ID = 0; + private static final int TASK_ID = 12; + + private CompatUIController mController; + private @Mock DisplayController mMockDisplayController; + private @Mock DisplayInsetsController mMockDisplayInsetsController; + private @Mock DisplayLayout mMockDisplayLayout; + private @Mock DisplayImeController mMockImeController; + private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener; + private @Mock SyncTransactionQueue mMockSyncQueue; + private @Mock ShellExecutor mMockExecutor; + private @Mock CompatUIWindowManager mMockLayout; + + @Captor + ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt()); + doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId(); + doReturn(TASK_ID).when(mMockLayout).getTaskId(); + mController = new CompatUIController(mContext, mMockDisplayController, + mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) { + @Override + CompatUIWindowManager createLayout(Context context, int displayId, int taskId, + Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) { + return mMockLayout; + } + }; + spyOn(mController); + } + + @Test + public void testListenerRegistered() { + verify(mMockDisplayController).addDisplayWindowListener(mController); + verify(mMockImeController).addPositionProcessor(mController); + } + + @Test + public void testOnCompatInfoChanged() { + final Configuration taskConfig = new Configuration(); + + // Verify that the restart button is added with non-null size compat info. + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + + verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig), + eq(mMockTaskListener)); + + // Verify that the restart button is updated with non-null new size compat info. + final Configuration newTaskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener); + + verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener, + true /* show */); + + // Verify that the restart button is removed with null size compat info. + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener); + + verify(mMockLayout).release(); + } + + @Test + public void testOnDisplayAdded() { + mController.onDisplayAdded(DISPLAY_ID); + mController.onDisplayAdded(DISPLAY_ID + 1); + + verify(mMockDisplayInsetsController).addInsetsChangedListener(eq(DISPLAY_ID), any()); + verify(mMockDisplayInsetsController).addInsetsChangedListener(eq(DISPLAY_ID + 1), any()); + } + + @Test + public void testOnDisplayRemoved() { + mController.onDisplayAdded(DISPLAY_ID); + final Configuration taskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockTaskListener); + + mController.onDisplayRemoved(DISPLAY_ID + 1); + + verify(mMockLayout, never()).release(); + verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID), + any()); + + mController.onDisplayRemoved(DISPLAY_ID); + + verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any()); + verify(mMockLayout).release(); + } + + @Test + public void testOnDisplayConfigurationChanged() { + final Configuration taskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockTaskListener); + + final Configuration newTaskConfig = new Configuration(); + mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig); + + verify(mMockLayout, never()).updateDisplayLayout(any()); + + mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig); + + verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout); + } + + @Test + public void testInsetsChanged() { + mController.onDisplayAdded(DISPLAY_ID); + final Configuration taskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockTaskListener); + InsetsState insetsState = new InsetsState(); + InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + insetsSource.setFrame(0, 0, 1000, 1000); + insetsState.addSource(insetsSource); + + verify(mMockDisplayInsetsController).addInsetsChangedListener(eq(DISPLAY_ID), + mOnInsetsChangedListenerCaptor.capture()); + mOnInsetsChangedListenerCaptor.getValue().insetsChanged(insetsState); + + verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout); + + // No update if the insets state is the same. + clearInvocations(mMockLayout); + mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState)); + verify(mMockLayout, never()).updateDisplayLayout(mMockDisplayLayout); + } + + @Test + public void testChangeButtonVisibilityOnImeShowHide() { + final Configuration taskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + + // Verify that the restart button is hidden after IME is showing. + mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); + + verify(mMockLayout).updateVisibility(false); + + // Verify button remains hidden while IME is showing. + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + + verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener, + false /* show */); + + // Verify button is shown after IME is hidden. + mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */); + + verify(mMockLayout).updateVisibility(true); + } + + @Test + public void testChangeButtonVisibilityOnKeyguardOccludedChanged() { + final Configuration taskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + + // Verify that the restart button is hidden after keyguard becomes occluded. + mController.onKeyguardOccludedChanged(true); + + verify(mMockLayout).updateVisibility(false); + + // Verify button remains hidden while keyguard is occluded. + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + + verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener, + false /* show */); + + // Verify button is shown after keyguard becomes not occluded. + mController.onKeyguardOccludedChanged(false); + + verify(mMockLayout).updateVisibility(true); + } + + @Test + public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() { + final Configuration taskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + + mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); + mController.onKeyguardOccludedChanged(true); + + verify(mMockLayout, times(2)).updateVisibility(false); + + clearInvocations(mMockLayout); + + // Verify button remains hidden after keyguard becomes not occluded since IME is showing. + mController.onKeyguardOccludedChanged(false); + + verify(mMockLayout).updateVisibility(false); + + // Verify button is shown after IME is not showing. + mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */); + + verify(mMockLayout).updateVisibility(true); + } + + @Test + public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() { + final Configuration taskConfig = new Configuration(); + mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); + + mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); + mController.onKeyguardOccludedChanged(true); + + verify(mMockLayout, times(2)).updateVisibility(false); + + clearInvocations(mMockLayout); + + // Verify button remains hidden after IME is hidden since keyguard is occluded. + mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */); + + verify(mMockLayout).updateVisibility(false); + + // Verify button is shown after keyguard becomes not occluded. + mController.onKeyguardOccludedChanged(false); + + verify(mMockLayout).updateVisibility(true); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index a20a5e9e8d91..2c3987bc358d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -14,17 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.sizecompatui; +package com.android.wm.shell.compatui; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; +import android.view.SurfaceControlViewHost; import android.widget.ImageButton; +import android.widget.LinearLayout; import androidx.test.filters.SmallTest; @@ -41,55 +44,68 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Tests for {@link SizeCompatRestartButton}. + * Tests for {@link CompatUILayout}. * * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatRestartButtonTest + * atest WMShellUnitTests:CompatUILayoutTest */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class SizeCompatRestartButtonTest extends ShellTestCase { +public class CompatUILayoutTest extends ShellTestCase { private static final int TASK_ID = 1; @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private SizeCompatUIController.SizeCompatUICallback mCallback; + @Mock private CompatUIController.CompatUICallback mCallback; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; - @Mock private DisplayLayout mDisplayLayout; + @Mock private SurfaceControlViewHost mViewHost; - private SizeCompatUILayout mLayout; - private SizeCompatRestartButton mButton; + private CompatUIWindowManager mWindowManager; + private CompatUILayout mCompatUILayout; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext, - new Configuration(), TASK_ID, mTaskListener, mDisplayLayout, + mWindowManager = new CompatUIWindowManager(mContext, new Configuration(), + mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(), false /* hasShownHint */); - mButton = (SizeCompatRestartButton) - LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null); - mButton.inject(mLayout); - spyOn(mLayout); + mCompatUILayout = (CompatUILayout) + LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null); + mCompatUILayout.inject(mWindowManager); + + spyOn(mWindowManager); + spyOn(mCompatUILayout); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); } @Test - public void testOnClick() { - final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button); + public void testOnClickForRestartButton() { + final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button); button.performClick(); - verify(mLayout).onRestartButtonClicked(); + verify(mWindowManager).onRestartButtonClicked(); + doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout(); verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); } @Test - public void testOnLongClick() { - doNothing().when(mLayout).onRestartButtonLongClicked(); + public void testOnLongClickForRestartButton() { + doNothing().when(mWindowManager).onRestartButtonLongClicked(); - final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button); + final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button); button.performLongClick(); - verify(mLayout).onRestartButtonLongClicked(); + verify(mWindowManager).onRestartButtonLongClicked(); + } + + @Test + public void testOnClickForSizeCompatHint() { + mWindowManager.createLayout(true /* show */); + final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint); + sizeCompatHint.performClick(); + + verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java new file mode 100644 index 000000000000..d5dcf2e11a46 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2021 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.compatui; + +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.view.DisplayInfo; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link CompatUIWindowManager}. + * + * Build/Install/Run: + * atest WMShellUnitTests:CompatUIWindowManagerTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class CompatUIWindowManagerTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private CompatUILayout mCompatUILayout; + @Mock private SurfaceControlViewHost mViewHost; + private Configuration mTaskConfig; + + private CompatUIWindowManager mWindowManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTaskConfig = new Configuration(); + + mWindowManager = new CompatUIWindowManager(mContext, new Configuration(), + mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(), + false /* hasShownHint */); + + spyOn(mWindowManager); + doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout(); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + } + + @Test + public void testCreateSizeCompatButton() { + // Not create layout if show is false. + mWindowManager.createLayout(false /* show */); + + verify(mWindowManager, never()).inflateCompatUILayout(); + + // Not create hint popup. + mWindowManager.mShouldShowHint = false; + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager).inflateCompatUILayout(); + verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */); + + // Create hint popup. + mWindowManager.release(); + mWindowManager.mShouldShowHint = true; + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager, times(2)).inflateCompatUILayout(); + assertNotNull(mCompatUILayout); + verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */); + assertFalse(mWindowManager.mShouldShowHint); + } + + @Test + public void testRelease() { + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager).inflateCompatUILayout(); + + mWindowManager.release(); + + verify(mViewHost).release(); + } + + @Test + public void testUpdateCompatInfo() { + mWindowManager.createLayout(true /* show */); + + // No diff + clearInvocations(mWindowManager); + mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */); + + verify(mWindowManager, never()).updateSurfacePosition(); + verify(mWindowManager, never()).release(); + verify(mWindowManager, never()).createLayout(anyBoolean()); + + // Change task listener, recreate button. + clearInvocations(mWindowManager); + final ShellTaskOrganizer.TaskListener newTaskListener = mock( + ShellTaskOrganizer.TaskListener.class); + mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, + true /* show */); + + verify(mWindowManager).release(); + verify(mWindowManager).createLayout(anyBoolean()); + + // Change task bounds, update position. + clearInvocations(mWindowManager); + final Configuration newTaskConfiguration = new Configuration(); + newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000)); + mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener, + true /* show */); + + verify(mWindowManager).updateSurfacePosition(); + } + + @Test + public void testUpdateDisplayLayout() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout1); + verify(mWindowManager).updateSurfacePosition(); + + // No update if the display bounds is the same. + clearInvocations(mWindowManager); + final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + mWindowManager.updateDisplayLayout(displayLayout2); + verify(mWindowManager, never()).updateSurfacePosition(); + } + + @Test + public void testUpdateDisplayLayoutInsets() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + + // Update if the insets change on the existing display layout + clearInvocations(mWindowManager); + InsetsState insetsState = new InsetsState(); + InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + insetsSource.setFrame(0, 0, 1000, 1000); + insetsState.addSource(insetsSource); + displayLayout.setInsets(mContext.getResources(), insetsState); + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + } + + @Test + public void testUpdateVisibility() { + // Create button if it is not created. + mWindowManager.mCompatUILayout = null; + mWindowManager.updateVisibility(true /* show */); + + verify(mWindowManager).createLayout(true /* show */); + + // Hide button. + clearInvocations(mWindowManager); + doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility(); + mWindowManager.updateVisibility(false /* show */); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mCompatUILayout).setVisibility(View.GONE); + + // Show button. + doReturn(View.GONE).when(mCompatUILayout).getVisibility(); + mWindowManager.updateVisibility(true /* show */); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mCompatUILayout).setVisibility(View.VISIBLE); + } + + @Test + public void testAttachToParentSurface() { + final SurfaceControl.Builder b = new SurfaceControl.Builder(); + mWindowManager.attachToParentSurface(b); + + verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b); + } + + @Test + public void testOnRestartButtonClicked() { + mWindowManager.onRestartButtonClicked(); + + verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); + } + + @Test + public void testOnRestartButtonLongClicked_showHint() { + // Not create hint popup. + mWindowManager.mShouldShowHint = false; + mWindowManager.createLayout(true /* show */); + + verify(mWindowManager).inflateCompatUILayout(); + verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */); + + mWindowManager.onRestartButtonLongClicked(); + + verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java new file mode 100644 index 000000000000..9f745208d3ed --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -0,0 +1,80 @@ +/* + * 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.draganddrop; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.os.RemoteException; +import android.view.Display; +import android.view.DragEvent; +import android.view.View; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.UiEventLogger; +import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for the drag and drop controller. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DragAndDropControllerTest { + + @Mock + private Context mContext; + + @Mock + private DisplayController mDisplayController; + + @Mock + private UiEventLogger mUiEventLogger; + + private DragAndDropController mController; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger, + mock(IconProvider.class), mock(ShellExecutor.class)); + } + + @Test + public void testIgnoreNonDefaultDisplays() { + final int nonDefaultDisplayId = 12345; + final View dragLayout = mock(View.class); + final Display display = mock(Display.class); + doReturn(nonDefaultDisplayId).when(display).getDisplayId(); + doReturn(display).when(dragLayout).getDisplay(); + + // Expect no per-display layout to be added + mController.onDisplayAdded(nonDefaultDisplayId); + assertFalse(mController.onDrag(dragLayout, mock(DragEvent.class))); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index ba73d555e334..fe66e225ad4a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -24,15 +24,14 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -64,6 +63,7 @@ import android.view.DisplayInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.internal.logging.InstanceId; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -95,6 +95,9 @@ public class DragAndDropPolicyTest { @Mock private SplitScreenController mSplitScreenStarter; + @Mock + private InstanceId mLoggerSessionId; + private DisplayLayout mLandscapeDisplayLayout; private DisplayLayout mPortraitDisplayLayout; private Insets mInsets; @@ -144,7 +147,6 @@ public class DragAndDropPolicyTest { mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); - setInSplitScreen(false); setRunningTask(mFullscreenAppTask); } @@ -193,122 +195,56 @@ public class DragAndDropPolicyTest { : ActivityInfo.RESIZE_MODE_UNRESIZEABLE; } - private void setInSplitScreen(boolean inSplitscreen) { - doReturn(inSplitscreen).when(mSplitScreenStarter).isSplitScreenVisible(); - } - @Test public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() { setRunningTask(mHomeTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any()); + eq(SPLIT_POSITION_UNDEFINED), any()); } @Test - public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets() { + public void testDragAppOverFullscreenApp_expectSplitScreenTargets() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); + mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData); verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any()); + eq(SPLIT_POSITION_TOP_OR_LEFT), any()); reset(mSplitScreenStarter); mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); } @Test - public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets() { + public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mPortraitDisplayLayout, mActivityClipData); - ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); - - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any()); - reset(mSplitScreenStarter); - - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); - } - - @Test - public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets() { - setRunningTask(mNonResizeableFullscreenAppTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); + mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); + mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any()); - } - - @Test - public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets() { - setRunningTask(mFullscreenAppTask); - mPolicy.start(mLandscapeDisplayLayout, mNonResizeableActivityClipData); - ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); - - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any()); - } - - @Test - public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() { - setInSplitScreen(true); - setRunningTask(mSplitPrimaryAppTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); - ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); - - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any()); - reset(mSplitScreenStarter); - - // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); - } - - @Test - public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets() { - setInSplitScreen(true); - setRunningTask(mSplitPrimaryAppTask); - mPolicy.start(mPortraitDisplayLayout, mActivityClipData); - ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); - - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData); verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any()); + eq(SPLIT_POSITION_TOP_OR_LEFT), any()); reset(mSplitScreenStarter); - // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); verify(mSplitScreenStarter).startIntent(any(), any(), - eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); } @Test public void testTargetHitRects() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); ArrayList<Target> targets = mPolicy.getTargets(mInsets); for (Target t : targets) { assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java new file mode 100644 index 000000000000..9cbdf1e2dbb6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 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.fullscreen; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager.RunningTaskInfo; +import android.app.WindowConfiguration; +import android.content.res.Configuration; +import android.graphics.Point; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.recents.RecentTasksController; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +@SmallTest +public class FullscreenTaskListenerTest { + + @Mock + private SyncTransactionQueue mSyncQueue; + @Mock + private FullscreenUnfoldController mUnfoldController; + @Mock + private RecentTasksController mRecentTasksController; + @Mock + private SurfaceControl mSurfaceControl; + + private Optional<FullscreenUnfoldController> mFullscreenUnfoldController; + + private FullscreenTaskListener mListener; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mFullscreenUnfoldController = Optional.of(mUnfoldController); + mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController, + Optional.empty()); + } + + @Test + public void testAnimatableTaskAppeared_notifiesUnfoldController() { + RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0); + + mListener.onTaskAppeared(info, mSurfaceControl); + + verify(mUnfoldController).onTaskAppeared(eq(info), any()); + } + + @Test + public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() { + RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0); + RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1); + + mListener.onTaskAppeared(animatable1, mSurfaceControl); + mListener.onTaskAppeared(animatable2, mSurfaceControl); + + InOrder order = inOrder(mUnfoldController); + order.verify(mUnfoldController).onTaskAppeared(eq(animatable1), any()); + order.verify(mUnfoldController).onTaskAppeared(eq(animatable2), any()); + } + + @Test + public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() { + RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); + + mListener.onTaskAppeared(info, mSurfaceControl); + + verifyNoMoreInteractions(mUnfoldController); + } + + @Test + public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() { + RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); + mListener.onTaskAppeared(info, mSurfaceControl); + + mListener.onTaskInfoChanged(info); + + verifyNoMoreInteractions(mUnfoldController); + } + + @Test + public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() { + RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); + mListener.onTaskAppeared(info, mSurfaceControl); + + mListener.onTaskVanished(info); + + verifyNoMoreInteractions(mUnfoldController); + } + + @Test + public void testAnimatableTaskBecameInactive_notifiesUnfoldController() { + RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0); + mListener.onTaskAppeared(animatableTask, mSurfaceControl); + RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0); + + mListener.onTaskInfoChanged(notAnimatableTask); + + verify(mUnfoldController).onTaskVanished(eq(notAnimatableTask)); + } + + @Test + public void testAnimatableTaskVanished_notifiesUnfoldController() { + RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0); + mListener.onTaskAppeared(taskInfo, mSurfaceControl); + + mListener.onTaskVanished(taskInfo); + + verify(mUnfoldController).onTaskVanished(eq(taskInfo)); + } + + private RunningTaskInfo createTaskInfo(boolean visible, int taskId) { + final RunningTaskInfo info = spy(new RunningTaskInfo()); + info.isVisible = visible; + info.positionInParent = new Point(); + when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + final Configuration configuration = new Configuration(); + configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); + when(info.getConfiguration()).thenReturn(configuration); + info.taskId = taskId; + return info; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java index 3c124bafc18a..078e2b6cf574 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java @@ -48,7 +48,6 @@ import android.window.WindowContainerToken; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import com.android.internal.R; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; @@ -124,6 +123,7 @@ public class HideDisplayCutoutOrganizerTest { @Test public void testEnableHideDisplayCutout() { + doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight(); mOrganizer.enableHideDisplayCutout(); verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT); @@ -154,8 +154,7 @@ public class HideDisplayCutoutOrganizerTest { doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation(); doReturn(mFakeDefaultCutoutInsets).when(mOrganizer) .getDisplayCutoutInsetsOfNaturalOrientation(); - mContext.getOrCreateTestableResources().addOverride( - R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait); + doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight(); doReturn(Surface.ROTATION_0).when(mDisplayLayout).rotation(); mOrganizer.enableHideDisplayCutout(); @@ -173,8 +172,7 @@ public class HideDisplayCutoutOrganizerTest { doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation(); doReturn(mFakeDefaultCutoutInsets).when(mOrganizer) .getDisplayCutoutInsetsOfNaturalOrientation(); - mContext.getOrCreateTestableResources().addOverride( - R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape); + doReturn(mFakeStatusBarHeightLandscape).when(mOrganizer).getStatusBarHeight(); doReturn(Surface.ROTATION_90).when(mDisplayLayout).rotation(); mOrganizer.enableHideDisplayCutout(); @@ -192,8 +190,7 @@ public class HideDisplayCutoutOrganizerTest { doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation(); doReturn(mFakeDefaultCutoutInsets).when(mOrganizer) .getDisplayCutoutInsetsOfNaturalOrientation(); - mContext.getOrCreateTestableResources().addOverride( - R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape); + doReturn(mFakeStatusBarHeightLandscape).when(mOrganizer).getStatusBarHeight(); doReturn(Surface.ROTATION_270).when(mDisplayLayout).rotation(); mOrganizer.enableHideDisplayCutout(); @@ -211,8 +208,7 @@ public class HideDisplayCutoutOrganizerTest { doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation(); doReturn(mFakeDefaultCutoutInsets).when(mOrganizer) .getDisplayCutoutInsetsOfNaturalOrientation(); - mContext.getOrCreateTestableResources().addOverride( - R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait); + doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight(); mOrganizer.enableHideDisplayCutout(); // disable hide display cutout @@ -230,8 +226,7 @@ public class HideDisplayCutoutOrganizerTest { doReturn(200).when(mDisplayLayout).height(); doReturn(mFakeDefaultCutoutInsets).when(mOrganizer) .getDisplayCutoutInsetsOfNaturalOrientation(); - mContext.getOrCreateTestableResources().addOverride( - R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait); + doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight(); doReturn(Surface.ROTATION_0).when(mDisplayLayout).rotation(); mOrganizer.enableHideDisplayCutout(); assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 15, 100, 200)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 911fe0753845..0a3a84923053 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -42,6 +42,7 @@ import android.util.ArrayMap; import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; +import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; @@ -332,6 +333,58 @@ public class OneHandedControllerTest extends OneHandedTestCase { } @Test + public void testOneHandedEnabledRotation90ShouldHandleRotate() { + when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(true); + when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( + false); + final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); + mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, handlerWCT); + + verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext), + eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); + } + + @Test + public void testOneHandedDisabledRotation90ShouldNotHandleRotate() { + when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(false); + when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( + false); + final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); + mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, handlerWCT); + + verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext), + eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); + } + + @Test + public void testSwipeToNotificationEnabledRotation90ShouldNotHandleRotate() { + when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(true); + when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( + true); + final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); + mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, handlerWCT); + + verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext), + eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); + } + + @Test + public void testSwipeToNotificationDisabledRotation90ShouldHandleRotate() { + when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(true); + when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( + false); + final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); + mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, handlerWCT); + + verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext), + eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); + } + + @Test public void testStateActive_shortcutRequestActivate_skipActions() { when(mSpiedTransitionState.getState()).thenReturn(STATE_ACTIVE); when(mSpiedTransitionState.isTransitioning()).thenReturn(false); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index a6215d3347a8..8e30f65cee78 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -188,7 +188,7 @@ public class PipBoundsStateTest extends ShellTestCase { final Rect newBounds = new Rect(50, 50, 100, 75); mPipBoundsState.setBounds(currentBounds); - mPipBoundsState.setPipExclusionBoundsChangeCallback(callback); + mPipBoundsState.addPipExclusionBoundsChangeCallback(callback); // Setting the listener immediately calls back with the current bounds. verify(callback).accept(currentBounds); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 9d7c82bb8550..0172cf324eea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PhonePipMenuController; +import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; @@ -75,10 +76,12 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipTransitionController mMockPipTransitionController; @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; - @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen; + @Mock private Optional<LegacySplitScreenController> mMockOptionalLegacySplitScreen; + @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; private TestShellExecutor mMainExecutor; private PipBoundsState mPipBoundsState; + private PipTransitionState mPipTransitionState; private PipBoundsAlgorithm mPipBoundsAlgorithm; private ComponentName mComponent1; @@ -90,15 +93,17 @@ public class PipTaskOrganizerTest extends ShellTestCase { mComponent1 = new ComponentName(mContext, "component1"); mComponent2 = new ComponentName(mContext, "component2"); mPipBoundsState = new PipBoundsState(mContext); + mPipTransitionState = new PipTransitionState(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, new PipSnapAlgorithm()); mMainExecutor = new TestShellExecutor(); mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, - mMockSyncTransactionQueue, mPipBoundsState, + mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController, mMockPipSurfaceTransactionHelper, - mMockPipTransitionController, mMockOptionalSplitScreen, mMockDisplayController, - mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor)); + mMockPipTransitionController, mMockOptionalLegacySplitScreen, + mMockOptionalSplitScreen, mMockDisplayController, mMockPipUiEventLogger, + mMockShellTaskOrganizer, mMainExecutor)); mMainExecutor.flushAll(); preparePipTaskOrg(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java new file mode 100644 index 000000000000..50f6bd7b4927 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2021 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.recents; + +import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import static java.lang.Integer.MAX_VALUE; + +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Rect; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.util.GroupedRecentTaskInfo; +import com.android.wm.shell.util.StagedSplitBounds; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; + +/** + * Tests for {@link RecentTasksController}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RecentTasksControllerTest extends ShellTestCase { + + @Mock + private Context mContext; + @Mock + private TaskStackListenerImpl mTaskStackListener; + + private ShellTaskOrganizer mShellTaskOrganizer; + private RecentTasksController mRecentTasksController; + private ShellExecutor mMainExecutor; + + @Before + public void setUp() { + mMainExecutor = new TestShellExecutor(); + mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener, + mMainExecutor)); + mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext, + null /* sizeCompatUI */, Optional.of(mRecentTasksController)); + } + + @Test + public void testAddRemoveSplitNotifyChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + setRawList(t1, t2); + + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(StagedSplitBounds.class)); + verify(mRecentTasksController).notifyRecentTasksChanged(); + + reset(mRecentTasksController); + mRecentTasksController.removeSplitPair(t1.taskId); + verify(mRecentTasksController).notifyRecentTasksChanged(); + } + + @Test + public void testAddSameSplitBoundsInfoSkipNotifyChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + setRawList(t1, t2); + + // Verify only one update if the split info is the same + StagedSplitBounds bounds1 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1); + StagedSplitBounds bounds2 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2); + verify(mRecentTasksController, times(1)).notifyRecentTasksChanged(); + } + + @Test + public void testGetRecentTasks() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + setRawList(t1, t2, t3); + + ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + assertGroupedTasksListEquals(recentTasks, + t1.taskId, -1, + t2.taskId, -1, + t3.taskId, -1); + } + + @Test + public void testGetRecentTasks_withPairs() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + setRawList(t1, t2, t3, t4, t5, t6); + + // Mark a couple pairs [t2, t4], [t3, t5] + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4); + StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5); + + mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); + mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); + + ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + assertGroupedTasksListEquals(recentTasks, + t1.taskId, -1, + t2.taskId, t4.taskId, + t3.taskId, t5.taskId, + t6.taskId, -1); + } + + @Test + public void testRemovedTaskRemovesSplit() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + setRawList(t1, t2, t3); + + // Add a pair + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3); + mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds); + reset(mRecentTasksController); + + // Remove one of the tasks and ensure the pair is removed + SurfaceControl mockLeash = mock(SurfaceControl.class); + ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2); + mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash); + mShellTaskOrganizer.onTaskVanished(rt2); + + verify(mRecentTasksController).removeSplitPair(t2.taskId); + } + + @Test + public void testTaskWindowingModeChangedNotifiesChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + setRawList(t1); + + // Remove one of the tasks and ensure the pair is removed + SurfaceControl mockLeash = mock(SurfaceControl.class); + ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2); + rt2Fullscreen.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_FULLSCREEN); + mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash); + + // Change the windowing mode and ensure the recent tasks change is notified + ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2); + rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW); + mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow); + + verify(mRecentTasksController).notifyRecentTasksChanged(); + } + + /** + * Helper to create a task with a given task id. + */ + private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { + ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); + info.taskId = taskId; + return info; + } + + /** + * Helper to create a running task with a given task id. + */ + private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) { + ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); + info.taskId = taskId; + return info; + } + + /** + * Helper to set the raw task list on the controller. + */ + private ArrayList<ActivityManager.RecentTaskInfo> setRawList( + ActivityManager.RecentTaskInfo... tasks) { + ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>(); + for (ActivityManager.RecentTaskInfo task : tasks) { + rawList.add(task); + } + doReturn(rawList).when(mRecentTasksController).getRawRecentTasks(anyInt(), anyInt(), + anyInt()); + return rawList; + } + + /** + * Asserts that the recent tasks matches the given task ids. + * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in + * the grouped task list + */ + private void assertGroupedTasksListEquals(ArrayList<GroupedRecentTaskInfo> recentTasks, + int... expectedTaskIds) { + int[] flattenedTaskIds = new int[recentTasks.size() * 2]; + for (int i = 0; i < recentTasks.size(); i++) { + GroupedRecentTaskInfo pair = recentTasks.get(i); + int taskId1 = pair.mTaskInfo1.taskId; + flattenedTaskIds[2 * i] = taskId1; + flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null + ? pair.mTaskInfo2.taskId + : -1; + + if (pair.mTaskInfo2 != null) { + assertNotNull(pair.mStagedSplitBounds); + int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId; + int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId; + // Unclear if pairs are ordered by split position, most likely not. + assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId); + assertTrue(bottomRightTaskId == taskId1 + || bottomRightTaskId == pair.mTaskInfo2.taskId); + } else { + assertNull(pair.mStagedSplitBounds); + } + } + assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + + " Received: " + Arrays.toString(flattenedTaskIds), + Arrays.equals(flattenedTaskIds, expectedTaskIds)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java new file mode 100644 index 000000000000..ad73c56950bd --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java @@ -0,0 +1,94 @@ +package com.android.wm.shell.recents; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.graphics.Rect; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.util.StagedSplitBounds; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StagedSplitBoundsTest { + private static final int DEVICE_WIDTH = 100; + private static final int DEVICE_LENGTH = 200; + private static final int DIVIDER_SIZE = 20; + private static final int TASK_ID_1 = 4; + private static final int TASK_ID_2 = 9; + + // Bounds in screen space + private final Rect mTopRect = new Rect(); + private final Rect mBottomRect = new Rect(); + private final Rect mLeftRect = new Rect(); + private final Rect mRightRect = new Rect(); + + @Before + public void setup() { + mTopRect.set(0, 0, DEVICE_WIDTH, DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2); + mBottomRect.set(0, DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, + DEVICE_WIDTH, DEVICE_LENGTH); + mLeftRect.set(0, 0, DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, DEVICE_LENGTH); + mRightRect.set(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, 0, + DEVICE_WIDTH, DEVICE_LENGTH); + } + + @Test + public void testVerticalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + assertTrue(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + assertFalse(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(0, dividerBounds.left); + assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top); + assertEquals(DEVICE_WIDTH, dividerBounds.right); + assertEquals(DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, dividerBounds.bottom); + } + + @Test + public void testVerticalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left); + assertEquals(0, dividerBounds.top); + assertEquals(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, dividerBounds.right); + assertEquals(DEVICE_LENGTH, dividerBounds.bottom); + } + + @Test + public void testEqualVerticalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH; + assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01); + } + + @Test + public void testEqualHorizontalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH; + assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java deleted file mode 100644 index 10fd7d705967..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2021 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.sizecompatui; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.verify; - -import android.content.res.Configuration; -import android.testing.AndroidTestingRunner; -import android.view.LayoutInflater; -import android.widget.Button; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.R; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link SizeCompatHintPopup}. - * - * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatHintPopupTest - */ -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class SizeCompatHintPopupTest extends ShellTestCase { - - @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private SizeCompatUIController.SizeCompatUICallback mCallback; - @Mock private ShellTaskOrganizer.TaskListener mTaskListener; - @Mock private DisplayLayout mDisplayLayout; - - private SizeCompatUILayout mLayout; - private SizeCompatHintPopup mHint; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - final int taskId = 1; - mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext, - new Configuration(), taskId, mTaskListener, mDisplayLayout, - false /* hasShownHint */); - mHint = (SizeCompatHintPopup) - LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null); - mHint.inject(mLayout); - - spyOn(mLayout); - } - - @Test - public void testOnClick() { - doNothing().when(mLayout).dismissHint(); - - final Button button = mHint.findViewById(R.id.got_it); - button.performClick(); - - verify(mLayout).dismissHint(); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java deleted file mode 100644 index 8839f58ea889..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2021 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.sizecompatui; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.content.res.Configuration; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link SizeCompatUIController}. - * - * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatUIControllerTest - */ -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class SizeCompatUIControllerTest extends ShellTestCase { - private static final int DISPLAY_ID = 0; - private static final int TASK_ID = 12; - - private SizeCompatUIController mController; - private @Mock DisplayController mMockDisplayController; - private @Mock DisplayLayout mMockDisplayLayout; - private @Mock DisplayImeController mMockImeController; - private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener; - private @Mock SyncTransactionQueue mMockSyncQueue; - private @Mock SizeCompatUILayout mMockLayout; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt()); - doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId(); - doReturn(TASK_ID).when(mMockLayout).getTaskId(); - mController = new SizeCompatUIController(mContext, mMockDisplayController, - mMockImeController, mMockSyncQueue) { - @Override - SizeCompatUILayout createLayout(Context context, int displayId, int taskId, - Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) { - return mMockLayout; - } - }; - spyOn(mController); - } - - @Test - public void testListenerRegistered() { - verify(mMockDisplayController).addDisplayWindowListener(mController); - verify(mMockImeController).addPositionProcessor(mController); - } - - @Test - public void testOnSizeCompatInfoChanged() { - final Configuration taskConfig = new Configuration(); - - // Verify that the restart button is added with non-null size compat info. - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, - mMockTaskListener); - - verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig), - eq(mMockTaskListener)); - - // Verify that the restart button is updated with non-null new size compat info. - final Configuration newTaskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, - mMockTaskListener); - - verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener, - false /* isImeShowing */); - - // Verify that the restart button is removed with null size compat info. - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener); - - verify(mMockLayout).release(); - } - - @Test - public void testOnDisplayRemoved() { - final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, - mMockTaskListener); - - mController.onDisplayRemoved(DISPLAY_ID + 1); - - verify(mMockLayout, never()).release(); - - mController.onDisplayRemoved(DISPLAY_ID); - - verify(mMockLayout).release(); - } - - @Test - public void testOnDisplayConfigurationChanged() { - final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, - mMockTaskListener); - - final Configuration newTaskConfig = new Configuration(); - mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig); - - verify(mMockLayout, never()).updateDisplayLayout(any()); - - mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig); - - verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout); - } - - @Test - public void testChangeButtonVisibilityOnImeShowHide() { - final Configuration taskConfig = new Configuration(); - mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, - mMockTaskListener); - - mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); - - verify(mMockLayout).updateImeVisibility(true); - - mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */); - - verify(mMockLayout).updateImeVisibility(false); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java deleted file mode 100644 index ee4c81547bbd..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2021 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.sizecompatui; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.res.Configuration; -import android.graphics.Rect; -import android.testing.AndroidTestingRunner; -import android.view.DisplayInfo; -import android.view.SurfaceControl; -import android.view.View; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link SizeCompatUILayout}. - * - * Build/Install/Run: - * atest WMShellUnitTests:SizeCompatUILayoutTest - */ -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class SizeCompatUILayoutTest extends ShellTestCase { - - private static final int TASK_ID = 1; - - @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private SizeCompatUIController.SizeCompatUICallback mCallback; - @Mock private ShellTaskOrganizer.TaskListener mTaskListener; - @Mock private DisplayLayout mDisplayLayout; - @Mock private SizeCompatRestartButton mButton; - @Mock private SizeCompatHintPopup mHint; - private Configuration mTaskConfig; - - private SizeCompatUILayout mLayout; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mTaskConfig = new Configuration(); - - mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext, - new Configuration(), TASK_ID, mTaskListener, mDisplayLayout, - false /* hasShownHint */); - - spyOn(mLayout); - spyOn(mLayout.mButtonWindowManager); - doReturn(mButton).when(mLayout.mButtonWindowManager).createSizeCompatButton(); - - final SizeCompatUIWindowManager hintWindowManager = mLayout.createHintWindowManager(); - spyOn(hintWindowManager); - doReturn(mHint).when(hintWindowManager).createSizeCompatHint(); - doReturn(hintWindowManager).when(mLayout).createHintWindowManager(); - } - - @Test - public void testCreateSizeCompatButton() { - // Not create button if IME is showing. - mLayout.createSizeCompatButton(true /* isImeShowing */); - - verify(mLayout.mButtonWindowManager, never()).createSizeCompatButton(); - assertNull(mLayout.mButton); - assertNull(mLayout.mHintWindowManager); - assertNull(mLayout.mHint); - - // Not create hint popup. - mLayout.mShouldShowHint = false; - mLayout.createSizeCompatButton(false /* isImeShowing */); - - verify(mLayout.mButtonWindowManager).createSizeCompatButton(); - assertNotNull(mLayout.mButton); - assertNull(mLayout.mHintWindowManager); - assertNull(mLayout.mHint); - - // Create hint popup. - mLayout.release(); - mLayout.mShouldShowHint = true; - mLayout.createSizeCompatButton(false /* isImeShowing */); - - verify(mLayout.mButtonWindowManager, times(2)).createSizeCompatButton(); - assertNotNull(mLayout.mButton); - assertNotNull(mLayout.mHintWindowManager); - verify(mLayout.mHintWindowManager).createSizeCompatHint(); - assertNotNull(mLayout.mHint); - assertFalse(mLayout.mShouldShowHint); - } - - @Test - public void testRelease() { - mLayout.createSizeCompatButton(false /* isImeShowing */); - final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager; - - mLayout.release(); - - assertNull(mLayout.mButton); - assertNull(mLayout.mHint); - verify(hintWindowManager).release(); - assertNull(mLayout.mHintWindowManager); - verify(mLayout.mButtonWindowManager).release(); - } - - @Test - public void testUpdateSizeCompatInfo() { - mLayout.createSizeCompatButton(false /* isImeShowing */); - - // No diff - clearInvocations(mLayout); - mLayout.updateSizeCompatInfo(mTaskConfig, mTaskListener, - false /* isImeShowing */); - - verify(mLayout, never()).updateButtonSurfacePosition(); - verify(mLayout, never()).release(); - verify(mLayout, never()).createSizeCompatButton(anyBoolean()); - - // Change task listener, recreate button. - clearInvocations(mLayout); - final ShellTaskOrganizer.TaskListener newTaskListener = mock( - ShellTaskOrganizer.TaskListener.class); - mLayout.updateSizeCompatInfo(mTaskConfig, newTaskListener, - false /* isImeShowing */); - - verify(mLayout).release(); - verify(mLayout).createSizeCompatButton(anyBoolean()); - - // Change task bounds, update position. - clearInvocations(mLayout); - final Configuration newTaskConfiguration = new Configuration(); - newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000)); - mLayout.updateSizeCompatInfo(newTaskConfiguration, newTaskListener, - false /* isImeShowing */); - - verify(mLayout).updateButtonSurfacePosition(); - verify(mLayout).updateHintSurfacePosition(); - } - - @Test - public void testUpdateDisplayLayout() { - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = 1000; - displayInfo.logicalHeight = 2000; - final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, - mContext.getResources(), false, false); - - mLayout.updateDisplayLayout(displayLayout1); - verify(mLayout).updateButtonSurfacePosition(); - verify(mLayout).updateHintSurfacePosition(); - - // No update if the display bounds is the same. - clearInvocations(mLayout); - final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, - mContext.getResources(), false, false); - mLayout.updateDisplayLayout(displayLayout2); - verify(mLayout, never()).updateButtonSurfacePosition(); - verify(mLayout, never()).updateHintSurfacePosition(); - } - - @Test - public void testUpdateImeVisibility() { - // Create button if it is not created. - mLayout.mButton = null; - mLayout.updateImeVisibility(false /* isImeShowing */); - - verify(mLayout).createSizeCompatButton(false /* isImeShowing */); - - // Hide button if ime is shown. - clearInvocations(mLayout); - doReturn(View.VISIBLE).when(mButton).getVisibility(); - mLayout.updateImeVisibility(true /* isImeShowing */); - - verify(mLayout, never()).createSizeCompatButton(anyBoolean()); - verify(mButton).setVisibility(View.GONE); - - // Show button if ime is not shown. - doReturn(View.GONE).when(mButton).getVisibility(); - mLayout.updateImeVisibility(false /* isImeShowing */); - - verify(mLayout, never()).createSizeCompatButton(anyBoolean()); - verify(mButton).setVisibility(View.VISIBLE); - } - - @Test - public void testAttachToParentSurface() { - final SurfaceControl.Builder b = new SurfaceControl.Builder(); - mLayout.attachToParentSurface(b); - - verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b); - } - - @Test - public void testOnRestartButtonClicked() { - mLayout.onRestartButtonClicked(); - - verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); - } - - @Test - public void testOnRestartButtonLongClicked_showHint() { - mLayout.dismissHint(); - - assertNull(mLayout.mHint); - - mLayout.onRestartButtonLongClicked(); - - assertNotNull(mLayout.mHint); - } - - @Test - public void testDismissHint() { - mLayout.onRestartButtonLongClicked(); - final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager; - assertNotNull(mLayout.mHint); - assertNotNull(hintWindowManager); - - mLayout.dismissHint(); - - assertNull(mLayout.mHint); - assertNull(mLayout.mHintWindowManager); - verify(hintWindowManager).release(); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java index 1bb5fd1e49e7..c9720671f49c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java @@ -25,10 +25,13 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.WindowContainerTransaction; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.SyncTransactionQueue; @@ -41,28 +44,31 @@ import org.mockito.MockitoAnnotations; /** Tests for {@link MainStage} */ @SmallTest @RunWith(AndroidJUnit4.class) -public class MainStageTests { +public class MainStageTests extends ShellTestCase { @Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private StageTaskListener.StageListenerCallbacks mCallbacks; @Mock private SyncTransactionQueue mSyncQueue; @Mock private ActivityManager.RunningTaskInfo mRootTaskInfo; @Mock private SurfaceControl mRootLeash; + @Mock private IconProvider mIconProvider; private WindowContainerTransaction mWct = new WindowContainerTransaction(); private SurfaceSession mSurfaceSession = new SurfaceSession(); private MainStage mMainStage; @Before + @UiThreadTest public void setup() { MockitoAnnotations.initMocks(this); mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); - mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue, - mSurfaceSession); + mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, + mSyncQueue, mSurfaceSession, mIconProvider, null); mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); } @Test public void testActiveDeactivate() { - mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct); + mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct, + true /* reparent */); assertThat(mMainStage.isActive()).isTrue(); mMainStage.deactivate(mWct); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index 56a005642ce2..a31aa58bdc26 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -29,10 +29,13 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.WindowContainerTransaction; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.SyncTransactionQueue; @@ -46,22 +49,24 @@ import org.mockito.Spy; /** Tests for {@link SideStage} */ @SmallTest @RunWith(AndroidJUnit4.class) -public class SideStageTests { +public class SideStageTests extends ShellTestCase { @Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private StageTaskListener.StageListenerCallbacks mCallbacks; @Mock private SyncTransactionQueue mSyncQueue; @Mock private ActivityManager.RunningTaskInfo mRootTask; @Mock private SurfaceControl mRootLeash; + @Mock private IconProvider mIconProvider; @Spy private WindowContainerTransaction mWct; private SurfaceSession mSurfaceSession = new SurfaceSession(); private SideStage mSideStage; @Before + @UiThreadTest public void setup() { MockitoAnnotations.initMocks(this); mRootTask = new TestRunningTaskInfoBuilder().build(); - mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue, - mSurfaceSession); + mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, + mSyncQueue, mSurfaceSession, mIconProvider, null); mSideStage.onTaskAppeared(mRootTask, mRootLeash); } @@ -69,7 +74,7 @@ public class SideStageTests { public void testAddTask() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); - mSideStage.addTask(task, mRootTask.configuration.windowConfiguration.getBounds(), mWct); + mSideStage.addTask(task, mWct); verify(mWct).reparent(eq(task.token), eq(mRootTask.token), eq(true)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index ab6f76996398..aab1e3a99c98 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; - import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -33,11 +32,17 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.transition.Transitions; +import java.util.Optional; + +import javax.inject.Provider; + public class SplitTestUtils { static SplitLayout createMockSplitLayout() { @@ -65,9 +70,14 @@ public class SplitTestUtils { TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage, DisplayImeController imeController, - SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool) { + DisplayInsetsController insetsController, SplitLayout splitLayout, + Transitions transitions, TransactionPool transactionPool, + SplitscreenEventLogger logger, + Optional<RecentTasksController> recentTasks, + Provider<Optional<StageTaskUnfoldController>> unfoldController) { super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage, - sideStage, imeController, splitLayout, transitions, transactionPool); + sideStage, imeController, insetsController, splitLayout, transitions, + transactionPool, logger, recentTasks, unfoldController); // Prepare default TaskDisplayArea for testing. mDisplayAreaInfo = new DisplayAreaInfo( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index aca80f3556b9..1eae625233a0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -46,22 +46,27 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; +import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -71,6 +76,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; +import java.util.Optional; + /** Tests for {@link StageCoordinator} */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -79,9 +86,12 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private SyncTransactionQueue mSyncQueue; @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @Mock private DisplayImeController mDisplayImeController; + @Mock private DisplayInsetsController mDisplayInsetsController; @Mock private TransactionPool mTransactionPool; @Mock private Transitions mTransitions; @Mock private SurfaceSession mSurfaceSession; + @Mock private SplitscreenEventLogger mLogger; + @Mock private IconProvider mIconProvider; private SplitLayout mSplitLayout; private MainStage mMainStage; private SideStage mSideStage; @@ -92,6 +102,7 @@ public class SplitTransitionTests extends ShellTestCase { private ActivityManager.RunningTaskInfo mSideChild; @Before + @UiThreadTest public void setup() { MockitoAnnotations.initMocks(this); final ShellExecutor mockExecutor = mock(ShellExecutor.class); @@ -99,15 +110,18 @@ public class SplitTransitionTests extends ShellTestCase { doReturn(mockExecutor).when(mTransitions).getAnimExecutor(); doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire(); mSplitLayout = SplitTestUtils.createMockSplitLayout(); - mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock( - StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession); + mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( + StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, + mIconProvider, null); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); - mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mock( - StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession); + mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( + StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, + mIconProvider, null); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, - mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, - mDisplayImeController, mSplitLayout, mTransitions, mTransactionPool); + mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, + mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, + mTransactionPool, mLogger, Optional.empty(), Optional::empty); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) .when(mTransitions).startTransition(anyInt(), any(), any()); @@ -125,12 +139,13 @@ public class SplitTransitionTests extends ShellTestCase { TestRemoteTransition testRemote = new TestRemoteTransition(); IBinder transition = mSplitScreenTransitions.startEnterTransition( - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), testRemote, - mStageCoordinator); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), + new RemoteTransition(testRemote), mStageCoordinator); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); assertTrue(accepted); @@ -168,6 +183,7 @@ public class SplitTransitionTests extends ShellTestCase { mSideStage.onTaskAppeared(newTask, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); assertFalse(accepted); assertTrue(mStageCoordinator.isSplitScreenVisible()); @@ -188,6 +204,7 @@ public class SplitTransitionTests extends ShellTestCase { mSideStage.onTaskVanished(newTask); accepted = mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); assertFalse(accepted); assertTrue(mStageCoordinator.isSplitScreenVisible()); @@ -223,6 +240,7 @@ public class SplitTransitionTests extends ShellTestCase { mSideStage.onTaskVanished(mSideChild); mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); assertFalse(mStageCoordinator.isSplitScreenVisible()); } @@ -244,6 +262,7 @@ public class SplitTransitionTests extends ShellTestCase { mSideStage.onTaskVanished(mSideChild); boolean accepted = mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); assertTrue(accepted); assertFalse(mStageCoordinator.isSplitScreenVisible()); @@ -274,6 +293,7 @@ public class SplitTransitionTests extends ShellTestCase { mSideStage.onTaskVanished(mSideChild); boolean accepted = mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); assertTrue(accepted); assertFalse(mStageCoordinator.isSplitScreenVisible()); @@ -293,13 +313,15 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo enterInfo = createEnterPairInfo(); IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), - new TestRemoteTransition(), mStageCoordinator); + new RemoteTransition(new TestRemoteTransition()), mStageCoordinator); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); - mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction()); + mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(), + true /* includingTopTask */); } private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) { @@ -335,10 +357,11 @@ public class SplitTransitionTests extends ShellTestCase { @Override public void startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) + SurfaceControl.Transaction startTransaction, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { mCalled = true; - finishCallback.onTransitionFinished(mRemoteFinishWCT); + finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 06b08686bf4c..85f6789c3435 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -16,17 +16,32 @@ package com.android.wm.shell.splitscreen; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.graphics.Rect; +import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -37,8 +52,10 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -47,37 +64,124 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** Tests for {@link StageCoordinator} */ +import java.util.Optional; + +import javax.inject.Provider; + +/** + * Tests for {@link StageCoordinator} + */ @SmallTest @RunWith(AndroidJUnit4.class) public class StageCoordinatorTests extends ShellTestCase { - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Mock private SyncTransactionQueue mSyncQueue; - @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; - @Mock private MainStage mMainStage; - @Mock private SideStage mSideStage; - @Mock private DisplayImeController mDisplayImeController; - @Mock private Transitions mTransitions; - @Mock private TransactionPool mTransactionPool; + @Mock + private ShellTaskOrganizer mTaskOrganizer; + @Mock + private SyncTransactionQueue mSyncQueue; + @Mock + private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; + @Mock + private MainStage mMainStage; + @Mock + private SideStage mSideStage; + @Mock + private StageTaskUnfoldController mMainUnfoldController; + @Mock + private StageTaskUnfoldController mSideUnfoldController; + @Mock + private SplitLayout mSplitLayout; + @Mock + private DisplayImeController mDisplayImeController; + @Mock + private DisplayInsetsController mDisplayInsetsController; + @Mock + private Transitions mTransitions; + @Mock + private TransactionPool mTransactionPool; + @Mock + private SplitscreenEventLogger mLogger; + + private final Rect mBounds1 = new Rect(10, 20, 30, 40); + private final Rect mBounds2 = new Rect(5, 10, 15, 20); + private StageCoordinator mStageCoordinator; @Before public void setup() { MockitoAnnotations.initMocks(this); - mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, - mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, - mDisplayImeController, null /* splitLayout */, mTransitions, mTransactionPool); + mStageCoordinator = spy(createStageCoordinator(/* splitLayout */ null)); + doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt()); + + when(mSplitLayout.getBounds1()).thenReturn(mBounds1); + when(mSplitLayout.getBounds2()).thenReturn(mBounds2); } @Test - public void testMoveToSideStage() { + public void testMoveToStage() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); - mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT); + mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT, + new WindowContainerTransaction()); + verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class)); + assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition()); - verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class)); - verify(mSideStage).addTask(eq(task), any(Rect.class), - any(WindowContainerTransaction.class)); + mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT, + new WindowContainerTransaction()); + verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class)); + assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); + } + + @Test + public void testMoveToUndefinedStage() { + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + + // Verify move to undefined stage while split screen not activated moves task to side stage. + when(mMainStage.isActive()).thenReturn(false); + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); + mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT, + new WindowContainerTransaction()); + verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class)); + assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); + + // Verify move to undefined stage after split screen activated moves task based on position. + when(mMainStage.isActive()).thenReturn(true); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); + mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, + new WindowContainerTransaction()); + verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class)); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); + } + + @Test + public void testDisplayAreaAppeared_initializesUnfoldControllers() { + mStageCoordinator.onDisplayAreaAppeared(mock(DisplayAreaInfo.class)); + + verify(mMainUnfoldController).init(); + verify(mSideUnfoldController).init(); + } + + @Test + public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() { + mStageCoordinator = createStageCoordinator(mSplitLayout); + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); + clearInvocations(mMainUnfoldController, mSideUnfoldController); + + mStageCoordinator.onLayoutSizeChanged(mSplitLayout); + + verify(mMainUnfoldController).onLayoutChanged(mBounds2); + verify(mSideUnfoldController).onLayoutChanged(mBounds1); + } + + @Test + public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() { + mStageCoordinator = createStageCoordinator(mSplitLayout); + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null); + clearInvocations(mMainUnfoldController, mSideUnfoldController); + + mStageCoordinator.onLayoutSizeChanged(mSplitLayout); + + verify(mMainUnfoldController).onLayoutChanged(mBounds1); + verify(mSideUnfoldController).onLayoutChanged(mBounds2); } @Test @@ -90,4 +194,119 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSideStage).removeTask( eq(task.taskId), any(), any(WindowContainerTransaction.class)); } + + @Test + public void testExitSplitScreen() { + when(mMainStage.isActive()).thenReturn(true); + mStageCoordinator.exitSplitScreen(INVALID_TASK_ID, EXIT_REASON_RETURN_HOME); + verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(false)); + verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false)); + } + + @Test + public void testExitSplitScreenToMainStage() { + when(mMainStage.isActive()).thenReturn(true); + final int testTaskId = 12345; + when(mMainStage.containsTask(eq(testTaskId))).thenReturn(true); + when(mSideStage.containsTask(eq(testTaskId))).thenReturn(false); + mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME); + verify(mMainStage).reorderChild(eq(testTaskId), eq(true), + any(WindowContainerTransaction.class)); + verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(false)); + verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(true)); + } + + @Test + public void testExitSplitScreenToSideStage() { + when(mMainStage.isActive()).thenReturn(true); + final int testTaskId = 12345; + when(mMainStage.containsTask(eq(testTaskId))).thenReturn(false); + when(mSideStage.containsTask(eq(testTaskId))).thenReturn(true); + mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME); + verify(mSideStage).reorderChild(eq(testTaskId), eq(true), + any(WindowContainerTransaction.class)); + verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true)); + verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false)); + } + + @Test + public void testResolveStartStage_beforeSplitActivated_setsStagePosition() { + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getSideStagePosition(), SPLIT_POSITION_BOTTOM_OR_RIGHT); + verify(mStageCoordinator).updateActivityOptions(any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT)); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getSideStagePosition(), SPLIT_POSITION_TOP_OR_LEFT); + verify(mStageCoordinator).updateActivityOptions(any(), eq(SPLIT_POSITION_TOP_OR_LEFT)); + } + + @Test + public void testResolveStartStage_afterSplitActivated_retrievesStagePosition() { + when(mMainStage.isActive()).thenReturn(true); + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getSideStagePosition(), SPLIT_POSITION_TOP_OR_LEFT); + verify(mStageCoordinator).updateActivityOptions(any(), eq(SPLIT_POSITION_TOP_OR_LEFT)); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getMainStagePosition(), SPLIT_POSITION_BOTTOM_OR_RIGHT); + verify(mStageCoordinator).updateActivityOptions(any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT)); + } + + @Test + public void testResolveStartStage_setsSideStagePosition() { + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getSideStagePosition(), SPLIT_POSITION_BOTTOM_OR_RIGHT); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getMainStagePosition(), SPLIT_POSITION_BOTTOM_OR_RIGHT); + } + + @Test + public void testResolveStartStage_retrievesStagePosition() { + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_SIDE, SPLIT_POSITION_UNDEFINED, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getSideStagePosition(), SPLIT_POSITION_TOP_OR_LEFT); + + mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN, SPLIT_POSITION_UNDEFINED, + null /* options */, null /* wct */); + assertEquals(mStageCoordinator.getMainStagePosition(), SPLIT_POSITION_BOTTOM_OR_RIGHT); + } + + private StageCoordinator createStageCoordinator(SplitLayout splitLayout) { + return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, + mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, + mDisplayImeController, mDisplayInsetsController, splitLayout, + mTransitions, mTransactionPool, mLogger, Optional.empty(), + new UnfoldControllerProvider()); + } + + private class UnfoldControllerProvider implements + Provider<Optional<StageTaskUnfoldController>> { + + private boolean isMain = true; + + @Override + public Optional<StageTaskUnfoldController> get() { + if (isMain) { + isMain = false; + return Optional.of(mMainUnfoldController); + } else { + return Optional.of(mSideUnfoldController); + } + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 90b5b37694c6..53d5076f5835 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -21,18 +21,27 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.ActivityManager; +import android.os.SystemProperties; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.WindowContainerTransaction; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.SyncTransactionQueue; @@ -47,31 +56,48 @@ import org.mockito.MockitoAnnotations; /** * Tests for {@link StageTaskListener} * Build/Install/Run: - * atest WMShellUnitTests:StageTaskListenerTests + * atest WMShellUnitTests:StageTaskListenerTests */ @SmallTest @RunWith(AndroidJUnit4.class) -public final class StageTaskListenerTests { - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Mock private StageTaskListener.StageListenerCallbacks mCallbacks; - @Mock private SyncTransactionQueue mSyncQueue; - @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; +public final class StageTaskListenerTests extends ShellTestCase { + private static final boolean ENABLE_SHELL_TRANSITIONS = + SystemProperties.getBoolean("persist.debug.shell_transit", false); + + @Mock + private ShellTaskOrganizer mTaskOrganizer; + @Mock + private StageTaskListener.StageListenerCallbacks mCallbacks; + @Mock + private SyncTransactionQueue mSyncQueue; + @Mock + private IconProvider mIconProvider; + @Mock + private StageTaskUnfoldController mStageTaskUnfoldController; + @Captor + private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; private SurfaceSession mSurfaceSession = new SurfaceSession(); + private SurfaceControl mSurfaceControl; private ActivityManager.RunningTaskInfo mRootTask; private StageTaskListener mStageTaskListener; @Before + @UiThreadTest public void setup() { MockitoAnnotations.initMocks(this); mStageTaskListener = new StageTaskListener( + mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue, - mSurfaceSession); + mSurfaceSession, + mIconProvider, + mStageTaskUnfoldController); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask.parentTaskId = INVALID_TASK_ID; - mStageTaskListener.onTaskAppeared(mRootTask, new SurfaceControl()); + mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); + mStageTaskListener.onTaskAppeared(mRootTask, mSurfaceControl); } @Test @@ -93,15 +119,39 @@ public final class StageTaskListenerTests { @Test public void testChildTaskAppeared() { + // With shell transitions, the transition manages status changes, so skip this test. + assumeFalse(ENABLE_SHELL_TRANSITIONS); final ActivityManager.RunningTaskInfo childTask = new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); - mStageTaskListener.onTaskAppeared(childTask, new SurfaceControl()); + mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl); assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue(); verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true)); } + @Test + public void testTaskAppeared_notifiesUnfoldListener() { + final ActivityManager.RunningTaskInfo task = + new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); + + mStageTaskListener.onTaskAppeared(task, mSurfaceControl); + + verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl)); + } + + @Test + public void testTaskVanished_notifiesUnfoldListener() { + final ActivityManager.RunningTaskInfo task = + new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); + mStageTaskListener.onTaskAppeared(task, mSurfaceControl); + clearInvocations(mStageTaskUnfoldController); + + mStageTaskListener.onTaskVanished(task); + + verify(mStageTaskUnfoldController).onTaskVanished(eq(task)); + } + @Test(expected = IllegalArgumentException.class) public void testUnknownTaskVanished() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); @@ -110,6 +160,8 @@ public final class StageTaskListenerTests { @Test public void testTaskVanished() { + // With shell transitions, the transition manages status changes, so skip this test. + assumeFalse(ENABLE_SHELL_TRANSITIONS); final ActivityManager.RunningTaskInfo childTask = new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); mStageTaskListener.mRootTaskInfo = mRootTask; @@ -131,4 +183,18 @@ public final class StageTaskListenerTests { mStageTaskListener.onTaskInfoChanged(childTask); verify(mCallbacks).onNoLongerSupportMultiWindow(); } + + @Test + public void testEvictAllChildren() { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mStageTaskListener.evictAllChildren(wct); + assertTrue(wct.isEmpty()); + + final ActivityManager.RunningTaskInfo childTask = + new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); + mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl); + + mStageTaskListener.evictAllChildren(wct); + assertFalse(wct.isEmpty()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index 18b8faf4dbe6..d92b12e60780 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -53,21 +54,23 @@ import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.testing.TestableContext; +import android.view.Display; import android.view.IWindowSession; import android.view.InsetsState; import android.view.Surface; -import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowMetrics; import android.window.StartingWindowInfo; +import android.window.StartingWindowRemovalInfo; import android.window.TaskSnapshot; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; @@ -92,6 +95,8 @@ public class StartingSurfaceDrawerTests { @Mock private WindowManager mMockWindowManager; @Mock + private IconProvider mIconProvider; + @Mock private TransactionPool mTransactionPool; private final Handler mTestHandler = new Handler(Looper.getMainLooper()); @@ -104,26 +109,28 @@ public class StartingSurfaceDrawerTests { int mAddWindowForTask = 0; TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, - TransactionPool pool) { - super(context, splashScreenExecutor, pool); + IconProvider iconProvider, TransactionPool pool) { + super(context, splashScreenExecutor, iconProvider, pool); } @Override - protected boolean addWindow(int taskId, IBinder appToken, - View view, WindowManager wm, WindowManager.LayoutParams params, int suggestType) { + protected boolean addWindow(int taskId, IBinder appToken, View view, Display display, + WindowManager.LayoutParams params, int suggestType) { // listen for addView mAddWindowForTask = taskId; + saveSplashScreenRecord(appToken, taskId, view, suggestType); // Do not wait for background color return false; } @Override - protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame, - boolean playRevealAnimation) { + protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, + boolean immediately) { // listen for removeView - if (mAddWindowForTask == taskId) { + if (mAddWindowForTask == removalInfo.taskId) { mAddWindowForTask = 0; } + mStartingWindowRecords.remove(removalInfo.taskId); } } @@ -156,7 +163,8 @@ public class StartingSurfaceDrawerTests { doNothing().when(mMockWindowManager).addView(any(), any()); mTestExecutor = new HandlerExecutor(mTestHandler); mStartingSurfaceDrawer = spy( - new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool)); + new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider, + mTransactionPool)); } @Test @@ -171,9 +179,11 @@ public class StartingSurfaceDrawerTests { eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN)); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); - mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false); + StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo(); + removalInfo.taskId = windowInfo.taskInfo.taskId; + mStartingSurfaceDrawer.removeStartingWindow(removalInfo); waitHandlerIdle(mTestHandler); - verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false)); + verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false)); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0); } @@ -261,11 +271,32 @@ public class StartingSurfaceDrawerTests { // Verify the task snapshot with IME snapshot will be removed when received the real IME // drawn callback. + // makeTaskSnapshotWindow shall call removeWindowSynced before there add a new + // StartingWindowRecord for the task. mStartingSurfaceDrawer.onImeDrawnOnTask(1); - verify(mockSnapshotWindow).removeImmediately(); + verify(mStartingSurfaceDrawer, times(2)) + .removeWindowSynced(any(), eq(true)); } } + @Test + public void testClearAllWindows() { + final int taskId = 1; + final StartingWindowInfo windowInfo = + createWindowInfo(taskId, android.R.style.Theme); + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, + STARTING_WINDOW_TYPE_SPLASH_SCREEN); + waitHandlerIdle(mTestHandler); + verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(), + eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN)); + assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); + + mStartingSurfaceDrawer.clearAllWindows(); + waitHandlerIdle(mTestHandler); + verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true)); + assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0); + } + private StartingWindowInfo createWindowInfo(int taskId, int themeResId) { StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo(); @@ -295,8 +326,8 @@ public class StartingSurfaceDrawerTests { System.currentTimeMillis(), new ComponentName("", ""), buffer, ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, taskSize, contentInsets, false, - true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, + Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */, + false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */, hasImeSurface /* hasImeSurface */); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java index a098a6863493..78e27c956807 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java @@ -83,8 +83,7 @@ public class TaskSnapshotWindowTest { createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), 0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */, taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD, - 100 /* delayRemovalTime */, new InsetsState(), - null /* clearWindow */, new TestShellExecutor()); + new InsetsState(), null /* clearWindow */, new TestShellExecutor()); } private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, @@ -95,8 +94,8 @@ public class TaskSnapshotWindowTest { System.currentTimeMillis(), new ComponentName("", ""), buffer, ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, taskSize, contentInsets, false, - true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, + Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */, + false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 2d2ab2c9f674..e39171343bb9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -20,12 +20,20 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; +import static android.window.TransitionInfo.FLAG_IS_DISPLAY; +import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -48,10 +56,14 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.view.IDisplayWindowListener; +import android.view.IWindowManager; +import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; +import android.window.RemoteTransition; import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -65,17 +77,23 @@ import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.ArrayList; /** * Tests for the shell transitions. + * + * Build/Install/Run: + * atest WMShellUnitTests:ShellTransitionTests */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -97,8 +115,7 @@ public class ShellTransitionTests { @Test public void testBasicTransitionFlow() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, - mMainExecutor, mAnimExecutor); + Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); IBinder transitToken = new Binder(); @@ -117,8 +134,7 @@ public class ShellTransitionTests { @Test public void testNonDefaultHandler() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, - mMainExecutor, mAnimExecutor); + Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); @@ -127,11 +143,13 @@ public class ShellTransitionTests { TestTransitionHandler testHandler = new TestTransitionHandler() { @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { for (TransitionInfo.Change chg : info.getChanges()) { if (chg.getMode() == TRANSIT_CHANGE) { - return super.startAnimation(transition, info, t, finishCallback); + return super.startAnimation(transition, info, startTransaction, + finishTransaction, finishCallback); } } return false; @@ -199,8 +217,7 @@ public class ShellTransitionTests { @Test public void testRequestRemoteTransition() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, - mMainExecutor, mAnimExecutor); + Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; @@ -211,7 +228,7 @@ public class ShellTransitionTests { SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; - finishCallback.onTransitionFinished(remoteFinishWCT); + finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */); } @Override @@ -222,7 +239,8 @@ public class ShellTransitionTests { }; IBinder transitToken = new Binder(); transitions.requestStartTransition(transitToken, - new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, testRemote)); + new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, + new RemoteTransition(testRemote))); verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any()); TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); @@ -273,9 +291,76 @@ public class ShellTransitionTests { } @Test + public void testTransitionFilterNotRequirement() { + // filter that requires one opening and NO translucent apps + TransitionFilter filter = new TransitionFilter(); + filter.mRequirements = new TransitionFilter.Requirement[]{ + new TransitionFilter.Requirement(), new TransitionFilter.Requirement()}; + filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + filter.mRequirements[1].mFlags = FLAG_TRANSLUCENT; + filter.mRequirements[1].mNot = true; + + final TransitionInfo openOnly = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).build(); + assertTrue(filter.matches(openOnly)); + + final TransitionInfo openAndTranslucent = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); + openAndTranslucent.getChanges().get(1).setFlags(FLAG_TRANSLUCENT); + assertFalse(filter.matches(openAndTranslucent)); + } + + @Test + public void testTransitionFilterChecksTypeSet() { + TransitionFilter filter = new TransitionFilter(); + filter.mTypeSet = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + + final TransitionInfo openOnly = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).build(); + assertTrue(filter.matches(openOnly)); + + final TransitionInfo toFrontOnly = new TransitionInfoBuilder(TRANSIT_TO_FRONT) + .addChange(TRANSIT_TO_FRONT).build(); + assertTrue(filter.matches(toFrontOnly)); + + final TransitionInfo closeOnly = new TransitionInfoBuilder(TRANSIT_CLOSE) + .addChange(TRANSIT_CLOSE).build(); + assertFalse(filter.matches(closeOnly)); + } + + @Test + public void testTransitionFilterChecksFlags() { + TransitionFilter filter = new TransitionFilter(); + filter.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; + + final TransitionInfo withFlag = new TransitionInfoBuilder(TRANSIT_TO_BACK, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY) + .addChange(TRANSIT_TO_BACK).build(); + assertTrue(filter.matches(withFlag)); + + final TransitionInfo withoutFlag = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).build(); + assertFalse(filter.matches(withoutFlag)); + } + + @Test + public void testTransitionFilterChecksNotFlags() { + TransitionFilter filter = new TransitionFilter(); + filter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; + + final TransitionInfo withFlag = new TransitionInfoBuilder(TRANSIT_TO_BACK, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY) + .addChange(TRANSIT_TO_BACK).build(); + assertFalse(filter.matches(withFlag)); + + final TransitionInfo withoutFlag = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).build(); + assertTrue(filter.matches(withoutFlag)); + } + + @Test public void testRegisteredRemoteTransition() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, - mMainExecutor, mAnimExecutor); + Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; @@ -285,7 +370,7 @@ public class ShellTransitionTests { SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; - finishCallback.onTransitionFinished(null /* wct */); + finishCallback.onTransitionFinished(null /* wct */, null /* sct */); } @Override @@ -300,7 +385,7 @@ public class ShellTransitionTests { new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()}; filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; - transitions.registerRemote(filter, testRemote); + transitions.registerRemote(filter, new RemoteTransition(testRemote)); mMainExecutor.flushAll(); IBinder transitToken = new Binder(); @@ -320,8 +405,7 @@ public class ShellTransitionTests { @Test public void testOneShotRemoteHandler() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, - mMainExecutor, mAnimExecutor); + Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; @@ -332,7 +416,7 @@ public class ShellTransitionTests { SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; - finishCallback.onTransitionFinished(remoteFinishWCT); + finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */); } @Override @@ -344,11 +428,12 @@ public class ShellTransitionTests { final int transitType = TRANSIT_FIRST_CUSTOM + 1; - OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor, testRemote); + OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor, + new RemoteTransition(testRemote)); // Verify that it responds to the remote but not other things. IBinder transitToken = new Binder(); assertNotNull(oneShot.handleRequest(transitToken, - new TransitionRequestInfo(transitType, null, testRemote))); + new TransitionRequestInfo(transitType, null, new RemoteTransition(testRemote)))); assertNull(oneShot.handleRequest(transitToken, new TransitionRequestInfo(transitType, null, null))); @@ -358,15 +443,16 @@ public class ShellTransitionTests { oneShot.setTransition(transitToken); IBinder anotherToken = new Binder(); assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0), - mock(SurfaceControl.Transaction.class), testFinish)); + mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), + testFinish)); assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0), - mock(SurfaceControl.Transaction.class), testFinish)); + mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), + testFinish)); } @Test public void testTransitionQueueing() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, - mMainExecutor, mAnimExecutor); + Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); IBinder transitToken1 = new Binder(); @@ -406,8 +492,7 @@ public class ShellTransitionTests { @Test public void testTransitionMerging() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, - mMainExecutor, mAnimExecutor); + Transitions transitions = createTestTransitions(); mDefaultHandler.setSimulateMerge(true); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -443,11 +528,80 @@ public class ShellTransitionTests { assertEquals(0, mDefaultHandler.activeCount()); } + @Test + public void testShouldRotateSeamlessly() throws Exception { + final RunningTaskInfo taskInfo = + createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final RunningTaskInfo taskInfoPip = + createTaskInfo(1, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); + + final DisplayController displays = createTestDisplayController(); + final @Surface.Rotation int upsideDown = displays + .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation(); + + final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate() + .build()) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build()) + .build(); + assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays)); + + // Seamless if all tasks are seamless + final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate() + .build()) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) + .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) + .build(); + assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays)); + + // Not seamless if there is PiP (or any other non-seamless task) + final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate() + .build()) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) + .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip) + .setRotate().build()) + .build(); + assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays)); + + // Not seamless if one of rotations is upside-down + final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) + .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build()) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) + .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build()) + .build(); + assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays)); + + // Not seamless if system alert windows + final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags( + FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build()) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) + .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) + .build(); + assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays)); + + // Not seamless if there is no changed task. + final TransitionInfo noTask = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) + .setRotate().build()) + .build(); + assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays)); + } + class TransitionInfoBuilder { final TransitionInfo mInfo; TransitionInfoBuilder(@WindowManager.TransitionType int type) { - mInfo = new TransitionInfo(type, 0 /* flags */); + this(type, 0 /* flags */); + } + + TransitionInfoBuilder(@WindowManager.TransitionType int type, + @WindowManager.TransitionFlags int flags) { + mInfo = new TransitionInfo(type, flags); mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0); } @@ -465,11 +619,53 @@ public class ShellTransitionTests { return addChange(mode, null /* taskInfo */); } + TransitionInfoBuilder addChange(TransitionInfo.Change change) { + mInfo.addChange(change); + return this; + } + TransitionInfo build() { return mInfo; } } + class ChangeBuilder { + final TransitionInfo.Change mChange; + + ChangeBuilder(@WindowManager.TransitionType int mode) { + mChange = new TransitionInfo.Change(null /* token */, null /* leash */); + mChange.setMode(mode); + } + + ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) { + mChange.setFlags(flags); + return this; + } + + ChangeBuilder setTask(RunningTaskInfo taskInfo) { + mChange.setTaskInfo(taskInfo); + return this; + } + + ChangeBuilder setRotate(int anim) { + return setRotate(Surface.ROTATION_90, anim); + } + + ChangeBuilder setRotate() { + return setRotate(ROTATION_ANIMATION_UNSPECIFIED); + } + + ChangeBuilder setRotate(@Surface.Rotation int target, int anim) { + mChange.setRotation(Surface.ROTATION_0, target); + mChange.setRotationAnimation(anim); + return this; + } + + TransitionInfo.Change build() { + return mChange; + } + } + class TestTransitionHandler implements Transitions.TransitionHandler { ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>(); final ArrayList<IBinder> mMerged = new ArrayList<>(); @@ -477,7 +673,8 @@ public class ShellTransitionTests { @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { mFinishes.add(finishCallback); return true; @@ -540,4 +737,34 @@ public class ShellTransitionTests { return taskInfo; } + private DisplayController createTestDisplayController() { + IWindowManager mockWM = mock(IWindowManager.class); + final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1]; + try { + doReturn(new int[] {DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any()); + } catch (RemoteException e) { + // No remote stuff happening, so this can't be hit + } + DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor); + out.initialize(); + return out; + } + + private Transitions createTestTransitions() { + return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(), + mContext, mMainExecutor, mAnimExecutor); + } +// +// private class TestDisplayController extends DisplayController { +// private final DisplayLayout mTestDisplayLayout; +// TestDisplayController() { +// super(mContext, mock(IWindowManager.class), mMainExecutor); +// mTestDisplayLayout = new DisplayLayout(); +// mTestDisplayLayout. +// } +// +// @Override +// DisplayLayout +// } + } |