Show the output switcher when no media is playing

- Support output switcher for system routing.
- Add an new string to indicate the device that
  audio will output to.

Bug: 284227163
Test: device/host atest
 atest MediaOutputPreferenceControllerTest
 atest AudioOutputSwitchPreferenceControllerTest
 atest MediaOutputIndicatorSliceTest

Change-Id: I94bcf84e7e93b3e4f5db1d95d5380a54a3e0c460
Signed-off-by: Jasmine Cha <chajasmine@google.com>
This commit is contained in:
Jasmine Cha
2023-10-03 16:56:43 +08:00
parent 1340427763
commit 9e45469833
7 changed files with 287 additions and 31 deletions

View File

@@ -10927,6 +10927,9 @@
<!-- Title with application label for media output settings. [CHAR LIMIT=NONE] --> <!-- Title with application label for media output settings. [CHAR LIMIT=NONE] -->
<string name="media_output_label_title">Play <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> <string name="media_output_label_title">Play <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
<!-- Title for media output settings without media is playing -->
<string name="media_output_title_without_playing">Audio will play on</string>
<!-- Summary for media output default settings. (this device) [CHAR LIMIT=30] --> <!-- Summary for media output default settings. (this device) [CHAR LIMIT=30] -->
<string name="media_output_default_summary">This device</string> <string name="media_output_default_summary">This device</string>

View File

@@ -17,6 +17,7 @@
package com.android.settings.media; package com.android.settings.media;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
import android.annotation.ColorInt; import android.annotation.ColorInt;
import android.content.Context; import android.content.Context;
@@ -58,7 +59,12 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
} }
final IconCompat icon = IconCompat.createWithResource(mContext, final IconCompat icon = IconCompat.createWithResource(mContext,
com.android.internal.R.drawable.ic_settings_bluetooth); com.android.internal.R.drawable.ic_settings_bluetooth);
final CharSequence title = mContext.getString(R.string.media_output_label_title, final int stringRes = enableOutputSwitcherForSystemRouting()
? (getWorker().getActiveLocalMediaController() != null
? R.string.media_output_label_title
: R.string.media_output_title_without_playing)
: R.string.media_output_label_title;
final CharSequence title = mContext.getString(stringRes,
Utils.getApplicationLabel(mContext, getWorker().getPackageName())); Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
final SliceAction primarySliceAction = SliceAction.create( final SliceAction primarySliceAction = SliceAction.create(
getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, title); getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, title);
@@ -117,28 +123,36 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
// 2. worker is not null // 2. worker is not null
// 3. Available devices are more than 0 // 3. Available devices are more than 0
// 4. The local media session is active and the state is playing. // 4. The local media session is active and the state is playing.
// - if !enableOutputSwitcherForSystemRouting(), (4) will be bypass.
return getWorker() != null return getWorker() != null
&& !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext) && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)
&& getWorker().getMediaDevices().size() > 0 && getWorker().getMediaDevices().size() > 0
&& getWorker().getActiveLocalMediaController() != null; && (enableOutputSwitcherForSystemRouting()
? true : getWorker().getActiveLocalMediaController() != null);
} }
@Override @Override
public void onNotifyChange(Intent intent) { public void onNotifyChange(Intent intent) {
final MediaController mediaController = getWorker().getActiveLocalMediaController(); final MediaController mediaController = getWorker().getActiveLocalMediaController();
if (mediaController == null) { // Launch media output dialog
if (enableOutputSwitcherForSystemRouting() && mediaController == null) {
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG));
} else if (mediaController != null) {
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
mediaController.getSessionToken())
.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
mediaController.getPackageName()));
} else {
Log.d(TAG, "No active local media controller"); Log.d(TAG, "No active local media controller");
return; return;
} }
// Launch media output dialog
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
mediaController.getSessionToken())
.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
mediaController.getPackageName()));
// Dismiss volume panel // Dismiss volume panel
mContext.sendBroadcast(new Intent() mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME) .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)

View File

