Merge "[Le Audio] Add a new BroadcastDialog in SystemUI from MediaPanel" into tm-qpr-dev

This commit is contained in:
Zoey Chen
2022-05-30 02:54:02 +00:00
committed by Android (Google) Code Review
9 changed files with 391 additions and 69 deletions

View File

@@ -70,6 +70,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.bluetooth.BroadcastDialogController;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -188,6 +189,11 @@ public class MediaControlPanel {
private final SeekBarViewModel.EnabledChangeListener mEnabledChangeListener =
this::setIsSeekBarEnabled;
private final BroadcastDialogController mBroadcastDialogController;
private boolean mIsCurrentBroadcastedApp = false;
private boolean mShowBroadcastDialogButton = false;
private String mSwitchBroadcastApp;
/**
* Initialize a new control panel
*
@@ -213,7 +219,8 @@ public class MediaControlPanel {
MediaUiEventLogger logger,
KeyguardStateController keyguardStateController,
ActivityIntentHelper activityIntentHelper,
NotificationLockscreenUserManager lockscreenUserManager) {
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDialogController broadcastDialogController) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mMainExecutor = mainExecutor;
@@ -230,6 +237,7 @@ public class MediaControlPanel {
mKeyguardStateController = keyguardStateController;
mActivityIntentHelper = activityIntentHelper;
mLockscreenUserManager = lockscreenUserManager;
mBroadcastDialogController = broadcastDialogController;
mSeekBarViewModel.setLogSeek(() -> {
if (mPackageName != null && mInstanceId != null) {
@@ -449,7 +457,10 @@ public class MediaControlPanel {
final MediaController controller = getController();
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
bindOutputSwitcherChip(data);
// Show the broadcast dialog button only when the le audio is enabled.
mShowBroadcastDialogButton =
data.getDevice() != null && data.getDevice().getShowBroadcastButton();
bindOutputSwitcherAndBroadcastButton(mShowBroadcastDialogButton, data);
bindGutsMenuForPlayer(data);
bindPlayerContentDescription(data);
bindScrubbingTime(data);
@@ -467,21 +478,40 @@ public class MediaControlPanel {
Trace.endSection();
}
private void bindOutputSwitcherChip(MediaData data) {
// Output switcher chip
private void bindOutputSwitcherAndBroadcastButton(boolean showBroadcastButton, MediaData data) {
ViewGroup seamlessView = mMediaViewHolder.getSeamless();
seamlessView.setVisibility(View.VISIBLE);
ImageView iconView = mMediaViewHolder.getSeamlessIcon();
TextView deviceName = mMediaViewHolder.getSeamlessText();
final MediaDeviceData device = data.getDevice();
// Disable clicking on output switcher for invalid devices and resumption controls
final boolean seamlessDisabled = (device != null && !device.getEnabled())
|| data.getResumption();
final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha);
seamlessView.setEnabled(!seamlessDisabled);
CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device);
final boolean enabled;
final boolean seamlessDisabled;
final int iconResource;
CharSequence deviceString;
if (showBroadcastButton) {
// TODO(b/233698402): Use the package name instead of app label to avoid the
// unexpected result.
mIsCurrentBroadcastedApp = device != null
&& TextUtils.equals(device.getName(),
MediaDataUtils.getAppLabel(mContext, mPackageName, mContext.getString(
R.string.bt_le_audio_broadcast_dialog_unknown_name)));
seamlessDisabled = !mIsCurrentBroadcastedApp;
// Always be enabled if the broadcast button is shown
enabled = true;
deviceString = mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name);
iconResource = R.drawable.settings_input_antenna;
} else {
// Disable clicking on output switcher for invalid devices and resumption controls
seamlessDisabled = (device != null && !device.getEnabled()) || data.getResumption();
enabled = !seamlessDisabled;
deviceString = mContext.getString(R.string.media_seamless_other_device);
iconResource = R.drawable.ic_media_home_devices;
}
mMediaViewHolder.getSeamlessButton().setAlpha(seamlessDisabled ? DISABLED_ALPHA : 1.0f);
seamlessView.setEnabled(enabled);
if (device != null) {
Drawable icon = device.getIcon();
if (icon instanceof AdaptiveIcon) {
@@ -494,7 +524,7 @@ public class MediaControlPanel {
deviceString = device.getName();
} else {
// Set to default icon
iconView.setImageResource(R.drawable.ic_media_home_devices);
iconView.setImageResource(iconResource);
}
deviceName.setText(deviceString);
seamlessView.setContentDescription(deviceString);
@@ -503,21 +533,39 @@ public class MediaControlPanel {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
if (device.getIntent() != null) {
if (device.getIntent().isActivity()) {
mActivityStarter.startActivity(
device.getIntent().getIntent(), true);
if (showBroadcastButton) {
// If the current media app is not broadcasted and users press the outputer
// button, we should pop up the broadcast dialog to check do they want to
// switch broadcast to the other media app, otherwise we still pop up the
// media output dialog.
if (!mIsCurrentBroadcastedApp) {
mLogger.logOpenBroadcastDialog(mUid, mPackageName, mInstanceId);
mSwitchBroadcastApp = device.getName().toString();
mBroadcastDialogController.createBroadcastDialog(mSwitchBroadcastApp,
mPackageName, true, mMediaViewHolder.getSeamlessButton());
} else {
try {
device.getIntent().send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Device pending intent was canceled");
}
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
mMediaOutputDialogFactory.create(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
} else {
mMediaOutputDialogFactory.create(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
if (device.getIntent() != null) {
if (device.getIntent().isActivity()) {
mActivityStarter.startActivity(
device.getIntent().getIntent(), true);
} else {
try {
device.getIntent().send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Device pending intent was canceled");
}
}
} else {
mMediaOutputDialogFactory.create(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
}
});
}

View File

@@ -215,5 +215,8 @@ data class MediaDeviceData
val intent: PendingIntent? = null,
/** Unique id for this device */
val id: String? = null
val id: String? = null,
/** Whether or not to show the broadcast button */
val showBroadcastButton: Boolean
)

