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:
Yuncheol Heo
2020-10-22 08:23:32 -07:00
parent 2902a86853
commit 72800c9029
23 changed files with 899 additions and 170 deletions

View File

@@ -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",
],
}

View File

@@ -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"/>

View File

@@ -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"

View File

@@ -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())
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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()
)

View File

@@ -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(

View File

@@ -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)

View File

@@ -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
) {
/**

View File

@@ -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)
}
}
}

View File

@@ -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) }
}
}
}

View File

@@ -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

View File

@@ -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) }
}
}
}

View File

@@ -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)
}
}
}
}
}
}
}

View File

@@ -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) }
}
}
}

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -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",
}

View File

@@ -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"/>

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}