@@ -18,6 +18,9 @@ package com.android.settings.sound;
import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION; import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION;
import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
@@ -28,6 +31,8 @@ import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo; import android.media.AudioDeviceInfo;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaRouter; import android.media.MediaRouter;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.FeatureFlagUtils; import android.util.FeatureFlagUtils;
@@ -79,6 +84,8 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
private final WiredHeadsetBroadcastReceiver mReceiver; private final WiredHeadsetBroadcastReceiver mReceiver;
private final Handler mHandler; private final Handler mHandler;
private LocalBluetoothManager mLocalBluetoothManager; private LocalBluetoothManager mLocalBluetoothManager;
@Nullable private MediaSessionManager.OnActiveSessionsChangedListener mSessionListener;
@Nullable private MediaSessionManager mMediaSessionManager;
public interface AudioSwitchCallback { public interface AudioSwitchCallback {
void onPreferenceDataChanged(ListPreference preference); void onPreferenceDataChanged(ListPreference preference);
@@ -107,6 +114,14 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
return; return;
} }
mProfileManager = mLocalBluetoothManager.getProfileManager(); mProfileManager = mLocalBluetoothManager.getProfileManager();
if (enableOutputSwitcherForSystemRouting()) {
mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
mSessionListener = new SessionChangeListener();
} else {
mMediaSessionManager = null;
mSessionListener = null;
}
} }
/** /**
@@ -329,13 +344,27 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
// Register for misc other intent broadcasts. // Register for misc other intent broadcasts.
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
intentFilter.addAction(STREAM_DEVICES_CHANGED_ACTION); intentFilter.addAction(STREAM_DEVICES_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, intentFilter);
if (enableOutputSwitcherForSystemRouting()) {
mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
if (mMediaSessionManager != null) {
mMediaSessionManager.addOnActiveSessionsChangedListener(
mSessionListener, null, mHandler);
}
} else {
mContext.registerReceiver(mReceiver, intentFilter);
}
} }
private void unregister() { private void unregister() {
mLocalBluetoothManager.getEventManager().unregisterCallback(this); mLocalBluetoothManager.getEventManager().unregisterCallback(this);
mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback); mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback);
mContext.unregisterReceiver(mReceiver); mContext.unregisterReceiver(mReceiver);
if (enableOutputSwitcherForSystemRouting()) {
if (mMediaSessionManager != null) {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionListener);
}
}
} }
/** Notifications of audio device connection and disconnection events. */ /** Notifications of audio device connection and disconnection events. */
@@ -362,4 +391,12 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
} }
} }
} }
private class SessionChangeListener
implements MediaSessionManager.OnActiveSessionsChangedListener {
@Override
public void onActiveSessionsChanged(List<MediaController> controllers) {
updateState(mPreference);
}
}
} }

View File

