Test PipNotification when there is a MediaSession
Bug: 171520419 Test: atest WMShellFlickerTests:TvPipNotificationTests Change-Id: Ie70f394e572e9adfeee7ecff9ba4a0e686197ca4
This commit is contained in:
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user