View File

@@ -686,7 +686,8 @@ class MediaDataManager(
val enabled = deviceIntent != null && deviceIntent.isActivity
val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon)
.loadDrawable(sbn.getPackageContext(context))
device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent)
device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent,
showBroadcastButton = false)
}
}

View File

@@ -16,15 +16,23 @@
package com.android.systemui.media
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
import android.content.Context
import android.graphics.drawable.Drawable
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import android.text.TextUtils
import android.util.Log
import androidx.annotation.AnyThread
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -36,16 +44,20 @@ import java.util.concurrent.Executor
import javax.inject.Inject
private const val PLAYBACK_TYPE_UNKNOWN = 0
private const val TAG = "MediaDeviceManager"
private const val DEBUG = true
/**
* Provides information about the route (ie. device) where playback is occurring.
*/
class MediaDeviceManager @Inject constructor(
private val context: Context,
private val controllerFactory: MediaControllerFactory,
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
private val configurationController: ConfigurationController,
private val localBluetoothManager: LocalBluetoothManager?,
@Main private val fgExecutor: Executor,
@Background private val bgExecutor: Executor,
dumpManager: DumpManager
@@ -147,7 +159,8 @@ class MediaDeviceManager @Inject constructor(
val controller: MediaController?,
val localMediaManager: LocalMediaManager,
val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
) : LocalMediaManager.DeviceCallback, MediaController.Callback(),
BluetoothLeBroadcast.Callback {
val token
get() = controller?.sessionToken
@@ -166,7 +179,7 @@ class MediaDeviceManager @Inject constructor(
// A device that is not yet connected but is expected to connect imminently. Because it's
// expected to connect imminently, it should be displayed as the current device.
private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
private var broadcastDescription: String? = null
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onLocaleListChanged() {
updateCurrent()
@@ -238,7 +251,11 @@ class MediaDeviceManager @Inject constructor(
) {
aboutToConnectDeviceOverride = AboutToConnectDevice(
fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
backupMediaDeviceData = MediaDeviceData(enabled = true, deviceIcon, deviceName)
backupMediaDeviceData = MediaDeviceData(
/* enabled */ enabled = true,
/* icon */ deviceIcon,
/* name */ deviceName,
/* showBroadcastButton */ showBroadcastButton = false)
)
updateCurrent()
}
@@ -248,23 +265,127 @@ class MediaDeviceManager @Inject constructor(
updateCurrent()
}
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastStarted(), reason = $reason , broadcastId = $broadcastId")
}
updateCurrent()
}
override fun onBroadcastStartFailed(reason: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastStartFailed(), reason = $reason")
}
}
override fun onBroadcastMetadataChanged(broadcastId: Int,
metadata: BluetoothLeBroadcastMetadata) {
if (DEBUG) {
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
"metadata = $metadata")
}
updateCurrent()
}
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastStopped(), reason = $reason , broadcastId = $broadcastId")
}
updateCurrent()
}
override fun onBroadcastStopFailed(reason: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastStopFailed(), reason = $reason")
}
}
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastUpdated(), reason = $reason , broadcastId = $broadcastId")
}
updateCurrent()
}
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastUpdateFailed(), reason = $reason , " +
"broadcastId = $broadcastId")
}
}
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
@WorkerThread
private fun updateCurrent() {
val aboutToConnect = aboutToConnectDeviceOverride
if (aboutToConnect != null &&
aboutToConnect.fullMediaDevice == null &&
aboutToConnect.backupMediaDeviceData != null) {
if (isLeAudioBroadcastEnabled()) {
current = MediaDeviceData(
/* enabled */ true,
/* icon */ context.getDrawable(R.drawable.settings_input_antenna),
/* name */ broadcastDescription,
/* intent */ null,
/* showBroadcastButton */ showBroadcastButton = true)
} else {
val aboutToConnect = aboutToConnectDeviceOverride
if (aboutToConnect != null &&
aboutToConnect.fullMediaDevice == null &&
aboutToConnect.backupMediaDeviceData != null) {
// Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
current = aboutToConnect.backupMediaDeviceData
return
}
val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
}
val device = aboutToConnect?.fullMediaDevice
?: localMediaManager.currentConnectedDevice
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
// If we have a controller but get a null route, then don't trust the device
val enabled = device != null && (controller == null || route != null)
val name = route?.name?.toString() ?: device?.name
current = MediaDeviceData(enabled, device?.iconWithoutBackground, name, id = device?.id)
// If we have a controller but get a null route, then don't trust the device
val enabled = device != null && (controller == null || route != null)
val name = route?.name?.toString() ?: device?.name
current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
id = device?.id, showBroadcastButton = false)
}
}
private fun isLeAudioBroadcastEnabled(): Boolean {
if (localBluetoothManager != null) {
val profileManager = localBluetoothManager.profileManager
if (profileManager != null) {
val bluetoothLeBroadcast = profileManager.leAudioBroadcastProfile
if (bluetoothLeBroadcast != null && bluetoothLeBroadcast.isEnabled(null)) {
getBroadcastingInfo(bluetoothLeBroadcast)
return true
} else if (DEBUG) {
Log.d(TAG, "Can not get LocalBluetoothLeBroadcast")
}
} else if (DEBUG) {
Log.d(TAG, "Can not get LocalBluetoothProfileManager")
}
} else if (DEBUG) {
Log.d(TAG, "Can not get LocalBluetoothManager")
}
return false
}
private fun getBroadcastingInfo(bluetoothLeBroadcast: LocalBluetoothLeBroadcast) {
var currentBroadcastedApp = bluetoothLeBroadcast.appSourceName
// TODO(b/233698402): Use the package name instead of app label to avoid the
// unexpected result.
// Check the current media app's name is the same with current broadcast app's name
// or not.
var mediaApp = MediaDataUtils.getAppLabel(
context, localMediaManager.packageName,
context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name))
var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
if (isCurrentBroadcastedApp) {
broadcastDescription = context.getString(
R.string.broadcasting_description_is_broadcasting)
} else {
broadcastDescription = currentBroadcastedApp
}
}
}
}

