diff options
| author | 2022-05-30 02:54:02 +0000 | |
|---|---|---|
| committer | 2022-05-30 02:54:02 +0000 | |
| commit | d13e721d2eb22ec90acff60c6591cbdbfb87aa1e (patch) | |
| tree | e8ed57b3cff54c5117c0708314d219fcaf41868e | |
| parent | b1f4e4cf8108a06af86051a3d1ae2f7d8f827904 (diff) | |
| parent | 7c86f9eff629a7a7f3f1fdbd2f82ef77f7995188 (diff) | |
Merge "[Le Audio] Add a new BroadcastDialog in SystemUI from MediaPanel" into tm-qpr-dev
9 files changed, 390 insertions, 68 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 972e93b886b8..d8bde551b7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -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()); + } } }); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 360f86548e13..b2ab12a81bcc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -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 ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 6a69d427929e..30ba476abce2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -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) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index 8558859638d5..b552d9fb584a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -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) } + + // 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) } - 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) + 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 + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt index 52f5cc568ba4..0baf01e7476f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt @@ -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 }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 241ed2443e6c..540f2a534854 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 3e335c5163a9..04b93d79f83b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 18ee79138b52..11fe87d363a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -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()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt index ae58fe67da23..3d9ed5fe7f7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt @@ -20,7 +20,8 @@ class MediaTestUtils { device = null, active = true, resumeAction = null, + isPlaying = false, instanceId = InstanceId.fakeInstanceId(-1), appUid = -1) } -}
\ No newline at end of file +} |