Add suitable output checker tests relevant for API 35+ in media3-ui

PiperOrigin-RevId: 675678257
This commit is contained in:
Googler 2024-09-17 13:02:16 -07:00 committed by Copybara-Service
parent acb8e71c6e
commit 087e75850e

View File

@ -37,7 +37,9 @@ import androidx.media3.common.FlagSet;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.Player.PlayWhenReadyChangeReason;
import androidx.media3.common.util.Util;
import androidx.media3.test.utils.FakeClock;
import androidx.media3.test.utils.FakeSuitableOutputChecker;
import androidx.media3.test.utils.TestExoPlayerBuilder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -51,6 +53,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.AudioDeviceInfoBuilder;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowAudioManager;
@ -74,13 +77,23 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
private ShadowPackageManager shadowPackageManager;
private ShadowApplication shadowApplication;
private Player testPlayer;
private FakeSuitableOutputChecker suitableMediaOutputChecker;
@Before
public void setUp() {
testPlayer =
TestExoPlayerBuilder builder =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setSuppressPlaybackOnUnsuitableOutput(true)
.build();
.setSuppressPlaybackOnUnsuitableOutput(true);
if (Util.SDK_INT >= 35) {
suitableMediaOutputChecker =
new FakeSuitableOutputChecker.Builder()
.setIsSuitableExternalOutputAvailable(/* isSuitableOutputAvailable= */ false)
.build();
builder.setSuitableOutputChecker(suitableMediaOutputChecker);
}
testPlayer = builder.build();
shadowApplication = shadowOf((Application) ApplicationProvider.getApplicationContext());
shadowPackageManager =
shadowOf(ApplicationProvider.getApplicationContext().getPackageManager());
@ -96,7 +109,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* playback is suppressed and then unsuppressed.
*/
@Test
public void playbackSuppressionFollowedByResolution_shouldLaunchOSwAndChangePlayerStateToPlaying()
public void playbackSuppressionFollowedByResolution_shouldLaunchOutputSwitcherAndStartPlayback()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
@ -133,8 +146,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
assertThat(intentTriggered)
.hasComponent(
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME, FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME);
assertThat(playWhenReadyChangeSequence).containsExactly(true, false, true);
assertThat(testPlayer.isPlaying()).isTrue();
assertThat(playWhenReadyChangeSequence).containsExactly(true, false, true).inOrder();
}
/**
@ -142,7 +154,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* unsuitable output and the system updated Output Switcher is present on the device.
*/
@Test
public void playEventWithPlaybackSuppressionWhenUpdatedSystemOSwPresent_shouldLaunchOSw()
public void playEventWithPlaybackSuppression_shouldLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
@ -175,7 +187,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
*/
@Test
public void
playbackSuppressionWhenBothSystemAndUserInstalledOutputSwitcherPresent_shouldLaunchSystemOSw()
playbackSuppressionWithSystemAndUserInstalledComponentsPresent_shouldLaunchSystemComponent()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
@ -319,7 +331,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* and both Output Switcher as well as the Bluetooth settings are present on the device.
*/
@Test
public void playEventWithPlaybackSuppressionWhenOSwAndBTSettingsBothPresent_shouldLaunchOSw()
public void playbackSuppressionWhenMultipleSystemComponentsPresent_shouldLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
@ -356,9 +368,8 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* is suppressed due to unsuitable output.
*/
@Test
public void
playbackSuppressionWhenOnlyUserInstalledOSwAndBTSettingsPresent_shouldNotLaunchAnyApp()
throws TimeoutException {
public void playbackSuppressionWhenOnlyUserInstalledComponentsPresent_shouldNotLaunchAnyApp()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
@ -390,7 +401,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* suppressed due to unsuitable output.
*/
@Test
public void playEventWithoutPlaybackSuppression_shouldNotLaunchEitherOSwOrBTSettings()
public void playEventWithoutPlaybackSuppression_shouldNotLaunchOutputSwitcherOrBTSettings()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
@ -423,9 +434,8 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* due to removal of all suitable audio outputs in mid of an ongoing playback.
*/
@Test
public void
playbackSuppressionDuringOngoingPlayback_shouldOnlyPauseButNotLaunchEitherOSwOrBTSettings()
throws TimeoutException {
public void playbackSuppressionDuringOngoingPlayback_shouldOnlyPauseButNotLaunchSystemComponent()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
@ -442,6 +452,15 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
testPlayer.addListener(
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
ApplicationProvider.getApplicationContext()));
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
@ -452,7 +471,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false);
assertThat(shadowApplication.getNextStartedActivity()).isNull();
assertThat(testPlayer.isPlaying()).isFalse();
assertThat(lastPlayWhenReady.get()).isFalse();
}
/** Test for pause on the Player when the playback is suppressed due to unsuitable output. */
@ -472,21 +491,20 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
AtomicBoolean isPlaybackPaused = new AtomicBoolean(false);
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
if (!playWhenReady) {
isPlaybackPaused.set(true);
}
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
assertThat(isPlaybackPaused.get()).isTrue();
assertThat(lastPlayWhenReady.get()).isFalse();
}
/**
@ -537,12 +555,20 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
WearUnsuitableOutputPlaybackSuppressionResolverListener
.DEFAULT_PLAYBACK_SUPPRESSION_AUTO_RESUME_TIMEOUT_MS,
fakeClock));
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
removeConnectedAudioOutput(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false);
@ -555,7 +581,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false);
assertThat(testPlayer.isPlaying()).isFalse();
assertThat(lastPlayWhenReady.get()).isFalse();
}
/**
@ -578,21 +604,20 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
AtomicBoolean isPlaybackPaused = new AtomicBoolean(false);
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
if (!playWhenReady) {
isPlaybackPaused.set(true);
}
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
assertThat(isPlaybackPaused.get()).isFalse();
assertThat(lastPlayWhenReady.get()).isTrue();
}
/**
@ -612,6 +637,15 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
WearUnsuitableOutputPlaybackSuppressionResolverListener
.DEFAULT_PLAYBACK_SUPPRESSION_AUTO_RESUME_TIMEOUT_MS,
fakeClock));
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
@ -626,7 +660,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false);
assertThat(testPlayer.isPlaying()).isFalse();
assertThat(lastPlayWhenReady.get()).isFalse();
}
/**
@ -670,6 +704,15 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
testPlayer.addListener(
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
ApplicationProvider.getApplicationContext(), TEST_TIME_OUT_MS, fakeClock));
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
@ -681,7 +724,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false);
assertThat(testPlayer.isPlaying()).isFalse();
assertThat(lastPlayWhenReady.get()).isFalse();
}
/** Test to ensure wake lock is acquired when playback is suppressed due to unsuitable output. */
@ -760,6 +803,128 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
assertThat(ShadowPowerManager.getLatestWakeLock().isHeld()).isFalse();
}
/** Test to verify that attempted playback is paused when the suitable output is not present. */
@Test
@Config(minSdk = 35)
public void playEvent_withSuitableOutputNotPresent_shouldPausePlaybackAndLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ false);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
testPlayer.addListener(
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
ApplicationProvider.getApplicationContext()));
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
shadowOf(Looper.getMainLooper()).idle();
assertThat(lastPlayWhenReady.get()).isFalse();
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNotNull();
assertThat(intentTriggered).hasAction(OUTPUT_SWITCHER_INTENT_ACTION_NAME);
assertThat(intentTriggered)
.hasComponent(
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME, FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME);
}
/**
* Test to verify that attempted playback is not paused when the suitable output is already
* present.
*/
@Test
@Config(minSdk = 35)
public void playEvent_withSuitableOutputPresent_shouldNotPausePlaybackOrLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ false);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ true);
testPlayer.addListener(
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
ApplicationProvider.getApplicationContext()));
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
shadowOf(Looper.getMainLooper()).idle();
assertThat(lastPlayWhenReady.get()).isTrue();
assertThat(shadowApplication.getNextStartedActivity()).isNull();
}
/**
* Test to verify that playback is not resumed when the suitable output is added after the tested
* time out.
*/
@Test
@Config(minSdk = 35)
public void playEvent_suitableOutputAddedAfterTimeOut_shouldNotResumePlayback()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ false);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
testPlayer.addListener(
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
ApplicationProvider.getApplicationContext(), TEST_TIME_OUT_MS, fakeClock));
AtomicBoolean lastPlayWhenReady = new AtomicBoolean();
testPlayer.addListener(
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
lastPlayWhenReady.set(playWhenReady);
}
});
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
fakeClock.advanceTime(TEST_TIME_OUT_MS * 2);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ true);
shadowOf(Looper.getMainLooper()).idle();
assertThat(lastPlayWhenReady.get()).isFalse();
}
private void registerFakeActivity(
String fakeActionName, String fakePackageName, String fakeClassName, int applicationFlags) {
ComponentName fakeComponentName = new ComponentName(fakePackageName, fakeClassName);