Player.Listener implementation to resolve playback suppression on Wear.

Add a Wear OS specific implementation of 'Player.Listener' to help resolving the playback suppression due to unsuitable output by launching a system media output switcher dialog.

PiperOrigin-RevId: 541698125
This commit is contained in:
Googler 2023-06-19 20:50:41 +01:00 committed by Ian Baker
parent 911a6430f3
commit abd1c006fc
3 changed files with 641 additions and 0 deletions

View File

@ -67,6 +67,11 @@
* IMA extension:
* Session:
* UI:
* Add a `Player.Listener` implementation for Wear OS devices that handles
playback suppression due to
`Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT` by
launching a system dialog to allow a user to connect a suitable audio
output (e.g. bluetooth headphones).
* Downloads:
* OkHttp Extension:
* Cronet Extension:

View File

@ -0,0 +1,168 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.ui;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.media3.common.Player;
import androidx.media3.common.Player.Events;
import androidx.media3.common.util.UnstableApi;
import java.util.List;
/**
* A {@link Player.Listener} that launches a system dialog in response to {@link
* Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} to allow the user to connect a
* suitable audio output.
*
* <p>This listener only reacts to {@link
* Player#PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT} on Wear OS devices, while being no-op
* for non-Wear OS devices.
*
* <p>The system dialog will be the <a
* href="https://developer.android.com/guide/topics/media/media-routing#output-switcher">Media
* Output Switcher</a> if it is available on the device, or otherwise the Bluetooth settings screen.
*
* <p>This implementation also pauses playback when launching the system dialog. The underlying
* {@link Player} implementation (e.g. ExoPlayer) is expected to resume playback automatically when
* a suitable audio device is connected by the user.
*/
@UnstableApi
public final class WearUnsuitableOutputPlaybackSuppressionResolverListener
implements Player.Listener {
/** Output switcher intent action for the Wear OS. */
private static final String OUTPUT_SWITCHER_INTENT_ACTION_NAME =
"com.android.settings.panel.action.MEDIA_OUTPUT";
/** A package name key for output switcher intent in the Wear OS. */
private static final String EXTRA_OUTPUT_SWITCHER_PACKAGE_NAME =
"com.android.settings.panel.extra.PACKAGE_NAME";
/**
* Extra in the Bluetooth Activity intent to control whether the fragment should close when a
* device connects.
*/
private static final String EXTRA_BLUETOOTH_SETTINGS_CLOSE_ON_CONNECT = "EXTRA_CLOSE_ON_CONNECT";
/**
* Extra in the Bluetooth Activity intent to indicate that the user only wants to connect or
* disconnect, not forget paired devices or do any other device management.
*/
private static final String EXTRA_BLUETOOTH_SETTINGS_CONNECTION_ONLY = "EXTRA_CONNECTION_ONLY";
/**
* Extra in the Bluetooth Activity intent to specify the type of filtering that needs to be be
* applied to the device list.
*/
private static final String EXTRA_BLUETOOTH_SETTINGS_FILTER_TYPE =
"android.bluetooth.devicepicker.extra.FILTER_TYPE";
/**
* The value for the {@link #EXTRA_BLUETOOTH_SETTINGS_FILTER_TYPE} in the Bluetooth intent to show
* BT devices that support AUDIO profiles
*/
private static final int FILTER_TYPE_AUDIO = 1;
private Context applicationContext;
/**
* Creates a new {@link WearUnsuitableOutputPlaybackSuppressionResolverListener} instance.
*
* @param context Any context.
*/
public WearUnsuitableOutputPlaybackSuppressionResolverListener(Context context) {
applicationContext = context.getApplicationContext();
}
@Override
public void onEvents(Player player, Events events) {
if (!isRunningOnWear(applicationContext)) {
return;
}
if (events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
&& player.getPlayWhenReady()
&& player.getPlaybackSuppressionReason()
== Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) {
player.pause();
launchSystemMediaOutputSwitcherUi(applicationContext);
}
}
/**
* Launches the system media output switcher app if it is available on the device, or otherwise
* the Bluetooth settings screen.
*/
private static void launchSystemMediaOutputSwitcherUi(Context context) {
Intent outputSwitcherLaunchIntent =
new Intent(OUTPUT_SWITCHER_INTENT_ACTION_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_OUTPUT_SWITCHER_PACKAGE_NAME, context.getPackageName());
ComponentName outputSwitcherSystemComponentName =
getSystemOrSystemUpdatedAppComponent(context, outputSwitcherLaunchIntent);
if (outputSwitcherSystemComponentName != null) {
outputSwitcherLaunchIntent.setComponent(outputSwitcherSystemComponentName);
context.startActivity(outputSwitcherLaunchIntent);
} else {
Intent bluetoothSettingsLaunchIntent =
new Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.putExtra(EXTRA_BLUETOOTH_SETTINGS_CLOSE_ON_CONNECT, true)
.putExtra(EXTRA_BLUETOOTH_SETTINGS_CONNECTION_ONLY, true)
.putExtra(EXTRA_BLUETOOTH_SETTINGS_FILTER_TYPE, FILTER_TYPE_AUDIO);
ComponentName bluetoothSettingsSystemComponentName =
getSystemOrSystemUpdatedAppComponent(context, bluetoothSettingsLaunchIntent);
if (bluetoothSettingsSystemComponentName != null) {
bluetoothSettingsLaunchIntent.setComponent(bluetoothSettingsSystemComponentName);
context.startActivity(bluetoothSettingsLaunchIntent);
}
}
}
/**
* Returns {@link ComponentName} of system or updated system app's activity resolved from the
* {@link Intent} passed to it.
*/
private static @Nullable ComponentName getSystemOrSystemUpdatedAppComponent(
Context context, Intent intent) {
PackageManager packageManager = context.getPackageManager();
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, /* flags= */ 0);
for (ResolveInfo resolveInfo : resolveInfos) {
ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null || activityInfo.applicationInfo == null) {
continue;
}
ApplicationInfo appInfo = activityInfo.applicationInfo;
int systemAndUpdatedSystemAppFlags =
ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
if ((systemAndUpdatedSystemAppFlags & appInfo.flags) != 0) {
return new ComponentName(activityInfo.packageName, activityInfo.name);
}
}
return null;
}
private static boolean isRunningOnWear(Context context) {
PackageManager packageManager = context.getPackageManager();
return packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
}
}

