Migrate some CTS tests about PIP presentation to WMShellFlickerTests
Test mappings are as follows: - testPinnedStackInBoundsAfterRotation => PipRotationTest - testEnterPipToOtherOrientation => PipOrientationTest - testSetRequestedOrientationWhilePinned => PipOrientationTest - testPinnedStackWithDockedStack => PipSplitScreenTest - testDisplayMetricsPinUnpin => EnterExitPipTest Some other accompanying changes are as follows: - add FixedAppHelper for FixedActivity. - add testapp.Components to provide the constants for each test app. - extend PipActivity to accept some extras from Intent/BroadcastIntent. Bug: 161980624 Test: atest WMShellFlickerTests Change-Id: I64d97a51387370cc086c9dcdded846e4a8c1c0ea
This commit is contained in:
@@ -33,6 +33,7 @@ android_test {
|
||||
"wm-flicker-common-assertions",
|
||||
"wm-flicker-common-app-helpers",
|
||||
"platform-test-annotations",
|
||||
"wmshell-flicker-test-components",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -54,5 +55,6 @@ android_test {
|
||||
"wm-flicker-common-assertions",
|
||||
"wm-flicker-common-app-helpers",
|
||||
"platform-test-annotations",
|
||||
"wmshell-flicker-test-components",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
|
||||
<!-- Control test app's media session -->
|
||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
|
||||
<!-- ATM.removeRootTasksWithActivityTypes() -->
|
||||
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner"/>
|
||||
|
||||
|
||||
@@ -16,35 +16,24 @@
|
||||
|
||||
package com.android.wm.shell.flicker
|
||||
|
||||
import android.content.ComponentName
|
||||
|
||||
const val IME_WINDOW_NAME = "InputMethod"
|
||||
const val PIP_WINDOW_NAME = "PipMenuActivity"
|
||||
const val SPLITSCREEN_PRIMARY_WINDOW_NAME = "SplitScreenActivity"
|
||||
const val SPLITSCREEN_SECONDARY_WINDOW_NAME = "SplitScreenSecondaryActivity"
|
||||
const val PIP_MENU_WINDOW_NAME = "PipMenuActivity"
|
||||
|
||||
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
|
||||
const val TEST_APP_PACKAGE_NAME = "com.android.wm.shell.flicker.testapp"
|
||||
|
||||
// Test App > Pip Activity
|
||||
val TEST_APP_PIP_ACTIVITY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
|
||||
TEST_APP_PACKAGE_NAME, ".PipActivity")
|
||||
const val TEST_APP_PIP_ACTIVITY_LABEL = "PipApp"
|
||||
const val TEST_APP_PIP_ACTIVITY_WINDOW_NAME = "PipActivity"
|
||||
const val TEST_APP_PIP_MENU_ACTION_NO_OP = "No-Op"
|
||||
const val TEST_APP_PIP_MENU_ACTION_ON = "On"
|
||||
const val TEST_APP_PIP_MENU_ACTION_OFF = "Off"
|
||||
const val TEST_APP_PIP_MENU_ACTION_CLEAR = "Clear"
|
||||
|
||||
// Test App > Ime Activity
|
||||
val TEST_APP_IME_ACTIVITY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
|
||||
TEST_APP_PACKAGE_NAME, ".ImeActivity")
|
||||
const val TEST_APP_IME_ACTIVITY_LABEL = "ImeApp"
|
||||
// Test App > Test Activity
|
||||
const val TEST_APP_FIXED_ACTIVITY_LABEL = "FixedApp"
|
||||
|
||||
// Test App > SplitScreen Activity
|
||||
val TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
|
||||
TEST_APP_PACKAGE_NAME, ".$SPLITSCREEN_PRIMARY_WINDOW_NAME")
|
||||
val TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
|
||||
TEST_APP_PACKAGE_NAME, ".$SPLITSCREEN_SECONDARY_WINDOW_NAME")
|
||||
const val TEST_APP_SPLITSCREEN_PRIMARY_LABEL = "SplitScreenPrimaryApp"
|
||||
const val TEST_APP_SPLITSCREEN_SECONDARY_LABEL = "SplitScreenSecondaryApp"
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
package com.android.wm.shell.flicker.apppairs
|
||||
|
||||
import com.android.wm.shell.flicker.NonRotationTestBase
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_LABEL
|
||||
import com.android.wm.shell.flicker.helpers.AppPairsHelper
|
||||
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
|
||||
abstract class AppPairsTestBase(
|
||||
rotationName: String,
|
||||
@@ -30,11 +29,11 @@ abstract class AppPairsTestBase(
|
||||
) : NonRotationTestBase(rotationName, rotation) {
|
||||
protected val appPairsHelper = AppPairsHelper(instrumentation,
|
||||
TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
|
||||
TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
|
||||
Components.SplitScreenActivity())
|
||||
protected val primaryApp = SplitScreenHelper(instrumentation,
|
||||
TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
|
||||
TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
|
||||
Components.SplitScreenActivity())
|
||||
protected val secondaryApp = SplitScreenHelper(instrumentation,
|
||||
TEST_APP_SPLITSCREEN_SECONDARY_LABEL,
|
||||
TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME)
|
||||
Components.SplitScreenSecondaryActivity())
|
||||
}
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
package com.android.wm.shell.flicker.helpers
|
||||
|
||||
import android.app.Instrumentation
|
||||
import android.content.ComponentName
|
||||
import android.graphics.Region
|
||||
import android.system.helpers.ActivityHelper
|
||||
import com.android.server.wm.flicker.helpers.WindowUtils
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
|
||||
class AppPairsHelper(
|
||||
instrumentation: Instrumentation,
|
||||
activityLabel: String,
|
||||
componentName: ComponentName
|
||||
componentsInfo: Components.ComponentsInfo
|
||||
) : BaseAppHelper(
|
||||
instrumentation,
|
||||
activityLabel,
|
||||
componentName
|
||||
componentsInfo
|
||||
) {
|
||||
val activityHelper = ActivityHelper.getInstance()
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.wm.shell.flicker.helpers
|
||||
|
||||
import android.app.Instrumentation
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager.FEATURE_LEANBACK
|
||||
@@ -28,15 +27,15 @@ import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiObject2
|
||||
import androidx.test.uiautomator.Until
|
||||
import com.android.server.wm.flicker.helpers.StandardAppHelper
|
||||
import com.android.wm.shell.flicker.TEST_APP_PACKAGE_NAME
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
|
||||
abstract class BaseAppHelper(
|
||||
instrumentation: Instrumentation,
|
||||
launcherName: String,
|
||||
private val launcherActivityComponent: ComponentName
|
||||
private val componentsInfo: Components.ComponentsInfo
|
||||
) : StandardAppHelper(
|
||||
instrumentation,
|
||||
TEST_APP_PACKAGE_NAME,
|
||||
Components.PACKAGE_NAME,
|
||||
launcherName,
|
||||
LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
|
||||
) {
|
||||
@@ -53,7 +52,7 @@ abstract class BaseAppHelper(
|
||||
}
|
||||
|
||||
val defaultWindowName: String
|
||||
get() = launcherActivityComponent.className
|
||||
get() = componentsInfo.activityName
|
||||
|
||||
val label: String
|
||||
get() = context.packageManager.run {
|
||||
@@ -63,8 +62,12 @@ abstract class BaseAppHelper(
|
||||
val ui: UiObject2?
|
||||
get() = uiDevice.findObject(appSelector)
|
||||
|
||||
fun launchViaIntent() {
|
||||
context.startActivity(openAppIntent)
|
||||
fun launchViaIntent(stringExtras: Map<String, String> = mapOf()) {
|
||||
val intent = openAppIntent
|
||||
stringExtras.forEach() {
|
||||
intent.putExtra(it.key, it.value)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
|
||||
uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS)
|
||||
}
|
||||
@@ -75,7 +78,7 @@ abstract class BaseAppHelper(
|
||||
|
||||
override fun getOpenAppIntent(): Intent {
|
||||
val intent = Intent()
|
||||
intent.component = launcherActivityComponent
|
||||
intent.component = componentsInfo.componentName
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
return intent
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.flicker.helpers
|
||||
|
||||
import android.app.Instrumentation
|
||||
import com.android.wm.shell.flicker.TEST_APP_FIXED_ACTIVITY_LABEL
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
|
||||
class FixedAppHelper(
|
||||
instrumentation: Instrumentation
|
||||
) : BaseAppHelper(
|
||||
instrumentation,
|
||||
TEST_APP_FIXED_ACTIVITY_LABEL,
|
||||
Components.FixedActivity()
|
||||
)
|
||||
@@ -21,8 +21,8 @@ import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
|
||||
import com.android.server.wm.flicker.helpers.waitForIME
|
||||
import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_COMPONENT_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_LABEL
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
import org.junit.Assert
|
||||
|
||||
open class ImeAppHelper(
|
||||
@@ -30,7 +30,7 @@ open class ImeAppHelper(
|
||||
) : BaseAppHelper(
|
||||
instrumentation,
|
||||
TEST_APP_IME_ACTIVITY_LABEL,
|
||||
TEST_APP_IME_ACTIVITY_COMPONENT_NAME
|
||||
Components.ImeActivity()
|
||||
) {
|
||||
fun openIME() {
|
||||
val editText = uiDevice.wait(
|
||||
|
||||
@@ -26,8 +26,8 @@ import androidx.test.uiautomator.Until
|
||||
import com.android.server.wm.flicker.helpers.closePipWindow
|
||||
import com.android.server.wm.flicker.helpers.hasPipWindow
|
||||
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_COMPONENT_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_LABEL
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.fail
|
||||
|
||||
@@ -36,7 +36,7 @@ class PipAppHelper(
|
||||
) : BaseAppHelper(
|
||||
instrumentation,
|
||||
TEST_APP_PIP_ACTIVITY_LABEL,
|
||||
TEST_APP_PIP_ACTIVITY_COMPONENT_NAME
|
||||
Components.PipActivity()
|
||||
) {
|
||||
private val mediaSessionManager: MediaSessionManager
|
||||
get() = context.getSystemService(MediaSessionManager::class.java)
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
package com.android.wm.shell.flicker.helpers
|
||||
|
||||
import android.app.Instrumentation
|
||||
import android.content.ComponentName
|
||||
import android.graphics.Region
|
||||
import android.os.SystemClock
|
||||
import com.android.server.wm.flicker.helpers.WindowUtils
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
|
||||
class SplitScreenHelper(
|
||||
instrumentation: Instrumentation,
|
||||
activityLabel: String,
|
||||
componentName: ComponentName
|
||||
componentsInfo: Components.ComponentsInfo
|
||||
) : BaseAppHelper(
|
||||
instrumentation,
|
||||
activityLabel,
|
||||
componentName
|
||||
componentsInfo
|
||||
) {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.app.ActivityTaskManager
|
||||
import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
|
||||
import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
|
||||
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
|
||||
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
|
||||
import android.os.SystemClock
|
||||
import com.android.wm.shell.flicker.NonRotationTestBase
|
||||
|
||||
abstract class AppTestBase(
|
||||
rotationName: String,
|
||||
rotation: Int
|
||||
) : NonRotationTestBase(rotationName, rotation) {
|
||||
companion object {
|
||||
fun removeAllTasksButHome() {
|
||||
val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
|
||||
ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
|
||||
ACTIVITY_TYPE_UNDEFINED)
|
||||
val atm = ActivityTaskManager.getService()
|
||||
atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
|
||||
}
|
||||
|
||||
fun waitForAnimationComplete() {
|
||||
// TODO: UiDevice doesn't have reliable way to wait for the completion of animation.
|
||||
// Consider to introduce WindowManagerStateHelper to access Activity state.
|
||||
SystemClock.sleep(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.dsl.runFlicker
|
||||
import com.android.server.wm.flicker.helpers.WindowUtils
|
||||
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
|
||||
import com.android.wm.shell.flicker.helpers.FixedAppHelper
|
||||
import com.android.wm.shell.flicker.helpers.PipAppHelper
|
||||
import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
|
||||
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`
|
||||
*/
|
||||
@RequiresDevice
|
||||
@RunWith(Parameterized::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class EnterExitPipTest(
|
||||
rotationName: String,
|
||||
rotation: Int
|
||||
) : AppTestBase(rotationName, rotation) {
|
||||
private val pipApp = PipAppHelper(instrumentation)
|
||||
private val testApp = FixedAppHelper(instrumentation)
|
||||
|
||||
@Test
|
||||
fun testDisplayMetricsPinUnpin() {
|
||||
runFlicker(instrumentation) {
|
||||
withTestName { "testDisplayMetricsPinUnpin" }
|
||||
setup {
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
device.wakeUpAndGoToHomeScreen()
|
||||
pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
|
||||
testApp.launchViaIntent()
|
||||
waitForAnimationComplete()
|
||||
}
|
||||
}
|
||||
transitions {
|
||||
// This will bring PipApp to fullscreen
|
||||
pipApp.launchViaIntent()
|
||||
waitForAnimationComplete()
|
||||
}
|
||||
teardown {
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
}
|
||||
}
|
||||
assertions {
|
||||
val displayBounds = WindowUtils.getDisplayBounds(rotation)
|
||||
windowManagerTrace {
|
||||
all("pipApp must remain inside visible bounds") {
|
||||
coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
|
||||
}
|
||||
all("Initially shows both app windows then pipApp hides testApp") {
|
||||
showsAppWindow(testApp.defaultWindowName)
|
||||
.and().showsAppWindowOnTop(pipApp.defaultWindowName)
|
||||
.then()
|
||||
.hidesAppWindow(testApp.defaultWindowName)
|
||||
}
|
||||
navBarWindowIsAlwaysVisible()
|
||||
statusBarWindowIsAlwaysVisible()
|
||||
}
|
||||
layersTrace {
|
||||
all("Initially shows both app layers then pipApp hides testApp") {
|
||||
showsLayer(testApp.defaultWindowName)
|
||||
.and().showsLayer(pipApp.defaultWindowName)
|
||||
.then()
|
||||
.hidesLayer(testApp.defaultWindowName)
|
||||
}
|
||||
start("testApp covers the fullscreen, pipApp remains inside display") {
|
||||
hasVisibleRegion(testApp.defaultWindowName, displayBounds)
|
||||
coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
|
||||
}
|
||||
end("pipApp covers the fullscreen") {
|
||||
hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
|
||||
}
|
||||
navBarLayerIsAlwaysVisible()
|
||||
statusBarLayerIsAlwaysVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
@JvmStatic
|
||||
fun getParams(): Collection<Array<Any>> {
|
||||
val supportedRotations = intArrayOf(Surface.ROTATION_0)
|
||||
return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,28 +16,21 @@
|
||||
|
||||
package com.android.wm.shell.flicker.pip
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.graphics.Region
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import android.view.WindowManager
|
||||
import androidx.test.filters.RequiresDevice
|
||||
import com.android.compatibility.common.util.SystemUtil
|
||||
import com.android.server.wm.flicker.dsl.FlickerBuilder
|
||||
import com.android.server.wm.flicker.dsl.runWithFlicker
|
||||
import com.android.server.wm.flicker.helpers.WindowUtils
|
||||
import com.android.server.wm.flicker.helpers.closePipWindow
|
||||
import com.android.server.wm.flicker.helpers.hasPipWindow
|
||||
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
|
||||
import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_COMPONENT_NAME
|
||||
import com.android.wm.shell.flicker.IME_WINDOW_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_WINDOW_NAME
|
||||
import com.android.wm.shell.flicker.helpers.ImeAppHelper
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Test Pip launch.
|
||||
@@ -50,9 +43,6 @@ class PipKeyboardTest(
|
||||
rotationName: String,
|
||||
rotation: Int
|
||||
) : PipTestBase(rotationName, rotation) {
|
||||
private val windowManager: WindowManager =
|
||||
instrumentation.context.getSystemService(WindowManager::class.java)
|
||||
|
||||
private val keyboardApp = ImeAppHelper(instrumentation)
|
||||
|
||||
private val keyboardScenario: FlickerBuilder
|
||||
@@ -71,7 +61,7 @@ class PipKeyboardTest(
|
||||
// open an app with an input field and a keyboard
|
||||
// UiAutomator doesn't support to launch the multiple Activities in a task.
|
||||
// So use launchActivity() for the Keyboard Activity.
|
||||
launchActivity(TEST_APP_IME_ACTIVITY_COMPONENT_NAME)
|
||||
keyboardApp.launchViaIntent()
|
||||
}
|
||||
}
|
||||
teardown {
|
||||
@@ -103,10 +93,8 @@ class PipKeyboardTest(
|
||||
assertions {
|
||||
windowManagerTrace {
|
||||
all("PiP window must remain inside visible bounds") {
|
||||
coversAtMostRegion(
|
||||
partialWindowTitle = "PipActivity",
|
||||
region = Region(windowManager.maximumWindowMetrics.bounds)
|
||||
)
|
||||
val displayBounds = WindowUtils.getDisplayBounds(rotation)
|
||||
coversAtMostRegion(testApp.defaultWindowName, displayBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,74 +120,13 @@ class PipKeyboardTest(
|
||||
assertions {
|
||||
windowManagerTrace {
|
||||
end {
|
||||
isAboveWindow(IME_WINDOW_NAME, TEST_APP_PIP_ACTIVITY_WINDOW_NAME)
|
||||
isAboveWindow(IME_WINDOW_NAME, testApp.defaultWindowName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchActivity(
|
||||
activity: ComponentName? = null,
|
||||
action: String? = null,
|
||||
flags: Set<Int> = setOf(),
|
||||
boolExtras: Map<String, Boolean> = mapOf(),
|
||||
intExtras: Map<String, Int> = mapOf(),
|
||||
stringExtras: Map<String, String> = mapOf()
|
||||
) {
|
||||
require(activity != null || !action.isNullOrBlank()) {
|
||||
"Cannot launch an activity with neither activity name nor action!"
|
||||
}
|
||||
val command = composeCommand(
|
||||
"start", activity, action, flags, boolExtras, intExtras, stringExtras)
|
||||
executeShellCommand(command)
|
||||
}
|
||||
|
||||
private fun composeCommand(
|
||||
command: String,
|
||||
activity: ComponentName?,
|
||||
action: String?,
|
||||
flags: Set<Int>,
|
||||
boolExtras: Map<String, Boolean>,
|
||||
intExtras: Map<String, Int>,
|
||||
stringExtras: Map<String, String>
|
||||
): String = buildString {
|
||||
append("am ")
|
||||
append(command)
|
||||
activity?.let {
|
||||
append(" -n ")
|
||||
append(it.flattenToShortString())
|
||||
}
|
||||
action?.let {
|
||||
append(" -a ")
|
||||
append(it)
|
||||
}
|
||||
flags.forEach {
|
||||
append(" -f ")
|
||||
append(it)
|
||||
}
|
||||
boolExtras.forEach {
|
||||
append(it.withFlag("ez"))
|
||||
}
|
||||
intExtras.forEach {
|
||||
append(it.withFlag("ei"))
|
||||
}
|
||||
stringExtras.forEach {
|
||||
append(it.withFlag("es"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value"
|
||||
|
||||
private fun executeShellCommand(cmd: String): String {
|
||||
try {
|
||||
return SystemUtil.runShellCommand(instrumentation, cmd)
|
||||
} catch (e: IOException) {
|
||||
Log.e("FlickerTests", "Error running shell command: $cmd")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TEST_REPETITIONS = 10
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.content.Intent
|
||||
import android.view.Surface
|
||||
import androidx.test.filters.RequiresDevice
|
||||
import com.android.server.wm.flicker.dsl.runFlicker
|
||||
import com.android.server.wm.flicker.helpers.WindowUtils
|
||||
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
|
||||
import com.android.wm.shell.flicker.helpers.FixedAppHelper
|
||||
import com.android.wm.shell.flicker.helpers.PipAppHelper
|
||||
import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
|
||||
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION
|
||||
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
|
||||
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION
|
||||
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
|
||||
import org.junit.Assert.assertEquals
|
||||
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 with orientation changes.
|
||||
* To run this test: `atest WMShellFlickerTests:PipOrientationTest`
|
||||
*/
|
||||
@RequiresDevice
|
||||
@RunWith(Parameterized::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class PipOrientationTest(
|
||||
rotationName: String,
|
||||
rotation: Int
|
||||
) : AppTestBase(rotationName, rotation) {
|
||||
// Helper class to process test actions by broadcast.
|
||||
private inner class BroadcastActionTrigger {
|
||||
private fun createIntentWithAction(broadcastAction: String): Intent {
|
||||
return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
|
||||
}
|
||||
fun doAction(broadcastAction: String) {
|
||||
instrumentation.getContext().sendBroadcast(createIntentWithAction(broadcastAction))
|
||||
}
|
||||
fun requestOrientationForPip(orientation: Int) {
|
||||
instrumentation.getContext()
|
||||
.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
|
||||
.putExtra(EXTRA_PIP_ORIENTATION, orientation.toString()))
|
||||
}
|
||||
}
|
||||
private val broadcastActionTrigger = BroadcastActionTrigger()
|
||||
|
||||
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
private val ORIENTATION_LANDSCAPE = 0
|
||||
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
private val ORIENTATION_PORTRAIT = 1
|
||||
|
||||
private val testApp = FixedAppHelper(instrumentation)
|
||||
private val pipApp = PipAppHelper(instrumentation)
|
||||
|
||||
@Test
|
||||
fun testEnterPipToOtherOrientation() {
|
||||
runFlicker(instrumentation) {
|
||||
withTestName { "testEnterPipToOtherOrientation" }
|
||||
setup {
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
device.wakeUpAndGoToHomeScreen()
|
||||
// Launch a portrait only app on the fullscreen stack
|
||||
testApp.launchViaIntent(stringExtras = mapOf(
|
||||
EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
|
||||
waitForAnimationComplete()
|
||||
// Launch the PiP activity fixed as landscape
|
||||
pipApp.launchViaIntent(stringExtras = mapOf(
|
||||
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
|
||||
waitForAnimationComplete()
|
||||
}
|
||||
}
|
||||
transitions {
|
||||
// Enter PiP, and assert that the PiP is within bounds now that the device is back
|
||||
// in portrait
|
||||
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
|
||||
waitForAnimationComplete()
|
||||
}
|
||||
teardown {
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
}
|
||||
}
|
||||
assertions {
|
||||
windowManagerTrace {
|
||||
all("pipApp window is always on top") {
|
||||
showsAppWindowOnTop(pipApp.defaultWindowName)
|
||||
}
|
||||
start("pipApp window hides testApp") {
|
||||
hidesAppWindow(testApp.defaultWindowName)
|
||||
}
|
||||
end("testApp windows is shown") {
|
||||
showsAppWindow(testApp.defaultWindowName)
|
||||
}
|
||||
navBarWindowIsAlwaysVisible()
|
||||
statusBarWindowIsAlwaysVisible()
|
||||
}
|
||||
layersTrace {
|
||||
val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
|
||||
val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
|
||||
start("pipApp layer hides testApp") {
|
||||
hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
|
||||
hidesLayer(testApp.defaultWindowName)
|
||||
}
|
||||
end("testApp layer covers fullscreen") {
|
||||
hasVisibleRegion(testApp.defaultWindowName, endingBounds)
|
||||
}
|
||||
navBarLayerIsAlwaysVisible(bugId = 140855415)
|
||||
statusBarLayerIsAlwaysVisible(bugId = 140855415)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetRequestedOrientationWhilePinned() {
|
||||
runFlicker(instrumentation) {
|
||||
withTestName { "testSetRequestedOrientationWhilePinned" }
|
||||
setup {
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
device.wakeUpAndGoToHomeScreen()
|
||||
// Launch the PiP activity fixed as landscape
|
||||
pipApp.launchViaIntent(stringExtras = mapOf(
|
||||
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
|
||||
EXTRA_ENTER_PIP to "true"))
|
||||
waitForAnimationComplete()
|
||||
assertEquals(Surface.ROTATION_0, device.displayRotation)
|
||||
}
|
||||
}
|
||||
transitions {
|
||||
// Request that the orientation is set to landscape
|
||||
broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
|
||||
|
||||
// Launch the activity back into fullscreen and ensure that it is now in landscape
|
||||
pipApp.launchViaIntent()
|
||||
waitForAnimationComplete()
|
||||
assertEquals(Surface.ROTATION_90, device.displayRotation)
|
||||
}
|
||||
teardown {
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
}
|
||||
}
|
||||
assertions {
|
||||
val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
|
||||
val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
|
||||
windowManagerTrace {
|
||||
start("PIP window must remain inside display") {
|
||||
coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
|
||||
}
|
||||
end("pipApp shows on top") {
|
||||
showsAppWindowOnTop(pipApp.defaultWindowName)
|
||||
}
|
||||
navBarWindowIsAlwaysVisible()
|
||||
statusBarWindowIsAlwaysVisible()
|
||||
}
|
||||
layersTrace {
|
||||
start("PIP layer must remain inside display") {
|
||||
coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
|
||||
}
|
||||
end("pipApp layer covers fullscreen") {
|
||||
hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
|
||||
}
|
||||
navBarLayerIsAlwaysVisible(bugId = 140855415)
|
||||
statusBarLayerIsAlwaysVisible(bugId = 140855415)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
@JvmStatic
|
||||
fun getParams(): Collection<Array<Any>> {
|
||||
val supportedRotations = intArrayOf(Surface.ROTATION_0)
|
||||
return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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 androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.server.wm.flicker.Flicker
|
||||
import com.android.server.wm.flicker.FlickerTestRunner
|
||||
import com.android.server.wm.flicker.FlickerTestRunnerFactory
|
||||
import com.android.server.wm.flicker.endRotation
|
||||
import com.android.server.wm.flicker.helpers.WindowUtils
|
||||
import com.android.server.wm.flicker.helpers.buildTestTag
|
||||
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.wm.shell.flicker.helpers.FixedAppHelper
|
||||
import com.android.wm.shell.flicker.helpers.PipAppHelper
|
||||
import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.navBarLayerRotatesAndScales
|
||||
import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.noUncoveredRegions
|
||||
import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.statusBarLayerRotatesScales
|
||||
import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
/**
|
||||
* Test Pip Stack in bounds after rotations.
|
||||
* To run this test: `atest WMShellFlickerTests:PipRotationTest`
|
||||
*/
|
||||
@RequiresDevice
|
||||
@RunWith(Parameterized::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class PipRotationTest(
|
||||
testName: String,
|
||||
flickerSpec: Flicker
|
||||
) : FlickerTestRunner(testName, flickerSpec) {
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
@JvmStatic
|
||||
fun getParams(): Collection<Array<Any>> {
|
||||
val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
val testApp = FixedAppHelper(instrumentation)
|
||||
val pipApp = PipAppHelper(instrumentation)
|
||||
return FlickerTestRunnerFactory(instrumentation,
|
||||
listOf(Surface.ROTATION_0, Surface.ROTATION_90))
|
||||
.buildRotationTest { configuration ->
|
||||
withTestName { buildTestTag("PipRotationTest", testApp, configuration) }
|
||||
repeat { configuration.repetitions }
|
||||
setup {
|
||||
test {
|
||||
AppTestBase.removeAllTasksButHome()
|
||||
device.wakeUpAndGoToHomeScreen()
|
||||
pipApp.launchViaIntent(stringExtras = mapOf(
|
||||
EXTRA_ENTER_PIP to "true"))
|
||||
testApp.launchViaIntent()
|
||||
AppTestBase.waitForAnimationComplete()
|
||||
}
|
||||
eachRun {
|
||||
setRotation(configuration.startRotation)
|
||||
}
|
||||
}
|
||||
transitions {
|
||||
setRotation(configuration.endRotation)
|
||||
}
|
||||
teardown {
|
||||
eachRun {
|
||||
setRotation(Surface.ROTATION_0)
|
||||
}
|
||||
test {
|
||||
AppTestBase.removeAllTasksButHome()
|
||||
}
|
||||
}
|
||||
assertions {
|
||||
windowManagerTrace {
|
||||
navBarWindowIsAlwaysVisible()
|
||||
statusBarWindowIsAlwaysVisible()
|
||||
}
|
||||
layersTrace {
|
||||
navBarLayerIsAlwaysVisible(bugId = 140855415)
|
||||
statusBarLayerIsAlwaysVisible(bugId = 140855415)
|
||||
noUncoveredRegions(configuration.startRotation,
|
||||
configuration.endRotation, allStates = false)
|
||||
navBarLayerRotatesAndScales(configuration.startRotation,
|
||||
configuration.endRotation)
|
||||
statusBarLayerRotatesScales(configuration.startRotation,
|
||||
configuration.endRotation)
|
||||
}
|
||||
layersTrace {
|
||||
val startingBounds = WindowUtils.getDisplayBounds(
|
||||
configuration.startRotation)
|
||||
val endingBounds = WindowUtils.getDisplayBounds(
|
||||
configuration.endRotation)
|
||||
start("appLayerRotates_StartingBounds") {
|
||||
hasVisibleRegion(testApp.defaultWindowName, startingBounds)
|
||||
coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
|
||||
}
|
||||
end("appLayerRotates_EndingBounds") {
|
||||
hasVisibleRegion(testApp.defaultWindowName, endingBounds)
|
||||
coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.dsl.runFlicker
|
||||
import com.android.server.wm.flicker.helpers.WindowUtils
|
||||
import com.android.server.wm.flicker.helpers.exitSplitScreen
|
||||
import com.android.server.wm.flicker.helpers.isInSplitScreen
|
||||
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.wm.shell.flicker.helpers.PipAppHelper
|
||||
import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
|
||||
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
|
||||
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 with split-screen.
|
||||
* To run this test: `atest WMShellFlickerTests:PipSplitScreenTest`
|
||||
*/
|
||||
@RequiresDevice
|
||||
@RunWith(Parameterized::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@FlakyTest(bugId = 161435597)
|
||||
class PipSplitScreenTest(
|
||||
rotationName: String,
|
||||
rotation: Int
|
||||
) : AppTestBase(rotationName, rotation) {
|
||||
private val pipApp = PipAppHelper(instrumentation)
|
||||
private val imeApp = ImeAppHelper(instrumentation)
|
||||
private val testApp = FixedAppHelper(instrumentation)
|
||||
|
||||
@Test
|
||||
fun testShowsPipLaunchingToSplitScreen() {
|
||||
runFlicker(instrumentation) {
|
||||
withTestName { "testShowsPipLaunchingToSplitScreen" }
|
||||
repeat { TEST_REPETITIONS }
|
||||
setup {
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
device.wakeUpAndGoToHomeScreen()
|
||||
pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
|
||||
waitForAnimationComplete()
|
||||
}
|
||||
}
|
||||
transitions {
|
||||
testApp.launchViaIntent()
|
||||
device.launchSplitScreen()
|
||||
imeApp.launchViaIntent()
|
||||
waitForAnimationComplete()
|
||||
}
|
||||
teardown {
|
||||
eachRun {
|
||||
imeApp.exit()
|
||||
if (device.isInSplitScreen()) {
|
||||
device.exitSplitScreen()
|
||||
}
|
||||
testApp.exit()
|
||||
}
|
||||
test {
|
||||
removeAllTasksButHome()
|
||||
}
|
||||
}
|
||||
assertions {
|
||||
val displayBounds = WindowUtils.getDisplayBounds(rotation)
|
||||
windowManagerTrace {
|
||||
all("PIP window must remain inside visible bounds") {
|
||||
coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
|
||||
}
|
||||
end("Both app windows should be visible") {
|
||||
showsAppWindow(testApp.defaultWindowName)
|
||||
showsAppWindow(imeApp.defaultWindowName)
|
||||
noWindowsOverlap(testApp.defaultWindowName, imeApp.defaultWindowName)
|
||||
}
|
||||
navBarWindowIsAlwaysVisible()
|
||||
statusBarWindowIsAlwaysVisible()
|
||||
}
|
||||
layersTrace {
|
||||
all("PIP layer must remain inside visible bounds") {
|
||||
coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
|
||||
}
|
||||
end("Both app layers should be visible") {
|
||||
coversAtMostRegion(displayBounds, testApp.defaultWindowName)
|
||||
coversAtMostRegion(displayBounds, imeApp.defaultWindowName)
|
||||
}
|
||||
navBarLayerIsAlwaysVisible()
|
||||
statusBarLayerIsAlwaysVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TEST_REPETITIONS = 2
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
@JvmStatic
|
||||
fun getParams(): Collection<Array<Any>> {
|
||||
val supportedRotations = intArrayOf(Surface.ROTATION_0)
|
||||
return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,11 @@
|
||||
|
||||
package com.android.wm.shell.flicker.pip
|
||||
|
||||
import com.android.wm.shell.flicker.NonRotationTestBase
|
||||
import com.android.wm.shell.flicker.helpers.PipAppHelper
|
||||
|
||||
abstract class PipTestBase(
|
||||
rotationName: String,
|
||||
rotation: Int
|
||||
) : NonRotationTestBase(rotationName, rotation) {
|
||||
) : AppTestBase(rotationName, rotation) {
|
||||
protected val testApp = PipAppHelper(instrumentation)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
package com.android.wm.shell.flicker.splitscreen
|
||||
|
||||
import com.android.wm.shell.flicker.NonRotationTestBase
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME
|
||||
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_LABEL
|
||||
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
|
||||
import com.android.wm.shell.flicker.testapp.Components
|
||||
|
||||
abstract class SplitScreenTestBase(
|
||||
rotationName: String,
|
||||
@@ -29,8 +28,8 @@ abstract class SplitScreenTestBase(
|
||||
) : NonRotationTestBase(rotationName, rotation) {
|
||||
protected val splitScreenApp = SplitScreenHelper(instrumentation,
|
||||
TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
|
||||
TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
|
||||
Components.SplitScreenActivity())
|
||||
protected val secondaryApp = SplitScreenHelper(instrumentation,
|
||||
TEST_APP_SPLITSCREEN_SECONDARY_LABEL,
|
||||
TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME)
|
||||
Components.SplitScreenSecondaryActivity())
|
||||
}
|
||||
|
||||
@@ -18,3 +18,9 @@ android_test {
|
||||
sdk_version: "current",
|
||||
test_suites: ["device-tests"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "wmshell-flicker-test-components",
|
||||
srcs: ["src/**/Components.java"],
|
||||
sdk_version: "test_current",
|
||||
}
|
||||
@@ -21,13 +21,25 @@
|
||||
android:targetSdkVersion="29"/>
|
||||
<application android:allowBackup="false"
|
||||
android:supportsRtl="true">
|
||||
<activity android:name=".FixedActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:launchMode="singleTop"
|
||||
android:label="FixedApp"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".PipActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
|
||||
android:label="PipApp"
|
||||
android:exported="true">
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:label="PipApp"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
@@ -39,9 +51,9 @@
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ImeActivity"
|
||||
android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
|
||||
android:label="ImeApp"
|
||||
android:exported="true">
|
||||
android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
|
||||
android:label="ImeApp"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.content.ComponentName;
|
||||
|
||||
public class Components {
|
||||
public abstract static class ComponentsInfo {
|
||||
public ComponentName getComponentName() {
|
||||
return ComponentName.createRelative(PACKAGE_NAME, "." + getActivityName());
|
||||
}
|
||||
public abstract String getActivityName();
|
||||
}
|
||||
|
||||
public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp";
|
||||
|
||||
public static class FixedActivity extends ComponentsInfo {
|
||||
// Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
|
||||
public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
|
||||
|
||||
@Override
|
||||
public String getActivityName() {
|
||||
return FixedActivity.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PipActivity extends ComponentsInfo {
|
||||
// Intent action that this activity dynamically registers to enter picture-in-picture
|
||||
public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP";
|
||||
// Intent action that this activity dynamically registers to set requested orientation.
|
||||
// Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
|
||||
public static final String ACTION_SET_REQUESTED_ORIENTATION =
|
||||
PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION";
|
||||
|
||||
// Calls enterPictureInPicture() on creation
|
||||
public static final String EXTRA_ENTER_PIP = "enter_pip";
|
||||
// Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
|
||||
public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
|
||||
// Adds a click listener to finish this activity when it is clicked
|
||||
public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
|
||||
|
||||
@Override
|
||||
public String getActivityName() {
|
||||
return PipActivity.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImeActivity extends ComponentsInfo {
|
||||
@Override
|
||||
public String getActivityName() {
|
||||
return ImeActivity.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SplitScreenActivity extends ComponentsInfo {
|
||||
@Override
|
||||
public String getActivityName() {
|
||||
return SplitScreenActivity.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SplitScreenSecondaryActivity extends ComponentsInfo {
|
||||
@Override
|
||||
public String getActivityName() {
|
||||
return SplitScreenSecondaryActivity.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class FixedActivity extends SimpleActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
// Set the fixed orientation if requested
|
||||
if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
|
||||
final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
|
||||
setRequestedOrientation(ori);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ import static android.media.session.PlaybackState.STATE_PAUSED;
|
||||
import static android.media.session.PlaybackState.STATE_PLAYING;
|
||||
import static android.media.session.PlaybackState.STATE_STOPPED;
|
||||
|
||||
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP;
|
||||
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
|
||||
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP;
|
||||
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PictureInPictureParams;
|
||||
@@ -32,12 +37,12 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
@@ -50,6 +55,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PipActivity extends Activity {
|
||||
private static final String TAG = PipActivity.class.getSimpleName();
|
||||
/**
|
||||
* A media session title for when the session is in {@link STATE_PLAYING}.
|
||||
* TvPipNotificationTests check whether the actual notification title matches this string.
|
||||
@@ -88,27 +94,43 @@ public class PipActivity extends Activity {
|
||||
|
||||
private final List<RemoteAction> mSwitchOffActions = new ArrayList<>();
|
||||
private final List<RemoteAction> mSwitchOnActions = new ArrayList<>();
|
||||
private final BroadcastReceiver mCustomActionReceiver = new BroadcastReceiver() {
|
||||
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_SWITCH_ON:
|
||||
mPipParamsBuilder.setActions(mSwitchOnActions);
|
||||
break;
|
||||
case ACTION_SWITCH_OFF:
|
||||
mPipParamsBuilder.setActions(mSwitchOffActions);
|
||||
break;
|
||||
case ACTION_CLEAR:
|
||||
mPipParamsBuilder.setActions(Collections.emptyList());
|
||||
break;
|
||||
case ACTION_NO_OP:
|
||||
default:
|
||||
return;
|
||||
if (isInPictureInPictureMode()) {
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_SWITCH_ON:
|
||||
mPipParamsBuilder.setActions(mSwitchOnActions);
|
||||
break;
|
||||
case ACTION_SWITCH_OFF:
|
||||
mPipParamsBuilder.setActions(mSwitchOffActions);
|
||||
break;
|
||||
case ACTION_CLEAR:
|
||||
mPipParamsBuilder.setActions(Collections.emptyList());
|
||||
break;
|
||||
case ACTION_NO_OP:
|
||||
return;
|
||||
default:
|
||||
Log.w(TAG, "Unhandled action=" + intent.getAction());
|
||||
return;
|
||||
}
|
||||
setPictureInPictureParams(mPipParamsBuilder.build());
|
||||
} else {
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_ENTER_PIP:
|
||||
enterPip(null);
|
||||
break;
|
||||
case ACTION_SET_REQUESTED_ORIENTATION:
|
||||
setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
|
||||
EXTRA_PIP_ORIENTATION)));
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unhandled action=" + intent.getAction());
|
||||
return;
|
||||
}
|
||||
}
|
||||
setPictureInPictureParams(mPipParamsBuilder.build());
|
||||
}
|
||||
};
|
||||
private boolean mIsReceiverRegistered = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -160,36 +182,25 @@ public class PipActivity extends Activity {
|
||||
final RemoteAction clearAllAction = buildRemoteAction(icon, PIP_ACTION_CLEAR, ACTION_CLEAR);
|
||||
mSwitchOffActions.addAll(Arrays.asList(switchOnAction, clearAllAction));
|
||||
mSwitchOnActions.addAll(Arrays.asList(noOpAction, switchOffAction, clearAllAction));
|
||||
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_NO_OP);
|
||||
filter.addAction(ACTION_SWITCH_ON);
|
||||
filter.addAction(ACTION_SWITCH_OFF);
|
||||
filter.addAction(ACTION_CLEAR);
|
||||
filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
|
||||
filter.addAction(ACTION_ENTER_PIP);
|
||||
registerReceiver(mBroadcastReceiver, filter);
|
||||
|
||||
handleIntentExtra(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (mIsReceiverRegistered) {
|
||||
unregisterReceiver(mCustomActionReceiver);
|
||||
mIsReceiverRegistered = false;
|
||||
}
|
||||
unregisterReceiver(mBroadcastReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
|
||||
Configuration newConfig) {
|
||||
if (isInPictureInPictureMode && !mIsReceiverRegistered) {
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_NO_OP);
|
||||
filter.addAction(ACTION_SWITCH_ON);
|
||||
filter.addAction(ACTION_SWITCH_OFF);
|
||||
filter.addAction(ACTION_CLEAR);
|
||||
registerReceiver(mCustomActionReceiver, filter);
|
||||
|
||||
mIsReceiverRegistered = true;
|
||||
} else if (!isInPictureInPictureMode && mIsReceiverRegistered) {
|
||||
unregisterReceiver(mCustomActionReceiver);
|
||||
|
||||
mIsReceiverRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
|
||||
final Intent intent = new Intent(action);
|
||||
final PendingIntent pendingIntent =
|
||||
@@ -254,4 +265,17 @@ public class PipActivity extends Activity {
|
||||
mMediaSession.setMetadata(mMediaMetadataBuilder.build());
|
||||
mMediaSession.setActive(newState != STATE_STOPPED);
|
||||
}
|
||||
|
||||
private void handleIntentExtra(Intent intent) {
|
||||
// Set the fixed orientation if requested
|
||||
if (intent.hasExtra(EXTRA_PIP_ORIENTATION)) {
|
||||
final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION));
|
||||
setRequestedOrientation(ori);
|
||||
}
|
||||
// Enter picture in picture with the given aspect ratio if provided
|
||||
if (intent.hasExtra(EXTRA_ENTER_PIP)) {
|
||||
mPipParamsBuilder.setActions(mSwitchOnActions);
|
||||
enterPip(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user