From 58dc55b6e84f32124a907fd9f44e55ee7a5fbeae Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Mon, 2 Nov 2020 16:07:41 +0000 Subject: [PATCH 1/2] Prepare .wm.shell.flicker for test migration Move common constants to a separate class. Rename abstract FlickerAppHelper to BaseAppHelper. Give BaseAppHelper a UiDevice (from androidx). Make BaseAppHelper resposiblt for creating launcher. Teach BaseAppHelper to launch app via intent and force-stop it. Teach PipAppHelper to properly close Pip on TVs and work around hasPipWindow() method not working on tv. Bug: 171520419 Test: atest WMShellFlickerTests Change-Id: Icc102a51351605b4adb27c3a1c2d73030eac33ff --- .../Shell/tests/flicker/AndroidManifest.xml | 2 + .../wm/shell/flicker/CommonConstants.kt | 36 +++++++++ .../wm/shell/flicker/helpers/BaseAppHelper.kt | 81 +++++++++++++++++++ .../shell/flicker/helpers/FlickerAppHelper.kt | 31 ------- .../wm/shell/flicker/helpers/ImeAppHelper.kt | 29 ++++--- .../wm/shell/flicker/helpers/PipAppHelper.kt | 59 ++++++++++---- .../wm/shell/flicker/pip/EnterPipTest.kt | 7 +- .../wm/shell/flicker/pip/PipKeyboardTest.kt | 32 ++++---- .../wm/shell/flicker/pip/PipTestBase.kt | 4 - 9 files changed, 194 insertions(+), 87 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt delete mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml index 8b2f6681554a4..967e853b6a48a 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml @@ -32,6 +32,8 @@ + + diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt new file mode 100644 index 0000000000000..ef0390fc2a730 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -0,0 +1,36 @@ +/* + * 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 + +import android.content.ComponentName + +const val IME_WINDOW_NAME = "InputMethod" +const val PIP_WINDOW_NAME = "PipMenuActivity" + +// Test App +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" +// 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" + +const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt new file mode 100644 index 0000000000000..e8cf7d978420c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt @@ -0,0 +1,81 @@ +/* + * 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.ActivityManager +import android.app.Instrumentation +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager.FEATURE_LEANBACK +import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.StandardAppHelper +import com.android.wm.shell.flicker.TEST_APP_PACKAGE_NAME + +abstract class BaseAppHelper( + instrumentation: Instrumentation, + launcherName: String, + private val launcherActivityComponent: ComponentName +) : StandardAppHelper( + instrumentation, + TEST_APP_PACKAGE_NAME, + launcherName, + LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy +) { + protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) + + private val context: Context + get() = mInstrumentation.context + + private val activityManager: ActivityManager? + get() = context.getSystemService(ActivityManager::class.java) + + private val appSelector = By.pkg(packageName).depth(0) + + protected val isTelevision: Boolean + get() = context.packageManager.run { + hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY) + } + + val label: String + get() = context.packageManager.run { + getApplicationLabel(getApplicationInfo(packageName, 0)).toString() + } + + fun launchViaIntent() { + context.startActivity(openAppIntent) + + uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS) + } + + fun forceStop() = activityManager?.forceStopPackage(packageName) + + override fun getOpenAppIntent(): Intent { + val intent = Intent() + intent.component = launcherActivityComponent + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + return intent + } + + companion object { + private const val APP_LAUNCH_WAIT_TIME_MS = 10_000L + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt deleted file mode 100644 index 47a62ce92d11a..0000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.helpers - -import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import com.android.server.wm.flicker.helpers.StandardAppHelper - -abstract class FlickerAppHelper( - instr: Instrumentation, - launcherName: String, - launcherStrategy: ILauncherStrategy -) : StandardAppHelper(instr, sFlickerPackage, launcherName, launcherStrategy) { - companion object { - var sFlickerPackage = "com.android.wm.shell.flicker.testapp" - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt index 0cedc0a7147ff..a6650d7f13d1f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt @@ -17,37 +17,36 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.FIND_TIMEOUT import com.android.server.wm.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 org.junit.Assert open class ImeAppHelper( - instr: Instrumentation, - launcherName: String = "ImeApp", - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : FlickerAppHelper(instr, launcherName, launcherStrategy) { - open fun openIME(device: UiDevice) { - val editText = device.wait( + instrumentation: Instrumentation +) : BaseAppHelper( + instrumentation, + TEST_APP_IME_ACTIVITY_LABEL, + TEST_APP_IME_ACTIVITY_COMPONENT_NAME +) { + fun openIME() { + val editText = uiDevice.wait( Until.findObject(By.res(getPackage(), "plain_text_input")), FIND_TIMEOUT) Assert.assertNotNull("Text field not found, this usually happens when the device " + "was left in an unknown state (e.g. in split screen)", editText) editText.click() - if (!device.waitForIME()) { + if (!uiDevice.waitForIME()) { Assert.fail("IME did not appear") } } - open fun closeIME(device: UiDevice) { - device.pressBack() + fun closeIME() { + uiDevice.pressBack() // Using only the AccessibilityInfo it is not possible to identify if the IME is active - device.waitForIdle(1000) + uiDevice.waitForIdle(1000) } } \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index 539170202b8ad..d5efa409e6adf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -17,29 +17,56 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.os.SystemClock +import android.view.KeyEvent.KEYCODE_WINDOW import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiDevice -import com.android.server.wm.flicker.helpers.hasPipWindow +import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.closePipWindow -import org.junit.Assert +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 org.junit.Assert.assertNotNull class PipAppHelper( - instr: Instrumentation, - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : FlickerAppHelper(instr, "PipApp", launcherStrategy) { - fun clickEnterPipButton(device: UiDevice) { - val enterPipButton = device.findObject(By.res(getPackage(), "enter_pip")) - Assert.assertNotNull("Pip button not found, this usually happens when the device " + + instrumentation: Instrumentation +) : BaseAppHelper( + instrumentation, + TEST_APP_PIP_ACTIVITY_LABEL, + TEST_APP_PIP_ACTIVITY_COMPONENT_NAME +) { + fun clickEnterPipButton() { + val enterPipButton = uiDevice.findObject(By.res(packageName, "enter_pip")) + assertNotNull("Pip button not found, this usually happens when the device " + "was left in an unknown state (e.g. in split screen)", enterPipButton) enterPipButton.click() - device.hasPipWindow() + + // TODO(b/172321238): remove this check once hasPipWindow is fixed on TVs + if (!isTelevision) { + uiDevice.hasPipWindow() + } else { + // Simply wait for 3 seconds + SystemClock.sleep(3_000) + } } - fun closePipWindow(device: UiDevice) { - device.closePipWindow() + fun closePipWindow() { + // TODO(b/172321238): remove this check once and simply call closePipWindow once the TV + // logic is integrated there. + if (!isTelevision) { + uiDevice.closePipWindow() + } else { + // Bring up Pip menu + uiDevice.pressKeyCode(KEYCODE_WINDOW) + + // Wait for the menu to come up and render the close button + val closeButton = uiDevice.wait( + Until.findObject(By.res(SYSTEM_UI_PACKAGE_NAME, "close_button")), 3_000) + assertNotNull("Pip menu close button is not found", closeButton) + closeButton.click() + + // Give it 1 second, just in case + SystemClock.sleep(1_000) + } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 010aa0d7d8327..a1da7c939f602 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -31,6 +31,7 @@ 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.PIP_WINDOW_NAME import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -79,7 +80,7 @@ class EnterPipTest( } } transitions { - testApp.clickEnterPipButton(device) + testApp.clickEnterPipButton() device.expandPipWindow() } assertions { @@ -89,7 +90,7 @@ class EnterPipTest( all("pipWindowBecomesVisible") { this.showsAppWindow(testApp.`package`) .then() - .showsAppWindow(sPipWindowTitle) + .showsAppWindow(PIP_WINDOW_NAME) } } @@ -103,7 +104,7 @@ class EnterPipTest( all("pipLayerBecomesVisible") { this.showsLayer(testApp.launcherName) .then() - .showsLayer(sPipWindowTitle) + .showsLayer(PIP_WINDOW_NAME) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 43e0225386855..d343f2a7093ba 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip import android.content.ComponentName import android.graphics.Region -import android.support.test.launcherhelper.LauncherStrategyFactory import android.util.Log import android.view.Surface import android.view.WindowManager @@ -29,6 +28,9 @@ import com.android.server.wm.flicker.dsl.runWithFlicker 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 @@ -51,19 +53,11 @@ class PipKeyboardTest( private val windowManager: WindowManager = instrumentation.context.getSystemService(WindowManager::class.java) - private val keyboardApp = ImeAppHelper(instrumentation, "ImeApp", - LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy) - - private val KEYBOARD_ACTIVITY: ComponentName = ComponentName.createRelative( - "com.android.wm.shell.flicker.testapp", ".ImeActivity") - private val PIP_ACTIVITY_WINDOW_NAME = "PipActivity" - private val INPUT_METHOD_WINDOW_NAME = "InputMethod" - - private val testRepetitions = 10 + private val keyboardApp = ImeAppHelper(instrumentation) private val keyboardScenario: FlickerBuilder get() = FlickerBuilder(instrumentation).apply { - repeat { testRepetitions } + repeat { TEST_REPETITIONS } // disable layer tracing withLayerTracing { null } setup { @@ -73,11 +67,11 @@ class PipKeyboardTest( // launch our target pip app testApp.open() this.setRotation(rotation) - testApp.clickEnterPipButton(device) + testApp.clickEnterPipButton() // 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(KEYBOARD_ACTIVITY) + launchActivity(TEST_APP_IME_ACTIVITY_COMPONENT_NAME) } } teardown { @@ -101,10 +95,10 @@ class PipKeyboardTest( withTestName { testTag } transitions { // open the soft keyboard - keyboardApp.openIME(device) + keyboardApp.openIME() // then close it again - keyboardApp.closeIME(device) + keyboardApp.closeIME() } assertions { windowManagerTrace { @@ -127,18 +121,18 @@ class PipKeyboardTest( withTestName { testTag } transitions { // open the soft keyboard - keyboardApp.openIME(device) + keyboardApp.openIME() } teardown { eachRun { // close the keyboard - keyboardApp.closeIME(device) + keyboardApp.closeIME() } } assertions { windowManagerTrace { end { - isAboveWindow(INPUT_METHOD_WINDOW_NAME, PIP_ACTIVITY_WINDOW_NAME) + isAboveWindow(IME_WINDOW_NAME, TEST_APP_PIP_ACTIVITY_WINDOW_NAME) } } } @@ -207,6 +201,8 @@ class PipKeyboardTest( } companion object { + private const val TEST_REPETITIONS = 10 + @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection> { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt index 3822d69a65f50..c1c34ecfbaec5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt @@ -24,8 +24,4 @@ abstract class PipTestBase( rotation: Int ) : NonRotationTestBase(rotationName, rotation) { protected val testApp = PipAppHelper(instrumentation) - - companion object { - const val sPipWindowTitle = "PipMenuActivity" - } } From 757e60e05726d28a7c8b5f79930d501cee74af7c Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Mon, 2 Nov 2020 08:42:50 +0000 Subject: [PATCH 2/2] Introduce .wm.flicker.pip.tv.TvPipNotificationTests Move first test out of android.systemui.cts.tv.PipNotificationTests to com.android.wm.shell.flicker.pip.tv.TvPipNotification tests. Bug: 171520419 Test: atest WMShellFlickerTests:TvPipNotificationTests Change-Id: I6a3e2fb96a7688814ce909f28d6d904356fad3fd --- .../Shell/tests/flicker/AndroidManifest.xml | 9 ++ .../wm/shell/flicker/FlickerTestBase.kt | 4 + .../wm/shell/flicker/NotificationListener.kt | 89 ++++++++++++++++++ .../com/android/wm/shell/flicker/WaitUtils.kt | 45 +++++++++ .../flicker/pip/tv/TvPipNotificationTests.kt | 92 +++++++++++++++++++ .../wm/shell/flicker/pip/tv/TvPipTestBase.kt | 44 +++++++++ 6 files changed, 283 insertions(+) create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml index 967e853b6a48a..58fc90e4d8275 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml @@ -36,6 +36,15 @@ + + + + + + = mutableMapOf() + + override fun onNotificationPosted(sbn: StatusBarNotification) { + if (DEBUG) Log.d(TAG, "onNotificationPosted: $sbn") + notifications[sbn.key] = sbn + } + + override fun onNotificationRemoved(sbn: StatusBarNotification) { + if (DEBUG) Log.d(TAG, "onNotificationRemoved: $sbn") + notifications.remove(sbn.key) + } + + override fun onListenerConnected() { + if (DEBUG) Log.d(TAG, "onListenerConnected") + instance = this + } + + override fun onListenerDisconnected() { + if (DEBUG) Log.d(TAG, "onListenerDisconnected") + instance = null + notifications.clear() + } + + companion object { + private const val DEBUG = false + private const val TAG = "WMShellFlickerTests_NotificationListener" + + private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s" + private const val CMD_NOTIFICATION_DISALLOW_LISTENER = + "cmd notification disallow_listener %s" + private const val COMPONENT_NAME = "com.android.wm.shell.flicker/.NotificationListener" + + private var instance: NotificationListener? = null + + fun startNotificationListener(): Boolean { + if (instance != null) { + return true + } + + runShellCommand(CMD_NOTIFICATION_ALLOW_LISTENER.format(COMPONENT_NAME)) + return wait { instance != null } + } + + fun stopNotificationListener(): Boolean { + if (instance == null) { + return true + } + + runShellCommand(CMD_NOTIFICATION_DISALLOW_LISTENER.format(COMPONENT_NAME)) + return wait { instance == null } + } + + fun waitForNotificationToAppear(predicate: (StatusBarNotification) -> Boolean): Boolean { + return instance?.let { + wait { it.notifications.values.any(predicate) } + } ?: throw IllegalStateException("NotificationListenerService is not connected") + } + + fun waitForNotificationToDisappear(predicate: (StatusBarNotification) -> Boolean): Boolean { + return instance?.let { + wait { it.notifications.values.none(predicate) } + } ?: throw IllegalStateException("NotificationListenerService is not connected") + } + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt new file mode 100644 index 0000000000000..a6d67355f2711 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt @@ -0,0 +1,45 @@ +/* + * 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 + +import android.os.SystemClock + +private const val DEFAULT_TIMEOUT = 10000L +private const val DEFAULT_POLL_INTERVAL = 1000L + +fun wait(condition: () -> Boolean): Boolean { + val (success, _) = waitForResult(extractor = condition, validator = { it }) + return success +} + +fun waitForResult( + timeout: Long = DEFAULT_TIMEOUT, + interval: Long = DEFAULT_POLL_INTERVAL, + extractor: () -> R, + validator: (R) -> Boolean = { it != null } +): Pair { + val startTime = SystemClock.uptimeMillis() + do { + val result = extractor() + if (validator(result)) { + return (true to result) + } + SystemClock.sleep(interval) + } while (SystemClock.uptimeMillis() - startTime < timeout) + + return (false to null) +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt new file mode 100644 index 0000000000000..569da1ddd953d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt @@ -0,0 +1,92 @@ +/* + * 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.tv + +import android.app.Notification +import android.service.notification.StatusBarNotification +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.NotificationListener.Companion.startNotificationListener +import com.android.wm.shell.flicker.NotificationListener.Companion.stopNotificationListener +import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToAppear +import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToDisappear +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Before +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 Notifications on TV. + * To run this test: `atest WMShellFlickerTests:TvPipNotificationTests` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class TvPipNotificationTests(rotationName: String, rotation: Int) + : TvPipTestBase(rotationName, rotation) { + + @Before + override fun setUp() { + super.setUp() + val started = startNotificationListener() + if (!started) { + error("NotificationListener hasn't started") + } + } + + @After + override fun tearDown() { + stopNotificationListener() + testApp.forceStop() + super.tearDown() + } + + @Test + fun pipNotification_postedAndDismissed() { + testApp.launchViaIntent() + testApp.clickEnterPipButton() + + assertTrue("Pip notification should have been posted", + waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.label) }) + + testApp.closePipWindow() + + assertTrue("Pip notification should have been dismissed", + waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.label) }) + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection> { + val supportedRotations = intArrayOf(Surface.ROTATION_0) + return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) } + } + } +} + +private const val PIP_NOTIFICATION_TAG = "PipNotification" + +private val StatusBarNotification.title: String + get() = notification?.extras?.getString(Notification.EXTRA_TITLE) ?: "" + +private fun StatusBarNotification.isPipNotificationWithTitle(expectedTitle: String): Boolean = + tag == PIP_NOTIFICATION_TAG && title == expectedTitle \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt new file mode 100644 index 0000000000000..104248c64dddf --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt @@ -0,0 +1,44 @@ +/* + * 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.tv + +import android.content.pm.PackageManager.FEATURE_LEANBACK +import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY +import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.wm.shell.flicker.pip.PipTestBase +import org.junit.After +import org.junit.Assume +import org.junit.Before + +abstract class TvPipTestBase(rotationName: String, rotation: Int) + : PipTestBase(rotationName, rotation) { + + private val isTelevision: Boolean + get() = packageManager.run { + hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY) + } + + @Before + open fun setUp() { + Assume.assumeTrue(isTelevision) + uiDevice.wakeUpAndGoToHomeScreen() + } + + @After + open fun tearDown() { + } +} \ No newline at end of file