View File

@ -0,0 +1,468 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.ui;
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
import static androidx.test.ext.truth.content.IntentSubject.assertThat;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
import android.app.Application;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Looper;
import android.provider.Settings;
import androidx.media3.common.FlagSet;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.Player.Listener;
import androidx.media3.test.utils.TestExoPlayerBuilder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.shadows.AudioDeviceInfoBuilder;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowAudioManager;
import org.robolectric.shadows.ShadowPackageManager;
/** Tests for the {@link WearUnsuitableOutputPlaybackSuppressionResolverListener}. */
@RunWith(AndroidJUnit4.class)
public class WearUnsuitableOutputPlaybackSuppressionResolverListenerTest {
private static final String OUTPUT_SWITCHER_INTENT_ACTION_NAME =
"com.android.settings.panel.action.MEDIA_OUTPUT";
private static final String FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME = "com.fake.outputswitcher";
private static final String FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME =
"com.fake.outputswitcher.OutputSwitcherActivity";
private static final String FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME = "com.fake.btsettings";
private static final String FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME =
"com.fake.btsettings.BluetoothSettingsActivity";
private ShadowPackageManager shadowPackageManager;
private ShadowApplication shadowApplication;
private Player testPlayer;
@Before
public void setUp() {
testPlayer =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setSuppressPlaybackOnUnsuitableOutput(true)
.build();
testPlayer.addListener(
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
ApplicationProvider.getApplicationContext()));
shadowApplication = shadowOf((Application) ApplicationProvider.getApplicationContext());
shadowPackageManager =
shadowOf(ApplicationProvider.getApplicationContext().getPackageManager());
}
@After
public void afterTest() {
testPlayer.release();
}
/**
* Test for the launch of system Output Switcher app when playback is suppressed due to unsuitable
* output and the system Output Switcher is present on the device.
*/
@Test
public void
playEventWithPlaybackSuppressionWhenSystemOutputSwitcherPresent_shouldLaunchOutputSwitcher()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNotNull();
assertThat(intentTriggered).hasAction(OUTPUT_SWITCHER_INTENT_ACTION_NAME);
assertThat(intentTriggered)
.hasComponent(
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME, FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME);
}
/**
* Test 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.
*/
@Test
public void playEventWithPlaybackSuppressionWhenUpdatedSystemOSwPresent_shouldLaunchOSw()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNotNull();
assertThat(intentTriggered).hasAction(OUTPUT_SWITCHER_INTENT_ACTION_NAME);
assertThat(intentTriggered)
.hasComponent(
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME, FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME);
}
/**
* Test 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.
*/
@Test
public void
playbackSuppressionWhenBothSystemAndUserInstalledOutputSwitcherPresent_shouldLaunchSystemOSw()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
"com.fake.userinstalled.outputswitcher",
"com.fake.userinstalled.outputswitcher.OutputSwitcherActivity",
/* applicationFlags= */ 0);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNotNull();
assertThat(intentTriggered).hasAction(OUTPUT_SWITCHER_INTENT_ACTION_NAME);
assertThat(intentTriggered)
.hasComponent(
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME, FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME);
}
/**
* Test 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.
*/
@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,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
shadowApplication.clearNextStartedActivities();
// Clear the system feature for "watch" to test on the non-Wear OS devices.
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ false);
Player.Listener testPlayerListener =
new WearUnsuitableOutputPlaybackSuppressionResolverListener(
ApplicationProvider.getApplicationContext());
testPlayer.addListener(testPlayerListener);
runUntilPlaybackState(testPlayer, Player.STATE_READY);
testPlayerListener.onEvents(
testPlayer,
new Player.Events(new FlagSet.Builder().add(Player.EVENT_PLAY_WHEN_READY_CHANGED).build()));
shadowOf(Looper.getMainLooper()).idle();
Intent activityIntentTriggered = shadowApplication.getNextStartedActivity();
assertThat(activityIntentTriggered).isNull();
List<Intent> broadcastIntents = shadowApplication.getBroadcastIntents();
assertThat(broadcastIntents).isEmpty();
}
/**
* Test for the launch of Bluetooth Settings app when playback is suppressed due to unsuitable
* output with the system Bluetooth Settings app present while the system Output Switcher app is
* not present on the device.
*/
@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,
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNotNull();
assertThat(intentTriggered).hasAction(Settings.ACTION_BLUETOOTH_SETTINGS);
assertThat(intentTriggered)
.hasComponent(FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME, FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME);
}
/**
* Test for the launch of Bluetooth Settings app when playback is suppressed due to unsuitable
* output with the updated system Bluetooth Settings app present while the Output Switcher app is
* not present on the device.
*/
@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,
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNotNull();
assertThat(intentTriggered).hasAction(Settings.ACTION_BLUETOOTH_SETTINGS);
assertThat(intentTriggered)
.hasComponent(FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME, FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME);
}
/**
* 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.
*/
@Test
public void playEventWithPlaybackSuppressionWhenOSwAndBTSettingsBothPresent_shouldLaunchOSw()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
registerFakeActivity(
Settings.ACTION_BLUETOOTH_SETTINGS,
FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME,
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNotNull();
assertThat(intentTriggered).hasAction(OUTPUT_SWITCHER_INTENT_ACTION_NAME);
assertThat(intentTriggered)
.hasComponent(
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME, FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME);
}
/**
* Test for no launch of the non-system and non-system updated Output Switcher app when playback
* is suppressed due to unsuitable output.
*/
@Test
public void
playbackSuppressionWhenOnlyUserInstalledOSwAndBTSettingsPresent_shouldNotLaunchAnyApp()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
"com.fake.userinstalled.outputswitcher",
"com.fake.userinstalled.outputswitcher.OutputSwitcherActivity",
/* applicationFlags= */ 0);
registerFakeActivity(
Settings.ACTION_BLUETOOTH_SETTINGS,
"com.fake.userinstalled.btsettings",
"com.fake.userinstalled.btsettings.BluetoothSettingsActivity",
/* applicationFlags= */ 0);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
Intent intentTriggered = shadowApplication.getNextStartedActivity();
assertThat(intentTriggered).isNull();
}
/**
* Test for no launch of any system media output switching dialog app when playback is not
* suppressed due to unsuitable output.
*/
@Test
public void playEventWithoutPlaybackSuppression_shouldNotLaunchEitherOSwOrBTSettings()
throws TimeoutException {
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_WATCH, /* supported= */ true);
registerFakeActivity(
OUTPUT_SWITCHER_INTENT_ACTION_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_PACKAGE_NAME,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
registerFakeActivity(
Settings.ACTION_BLUETOOTH_SETTINGS,
FAKE_SYSTEM_BT_SETTINGS_PACKAGE_NAME,
FAKE_SYSTEM_BT_SETTINGS_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
assertThat(shadowApplication.getNextStartedActivity()).isNull();
}
/** Test for pause on the Player when the playback is suppressed due to unsuitable output. */
@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,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
AtomicBoolean isPlaybackPaused = new AtomicBoolean(false);
testPlayer.addListener(
new Listener() {
@Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
if (!playWhenReady) {
isPlaybackPaused.set(true);
}
}
});
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
assertThat(isPlaybackPaused.get()).isTrue();
}
/**
* Test for no pause on the Player when the playback is not suppressed due to unsuitable output.
*/
@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,
FAKE_SYSTEM_OUTPUT_SWITCHER_CLASS_NAME,
ApplicationInfo.FLAG_SYSTEM);
setupConnectedAudioOutput(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
testPlayer.setMediaItem(
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
testPlayer.prepare();
AtomicBoolean isPlaybackPaused = new AtomicBoolean(false);
testPlayer.addListener(
new Listener() {
@Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
if (!playWhenReady) {
isPlaybackPaused.set(true);
}
}
});
testPlayer.play();
runUntilPlaybackState(testPlayer, Player.STATE_READY);
assertThat(isPlaybackPaused.get()).isFalse();
}
private void registerFakeActivity(
String fakeActionName, String fakePackageName, String fakeClassName, int applicationFlags) {
ComponentName fakeComponentName = new ComponentName(fakePackageName, fakeClassName);
ApplicationInfo systemAppInfo = new ApplicationInfo();
systemAppInfo.flags |= applicationFlags;
ActivityInfo fakeActivityInfo = new ActivityInfo();
fakeActivityInfo.applicationInfo = systemAppInfo;
fakeActivityInfo.name = fakeComponentName.getClassName();
fakeActivityInfo.packageName = fakeComponentName.getPackageName();
shadowPackageManager.addOrUpdateActivity(fakeActivityInfo);
shadowPackageManager.addIntentFilterForActivity(
fakeComponentName, new IntentFilter(fakeActionName));
}
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.setOutputDevices(deviceListBuilder.build());
}
}