Move unsuitable output path logic <API31 into SuitableOutputChecker

This avoids distributing the logic between multiple classes and
keeps ExoPlayerImpl simpler.

PiperOrigin-RevId: 726874038
(cherry picked from commit 1015ef8b565ed04e88a9c596798d294327d05536)
This commit is contained in:
tonihei 2025-02-14 04:34:51 -08:00
parent 841e27ae5c
commit 41af00f100
8 changed files with 301 additions and 220 deletions

View File

@ -15,9 +15,14 @@
*/
package androidx.media3.exoplayer;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.ControllerCallback;
@ -31,88 +36,231 @@ import androidx.annotation.RequiresApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Default implementation for {@link SuitableOutputChecker}. */
@RequiresApi(35)
/* package */ final class DefaultSuitableOutputChecker implements SuitableOutputChecker {
private static final RouteDiscoveryPreference EMPTY_DISCOVERY_PREFERENCE =
new RouteDiscoveryPreference.Builder(
/* preferredFeatures= */ ImmutableList.of(), /* activeScan= */ false)
.build();
private final MediaRouter2 router;
private final RouteCallback routeCallback;
private final Executor executor;
@Nullable private ControllerCallback controllerCallback;
private boolean isPreviousSelectedOutputSuitableForPlayback;
@Nullable private final SuitableOutputChecker impl;
/**
* Creates the default {@link SuitableOutputChecker}.
*
* @param context A {@link Context}.
* @param eventHandler A {@link Handler} to trigger {@link Callback} methods on.
*/
public DefaultSuitableOutputChecker(Context context, Handler eventHandler) {
router = MediaRouter2.getInstance(context);
routeCallback = new RouteCallback() {};
executor =
new Executor() {
@Override
public void execute(Runnable command) {
Util.postOrRun(eventHandler, command);
}
};
if (Util.SDK_INT >= 35) {
impl = new ImplApi35(context, eventHandler);
} else if (Util.SDK_INT >= 23) {
impl = new ImplApi23(context, eventHandler);
} else {
impl = null;
}
}
@Override
public void enable(Callback callback) {
router.registerRouteCallback(executor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
controllerCallback =
new ControllerCallback() {
@Override
public void onControllerUpdated(RoutingController controller) {
boolean isCurrentSelectedOutputSuitableForPlayback =
isSelectedOutputSuitableForPlayback();
if (isPreviousSelectedOutputSuitableForPlayback
!= isCurrentSelectedOutputSuitableForPlayback) {
isPreviousSelectedOutputSuitableForPlayback =
isCurrentSelectedOutputSuitableForPlayback;
callback.onSelectedOutputSuitabilityChanged(
isCurrentSelectedOutputSuitableForPlayback);
}
}
};
router.registerControllerCallback(executor, controllerCallback);
isPreviousSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback();
if (impl != null) {
impl.enable(callback);
}
}
@Override
public void disable() {
checkStateNotNull(controllerCallback, "SuitableOutputChecker is not enabled");
router.unregisterControllerCallback(controllerCallback);
controllerCallback = null;
router.unregisterRouteCallback(routeCallback);
if (impl != null) {
impl.disable();
}
}
@Override
public boolean isSelectedOutputSuitableForPlayback() {
checkStateNotNull(controllerCallback, "SuitableOutputChecker is not enabled");
int transferReason = router.getSystemController().getRoutingSessionInfo().getTransferReason();
boolean wasTransferInitiatedBySelf = router.getSystemController().wasTransferInitiatedBySelf();
for (MediaRoute2Info routeInfo : router.getSystemController().getSelectedRoutes()) {
if (isRouteSuitableForMediaPlayback(routeInfo, transferReason, wasTransferInitiatedBySelf)) {
return true;
return impl == null || impl.isSelectedOutputSuitableForPlayback();
}
@RequiresApi(35)
private static final class ImplApi35 implements SuitableOutputChecker {
private static final RouteDiscoveryPreference EMPTY_DISCOVERY_PREFERENCE =
new RouteDiscoveryPreference.Builder(
/* preferredFeatures= */ ImmutableList.of(), /* activeScan= */ false)
.build();
private final Context applicationContext;
private final Handler eventHandler;
private @MonotonicNonNull MediaRouter2 router;
private @MonotonicNonNull RouteCallback routeCallback;
@Nullable private ControllerCallback controllerCallback;
private boolean isSelectedOutputSuitableForPlayback;
public ImplApi35(Context context, Handler eventHandler) {
this.applicationContext = context.getApplicationContext();
this.eventHandler = eventHandler;
}
@SuppressLint("ThreadSafe") // Handler is thread-safe, but not annotated.
@Override
public void enable(Callback callback) {
router = MediaRouter2.getInstance(applicationContext);
routeCallback = new RouteCallback() {};
Executor executor =
new Executor() {
@Override
public void execute(Runnable command) {
Util.postOrRun(eventHandler, command);
}
};
router.registerRouteCallback(executor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
controllerCallback =
new ControllerCallback() {
@Override
public void onControllerUpdated(RoutingController controller) {
boolean isCurrentSelectedOutputSuitableForPlayback =
isSelectedOutputSuitableForPlayback(router);
if (isSelectedOutputSuitableForPlayback
!= isCurrentSelectedOutputSuitableForPlayback) {
isSelectedOutputSuitableForPlayback = isCurrentSelectedOutputSuitableForPlayback;
callback.onSelectedOutputSuitabilityChanged(
isCurrentSelectedOutputSuitableForPlayback);
}
}
};
router.registerControllerCallback(executor, controllerCallback);
isSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback(router);
}
@Override
public void disable() {
checkStateNotNull(controllerCallback, "SuitableOutputChecker is not enabled");
checkNotNull(router).unregisterControllerCallback(controllerCallback);
controllerCallback = null;
router.unregisterRouteCallback(checkNotNull(routeCallback));
}
@Override
public boolean isSelectedOutputSuitableForPlayback() {
return isSelectedOutputSuitableForPlayback;
}
private static boolean isSelectedOutputSuitableForPlayback(MediaRouter2 router) {
int transferReason =
checkNotNull(router).getSystemController().getRoutingSessionInfo().getTransferReason();
boolean wasTransferInitiatedBySelf =
router.getSystemController().wasTransferInitiatedBySelf();
for (MediaRoute2Info routeInfo : router.getSystemController().getSelectedRoutes()) {
if (isRouteSuitableForMediaPlayback(
routeInfo, transferReason, wasTransferInitiatedBySelf)) {
return true;
}
}
return false;
}
private static boolean isRouteSuitableForMediaPlayback(
MediaRoute2Info routeInfo, int transferReason, boolean wasTransferInitiatedBySelf) {
int suitabilityStatus = routeInfo.getSuitabilityStatus();
if (suitabilityStatus == MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER) {
return (transferReason == RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST
|| transferReason == RoutingSessionInfo.TRANSFER_REASON_APP)
&& wasTransferInitiatedBySelf;
}
return suitabilityStatus == MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
}
}
@RequiresApi(23)
private static final class ImplApi23 implements SuitableOutputChecker {
private final Context applicationContext;
private final Handler eventHandler;
@Nullable private AudioManager audioManager;
private @MonotonicNonNull AudioDeviceCallback audioDeviceCallback;
private boolean isSelectedOutputSuitableForPlayback;
public ImplApi23(Context context, Handler eventHandler) {
this.applicationContext = context.getApplicationContext();
this.eventHandler = eventHandler;
}
@Override
public void enable(Callback callback) {
AudioManager audioManager =
(AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) {
isSelectedOutputSuitableForPlayback = true;
return;
}
this.audioManager = audioManager;
audioDeviceCallback =
new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
updateIsSelectedOutputSuitableForPlayback(callback);
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
updateIsSelectedOutputSuitableForPlayback(callback);
}
};
audioManager.registerAudioDeviceCallback(audioDeviceCallback, eventHandler);
isSelectedOutputSuitableForPlayback = hasSupportedAudioOutput();
}
@Override
public void disable() {
if (audioManager != null) {
audioManager.unregisterAudioDeviceCallback(checkNotNull(audioDeviceCallback));
}
}
return false;
}
private static boolean isRouteSuitableForMediaPlayback(
MediaRoute2Info routeInfo, int transferReason, boolean wasTransferInitiatedBySelf) {
int suitabilityStatus = routeInfo.getSuitabilityStatus();
if (suitabilityStatus == MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER) {
return (transferReason == RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST
|| transferReason == RoutingSessionInfo.TRANSFER_REASON_APP)
&& wasTransferInitiatedBySelf;
@Override
public boolean isSelectedOutputSuitableForPlayback() {
return isSelectedOutputSuitableForPlayback;
}
return suitabilityStatus == MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
private void updateIsSelectedOutputSuitableForPlayback(Callback callback) {
boolean isSelectedOutputSuitableForPlayback = hasSupportedAudioOutput();
if (this.isSelectedOutputSuitableForPlayback != isSelectedOutputSuitableForPlayback) {
this.isSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback;
callback.onSelectedOutputSuitabilityChanged(isSelectedOutputSuitableForPlayback);
}
}
private boolean hasSupportedAudioOutput() {
if (!Util.isWear(applicationContext)) {
return true;
}
AudioDeviceInfo[] audioDeviceInfos =
checkStateNotNull(audioManager).getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo device : audioDeviceInfos) {
if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
|| device.getType() == AudioDeviceInfo.TYPE_LINE_ANALOG
|| device.getType() == AudioDeviceInfo.TYPE_LINE_DIGITAL
|| device.getType() == AudioDeviceInfo.TYPE_USB_DEVICE
|| device.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
|| device.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
return true;
}
if (Util.SDK_INT >= 26 && device.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
return true;
}
if (Util.SDK_INT >= 28 && device.getType() == AudioDeviceInfo.TYPE_HEARING_AID) {
return true;
}
if (Util.SDK_INT >= 31
&& (device.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET
|| device.getType() == AudioDeviceInfo.TYPE_BLE_SPEAKER)) {
return true;
}
if (Util.SDK_INT >= 33 && device.getType() == AudioDeviceInfo.TYPE_BLE_BROADCAST) {
return true;
}
}
return false;
}
}
}

View File

@ -252,7 +252,7 @@ public interface ExoPlayer extends Player {
/* package */ boolean suppressPlaybackOnUnsuitableOutput;
/* package */ String playerName;
/* package */ boolean dynamicSchedulingEnabled;
@Nullable /* package */ SuitableOutputChecker suitableOutputChecker;
/* package */ SuitableOutputChecker suitableOutputChecker;
/**
* Creates a builder.
@ -282,6 +282,7 @@ public interface ExoPlayer extends Player {
* <li>{@link AudioAttributes}: {@link AudioAttributes#DEFAULT}, not handling audio focus
* <li>{@link C.WakeMode}: {@link C#WAKE_MODE_NONE}
* <li>{@code handleAudioBecomingNoisy}: {@code false}
* <li>{@code suppressPlaybackOnUnsuitableOutput}: {@code false}
* <li>{@code skipSilenceEnabled}: {@code false}
* <li>{@link C.VideoScalingMode}: {@link C#VIDEO_SCALING_MODE_DEFAULT}
* <li>{@link C.VideoChangeFrameRateStrategy}: {@link
@ -459,6 +460,7 @@ public interface ExoPlayer extends Player {
usePlatformDiagnostics = true;
playerName = "";
priority = C.PRIORITY_PLAYBACK;
suitableOutputChecker = new DefaultSuitableOutputChecker(context, new Handler(looper));
}
/**
@ -1021,7 +1023,6 @@ public interface ExoPlayer extends Player {
@UnstableApi
@RestrictTo(LIBRARY_GROUP)
@VisibleForTesting
@RequiresApi(35)
public Builder setSuitableOutputChecker(SuitableOutputChecker suitableOutputChecker) {
checkState(!buildCalled);
this.suitableOutputChecker = suitableOutputChecker;
@ -1088,11 +1089,6 @@ public interface ExoPlayer extends Player {
public ExoPlayer build() {
checkState(!buildCalled);
buildCalled = true;
if (suitableOutputChecker == null
&& Util.SDK_INT >= 35
&& suppressPlaybackOnUnsuitableOutput) {
suitableOutputChecker = new DefaultSuitableOutputChecker(context, new Handler(looper));
}
return new ExoPlayerImpl(/* builder= */ this, /* wrappingPlayer= */ null);
}