@@ -16,6 +16,9 @@
package com.android.settings.sound; package com.android.settings.sound;
import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -46,21 +49,22 @@ import java.util.List;
*/ */
public class MediaOutputPreferenceController extends AudioSwitchPreferenceController { public class MediaOutputPreferenceController extends AudioSwitchPreferenceController {
private MediaController mMediaController; private static final String TAG = "MediaOutputPreferenceController";
@Nullable private MediaController mMediaController;
private MediaSessionManager mMediaSessionManager;
public MediaOutputPreferenceController(Context context, String key) { public MediaOutputPreferenceController(Context context, String key) {
super(context, key); super(context, key);
mMediaController = MediaOutputUtils.getActiveLocalMediaController(context.getSystemService( mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
MediaSessionManager.class)); mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager);
} }
@Override @Override
public void displayPreference(PreferenceScreen screen) { public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen); super.displayPreference(screen);
if (!Utils.isAudioModeOngoingCall(mContext) && mMediaController != null) { mPreference.setVisible(!Utils.isAudioModeOngoingCall(mContext)
mPreference.setVisible(true); && (enableOutputSwitcherForSystemRouting() ? true : mMediaController != null));
}
} }
@Override @Override
@@ -70,11 +74,16 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro
return; return;
} }
if (mMediaController == null) { if (enableOutputSwitcherForSystemRouting()) {
// No active local playback mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager);
return; } else {
if (mMediaController == null) {
// No active local playback
return;
}
} }
if (Utils.isAudioModeOngoingCall(mContext)) { if (Utils.isAudioModeOngoingCall(mContext)) {
// Ongoing call status, switch entry for media will be disabled. // Ongoing call status, switch entry for media will be disabled.
mPreference.setVisible(false); mPreference.setVisible(false);
@@ -95,9 +104,14 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro
|| (connectedLeAudioDevices != null && !connectedLeAudioDevices.isEmpty()))) { || (connectedLeAudioDevices != null && !connectedLeAudioDevices.isEmpty()))) {
activeDevice = findActiveDevice(); activeDevice = findActiveDevice();
} }
mPreference.setTitle(mContext.getString(R.string.media_output_label_title,
com.android.settings.Utils.getApplicationLabel(mContext, if (mMediaController == null) {
mMediaController.getPackageName()))); mPreference.setTitle(mContext.getString(R.string.media_output_title_without_playing));
} else {
mPreference.setTitle(mContext.getString(R.string.media_output_label_title,
com.android.settings.Utils.getApplicationLabel(mContext,
mMediaController.getPackageName())));
}
mPreference.setSummary((activeDevice == null) ? mPreference.setSummary((activeDevice == null) ?
mContext.getText(R.string.media_output_default_summary) : mContext.getText(R.string.media_output_default_summary) :
activeDevice.getAlias()); activeDevice.getAlias());
@@ -145,13 +159,19 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro
@Override @Override
public boolean handlePreferenceTreeClick(Preference preference) { public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) { if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
mContext.sendBroadcast(new Intent() if (enableOutputSwitcherForSystemRouting() && mMediaController == null) {
.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG) mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME) .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME));
mMediaController.getPackageName()) } else if (mMediaController != null) {
.putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN, mContext.sendBroadcast(new Intent()
mMediaController.getSessionToken())); .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
mMediaController.getPackageName())
.putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
mMediaController.getSessionToken()));
}
return true; return true;
} }
return false; return false;

View File

