Move DefaultSuitableOutputChecker operations to playback thread

PiperOrigin-RevId: 726879236
(cherry picked from commit e0ef6e51829dd1627d952d2677f532230cb2dbc9)
This commit is contained in:
tonihei 2025-02-14 04:54:30 -08:00
parent 41af00f100
commit 8bd1db5f2c
7 changed files with 157 additions and 114 deletions

View File

@ -31,8 +31,11 @@ import android.media.MediaRouter2.RoutingController;
import android.media.RouteDiscoveryPreference; import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo; import android.media.RoutingSessionInfo;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.media3.common.util.BackgroundThreadStateHandler;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -43,26 +46,26 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable private final SuitableOutputChecker impl; @Nullable private final SuitableOutputChecker impl;
/** /** Creates the default {@link SuitableOutputChecker}. */
* Creates the default {@link SuitableOutputChecker}. public DefaultSuitableOutputChecker() {
*
* @param context A {@link Context}.
* @param eventHandler A {@link Handler} to trigger {@link Callback} methods on.
*/
public DefaultSuitableOutputChecker(Context context, Handler eventHandler) {
if (Util.SDK_INT >= 35) { if (Util.SDK_INT >= 35) {
impl = new ImplApi35(context, eventHandler); impl = new ImplApi35();
} else if (Util.SDK_INT >= 23) { } else if (Util.SDK_INT >= 23) {
impl = new ImplApi23(context, eventHandler); impl = new ImplApi23();
} else { } else {
impl = null; impl = null;
} }
} }
@Override @Override
public void enable(Callback callback) { public void enable(
Callback callback,
Context context,
Looper callbackLooper,
Looper backgroundLooper,
Clock clock) {
if (impl != null) { if (impl != null) {
impl.enable(callback); impl.enable(callback, context, callbackLooper, backgroundLooper, clock);
} }
} }
@ -85,61 +88,63 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* preferredFeatures= */ ImmutableList.of(), /* activeScan= */ false) /* preferredFeatures= */ ImmutableList.of(), /* activeScan= */ false)
.build(); .build();
private final Context applicationContext;
private final Handler eventHandler;
private @MonotonicNonNull MediaRouter2 router; private @MonotonicNonNull MediaRouter2 router;
private @MonotonicNonNull RouteCallback routeCallback; private @MonotonicNonNull RouteCallback routeCallback;
@Nullable private ControllerCallback controllerCallback; @Nullable private ControllerCallback controllerCallback;
private boolean isSelectedOutputSuitableForPlayback; private @MonotonicNonNull BackgroundThreadStateHandler<Boolean> isSuitableForPlaybackState;
public ImplApi35(Context context, Handler eventHandler) {
this.applicationContext = context.getApplicationContext();
this.eventHandler = eventHandler;
}
@SuppressLint("ThreadSafe") // Handler is thread-safe, but not annotated. @SuppressLint("ThreadSafe") // Handler is thread-safe, but not annotated.
@Override @Override
public void enable(Callback callback) { public void enable(
router = MediaRouter2.getInstance(applicationContext); Callback callback,
Context context,
Looper callbackLooper,
Looper backgroundLooper,
Clock clock) {
isSuitableForPlaybackState =
new BackgroundThreadStateHandler<>(
/* initialState= */ true,
backgroundLooper,
callbackLooper,
clock,
/* onStateChanged= */ (oldState, newState) ->
callback.onSelectedOutputSuitabilityChanged(newState));
isSuitableForPlaybackState.runInBackground(
() -> {
checkNotNull(isSuitableForPlaybackState);
router = MediaRouter2.getInstance(context);
routeCallback = new RouteCallback() {}; routeCallback = new RouteCallback() {};
Executor executor = Executor backgroundExecutor = isSuitableForPlaybackState::runInBackground;
new Executor() { router.registerRouteCallback(
@Override backgroundExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
public void execute(Runnable command) {
Util.postOrRun(eventHandler, command);
}
};
router.registerRouteCallback(executor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
controllerCallback = controllerCallback =
new ControllerCallback() { new ControllerCallback() {
@Override @Override
public void onControllerUpdated(RoutingController controller) { public void onControllerUpdated(RoutingController controller) {
boolean isCurrentSelectedOutputSuitableForPlayback = isSuitableForPlaybackState.setStateInBackground(
isSelectedOutputSuitableForPlayback(router); isSelectedOutputSuitableForPlayback(router));
if (isSelectedOutputSuitableForPlayback
!= isCurrentSelectedOutputSuitableForPlayback) {
isSelectedOutputSuitableForPlayback = isCurrentSelectedOutputSuitableForPlayback;
callback.onSelectedOutputSuitabilityChanged(
isCurrentSelectedOutputSuitableForPlayback);
}
} }
}; };
router.registerControllerCallback(executor, controllerCallback); router.registerControllerCallback(backgroundExecutor, controllerCallback);
isSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback(router); isSuitableForPlaybackState.setStateInBackground(
isSelectedOutputSuitableForPlayback(router));
});
} }
@Override @Override
public void disable() { public void disable() {
checkStateNotNull(controllerCallback, "SuitableOutputChecker is not enabled"); checkStateNotNull(isSuitableForPlaybackState)
checkNotNull(router).unregisterControllerCallback(controllerCallback); .runInBackground(
() -> {
checkNotNull(router).unregisterControllerCallback(checkNotNull(controllerCallback));
controllerCallback = null; controllerCallback = null;
router.unregisterRouteCallback(checkNotNull(routeCallback)); router.unregisterRouteCallback(checkNotNull(routeCallback));
});
} }
@Override @Override
public boolean isSelectedOutputSuitableForPlayback() { public boolean isSelectedOutputSuitableForPlayback() {
return isSelectedOutputSuitableForPlayback; return isSuitableForPlaybackState == null ? true : isSuitableForPlaybackState.get();
} }
private static boolean isSelectedOutputSuitableForPlayback(MediaRouter2 router) { private static boolean isSelectedOutputSuitableForPlayback(MediaRouter2 router) {
@ -173,24 +178,34 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@RequiresApi(23) @RequiresApi(23)
private static final class ImplApi23 implements SuitableOutputChecker { private static final class ImplApi23 implements SuitableOutputChecker {
private final Context applicationContext;
private final Handler eventHandler;
@Nullable private AudioManager audioManager; @Nullable private AudioManager audioManager;
private @MonotonicNonNull AudioDeviceCallback audioDeviceCallback; private @MonotonicNonNull AudioDeviceCallback audioDeviceCallback;
private boolean isSelectedOutputSuitableForPlayback; private @MonotonicNonNull BackgroundThreadStateHandler<Boolean> isSuitableForPlaybackState;
public ImplApi23(Context context, Handler eventHandler) {
this.applicationContext = context.getApplicationContext();
this.eventHandler = eventHandler;
}
@Override @Override
public void enable(Callback callback) { public void enable(
Callback callback,
Context context,
Looper callbackLooper,
Looper backgroundLooper,
Clock clock) {
isSuitableForPlaybackState =
new BackgroundThreadStateHandler<>(
/* initialState= */ true,
backgroundLooper,
callbackLooper,
clock,
/* onStateChanged= */ (oldState, newState) ->
callback.onSelectedOutputSuitabilityChanged(newState));
isSuitableForPlaybackState.runInBackground(
() -> {
checkNotNull(isSuitableForPlaybackState);
if (!Util.isWear(context)) {
return;
}
AudioManager audioManager = AudioManager audioManager =
(AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE); (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) { if (audioManager == null) {
isSelectedOutputSuitableForPlayback = true;
return; return;
} }
this.audioManager = audioManager; this.audioManager = audioManager;
@ -198,42 +213,37 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
new AudioDeviceCallback() { new AudioDeviceCallback() {
@Override @Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
updateIsSelectedOutputSuitableForPlayback(callback); isSuitableForPlaybackState.setStateInBackground(hasSupportedAudioOutput());
} }
@Override @Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
updateIsSelectedOutputSuitableForPlayback(callback); isSuitableForPlaybackState.setStateInBackground(hasSupportedAudioOutput());
} }
}; };
audioManager.registerAudioDeviceCallback(audioDeviceCallback, eventHandler); audioManager.registerAudioDeviceCallback(
isSelectedOutputSuitableForPlayback = hasSupportedAudioOutput(); audioDeviceCallback, new Handler(checkNotNull(Looper.myLooper())));
isSuitableForPlaybackState.setStateInBackground(hasSupportedAudioOutput());
});
} }
@Override @Override
public void disable() { public void disable() {
checkNotNull(isSuitableForPlaybackState)
.runInBackground(
() -> {
if (audioManager != null) { if (audioManager != null) {
audioManager.unregisterAudioDeviceCallback(checkNotNull(audioDeviceCallback)); audioManager.unregisterAudioDeviceCallback(checkNotNull(audioDeviceCallback));
} }
});
} }
@Override @Override
public boolean isSelectedOutputSuitableForPlayback() { public boolean isSelectedOutputSuitableForPlayback() {
return isSelectedOutputSuitableForPlayback; return isSuitableForPlaybackState == null ? true : isSuitableForPlaybackState.get();
}
private void updateIsSelectedOutputSuitableForPlayback(Callback callback) {
boolean isSelectedOutputSuitableForPlayback = hasSupportedAudioOutput();
if (this.isSelectedOutputSuitableForPlayback != isSelectedOutputSuitableForPlayback) {
this.isSelectedOutputSuitableForPlayback = isSelectedOutputSuitableForPlayback;
callback.onSelectedOutputSuitabilityChanged(isSelectedOutputSuitableForPlayback);
}
} }
private boolean hasSupportedAudioOutput() { private boolean hasSupportedAudioOutput() {
if (!Util.isWear(applicationContext)) {
return true;
}
AudioDeviceInfo[] audioDeviceInfos = AudioDeviceInfo[] audioDeviceInfos =
checkStateNotNull(audioManager).getDevices(AudioManager.GET_DEVICES_OUTPUTS); checkStateNotNull(audioManager).getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo device : audioDeviceInfos) { for (AudioDeviceInfo device : audioDeviceInfos) {

View File

@ -24,7 +24,6 @@ import android.content.Context;
import android.media.AudioDeviceInfo; import android.media.AudioDeviceInfo;
import android.media.AudioTrack; import android.media.AudioTrack;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Process; import android.os.Process;
import android.view.Surface; import android.view.Surface;
@ -460,7 +459,7 @@ public interface ExoPlayer extends Player {
usePlatformDiagnostics = true; usePlatformDiagnostics = true;
playerName = ""; playerName = "";
priority = C.PRIORITY_PLAYBACK; priority = C.PRIORITY_PLAYBACK;
suitableOutputChecker = new DefaultSuitableOutputChecker(context, new Handler(looper)); suitableOutputChecker = new DefaultSuitableOutputChecker();
} }
/** /**

View File

@ -413,7 +413,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (builder.suppressPlaybackOnUnsuitableOutput) { if (builder.suppressPlaybackOnUnsuitableOutput) {
suitableOutputChecker = builder.suitableOutputChecker; suitableOutputChecker = builder.suitableOutputChecker;
suitableOutputChecker.enable(this::onSelectedOutputSuitabilityChanged); suitableOutputChecker.enable(
this::onSelectedOutputSuitabilityChanged,
applicationContext,
applicationLooper,
playbackLooper,
clock);
} else { } else {
suitableOutputChecker = null; suitableOutputChecker = null;
} }

View File

@ -17,7 +17,10 @@ package androidx.media3.exoplayer;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.content.Context;
import android.os.Looper;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
/** Provides methods to check the suitability of selected media outputs. */ /** Provides methods to check the suitability of selected media outputs. */
@ -45,8 +48,17 @@ public interface SuitableOutputChecker {
* #disable()}. * #disable()}.
* *
* @param callback To receive notifications of changes in suitable media output changes. * @param callback To receive notifications of changes in suitable media output changes.
* @param context A {@link Context}.
* @param callbackLooper The {@link Looper} to call {@link Callback} methods on.
* @param backgroundLooper The {@link Looper} to run background operations on.
* @param clock The {@link Clock}.
*/ */
void enable(Callback callback); void enable(
Callback callback,
Context context,
Looper callbackLooper,
Looper backgroundLooper,
Clock clock);
/** /**
* Disables the current instance to receive updates on the selected media outputs and clears the * Disables the current instance to receive updates on the selected media outputs and clears the

View File

@ -15035,6 +15035,7 @@ public class ExoPlayerTest {
parameterizeTestExoPlayerBuilder( parameterizeTestExoPlayerBuilder(
new TestExoPlayerBuilder(context).setSuppressPlaybackOnUnsuitableOutput(true)) new TestExoPlayerBuilder(context).setSuppressPlaybackOnUnsuitableOutput(true))
.build(); .build();
advance(player).untilPendingCommandsAreFullyHandled();
player.setMediaItem( player.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4")); MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
player.addListener( player.addListener(
@ -15051,7 +15052,7 @@ public class ExoPlayerTest {
player.play(); player.play();
player.stop(); player.stop();
runUntilPlaybackState(player, Player.STATE_IDLE); advance(player).untilState(Player.STATE_IDLE);
assertThat(playbackSuppressionList) assertThat(playbackSuppressionList)
.containsExactly(Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT); .containsExactly(Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT);
@ -15113,6 +15114,7 @@ public class ExoPlayerTest {
parameterizeTestExoPlayerBuilder( parameterizeTestExoPlayerBuilder(
new TestExoPlayerBuilder(context).setSuppressPlaybackOnUnsuitableOutput(true)) new TestExoPlayerBuilder(context).setSuppressPlaybackOnUnsuitableOutput(true))
.build(); .build();
advance(player).untilPendingCommandsAreFullyHandled();
player.setMediaItem( player.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4")); MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
player.addListener( player.addListener(
@ -15131,7 +15133,7 @@ public class ExoPlayerTest {
player.play(); player.play();
player.play(); player.play();
player.stop(); player.stop();
runUntilPlaybackState(player, Player.STATE_IDLE); advance(player).untilState(Player.STATE_IDLE);
assertThat(playbackSuppressionList) assertThat(playbackSuppressionList)
.containsExactly( .containsExactly(
@ -15241,12 +15243,13 @@ public class ExoPlayerTest {
player.prepare(); player.prepare();
player.play(); player.play();
player.pause(); player.pause();
runUntilPlaybackState(player, Player.STATE_READY); advance(player).untilState(Player.STATE_READY);
addConnectedAudioOutput( addConnectedAudioOutput(
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true); AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
advance(player).untilPendingCommandsAreFullyHandled();
player.stop(); player.stop();
runUntilPlaybackState(player, Player.STATE_IDLE); advance(player).untilState(Player.STATE_IDLE);
assertThat(playbackSuppressionList) assertThat(playbackSuppressionList)
.containsExactly( .containsExactly(
@ -15298,11 +15301,12 @@ public class ExoPlayerTest {
public void addSuitableOutputWhenPlaybackNotSuppressed_shouldNotRemovePlaybackSuppression() public void addSuitableOutputWhenPlaybackNotSuppressed_shouldNotRemovePlaybackSuppression()
throws Exception { throws Exception {
addWatchAsSystemFeature(); addWatchAsSystemFeature();
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); setupConnectedAudioOutput(AudioDeviceInfo.TYPE_USB_DEVICE);
ExoPlayer player = ExoPlayer player =
parameterizeTestExoPlayerBuilder( parameterizeTestExoPlayerBuilder(
new TestExoPlayerBuilder(context).setSuppressPlaybackOnUnsuitableOutput(true)) new TestExoPlayerBuilder(context).setSuppressPlaybackOnUnsuitableOutput(true))
.build(); .build();
advance(player).untilPendingCommandsAreFullyHandled();
player.setMediaItem( player.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4")); MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
PlaybackSuppressionReasonChangedListener playbackSuppressionReasonChangedListener = PlaybackSuppressionReasonChangedListener playbackSuppressionReasonChangedListener =
@ -15313,8 +15317,9 @@ public class ExoPlayerTest {
addConnectedAudioOutput( addConnectedAudioOutput(
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true); AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
advance(player).untilPendingCommandsAreFullyHandled();
player.stop(); player.stop();
runUntilPlaybackState(player, Player.STATE_IDLE); advance(player).untilState(Player.STATE_IDLE);
assertThat(playbackSuppressionReasonChangedListener.getReasonChangedList()).isEmpty(); assertThat(playbackSuppressionReasonChangedListener.getReasonChangedList()).isEmpty();
player.release(); player.release();
@ -15341,14 +15346,15 @@ public class ExoPlayerTest {
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4")); MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
player.prepare(); player.prepare();
player.play(); player.play();
runUntilPlaybackState(player, Player.STATE_READY); advance(player).untilState(Player.STATE_READY);
PlaybackSuppressionReasonChangedListener playbackSuppressionReasonChangedListener = PlaybackSuppressionReasonChangedListener playbackSuppressionReasonChangedListener =
new PlaybackSuppressionReasonChangedListener(); new PlaybackSuppressionReasonChangedListener();
player.addListener(playbackSuppressionReasonChangedListener); player.addListener(playbackSuppressionReasonChangedListener);
removeConnectedAudioOutput(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP); removeConnectedAudioOutput(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
advance(player).untilPendingCommandsAreFullyHandled();
player.stop(); player.stop();
runUntilPlaybackState(player, Player.STATE_IDLE); advance(player).untilState(Player.STATE_IDLE);
assertThat(playbackSuppressionReasonChangedListener.getReasonChangedList()) assertThat(playbackSuppressionReasonChangedListener.getReasonChangedList())
.containsExactly(Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT); .containsExactly(Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT);

View File

@ -18,8 +18,11 @@ package androidx.media3.test.utils;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.os.Looper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.SuitableOutputChecker; import androidx.media3.exoplayer.SuitableOutputChecker;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
@ -64,7 +67,12 @@ public final class FakeSuitableOutputChecker implements SuitableOutputChecker {
} }
@Override @Override
public void enable(Callback callback) { public void enable(
Callback callback,
Context context,
Looper callbackLooper,
Looper backgroundLooper,
Clock clock) {
this.callback = callback; this.callback = callback;
} }

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.ui; package androidx.media3.ui;
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.advance;
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlayWhenReady; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlayWhenReady;
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
import static androidx.test.ext.truth.content.IntentSubject.assertThat; import static androidx.test.ext.truth.content.IntentSubject.assertThat;
@ -38,6 +39,7 @@ import androidx.media3.common.MediaItem;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.Player.PlayWhenReadyChangeReason; import androidx.media3.common.Player.PlayWhenReadyChangeReason;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.test.utils.FakeClock; import androidx.media3.test.utils.FakeClock;
import androidx.media3.test.utils.FakeSuitableOutputChecker; import androidx.media3.test.utils.FakeSuitableOutputChecker;
import androidx.media3.test.utils.TestExoPlayerBuilder; import androidx.media3.test.utils.TestExoPlayerBuilder;
@ -75,11 +77,11 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
private ShadowPackageManager shadowPackageManager; private ShadowPackageManager shadowPackageManager;
private ShadowApplication shadowApplication; private ShadowApplication shadowApplication;
private Player testPlayer; private ExoPlayer testPlayer;
private FakeSuitableOutputChecker suitableMediaOutputChecker; private FakeSuitableOutputChecker suitableMediaOutputChecker;
@Before @Before
public void setUp() { public void setUp() throws Exception {
shadowPackageManager = shadowPackageManager =
shadowOf(ApplicationProvider.getApplicationContext().getPackageManager()); shadowOf(ApplicationProvider.getApplicationContext().getPackageManager());
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true); shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
@ -95,6 +97,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
builder.setSuitableOutputChecker(suitableMediaOutputChecker); builder.setSuitableOutputChecker(suitableMediaOutputChecker);
} }
testPlayer = builder.build(); testPlayer = builder.build();
advance(testPlayer).untilPendingCommandsAreFullyHandled();
shadowApplication = shadowOf((Application) ApplicationProvider.getApplicationContext()); shadowApplication = shadowOf((Application) ApplicationProvider.getApplicationContext());
} }
@ -816,8 +819,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
addConnectedAudioOutput( addConnectedAudioOutput(
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true); AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, /* notifyAudioDeviceCallbacks= */ true);
runUntilPlayWhenReady(testPlayer, /* expectedPlayWhenReady= */ false); advance(testPlayer).untilPendingCommandsAreFullyHandled();
shadowOf(Looper.getMainLooper()).idle();
assertThat(ShadowPowerManager.getLatestWakeLock()).isNotNull(); assertThat(ShadowPowerManager.getLatestWakeLock()).isNotNull();
assertThat(ShadowPowerManager.getLatestWakeLock().isHeld()).isFalse(); assertThat(ShadowPowerManager.getLatestWakeLock().isHeld()).isFalse();
@ -959,7 +961,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
fakeComponentName, new IntentFilter(fakeActionName)); fakeComponentName, new IntentFilter(fakeActionName));
} }
private void setupConnectedAudioOutput(int... deviceTypes) { private void setupConnectedAudioOutput(int... deviceTypes) throws TimeoutException {
ShadowAudioManager shadowAudioManager = ShadowAudioManager shadowAudioManager =
shadowOf(ApplicationProvider.getApplicationContext().getSystemService(AudioManager.class)); shadowOf(ApplicationProvider.getApplicationContext().getSystemService(AudioManager.class));
for (int deviceType : deviceTypes) { for (int deviceType : deviceTypes) {
@ -967,6 +969,7 @@ public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
AudioDeviceInfoBuilder.newBuilder().setType(deviceType).build(), AudioDeviceInfoBuilder.newBuilder().setType(deviceType).build(),
/* notifyAudioDeviceCallbacks= */ true); /* notifyAudioDeviceCallbacks= */ true);
} }
advance(testPlayer).untilPendingCommandsAreFullyHandled();
} }
private void addConnectedAudioOutput(int deviceTypes, boolean notifyAudioDeviceCallbacks) { private void addConnectedAudioOutput(int deviceTypes, boolean notifyAudioDeviceCallbacks) {