View File

@ -45,9 +45,7 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.Looper;
@ -176,8 +174,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final WakeLockManager wakeLockManager;
private final WifiLockManager wifiLockManager;
private final long detachSurfaceTimeoutMs;
@Nullable private AudioManager audioManager;
private final boolean suppressPlaybackOnUnsuitableOutput;
@Nullable private final SuitableOutputChecker suitableOutputChecker;
private final BackgroundThreadStateHandler<Integer> audioSessionIdState;
@ -293,7 +289,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
this.applicationLooper = builder.looper;
this.clock = builder.clock;
this.wrappingPlayer = wrappingPlayer == null ? this : wrappingPlayer;
this.suppressPlaybackOnUnsuitableOutput = builder.suppressPlaybackOnUnsuitableOutput;
listeners =
new ListenerSet<>(
applicationLooper,
@ -416,15 +411,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener);
audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null);
suitableOutputChecker = builder.suitableOutputChecker;
if (suitableOutputChecker != null && Util.SDK_INT >= 35) {
if (builder.suppressPlaybackOnUnsuitableOutput) {
suitableOutputChecker = builder.suitableOutputChecker;
suitableOutputChecker.enable(this::onSelectedOutputSuitabilityChanged);
} else if (suppressPlaybackOnUnsuitableOutput && Util.SDK_INT >= 23) {
audioManager = (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
Api23.registerAudioDeviceCallback(
audioManager,
new NoSuitableOutputPlaybackSuppressionAudioDeviceCallback(),
new Handler(applicationLooper));
} else {
suitableOutputChecker = null;
}
if (builder.deviceVolumeControlEnabled) {
@ -1037,6 +1028,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false);
audioFocusManager.release();
if (suitableOutputChecker != null) {
suitableOutputChecker.disable();
}
if (!internalPlayer.release()) {
// One of the renderers timed out releasing its resources.
listeners.sendEvent(
@ -1053,9 +1047,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (playbackInfo.sleepingForOffload) {
playbackInfo = playbackInfo.copyWithEstimatedPosition();
}
if (suitableOutputChecker != null && Util.SDK_INT >= 35) {
suitableOutputChecker.disable();
}
playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE);
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId);
playbackInfo.bufferedPositionUs = playbackInfo.positionUs;
@ -2793,8 +2784,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (playerCommand == AudioFocusManager.PLAYER_COMMAND_WAIT_FOR_CALLBACK) {
return Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
}
if (suppressPlaybackOnUnsuitableOutput) {
if (playWhenReady && !hasSupportedAudioOutput()) {
if (suitableOutputChecker != null) {
if (playWhenReady && !suitableOutputChecker.isSelectedOutputSuitableForPlayback()) {
return Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT;
}
if (!playWhenReady
@ -2806,19 +2797,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
return Player.PLAYBACK_SUPPRESSION_REASON_NONE;
}
private boolean hasSupportedAudioOutput() {
if (Util.SDK_INT >= 35 && suitableOutputChecker != null) {
return suitableOutputChecker.isSelectedOutputSuitableForPlayback();
} else if (Util.SDK_INT >= 23 && audioManager != null) {
return Api23.isSuitableExternalAudioOutputPresentInAudioDeviceInfoList(
applicationContext, audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS));
} else {
// The Audio Manager API to determine the list of connected audio devices is available only in
// API >= 23.
return true;
}
}
private void updateWakeAndWifiLock() {
@State int playbackState = getPlaybackState();
switch (playbackState) {
@ -2927,6 +2905,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
private void onSelectedOutputSuitabilityChanged(boolean isSelectedOutputSuitableForPlayback) {
if (playerReleased) {
// Stale event.
return;
}
if (isSelectedOutputSuitableForPlayback) {
if (playbackInfo.playbackSuppressionReason
== Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) {
@ -3397,78 +3379,4 @@ import java.util.concurrent.CopyOnWriteArraySet;
});
}
}
@RequiresApi(23)
private static final class Api23 {
private Api23() {}
public static boolean isSuitableExternalAudioOutputPresentInAudioDeviceInfoList(
Context context, AudioDeviceInfo[] audioDeviceInfos) {
if (!Util.isWear(context)) {
return true;
}
for (AudioDeviceInfo device : audioDeviceInfos) {
if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
|| device.getType() == AudioDeviceInfo.TYPE_LINE_ANALOG
|| device.getType() == AudioDeviceInfo.TYPE_LINE_DIGITAL
|| device.getType() == AudioDeviceInfo.TYPE_USB_DEVICE
|| device.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
|| device.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
return true;
}
if (Util.SDK_INT >= 26 && device.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
return true;
}
if (Util.SDK_INT >= 28 && device.getType() == AudioDeviceInfo.TYPE_HEARING_AID) {
return true;
}
if (Util.SDK_INT >= 31
&& (device.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET
|| device.getType() == AudioDeviceInfo.TYPE_BLE_SPEAKER)) {
return true;
}
if (Util.SDK_INT >= 33 && device.getType() == AudioDeviceInfo.TYPE_BLE_BROADCAST) {
return true;
}
}
return false;
}
public static void registerAudioDeviceCallback(
AudioManager audioManager, AudioDeviceCallback audioDeviceCallback, Handler handler) {
audioManager.registerAudioDeviceCallback(audioDeviceCallback, handler);
}
}
/**
* A {@link AudioDeviceCallback} to change playback suppression reason when suitable audio outputs
* are either added in unsuitable output based playback suppression state or removed during an
* ongoing playback.
*/
@RequiresApi(23)
private final class NoSuitableOutputPlaybackSuppressionAudioDeviceCallback
extends AudioDeviceCallback {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
if (hasSupportedAudioOutput()
&& playbackInfo.playbackSuppressionReason
== Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) {
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
playbackInfo.playWhenReady,
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
}
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
if (!hasSupportedAudioOutput()) {
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
playbackInfo.playWhenReady,
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT);
}
}
}
}

View File

@ -17,12 +17,10 @@ package androidx.media3.exoplayer;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi;
/** Provides methods to check the suitability of selected media outputs. */
@RequiresApi(35)
@RestrictTo(LIBRARY_GROUP)
@UnstableApi
public interface SuitableOutputChecker {

View File

@ -94,6 +94,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.annotation.Config.ALL_SDKS;
import android.content.Context;
import android.content.Intent;
@ -14022,7 +14023,7 @@ public class ExoPlayerTest {
}
@Test
@Config(sdk = Config.ALL_SDKS)
@Config(sdk = ALL_SDKS)
public void builder_inBackgroundThreadWithAllowedAnyThreadMethods_doesNotThrow()
throws Exception {
AtomicReference<Player> playerReference = new AtomicReference<>();
@ -15023,6 +15024,8 @@ public class ExoPlayerTest {
* Tests playback suppression for playback with only unsuitable outputs (e.g. builtin speaker) on
* the Wear OS.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void play_withOnlyUnsuitableOutputsOnWear_shouldSuppressPlayback() throws Exception {
addWatchAsSystemFeature();
@ -15059,6 +15062,8 @@ public class ExoPlayerTest {
* Tests no playback suppression for playback with suitable output (e.g. BluetoothA2DP) on the
* Wear OS.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void play_withAtleastOneSuitableOutputOnWear_shouldNotSuppressPlayback() throws Exception {
addWatchAsSystemFeature();
@ -15095,6 +15100,8 @@ public class ExoPlayerTest {
* Tests same playback suppression reason for multiple play calls with only unsuitable output
* (e.g. builtin speaker) on the Wear OS.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
play_callMultipleTimesOnUnsuitableOutputFollowedByPause_shouldRetainSameSuppressionReason()
@ -15134,6 +15141,8 @@ public class ExoPlayerTest {
}
/** Tests playback suppression for playback on the built-speaker on non-Wear OS surfaces. */
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void play_onBuiltinSpeakerWithoutWearPresentAsSystemFeature_shouldNotSuppressPlayback()
throws Exception {
@ -15171,6 +15180,8 @@ public class ExoPlayerTest {
* speaker) on Wear OS but {@link
* ExoPlayer.Builder#setSuppressPlaybackOnUnsuitableOutput(boolean)} is not called with true.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
play_withOnlyUnsuitableOutputsWithoutEnablingPlaybackSuppression_shouldNotSuppressPlayback()
@ -15206,6 +15217,8 @@ public class ExoPlayerTest {
* Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} when a suitable audio output is
* added.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void addSuitableOutputWhenPlaybackSuppressed_shouldRemovePlaybackSuppression()
throws Exception {
@ -15246,6 +15259,8 @@ public class ExoPlayerTest {
* Tests no change in the playback suppression reason when an unsuitable audio output is connected
* while playback was suppressed earlier.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void addUnsuitableOutputWhenPlaybackIsSuppressed_shouldNotRemovePlaybackSuppression()
throws Exception {
@ -15277,6 +15292,8 @@ public class ExoPlayerTest {
* Tests no change in the playback suppression reason when a suitable audio output is added but
* playback was not suppressed earlier.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void addSuitableOutputWhenPlaybackNotSuppressed_shouldNotRemovePlaybackSuppression()
throws Exception {
@ -15308,6 +15325,8 @@ public class ExoPlayerTest {
* Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} when all the suitable audio outputs
* have been removed during an ongoing playback.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void removeAllSuitableOutputsWhenPlaybackOngoing_shouldSetPlaybackSuppression()
throws Exception {
@ -15340,6 +15359,8 @@ public class ExoPlayerTest {
* Tests no change in the playback suppression reason when any unsuitable audio outputs has been
* removed during an ongoing playback but some suitable audio outputs are still available.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void removeAnyUnsuitableOutputWhenPlaybackOngoing_shouldNotSetPlaybackSuppression()
throws Exception {
@ -15376,6 +15397,8 @@ public class ExoPlayerTest {
* removed during an ongoing playback but at least one another suitable audio output is still
* connected to the device.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
removeAnySuitableOutputButOneSuitableDeviceStillConnected_shouldNotSetPlaybackSuppression()
@ -15406,9 +15429,8 @@ public class ExoPlayerTest {
player.release();
}
/** Tests suppression of playback when no situable output is found. */
/** Tests suppression of playback when no suitable output is found. */
@Test
@Config(minSdk = 35)
public void verifySuitableOutput_shouldSuppressPlaybackWhenNoSuitableOutputAvailable()
throws Exception {
FakeSuitableOutputChecker suitableMediaOutputChecker =
@ -15439,7 +15461,6 @@ public class ExoPlayerTest {
/** Tests no occurrences of suppression of playback when situable output is found. */
@Test
@Config(minSdk = 35)
public void verifySuitableOutput_shouldNotSuppressPlaybackWhenSuitableOutputIsAvailable()
throws Exception {
FakeSuitableOutputChecker suitableMediaOutputChecker =
@ -15472,7 +15493,6 @@ public class ExoPlayerTest {
* disabled.
*/
@Test
@Config(minSdk = 35)
public void
verifySuitableOutput_playbackSuppressionOnUnsuitableOutputDisabled_shouldNotSuppressPlayback()
throws Exception {
@ -15502,7 +15522,6 @@ public class ExoPlayerTest {
/** Tests removal of suppression of playback when a suitable output is added. */
@Test
@Config(minSdk = 35)
public void verifySuitableOutput_shouldRemovePlaybackSuppressionOnAdditionOfSuitableOutput()
throws Exception {
FakeSuitableOutputChecker suitableMediaOutputChecker =
@ -15538,7 +15557,6 @@ public class ExoPlayerTest {
/** Tests suppression of playback when a suitable output is removed. */
@Test
@Config(minSdk = 35)
public void verifySuitableOutput_shouldSuppressPlaybackOnRemovalOfSuitableOutput()
throws Exception {
FakeSuitableOutputChecker suitableMediaOutputChecker =
@ -15571,7 +15589,6 @@ public class ExoPlayerTest {
/** Tests suppression of playback back again when a suitable output added before is removed. */
@Test
@Config(minSdk = 35)
public void verifySuitableOutput_shouldSuppressPlaybackAgainAfterRemovalOfAddedSuitableOutput()
throws Exception {
FakeSuitableOutputChecker suitableMediaOutputChecker =

View File

@ -19,7 +19,6 @@ import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.SuitableOutputChecker;
@ -28,7 +27,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** Fake implementation for {@link SuitableOutputChecker}. */
@RestrictTo(LIBRARY_GROUP)
@UnstableApi
@RequiresApi(35)
public final class FakeSuitableOutputChecker implements SuitableOutputChecker {
/** Builder for {@link FakeSuitableOutputChecker} instance. */

View File

@ -18,7 +18,6 @@ package androidx.media3.test.utils;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@ -423,7 +422,7 @@ public class TestExoPlayerBuilder {
.setDeviceVolumeControlEnabled(deviceVolumeControlEnabled)
.setSuppressPlaybackOnUnsuitableOutput(suppressPlaybackWhenUnsuitableOutput)
.experimentalSetDynamicSchedulingEnabled(dynamicSchedulingEnabled);
if (VERSION.SDK_INT >= 35 && suitableOutputChecker != null) {
if (suitableOutputChecker != null) {
builder.setSuitableOutputChecker(suitableOutputChecker);
}
if (mediaSourceFactory != null) {

View File

@ -43,7 +43,6 @@ 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;
import com.google.common.collect.ImmutableList;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@ -81,10 +80,13 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
@Before
public void setUp() {
shadowPackageManager =
shadowOf(ApplicationProvider.getApplicationContext().getPackageManager());
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
TestExoPlayerBuilder builder =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setSuppressPlaybackOnUnsuitableOutput(true);
if (Util.SDK_INT >= 35) {
suitableMediaOutputChecker =
new FakeSuitableOutputChecker.Builder()
@ -92,11 +94,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
.build();
builder.setSuitableOutputChecker(suitableMediaOutputChecker);
}
testPlayer = builder.build();
shadowApplication = shadowOf((Application) ApplicationProvider.getApplicationContext());
shadowPackageManager =
shadowOf(ApplicationProvider.getApplicationContext().getPackageManager());
}
@After
@ -108,10 +108,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test end-to-end flow from launch of output switcher to playback getting resumed when the
* playback is suppressed and then unsuppressed.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playbackSuppressionFollowedByResolution_shouldLaunchOutputSwitcherAndStartPlayback()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -153,10 +154,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for the launch of system updated Output Switcher app when playback is suppressed due to
* unsuitable output and the system updated Output Switcher is present on the device.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playEventWithPlaybackSuppression_shouldLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -185,11 +187,12 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for the launch of system Output Switcher app when playback is suppressed due to unsuitable
* output and both the system as well as user installed Output Switcher are present on the device.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
playbackSuppressionWithSystemAndUserInstalledComponentsPresent_shouldLaunchSystemComponent()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -223,11 +226,12 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for no launch of system Output Switcher app when running on non-Wear OS device with
* playback suppression conditions and the system Output Switcher present on the device.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
playEventWithPlaybackSuppressionConditionsOnNonWearOSDevice_shouldNotLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -266,11 +270,12 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* output with the system Bluetooth Settings app present while the system Output Switcher app is
* not present on the device.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
playEventWithPlaybackSuppressionWhenOnlySystemBTSettingsPresent_shouldLaunchBTSettings()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
Settings.ACTION_BLUETOOTH_SETTINGS,
FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME,
@ -299,10 +304,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* output with the updated system Bluetooth Settings app present while the Output Switcher app is
* not present on the device.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playbackSuppressionWhenOnlyUpdatedSystemBTSettingsPresent_shouldLaunchBTSettings()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
Settings.ACTION_BLUETOOTH_SETTINGS,
FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME,
@ -330,10 +336,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for the launch of Output Switcher app when playback is suppressed due to unsuitable output
* and both Output Switcher as well as the Bluetooth settings are present on the device.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playbackSuppressionWhenMultipleSystemComponentsPresent_shouldLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -367,10 +374,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for no launch of the non-system and non-system updated Output Switcher app when playback
* is suppressed due to unsuitable output.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playbackSuppressionWhenOnlyUserInstalledComponentsPresent_shouldNotLaunchAnyApp()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
"com.fake.userinstalled.outputswitcher",
@ -400,10 +408,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for no launch of any system media output switching dialog app when playback is not
* suppressed due to unsuitable output.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playEventWithoutPlaybackSuppression_shouldNotLaunchOutputSwitcherOrBTSettings()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -433,10 +442,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* 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.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playbackSuppressionDuringOngoingPlayback_shouldOnlyPauseButNotLaunchSystemComponent()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -475,10 +485,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
}
/** Test for pause on the Player when the playback is suppressed due to unsuitable output. */
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playEventWithSuppressedPlaybackCondition_shouldCallPauseOnPlayer()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -511,11 +522,12 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for automatic resumption of the ongoing playback when it is transferred from one suitable
* device to another within set time out.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
transferOnGoingPlaybackFromOneSuitableDeviceToAnotherWithinSetTimeOut_shouldContinuePlayback()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
setupConnectedAudioOutput(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
testPlayer.addListener(
@ -541,11 +553,12 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test for automatic pause of the ongoing playback when it is transferred from one suitable
* device to another and the time difference between switching is more than default time out
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void
transferOnGoingPlaybackFromOneSuitableDeviceToAnotherAfterTimeOut_shouldNotContinuePlayback()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
setupConnectedAudioOutput(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
@ -587,10 +600,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
/**
* Test for no pause on the Player when the playback is not suppressed due to unsuitable output.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playEventWithoutSuppressedPlaybackCondition_shouldNotCallPauseOnPlayer()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
@ -624,11 +638,12 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test to ensure player is not playing when the playback suppression due to unsuitable output is
* removed after the default timeout.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@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(
@ -667,10 +682,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test to ensure player is playing when the playback suppression due to unsuitable output is
* removed within the set timeout.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@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(
@ -694,11 +710,12 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test to ensure player is not playing when the playback suppression due to unsuitable output is
* removed after the set timeout.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@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(
@ -728,10 +745,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
}
/** Test to ensure wake lock is acquired when playback is suppressed due to unsuitable output. */
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playEventWithSuppressedPlaybackCondition_shouldAcquireWakeLock()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
testPlayer.addListener(
@ -752,10 +770,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test to ensure that the wake lock acquired with playback suppression due to unsuitable output
* is released after the set timeout.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playEventWithSuppressedPlaybackCondition_shouldReleaseAcquiredWakeLockAfterTimeout()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
testPlayer.addListener(
@ -778,10 +797,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* Test to ensure that the wake lock acquired with playback suppression due to unsuitable output
* is released after suitable output gets added.
*/
// TODO: remove maxSdk once Robolectric supports MediaRouter2 (b/382017156)
@Config(minSdk = 23, maxSdk = 34)
@Test
public void playEventWithSuppressedPlaybackConditionRemoved_shouldReleaseAcquiredWakeLock()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
FakeClock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
testPlayer.addListener(
@ -805,10 +825,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
/** Test to verify that attempted playback is paused when the suitable output is not present. */
@Test
@Config(minSdk = 35)
@Config(minSdk = 35) // Remove minSdk once Robolectric supports MediaRouter2 (b/382017156)
public void playEvent_withSuitableOutputNotPresent_shouldPausePlaybackAndLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ false);
registerFakeActivity(
@ -850,10 +869,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* present.
*/
@Test
@Config(minSdk = 35)
@Config(minSdk = 35) // Remove minSdk once Robolectric supports MediaRouter2 (b/382017156)
public void playEvent_withSuitableOutputPresent_shouldNotPausePlaybackOrLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ false);
registerFakeActivity(
@ -892,10 +910,9 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
* time out.
*/
@Test
@Config(minSdk = 35)
@Config(minSdk = 35) // Remove minSdk once Robolectric supports MediaRouter2 (b/382017156)
public void playEvent_suitableOutputAddedAfterTimeOut_shouldNotResumePlayback()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
suitableMediaOutputChecker.updateIsSelectedSuitableOutputAvailableAndNotify(
/* isSelectedOutputSuitableForPlayback= */ false);
testPlayer.setMediaItem(
@ -945,11 +962,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
private void setupConnectedAudioOutput(int... deviceTypes) {
ShadowAudioManager shadowAudioManager =
shadowOf(ApplicationProvider.getApplicationContext().getSystemService(AudioManager.class));
ImmutableList.Builder<AudioDeviceInfo> deviceListBuilder = ImmutableList.builder();
for (int deviceType : deviceTypes) {
deviceListBuilder.add(AudioDeviceInfoBuilder.newBuilder().setType(deviceType).build());
shadowAudioManager.addOutputDevice(
AudioDeviceInfoBuilder.newBuilder().setType(deviceType).build(),
/* notifyAudioDeviceCallbacks= */ true);
}
shadowAudioManager.setOutputDevices(deviceListBuilder.build());
}
private void addConnectedAudioOutput(int deviceTypes, boolean notifyAudioDeviceCallbacks) {