View File

@@ -176,6 +176,11 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName,
instanceId)
}
fun logOpenBroadcastDialog(uid: Int, packageName: String, instanceId: InstanceId) {
logger.logWithInstanceId(MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG, uid, packageName,
instanceId)
}
}
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -273,7 +278,10 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
MEDIA_RECOMMENDATION_ITEM_TAP(1044),
@UiEvent(doc = "User tapped on a media recommendation card")
MEDIA_RECOMMENDATION_CARD_TAP(1045);
MEDIA_RECOMMENDATION_CARD_TAP(1045),
@UiEvent(doc = "User opened the broadcast dialog from a media control")
MEDIA_OPEN_BROADCAST_DIALOG(1079);
override fun getId() = metricId
}

View File

@@ -58,6 +58,7 @@ import com.android.internal.logging.InstanceId
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
import com.android.systemui.media.dialog.MediaOutputDialogFactory
@@ -104,6 +105,7 @@ private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
private const val REC_APP_NAME = "REC APP NAME"
private const val APP_NAME = "APP_NAME"
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -130,6 +132,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var transitionParent: ViewGroup
@Mock private lateinit var broadcastDialogController: BroadcastDialogController
private lateinit var appIcon: ImageView
@Mock private lateinit var albumView: ImageView
private lateinit var titleText: TextView
@@ -160,8 +163,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var dismissText: TextView
private lateinit var session: MediaSession
private val device = MediaDeviceData(true, null, DEVICE_NAME)
private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME)
private lateinit var device: MediaDeviceData
private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null,
showBroadcastButton = false)
private lateinit var mediaData: MediaData
private val clock = FakeSystemClock()
@Mock private lateinit var logger: MediaUiEventLogger
@@ -187,6 +191,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var recSubtitle1: TextView
private lateinit var recSubtitle2: TextView
private lateinit var recSubtitle3: TextView
private var shouldShowBroadcastButton: Boolean = false
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -223,7 +228,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
logger,
keyguardStateController,
activityIntentHelper,
lockscreenUserManager) {
lockscreenUserManager,
broadcastDialogController) {
override fun loadAnimator(
animId: Int,
otionInterpolator: Interpolator,
@@ -236,28 +242,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
initGutsViewHolderMocks()
initMediaViewHolderMocks()
// Create media session
val metadataBuilder = MediaMetadata.Builder().apply {
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
val playbackBuilder = PlaybackState.Builder().apply {
setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
setActions(PlaybackState.ACTION_PLAY)
}
session = MediaSession(context, SESSION_KEY).apply {
setMetadata(metadataBuilder.build())
setPlaybackState(playbackBuilder.build())
}
session.setActive(true)
mediaData = MediaTestUtils.emptyMediaData.copy(
artist = ARTIST,
song = TITLE,
packageName = PACKAGE,
token = session.sessionToken,
device = device,
instanceId = instanceId)
initDeviceMediaData(false, DEVICE_NAME)
// Set up recommendation view
initRecommendationViewHolderMocks()
@@ -293,6 +278,34 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(gutsViewHolder.dismissText).thenReturn(dismissText)
}
private fun initDeviceMediaData(shouldShowBroadcastButton: Boolean, name: String) {
device = MediaDeviceData(true, null, name, null,
showBroadcastButton = shouldShowBroadcastButton)
// Create media session
val metadataBuilder = MediaMetadata.Builder().apply {
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
val playbackBuilder = PlaybackState.Builder().apply {
setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
setActions(PlaybackState.ACTION_PLAY)
}
session = MediaSession(context, SESSION_KEY).apply {
setMetadata(metadataBuilder.build())
setPlaybackState(playbackBuilder.build())
}
session.setActive(true)
mediaData = MediaTestUtils.emptyMediaData.copy(
artist = ARTIST,
song = TITLE,
packageName = PACKAGE,
token = session.sessionToken,
device = device,
instanceId = instanceId)
}
/**
* Initialize elements in media view holder
*/
@@ -1032,6 +1045,28 @@ public class MediaControlPanelTest : SysuiTestCase() {
assertThat(seamless.isEnabled()).isFalse()
}
@Test
fun bindBroadcastButton() {
initMediaViewHolderMocks()
initDeviceMediaData(true, APP_NAME)
val mockAvd0 = mock(AnimatedVectorDrawable::class.java)
whenever(mockAvd0.mutate()).thenReturn(mockAvd0)
val semanticActions0 = MediaButton(
playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)
)
val state = mediaData.copy(resumption = true, semanticActions = semanticActions0,
isPlaying = false)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
assertThat(seamlessText.getText()).isEqualTo(APP_NAME)
assertThat(seamless.isEnabled()).isTrue()
seamless.callOnClick()
verify(logger).logOpenBroadcastDialog(anyInt(), eq(PACKAGE), eq(instanceId))
}
/* ***** Guts tests for the player ***** */
@Test

View File

@@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
InstanceId.fakeInstanceId(-1), -1);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
@Test

View File

@@ -16,6 +16,10 @@
package com.android.systemui.media
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.media.MediaRouter2Manager
import android.media.RoutingSessionInfo
@@ -25,8 +29,12 @@ import android.media.session.MediaSession
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
@@ -42,6 +50,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
@@ -57,6 +66,8 @@ private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
private const val DEVICE_NAME = "DEVICE_NAME"
private const val REMOTE_DEVICE_NAME = "REMOTE_DEVICE_NAME"
private const val BROADCAST_APP_NAME = "BROADCAST_APP_NAME"
private const val NORMAL_APP_NAME = "NORMAL_APP_NAME"
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -80,6 +91,12 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Mock private lateinit var controller: MediaController
@Mock private lateinit var playbackInfo: PlaybackInfo
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var bluetoothLeBroadcast: BluetoothLeBroadcast
@Mock private lateinit var localBluetoothProfileManager: LocalBluetoothProfileManager
@Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var applicationInfo: ApplicationInfo
private lateinit var localBluetoothManager: LocalBluetoothManager
private lateinit var session: MediaSession
private lateinit var mediaData: MediaData
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -88,12 +105,15 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fun setUp() {
fakeFgExecutor = FakeExecutor(FakeSystemClock())
fakeBgExecutor = FakeExecutor(FakeSystemClock())
localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java)
manager = MediaDeviceManager(
context,
controllerFactory,
lmmFactory,
mr2,
muteAwaitFactory,
configurationController,
localBluetoothManager,
fakeFgExecutor,
fakeBgExecutor,
dumpster
@@ -116,6 +136,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
token = session.sessionToken)
whenever(controllerFactory.create(session.sessionToken))
.thenReturn(controller)
setupLeAudioConfiguration(false)
}
@After
@@ -459,7 +480,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Test
fun testRemotePlaybackDeviceOverride() {
whenever(route.name).thenReturn(DEVICE_NAME)
val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null)
val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null,
showBroadcastButton = false)
val mediaDataWithDevice = mediaData.copy(device = deviceData)
// GIVEN media data that already has a device set
@@ -474,12 +496,95 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
verify(lmm, never()).registerCallback(any())
}
@Test
fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(true)
setupBroadcastPackage(BROADCAST_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
val data = captureDeviceData(KEY)
assertThat(data.showBroadcastButton).isTrue()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(context.getString(
R.string.broadcasting_description_is_broadcasting))
}
@Test
fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(true)
setupBroadcastPackage(NORMAL_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
val data = captureDeviceData(KEY)
assertThat(data.showBroadcastButton).isTrue()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(BROADCAST_APP_NAME)
}
@Test
fun onBroadcastStopped_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(false)
broadcastCallback.onBroadcastStopped(1, 1)
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
val data = captureDeviceData(KEY)
assertThat(data.showBroadcastButton).isFalse()
}
fun captureCallback(): LocalMediaManager.DeviceCallback {
val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
verify(lmm).registerCallback(captor.capture())
return captor.getValue()
}
fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback {
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
override fun onBroadcastStartFailed(reason: Int) {}
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
override fun onBroadcastStopFailed(reason: Int) {}
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
override fun onBroadcastMetadataChanged(broadcastId: Int,
metadata: BluetoothLeBroadcastMetadata) {}
}
bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback)
return callback;
}
fun setupLeAudioConfiguration(isLeAudio: Boolean) {
whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
.thenReturn(localBluetoothLeBroadcast)
whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio)
whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
}
fun setupBroadcastPackage(currentName: String) {
whenever(lmm.packageName).thenReturn(PACKAGE)
whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
.thenReturn(applicationInfo)
whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName)
context.setMockPackageManager(packageManager)
}
fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())

View File

@@ -20,7 +20,8 @@ class MediaTestUtils {
device = null,
active = true,
resumeAction = null,
isPlaying = false,
instanceId = InstanceId.fakeInstanceId(-1),
appUid = -1)
}
}
}