Resume/Pause on playback suppression changes with timeout.
Auto-resume playback when the removal of playback suppression due to unsuitable output is conveyed via change in playback suppression to Player.PLAYBACK_SUPPRESSION_REASON_NONE within a configurable timeout defaulting to 5 minutes. PiperOrigin-RevId: 544411987
This commit is contained in:
parent
832d5b5f98
commit
6732c0e286
@ -79,7 +79,9 @@
|
||||
playback suppression due to
|
||||
`Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT` by
|
||||
launching a system dialog to allow a user to connect a suitable audio
|
||||
output (e.g. bluetooth headphones).
|
||||
output (e.g. bluetooth headphones). The listener will auto-resume
|
||||
playback if a suitable device is connected within a configurable timeout
|
||||
(default is 5 minutes).
|
||||
* Downloads:
|
||||
* OkHttp Extension:
|
||||
* Cronet Extension:
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package androidx.media3.ui;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -23,9 +26,14 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.provider.Settings;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Player.Events;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.SystemClock;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.util.List;
|
||||
@ -33,7 +41,9 @@ import java.util.List;
|
||||
/**
|
||||
* A {@link Player.Listener} that launches a system dialog in response to {@link
|
||||
* Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} to allow the user to connect a
|
||||
* suitable audio output.
|
||||
* suitable audio output. Also, it auto-resumes the playback when the playback suppression reason is
|
||||
* changed from {@link Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} to {@link
|
||||
* Player#PLAYBACK_SUPPRESSION_REASON_NONE}.
|
||||
*
|
||||
* <p>This listener only reacts to {@link
|
||||
* Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} on Wear OS devices, while being no-op
|
||||
@ -84,15 +94,56 @@ public final class WearUnsuitableOutputPlaybackSuppressionResolverListener
|
||||
*/
|
||||
private static final int FILTER_TYPE_AUDIO = 1;
|
||||
|
||||
private Context applicationContext;
|
||||
/**
|
||||
* The default timeout for auto-resume of suppressed playback when the playback suppression reason
|
||||
* as {@link Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} is removed, in
|
||||
* milliseconds.
|
||||
*/
|
||||
public static final long DEFAULT_PLAYBACK_SUPPRESSION_AUTO_RESUME_TIMEOUT_MS =
|
||||
MINUTES.toMillis(5);
|
||||
|
||||
private final Context applicationContext;
|
||||
private final long autoResumeTimeoutAfterUnsuitableOutputSuppressionMs;
|
||||
private final Clock clock;
|
||||
|
||||
private long unsuitableOutputPlaybackSuppressionStartRealtimeMs;
|
||||
|
||||
/**
|
||||
* Creates a new {@link WearUnsuitableOutputPlaybackSuppressionResolverListener} instance.
|
||||
* Creates a new instance.
|
||||
*
|
||||
* <p>See {@link #WearUnsuitableOutputPlaybackSuppressionResolverListener(Context, long)} for more
|
||||
* details. The auto-resume timeout defaults to {@link
|
||||
* #DEFAULT_PLAYBACK_SUPPRESSION_AUTO_RESUME_TIMEOUT_MS}.
|
||||
*
|
||||
* @param context Any context.
|
||||
*/
|
||||
public WearUnsuitableOutputPlaybackSuppressionResolverListener(Context context) {
|
||||
this(context, DEFAULT_PLAYBACK_SUPPRESSION_AUTO_RESUME_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param context Any context.
|
||||
* @param autoResumeTimeoutMs Duration in milliseconds after the playback suppression during which
|
||||
* playback will be resumed automatically if the playback suppression reason is changed from
|
||||
* {@link Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} to {@link
|
||||
* Player#PLAYBACK_SUPPRESSION_REASON_NONE}. Calling with {@code autoResumeTimeoutMs = 0} will
|
||||
* cause playback to never resume automatically.
|
||||
*/
|
||||
public WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
Context context, @IntRange(from = 0) long autoResumeTimeoutMs) {
|
||||
this(context, autoResumeTimeoutMs, SystemClock.DEFAULT);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
Context context, @IntRange(from = 0) long autoResumeTimeoutMs, Clock clock) {
|
||||
checkArgument(autoResumeTimeoutMs >= 0);
|
||||
applicationContext = context.getApplicationContext();
|
||||
autoResumeTimeoutAfterUnsuitableOutputSuppressionMs = autoResumeTimeoutMs;
|
||||
this.clock = clock;
|
||||
unsuitableOutputPlaybackSuppressionStartRealtimeMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,12 +151,23 @@ public final class WearUnsuitableOutputPlaybackSuppressionResolverListener
|
||||
if (!Util.isWear(applicationContext)) {
|
||||
return;
|
||||
}
|
||||
if (events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
|
||||
if ((events.contains(Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)
|
||||
|| events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED))
|
||||
&& player.getPlayWhenReady()
|
||||
&& player.getPlaybackSuppressionReason()
|
||||
== Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) {
|
||||
player.pause();
|
||||
if (events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
|
||||
launchSystemMediaOutputSwitcherUi(applicationContext);
|
||||
unsuitableOutputPlaybackSuppressionStartRealtimeMs = clock.elapsedRealtime();
|
||||
}
|
||||
} else if (events.contains(Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)
|
||||
&& player.getPlaybackSuppressionReason() == Player.PLAYBACK_SUPPRESSION_REASON_NONE
|
||||
&& unsuitableOutputPlaybackSuppressionStartRealtimeMs != C.TIME_UNSET
|
||||
&& (clock.elapsedRealtime() - unsuitableOutputPlaybackSuppressionStartRealtimeMs
|
||||
< autoResumeTimeoutAfterUnsuitableOutputSuppressionMs)) {
|
||||
unsuitableOutputPlaybackSuppressionStartRealtimeMs = C.TIME_UNSET;
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,11 @@
|
||||
*/
|
||||
package androidx.media3.ui;
|
||||
|
||||
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlayWhenReady;
|
||||
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
|
||||
import static androidx.test.ext.truth.content.IntentSubject.assertThat;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Application;
|
||||
@ -34,11 +36,14 @@ import android.provider.Settings;
|
||||
import androidx.media3.common.FlagSet;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Player.Listener;
|
||||
import androidx.media3.common.Player.PlayWhenReadyChangeReason;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@ -63,6 +68,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
private static final String FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME = "com.fake.btsettings";
|
||||
private static final String FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME =
|
||||
"com.fake.btsettings.BluetoothSettingsActivity";
|
||||
private static final long TEST_TIME_OUT_MS = Duration.ofMinutes(10).toMillis();
|
||||
|
||||
private ShadowPackageManager shadowPackageManager;
|
||||
private ShadowApplication shadowApplication;
|
||||
@ -74,9 +80,6 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
||||
.setSuppressPlaybackOnUnsuitableOutput(true)
|
||||
.build();
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
shadowApplication = shadowOf((Application) ApplicationProvider.getApplicationContext());
|
||||
shadowPackageManager =
|
||||
shadowOf(ApplicationProvider.getApplicationContext().getPackageManager());
|
||||
@ -88,12 +91,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for the launch of system Output Switcher app when playback is suppressed due to unsuitable
|
||||
* output and the system Output Switcher is present on the device.
|
||||
* Test end-to-end flow from launch of output switcher to playback getting resumed when the
|
||||
* playback is suppressed and then unsuppressed.
|
||||
*/
|
||||
@Test
|
||||
public void
|
||||
playEventWithPlaybackSuppressionWhenSystemOutputSwitcherPresent_shouldLaunchOutputSwitcher()
|
||||
public void playbackSuppressionFollowedByResolution_shouldLaunchOSwAndChangePlayerStateToPlaying()
|
||||
throws TimeoutException {
|
||||
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
|
||||
registerFakeActivity(
|
||||
@ -102,12 +104,27 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
List<Boolean> playWhenReadyChangeSequence = new ArrayList<>();
|
||||
testPlayer.addListener(
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onPlayWhenReadyChanged(
|
||||
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
|
||||
playWhenReadyChangeSequence.add(playWhenReady);
|
||||
}
|
||||
});
|
||||
|
||||
testPlayer.play();
|
||||
runUntilPlaybackState(testPlayer, Player.STATE_READY);
|
||||
addConnectedAudioOutput(
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
|
||||
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ true);
|
||||
|
||||
Intent intentTriggered = shadowApplication.getNextStartedActivity();
|
||||
assertThat(intentTriggered).isNotNull();
|
||||
@ -115,6 +132,8 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,6 +150,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -166,6 +188,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
"com.fake.userinstalled.outputswitcher.OutputSwitcherActivity",
|
||||
/* applicationFlags= */ 0);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -196,6 +221,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -236,6 +264,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -265,6 +296,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -298,6 +332,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -333,6 +370,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
"com.fake.userinstalled.btsettings.BluetoothSettingsActivity",
|
||||
/* applicationFlags= */ 0);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -364,6 +404,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(
|
||||
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
@ -374,6 +417,43 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
assertThat(shadowApplication.getNextStartedActivity()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for no launch of any system media output switching dialog app when playback is suppressed
|
||||
* due to removal of all suitable audio outputs in mid of an ongoing playback.
|
||||
*/
|
||||
@Test
|
||||
public void
|
||||
playbackSuppressionDuringOngoingPlayback_shouldOnlyPauseButNotLaunchEitherOSwOrBTSettings()
|
||||
throws TimeoutException {
|
||||
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
|
||||
registerFakeActivity(
|
||||
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
|
||||
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
|
||||
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
registerFakeActivity(
|
||||
Settings.ACTION_BLUETOOTH_SETTINGS,
|
||||
FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME,
|
||||
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(
|
||||
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
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);
|
||||
|
||||
assertThat(shadowApplication.getNextStartedActivity()).isNull();
|
||||
assertThat(testPlayer.isPlaying()).isFalse();
|
||||
}
|
||||
|
||||
/** Test for pause on the Player when the playback is suppressed due to unsuitable output. */
|
||||
@Test
|
||||
public void playEventWithSuppressedPlaybackCondition_shouldCallPauseOnPlayer()
|
||||
@ -385,12 +465,15 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
AtomicBoolean isPlaybackPaused = new AtomicBoolean(false);
|
||||
testPlayer.addListener(
|
||||
new Listener() {
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
|
||||
if (!playWhenReady) {
|
||||
@ -419,12 +502,15 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
ApplicationInfo.FLAG_SYSTEM);
|
||||
setupConnectedAudioOutput(
|
||||
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext()));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
AtomicBoolean isPlaybackPaused = new AtomicBoolean(false);
|
||||
testPlayer.addListener(
|
||||
new Listener() {
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
|
||||
if (!playWhenReady) {
|
||||
@ -439,6 +525,95 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
assertThat(isPlaybackPaused.get()).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure player is not playing when the playback suppression due to unsuitable output is
|
||||
* removed after the default timeout.
|
||||
*/
|
||||
@Test
|
||||
public void
|
||||
playbackSuppressionChangeToNoneAfterDefaultTimeout_shouldNotChangePlaybackStateToPlaying()
|
||||
throws TimeoutException {
|
||||
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
WearUnsuitableOutputPlaybackSuppressionResolverListener
|
||||
.DEFAULT_PLAYBACK_SUPPRESSION_AUTO_RESUME_TIMEOUT_MS,
|
||||
fakeClock));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
testPlayer.play();
|
||||
runUntilPlaybackState(testPlayer, Player.STATE_READY);
|
||||
|
||||
fakeClock.advanceTime(
|
||||
WearUnsuitableOutputPlaybackSuppressionResolverListener
|
||||
.DEFAULT_PLAYBACK_SUPPRESSION_AUTO_RESUME_TIMEOUT_MS
|
||||
* 2);
|
||||
addConnectedAudioOutput(
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
|
||||
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false);
|
||||
|
||||
assertThat(testPlayer.isPlaying()).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure player is playing when the playback suppression due to unsuitable output is
|
||||
* removed within the set timeout.
|
||||
*/
|
||||
@Test
|
||||
public void playbackSuppressionChangeToNoneWithinSetTimeout_shouldChangePlaybackStateToPlaying()
|
||||
throws TimeoutException {
|
||||
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext(), TEST_TIME_OUT_MS, fakeClock));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
testPlayer.play();
|
||||
runUntilPlaybackState(testPlayer, Player.STATE_READY);
|
||||
|
||||
fakeClock.advanceTime(TEST_TIME_OUT_MS / 2);
|
||||
addConnectedAudioOutput(
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
|
||||
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ true);
|
||||
|
||||
assertThat(testPlayer.isPlaying()).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure player is not playing when the playback suppression due to unsuitable output is
|
||||
* removed after the set timeout.
|
||||
*/
|
||||
@Test
|
||||
public void
|
||||
playbackSuppressionChangeToNoneAfterSetTimeout_shouldNotChangeFinalPlaybackStateToPlaying()
|
||||
throws TimeoutException {
|
||||
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
|
||||
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
|
||||
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||
testPlayer.addListener(
|
||||
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
|
||||
ApplicationProvider.getApplicationContext(), TEST_TIME_OUT_MS, fakeClock));
|
||||
testPlayer.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
testPlayer.prepare();
|
||||
testPlayer.play();
|
||||
runUntilPlaybackState(testPlayer, Player.STATE_READY);
|
||||
|
||||
fakeClock.advanceTime(TEST_TIME_OUT_MS * 2);
|
||||
addConnectedAudioOutput(
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
|
||||
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false);
|
||||
|
||||
assertThat(testPlayer.isPlaying()).isFalse();
|
||||
}
|
||||
|
||||
private void registerFakeActivity(
|
||||
String fakeActionName, String fakePackageName, String fakeClassName, int applicationFlags) {
|
||||
ComponentName fakeComponentName = new ComponentName(fakePackageName, fakeClassName);
|
||||
@ -465,4 +640,24 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
|
||||
}
|
||||
shadowAudioManager.setOutputDevices(deviceListBuilder.build());
|
||||
}
|
||||
|
||||
private void addConnectedAudioOutput(int deviceTypes, boolean notifyAudioDeviceCallbacks) {
|
||||
ShadowAudioManager shadowAudioManager =
|
||||
shadowOf(ApplicationProvider.getApplicationContext().getSystemService(AudioManager.class));
|
||||
shadowAudioManager.addOutputDevice(
|
||||
AudioDeviceInfoBuilder.newBuilder().setType(deviceTypes).build(),
|
||||
notifyAudioDeviceCallbacks);
|
||||
}
|
||||
|
||||
private void removeConnectedAudioOutput(int deviceType) {
|
||||
ShadowAudioManager shadowAudioManager =
|
||||
shadowOf(ApplicationProvider.getApplicationContext().getSystemService(AudioManager.class));
|
||||
stream(shadowAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(audioDeviceInfo -> deviceType == audioDeviceInfo.getType())
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
filteredAudioDeviceInfo ->
|
||||
shadowAudioManager.removeOutputDevice(
|
||||
filteredAudioDeviceInfo, /* notifyAudioDeviceCallbacks= */ true));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user