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
This commit is contained in:
Sergey Nikolaienkov
2020-11-02 08:42:50 +00:00
parent 58dc55b6e8
commit 757e60e057
6 changed files with 283 additions and 0 deletions

View File

@@ -36,6 +36,15 @@
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<application>
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
android:exported="true"
android:label="WMShellTestsNotificationListenerService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"

View File

@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker
import android.content.pm.PackageManager
import android.os.RemoteException
import android.os.SystemClock
import android.platform.helpers.IAppHelper
@@ -41,6 +42,9 @@ abstract class FlickerTestBase {
val uiDevice by lazy {
UiDevice.getInstance(instrumentation)
}
val packageManager: PackageManager by lazy {
instrumentation.context.getPackageManager()
}
/**
* Build a test tag for the test

View File

@@ -0,0 +1,89 @@
/*
* 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.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
import com.android.compatibility.common.util.SystemUtil.runShellCommand
class NotificationListener : NotificationListenerService() {
private val notifications: MutableMap<Any, StatusBarNotification> = 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")
}
}
}

View File

@@ -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 <R> waitForResult(
timeout: Long = DEFAULT_TIMEOUT,
interval: Long = DEFAULT_POLL_INTERVAL,
extractor: () -> R,
validator: (R) -> Boolean = { it != null }
): Pair<Boolean, R?> {
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)
}

View File

@@ -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<Array<Any>> {
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

View File

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