diff options
| author | 2020-11-05 13:54:46 +0000 | |
|---|---|---|
| committer | 2020-11-06 08:17:01 +0000 | |
| commit | fbc3469d7bdf75e42517f5fcaf7e0140f3998f4e (patch) | |
| tree | ee1e65486035b982fac9b10ebb3ccbc9d1271b86 | |
| parent | 2d2d6357e8693f05cd1e80152c5ceff6a5ea6479 (diff) | |
Test PipNotification when there is a MediaSession
Bug: 171520419
Test: atest WMShellFlickerTests:TvPipNotificationTests
Change-Id: Ie70f394e572e9adfeee7ecff9ba4a0e686197ca4
7 files changed, 211 insertions, 17 deletions
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml index 58fc90e4d827..9e8330973b40 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml @@ -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"/> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt index 3957f5a1c6f5..51f7a18f60dd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt @@ -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") } 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 index a588888c5902..55796da100c4 100644 --- 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 @@ -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? 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 edde581824d1..42686e7e87d7 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,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. 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 index 8bb78c232732..1d44658d71ad 100644 --- 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 @@ -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>> { diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml index e1870d9c523d..0e79d03cc3ee 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml @@ -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> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java index 305281691e11..f70603ede0e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java @@ -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 + + final Window window = getWindow(); + final WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); + window.setAttributes(layoutParams); + setContentView(R.layout.activity_pip); - Button enterPip = (Button) findViewById(R.id.enter_pip); - PictureInPictureParams params = new PictureInPictureParams.Builder() + 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)); + + 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); - enterPip.setOnClickListener((v) -> enterPictureInPictureMode(params)); + mMediaSession.setPlaybackState(mPlaybackState); + mMediaSession.setMetadata(mMediaMetadataBuilder.build()); + mMediaSession.setActive(newState != STATE_STOPPED); } } |