Test PipNotification when there is a MediaSession

Bug: 171520419
Test: atest WMShellFlickerTests:TvPipNotificationTests
Change-Id: Ie70f394e572e9adfeee7ecff9ba4a0e686197ca4
This commit is contained in:
Sergey Nikolaienkov
2020-11-05 13:54:46 +00:00
parent 2d2d6357e8
commit fbc3469d7b
7 changed files with 213 additions and 19 deletions

View File

@@ -34,6 +34,8 @@
<uses-permission android:name="android.permission.READ_LOGS"/>
<!-- Force-stop test apps -->
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<!-- Control test app's media session -->
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
<application>
<uses-library android:name="android.test.runner"/>

View File

@@ -74,11 +74,21 @@ class NotificationListener : NotificationListenerService() {
return wait { instance == null }
}
fun findNotification(
predicate: (StatusBarNotification) -> Boolean
): StatusBarNotification? {
instance?.run {
return notifications.values.firstOrNull(predicate)
} ?: throw IllegalStateException("NotificationListenerService is not connected")
}
fun waitForNotificationToAppear(
predicate: (StatusBarNotification) -> Boolean
): StatusBarNotification? {
return instance?.let {
waitForResult(extractor = { it.notifications.values.first(predicate) }).second
instance?.let {
return waitForResult(extractor = {
it.notifications.values.firstOrNull(predicate)
}).second
} ?: throw IllegalStateException("NotificationListenerService is not connected")
}

View File

@@ -42,7 +42,7 @@ abstract class BaseAppHelper(
) {
protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
private val context: Context
protected val context: Context
get() = mInstrumentation.context
private val activityManager: ActivityManager?

View File

@@ -17,6 +17,8 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.os.SystemClock
import android.view.KeyEvent.KEYCODE_WINDOW
import androidx.test.uiautomator.By
@@ -35,6 +37,15 @@ class PipAppHelper(
TEST_APP_PIP_ACTIVITY_LABEL,
TEST_APP_PIP_ACTIVITY_COMPONENT_NAME
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
?: error("Could not get MediaSessionManager")
private val mediaController: MediaController?
get() = mediaSessionManager.getActiveSessions(null).firstOrNull {
it.packageName == packageName
}
fun clickEnterPipButton() {
val enterPipButton = uiDevice.findObject(By.res(packageName, "enter_pip"))
assertNotNull("Pip button not found, this usually happens when the device " +
@@ -50,6 +61,19 @@ class PipAppHelper(
}
}
fun clickStartMediaSessionButton() {
val startButton = uiDevice.findObject(By.res(packageName, "media_session_start"))
assertNotNull("Start button not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)", startButton)
startButton.click()
}
fun pauseMedia() = mediaController?.transportControls?.pause()
?: error("No active media session found")
fun stopMedia() = mediaController?.transportControls?.stop()
?: error("No active media session found")
fun closePipWindow() {
// TODO(b/172321238): remove this check once and simply call closePipWindow once the TV
// logic is integrated there.

View File

@@ -22,12 +22,14 @@ import android.os.Bundle
import android.service.notification.StatusBarNotification
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.NotificationListener.Companion.findNotification
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.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.FixMethodOrder
@@ -83,10 +85,10 @@ class TvPipNotificationTests(rotationName: String, rotation: Int)
val notification: StatusBarNotification = waitForNotificationToAppear {
it.isPipNotificationWithTitle(testApp.label)
} ?: throw AssertionError("Pip notification should have been posted")
} ?: fail("Pip notification should have been posted")
notification.deleteIntent?.send()
?: throw AssertionError("Pip notification should contain `delete_intent`")
?: fail("Pip notification should contain `delete_intent`")
assertTrue("Pip should have closed by sending the `delete_intent`",
testApp.waitUntilClosed())
@@ -101,10 +103,10 @@ class TvPipNotificationTests(rotationName: String, rotation: Int)
val notification: StatusBarNotification = waitForNotificationToAppear {
it.isPipNotificationWithTitle(testApp.label)
} ?: throw AssertionError("Pip notification should have been posted")
} ?: fail("Pip notification should have been posted")
notification.contentIntent?.send()
?: throw AssertionError("Pip notification should contain `content_intent`")
?: fail("Pip notification should contain `content_intent`")
assertTrue("Pip menu should have been shown after sending `content_intent`",
uiDevice.waitForTvPipMenu())
@@ -113,7 +115,53 @@ class TvPipNotificationTests(rotationName: String, rotation: Int)
testApp.closePipWindow()
}
@Test
fun pipNotification_mediaSessionTitle_isDisplayed() {
testApp.launchViaIntent()
// Start media session and to PiP
testApp.clickStartMediaSessionButton()
testApp.clickEnterPipButton()
// Wait for the correct notification to show up...
waitForNotificationToAppear {
it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING)
} ?: fail("Pip notification with media session title should have been posted")
// ... and make sure "regular" PiP notification is now shown
assertNull("Regular notification should not have been posted",
findNotification { it.isPipNotificationWithTitle(testApp.label) })
// Pause the media session. When paused the application updates the title for the media
// session. This change should be reflected in the notification.
testApp.pauseMedia()
// Wait for the "paused" notification to show up...
waitForNotificationToAppear {
it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED)
} ?: fail("Pip notification with media session title should have been posted")
// ... and make sure "playing" PiP notification is gone
assertNull("Regular notification should not have been posted",
findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) })
// Now stop the media session, which should revert the title to the "default" one.
testApp.stopMedia()
// Wait for the "regular" notification to show up...
waitForNotificationToAppear {
it.isPipNotificationWithTitle(testApp.label)
} ?: fail("Pip notification with media session title should have been posted")
// ... and make sure previous ("paused") notification is gone
assertNull("Regular notification should not have been posted",
findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) })
testApp.closePipWindow()
}
private fun fail(message: String): Nothing = throw AssertionError(message)
companion object {
private const val TITLE_MEDIA_SESSION_PLAYING = "TestApp media is playing"
private const val TITLE_MEDIA_SESSION_PAUSED = "TestApp media is paused"
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {

View File

@@ -18,9 +18,36 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/holo_blue_bright">
<Button android:layout_width="wrap_content"
<Button
android:id="@+id/enter_pip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enter PIP"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Media Session"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/media_session_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/enter_pip"
android:text="Enter PIP"/>
android:text="Start"/>
<Button
android:id="@+id/media_session_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop"/>
</LinearLayout>
</LinearLayout>

View File

@@ -16,30 +16,113 @@
package com.android.wm.shell.flicker.testapp;
import static android.media.MediaMetadata.METADATA_KEY_TITLE;
import static android.media.session.PlaybackState.ACTION_PLAY_PAUSE;
import static android.media.session.PlaybackState.ACTION_STOP;
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 android.app.Activity;
import android.app.PictureInPictureParams;
import android.graphics.Rect;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.util.Rational;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
public class PipActivity extends Activity {
/**
* A media session title for when the session is in {@link STATE_PLAYING}.
* TvPipNotificationTests check whether the actual notification title matches this string.
*/
private static final String TITLE_STATE_PLAYING = "TestApp media is playing";
/**
* A media session title for when the session is in {@link STATE_PAUSED}.
* TvPipNotificationTests check whether the actual notification title matches this string.
*/
private static final String TITLE_STATE_PAUSED = "TestApp media is paused";
private MediaSession mMediaSession;
private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
.setActions(ACTION_PLAY_PAUSE | ACTION_STOP)
.setState(STATE_STOPPED, 0, 1f);
private PlaybackState mPlaybackState = mPlaybackStateBuilder.build();
private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WindowManager.LayoutParams p = getWindow().getAttributes();
p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(p);
setContentView(R.layout.activity_pip);
Button enterPip = (Button) findViewById(R.id.enter_pip);
PictureInPictureParams params = new PictureInPictureParams.Builder()
final Window window = getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
window.setAttributes(layoutParams);
setContentView(R.layout.activity_pip);
final PictureInPictureParams pipParams = new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 1))
.setSourceRectHint(new Rect(0, 0, 100, 100))
.build();
findViewById(R.id.enter_pip).setOnClickListener(v -> enterPictureInPictureMode(pipParams));
enterPip.setOnClickListener((v) -> enterPictureInPictureMode(params));
findViewById(R.id.media_session_start)
.setOnClickListener(v -> updateMediaSessionState(STATE_PLAYING));
findViewById(R.id.media_session_stop)
.setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED));
mMediaSession = new MediaSession(this, "WMShell_TestApp");
mMediaSession.setPlaybackState(mPlaybackStateBuilder.build());
mMediaSession.setCallback(new MediaSession.Callback() {
@Override
public void onPlay() {
updateMediaSessionState(STATE_PLAYING);
}
@Override
public void onPause() {
updateMediaSessionState(STATE_PAUSED);
}
@Override
public void onStop() {
updateMediaSessionState(STATE_STOPPED);
}
});
}
private void updateMediaSessionState(int newState) {
if (mPlaybackState.getState() == newState) {
return;
}
final String title;
switch (newState) {
case STATE_PLAYING:
title = TITLE_STATE_PLAYING;
break;
case STATE_PAUSED:
title = TITLE_STATE_PAUSED;
break;
case STATE_STOPPED:
title = "";
break;
default:
throw new IllegalArgumentException("Unknown state " + newState);
}
mPlaybackStateBuilder.setState(newState, 0, 1f);
mPlaybackState = mPlaybackStateBuilder.build();
mMediaMetadataBuilder.putText(METADATA_KEY_TITLE, title);
mMediaSession.setPlaybackState(mPlaybackState);
mMediaSession.setMetadata(mMediaMetadataBuilder.build());
mMediaSession.setActive(newState != STATE_STOPPED);
}
}