From 41af00f1004252708b3481c11f170c97e96864f9 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 14 Feb 2025 04:34:51 -0800 Subject: [PATCH] Move unsuitable output path logic = 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; + } } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java index 3ea6c00f98..ce2b642216 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -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 { *
  • {@link AudioAttributes}: {@link AudioAttributes#DEFAULT}, not handling audio focus *
  • {@link C.WakeMode}: {@link C#WAKE_MODE_NONE} *
  • {@code handleAudioBecomingNoisy}: {@code false} + *
  • {@code suppressPlaybackOnUnsuitableOutput}: {@code false} *
  • {@code skipSilenceEnabled}: {@code false} *
  • {@link C.VideoScalingMode}: {@link C#VIDEO_SCALING_MODE_DEFAULT} *
  • {@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); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index 94da63f41b..280c70cd7a 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -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 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); - } - } - } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SuitableOutputChecker.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SuitableOutputChecker.java index 1bac5c6a8c..1bb8d9b3f5 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SuitableOutputChecker.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SuitableOutputChecker.java @@ -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 { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index 7780aaecce..2b77c04c93 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -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 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 = diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeSuitableOutputChecker.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeSuitableOutputChecker.java index 21268d95b3..08279b4944 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeSuitableOutputChecker.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeSuitableOutputChecker.java @@ -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. */ diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java index d9f233df4c..4b031e6a43 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java @@ -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) { diff --git a/libraries/ui/src/test/java/androidx/media3/ui/WearUnsuitableOutputPlaybackSuppressionResolverListenerTest.java b/libraries/ui/src/test/java/androidx/media3/ui/WearUnsuitableOutputPlaybackSuppressionResolverListenerTest.java index c612d461f3..7bd44c281b 100644 --- a/libraries/ui/src/test/java/androidx/media3/ui/WearUnsuitableOutputPlaybackSuppressionResolverListenerTest.java +++ b/libraries/ui/src/test/java/androidx/media3/ui/WearUnsuitableOutputPlaybackSuppressionResolverListenerTest.java @@ -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 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) {