@@ -18,6 +18,7 @@
package com.android.settings.media; package com.android.settings.media;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +39,7 @@ import android.media.session.MediaController;
import android.media.session.MediaSession; import android.media.session.MediaSession;
import android.net.Uri; import android.net.Uri;
import android.os.Process; import android.os.Process;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.slice.Slice; import androidx.slice.Slice;
@@ -55,6 +57,7 @@ import com.android.settingslib.media.MediaOutputConstants;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -96,6 +99,9 @@ public class MediaOutputIndicatorSliceTest {
@Mock @Mock
private Drawable mTestDrawable; private Drawable mTestDrawable;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext; private Context mContext;
private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice; private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice;
private AudioManager mAudioManager; private AudioManager mAudioManager;
@@ -254,6 +260,34 @@ public class MediaOutputIndicatorSliceTest {
MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN) == null).isTrue(); MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN) == null).isTrue();
} }
@Test
public void onNotifyChange_withoutMediaControllerFlagEnabled_verifyIntentExtra() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
doReturn(null).when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
mMediaOutputIndicatorSlice.onNotifyChange(null);
verify(mContext, times(2)).sendBroadcast(argument.capture());
List<Intent> intentList = argument.getAllValues();
Intent intent = intentList.get(0);
assertThat(intent.getAction()).isEqualTo(
MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
assertThat(TextUtils.equals(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME,
intent.getPackage())).isTrue();
}
@Test
public void onNotifyChange_withoutMediaControllerFlagDisabled_doNothing() {
mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
doReturn(null).when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
mMediaOutputIndicatorSlice.onNotifyChange(null);
}
@Test @Test
public void isVisible_allConditionMatched_returnTrue() { public void isVisible_allConditionMatched_returnTrue() {
mAudioManager.setMode(AudioManager.MODE_NORMAL); mAudioManager.setMode(AudioManager.MODE_NORMAL);
@@ -268,6 +302,7 @@ public class MediaOutputIndicatorSliceTest {
@Test @Test
public void isVisible_noActiveSession_returnFalse() { public void isVisible_noActiveSession_returnFalse() {
mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mAudioManager.setMode(AudioManager.MODE_NORMAL); mAudioManager.setMode(AudioManager.MODE_NORMAL);
mDevices.add(mDevice1); mDevices.add(mDevice1);
@@ -278,6 +313,19 @@ public class MediaOutputIndicatorSliceTest {
assertThat(mMediaOutputIndicatorSlice.isVisible()).isFalse(); assertThat(mMediaOutputIndicatorSlice.isVisible()).isFalse();
} }
@Test
public void isVisible_noActiveSession_returnTrue() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mDevices.add(mDevice1);
when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
assertThat(mMediaOutputIndicatorSlice.isVisible()).isTrue();
}
private void initPackage() { private void initPackage() {
mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
mAppInfo = new ApplicationInfo(); mAppInfo = new ApplicationInfo();

View File

@@ -21,10 +21,12 @@ import static android.media.AudioSystem.STREAM_MUSIC;
import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -38,6 +40,8 @@ import android.content.Context;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.session.MediaSessionManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.FeatureFlagUtils; import android.util.FeatureFlagUtils;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
@@ -61,6 +65,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -113,6 +118,9 @@ public class AudioOutputSwitchPreferenceControllerTest {
@Mock @Mock
private CachedBluetoothDevice mCachedBluetoothDeviceR; private CachedBluetoothDevice mCachedBluetoothDeviceR;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext; private Context mContext;
private PreferenceScreen mScreen; private PreferenceScreen mScreen;
private ListPreference mPreference; private ListPreference mPreference;
@@ -238,6 +246,7 @@ public class AudioOutputSwitchPreferenceControllerTest {
@Test @Test
public void onStart_shouldRegisterCallbackAndRegisterReceiver() { public void onStart_shouldRegisterCallbackAndRegisterReceiver() {
mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mController.onStart(); mController.onStart();
verify(mLocalBluetoothManager.getEventManager()).registerCallback( verify(mLocalBluetoothManager.getEventManager()).registerCallback(
@@ -248,6 +257,7 @@ public class AudioOutputSwitchPreferenceControllerTest {
@Test @Test
public void onStop_shouldUnregisterCallbackAndUnregisterReceiver() { public void onStop_shouldUnregisterCallbackAndUnregisterReceiver() {
mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mController.onStart(); mController.onStart();
mController.onStop(); mController.onStop();
@@ -257,6 +267,45 @@ public class AudioOutputSwitchPreferenceControllerTest {
verify(mLocalBluetoothManager).setForegroundActivity(null); verify(mLocalBluetoothManager).setForegroundActivity(null);
} }
@Test
public void onStart_shouldRegisterCallbackAndRegisterReceiverWithDefaultMediaOutput() {
MediaSessionManager mediaSessionManager =
spy(mContext.getSystemService(MediaSessionManager.class));
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager);
mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY);
mController.onStart();
verify(mLocalBluetoothManager.getEventManager()).registerCallback(
any(BluetoothCallback.class));
verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
eq(Context.RECEIVER_NOT_EXPORTED));
verify(mLocalBluetoothManager).setForegroundActivity(mContext);
verify(mediaSessionManager).addOnActiveSessionsChangedListener(
any(MediaSessionManager.OnActiveSessionsChangedListener.class), any(), any());
}
@Test
public void onStop_shouldUnregisterCallbackAndUnregisterReceiverWithDefaultMediaOutput() {
MediaSessionManager mediaSessionManager =
spy(mContext.getSystemService(MediaSessionManager.class));
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager);
mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY);
mController.onStart();
mController.onStop();
verify(mLocalBluetoothManager.getEventManager()).unregisterCallback(
any(BluetoothCallback.class));
verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
verify(mLocalBluetoothManager).setForegroundActivity(null);
verify(mediaSessionManager).removeOnActiveSessionsChangedListener(
any(MediaSessionManager.OnActiveSessionsChangedListener.class));
}
/** /**
* Audio stream output to bluetooth sco headset which is the subset of all sco device. * Audio stream output to bluetooth sco headset which is the subset of all sco device.
* isStreamFromOutputDevice should return true. * isStreamFromOutputDevice should return true.

View File

@@ -21,6 +21,8 @@ import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
import static android.media.AudioSystem.DEVICE_OUT_EARPIECE; import static android.media.AudioSystem.DEVICE_OUT_EARPIECE;
import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID; import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@@ -45,6 +47,7 @@ import android.media.VolumeProvider;
import android.media.session.MediaController; import android.media.session.MediaController;
import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState; import android.media.session.PlaybackState;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@@ -66,6 +69,7 @@ import com.android.settingslib.media.MediaOutputConstants;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -103,6 +107,9 @@ public class MediaOutputPreferenceControllerTest {
private static final String TEST_PACKAGE_NAME = "com.test.packagename"; private static final String TEST_PACKAGE_NAME = "com.test.packagename";
private static final String TEST_APPLICATION_LABEL = "APP Test Label"; private static final String TEST_APPLICATION_LABEL = "APP Test Label";
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock @Mock
private LocalBluetoothManager mLocalManager; private LocalBluetoothManager mLocalManager;
@Mock @Mock
@@ -227,6 +234,8 @@ public class MediaOutputPreferenceControllerTest {
mScreen.addPreference(mPreference); mScreen.addPreference(mPreference);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.setCallback(mAudioSwitchPreferenceCallback); mController.setCallback(mAudioSwitchPreferenceCallback);
mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
} }
@After @After
@@ -314,6 +323,7 @@ public class MediaOutputPreferenceControllerTest {
@Test @Test
public void updateState_noActiveLocalPlayback_noTitle() { public void updateState_noActiveLocalPlayback_noTitle() {
mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mPlaybackState = new PlaybackState.Builder() mPlaybackState = new PlaybackState.Builder()
.setState(PlaybackState.STATE_NONE, 0, 1) .setState(PlaybackState.STATE_NONE, 0, 1)
.build(); .build();
@@ -325,6 +335,48 @@ public class MediaOutputPreferenceControllerTest {
assertThat(mPreference.getTitle()).isNull(); assertThat(mPreference.getTitle()).isNull();
} }
@Test
public void updateState_noActiveLocalPlayback_checkTitle() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mPlaybackState = new PlaybackState.Builder()
.setState(PlaybackState.STATE_NONE, 0, 1)
.build();
when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
mController.displayPreference(mScreen);
mController.updateState(mPreference);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.media_output_title_without_playing,
TEST_APPLICATION_LABEL));
}
@Test
public void updateState_withNullMediaController_noTitle() {
mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mMediaControllers.clear();
mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
mController.updateState(mPreference);
assertThat(mPreference.getTitle()).isNull();
}
@Test
public void updateState_withNullMediaController_checkTitle() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mMediaControllers.clear();
mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
mController.displayPreference(mScreen);
mController.updateState(mPreference);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.media_output_title_without_playing,
TEST_APPLICATION_LABEL));
}
@Test @Test
public void updateState_withActiveLocalPlayback_checkTitle() { public void updateState_withActiveLocalPlayback_checkTitle() {
initPackage(); initPackage();
@@ -349,6 +401,39 @@ public class MediaOutputPreferenceControllerTest {
.isEqualTo(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG); .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
} }
@Test
public void handlePreferenceTreeClick_WithNoLocalPlaybackFlagEnabled_verifyIntentExtra() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
mPlaybackState = new PlaybackState.Builder()
.setState(PlaybackState.STATE_NONE, 0, 1)
.build();
when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
mPreference.setKey(TEST_KEY);
mController.handlePreferenceTreeClick(mPreference);
verify(mContext).sendBroadcast(intentCaptor.capture());
assertThat(intentCaptor.getValue().getAction())
.isEqualTo(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
}
@Test
public void handlePreferenceTreeClick_WithNullControllerFlagEnabled_verifyIntentExtra() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
mMediaControllers.clear();
mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
mPreference.setKey(TEST_KEY);
mController.handlePreferenceTreeClick(mPreference);
verify(mContext).sendBroadcast(intentCaptor.capture());
assertThat(intentCaptor.getValue().getAction())
.isEqualTo(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
}
/** /**
* Default status * Default status
* Preference should be invisible * Preference should be invisible