Loading packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +14 −0 Original line number Diff line number Diff line Loading @@ -317,6 +317,12 @@ public class LocalMediaManager implements BluetoothCallback { } } void dispatchOnSuggestedDeviceUpdated(@Nullable SuggestedDeviceState device) { for (DeviceCallback callback : getCallbacks()) { callback.onSuggestedDeviceUpdated(device); } } /** * Dispatch a change in the about-to-connect device. See * {@link DeviceCallback#onAboutToConnectDeviceAdded} for more information. Loading Loading @@ -746,6 +752,11 @@ public class LocalMediaManager implements BluetoothCallback { } dispatchOnRequestFailed(reason); } @Override public void onSuggestedDeviceUpdated(@Nullable SuggestedDeviceState device) { dispatchOnSuggestedDeviceUpdated(device); } } private void unRegisterDeviceAttributeChangeCallback() { Loading Loading @@ -823,6 +834,9 @@ public class LocalMediaManager implements BluetoothCallback { * Callback for notifying that we no longer have an about-to-connect device. */ default void onAboutToConnectDeviceRemoved() {} /** Callback for notifying that the suggested device has been updated. */ default void onSuggestedDeviceUpdated(@Nullable SuggestedDeviceState device) {} } /** Loading packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java +83 −6 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import com.android.internal.logging.InstanceId; import com.android.systemui.SysuiTestCase; import com.android.systemui.media.controls.shared.model.MediaData; import com.android.systemui.media.controls.shared.model.MediaDeviceData; import com.android.systemui.media.controls.shared.model.SuggestedMediaDeviceData; import org.junit.Before; import org.junit.Rule; Loading Loading @@ -68,17 +69,44 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { private MediaData mMediaData; private MediaDeviceData mDeviceData; @Mock private SuggestedMediaDeviceData mSuggestedDeviceData; @Before public void setUp() { mManager = new MediaDataCombineLatest(); mManager.addListener(mListener); mMediaData = new MediaData( USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L, InstanceId.fakeInstanceId(-1), -1, false, null); mMediaData = new MediaData( USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L, InstanceId.fakeInstanceId(-1), -1, false, null); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false); } Loading Loading @@ -111,7 +139,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { } @Test public void emitEventAfterMediaFirst() { public void emitEventAfterMediaFirstAndMediaDeviceChangedSecond() { // GIVEN that media event has already been received mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */); // WHEN device event is received Loading @@ -122,6 +150,21 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void emitEventAfterMediaFirstAndMediaDeviceChangedSecondAndSuggestionChangedThird() { // GIVEN that media event has already been received mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */); mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); reset(mListener); // WHEN suggestion event is received mManager.onSuggestedMediaDeviceChanged(KEY, null, mSuggestedDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener) .onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean()); assertThat(captor.getValue().getSuggestedDevice()).isNotNull(); } @Test public void migrateKeyMediaFirst() { // GIVEN that media and device info has already been received Loading Loading @@ -150,6 +193,23 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void migrateKeySuggestionFirst() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */); mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); mManager.onSuggestedMediaDeviceChanged(OLD_KEY, null, mSuggestedDeviceData); reset(mListener); // WHEN a key migration event is received mManager.onSuggestedMediaDeviceChanged(KEY, OLD_KEY, mSuggestedDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener) .onMediaDataLoaded( eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean()); assertThat(captor.getValue().getSuggestedDevice()).isNotNull(); } @Test public void migrateKeyMediaAfter() { // GIVEN that media and device info has already been received Loading Loading @@ -180,6 +240,23 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void migrateKeySuggestionAfter() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */); mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); mManager.onSuggestedMediaDeviceChanged(OLD_KEY, null, mSuggestedDeviceData); mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */); reset(mListener); // WHEN a second key migration event is received for the device mManager.onSuggestedMediaDeviceChanged(KEY, OLD_KEY, mSuggestedDeviceData); // THEN the key has already be migrated ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener) .onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean()); assertThat(captor.getValue().getSuggestedDevice()).isNotNull(); } @Test public void mediaDataRemoved() { // WHEN media data is removed without first receiving device or data Loading packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt +42 −8 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.media.controls.domain.pipeline import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.shared.model.SuggestedMediaDeviceData import javax.inject.Inject /** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */ Loading @@ -25,7 +26,9 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, MediaDeviceManager.Listener { private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf() private val entries: MutableMap<String, Triple<MediaData?, MediaDeviceData?, SuggestedMediaDeviceData?>> = mutableMapOf() override fun onMediaDataLoaded( key: String, Loading @@ -34,10 +37,16 @@ class MediaDataCombineLatest @Inject constructor() : immediately: Boolean, ) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = data to entries.remove(oldKey)?.second val previousEntry = entries.remove(oldKey) val (mediaDeviceData, suggestedMediaDeviceData) = previousEntry?.second to previousEntry?.third entries[key] = Triple(data, mediaDeviceData, suggestedMediaDeviceData) update(key, oldKey) } else { entries[key] = data to entries[key]?.second val previousEntry = entries[key] val (mediaDeviceData, suggestedMediaDeviceData) = previousEntry?.second to previousEntry?.third entries[key] = Triple(data, mediaDeviceData, suggestedMediaDeviceData) update(key, key) } } Loading @@ -48,10 +57,32 @@ class MediaDataCombineLatest @Inject constructor() : override fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = entries.remove(oldKey)?.first to data val previousEntry = entries.remove(oldKey) val (mediaData, suggestedMediaDeviceData) = previousEntry?.first to previousEntry?.third entries[key] = Triple(mediaData, data, suggestedMediaDeviceData) update(key, oldKey) } else { entries[key] = entries[key]?.first to data val previousEntry = entries[key] val (mediaData, suggestedMediaDeviceData) = previousEntry?.first to previousEntry?.third entries[key] = Triple(mediaData, data, suggestedMediaDeviceData) update(key, key) } } override fun onSuggestedMediaDeviceChanged( key: String, oldKey: String?, data: SuggestedMediaDeviceData?, ) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { val previousEntry = entries.remove(oldKey) val (mediaData, mediaDeviceData) = previousEntry?.first to previousEntry?.second entries[key] = Triple(mediaData, mediaDeviceData, data) update(key, oldKey) } else { val previousEntry = entries[key] val (mediaData, mediaDeviceData) = previousEntry?.first to previousEntry?.second entries[key] = Triple(mediaData, mediaDeviceData, data) update(key, key) } } Loading @@ -69,9 +100,12 @@ class MediaDataCombineLatest @Inject constructor() : fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) private fun update(key: String, oldKey: String?) { val (entry, device) = entries[key] ?: null to null if (entry != null && device != null) { val data = entry.copy(device = device) val mediaData = entries[key]?.first val mediaDeviceData = entries[key]?.second val suggestedMediaDeviceData = entries[key]?.third if (mediaData != null && mediaDeviceData != null) { val data = mediaData.copy(device = mediaDeviceData, suggestedDevice = suggestedMediaDeviceData) val listenersCopy = listeners.toSet() listenersCopy.forEach { it.onMediaDataLoaded(key, oldKey, data) } } Loading packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +40 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.flags.Flags.enableLeAudioSharing import com.android.settingslib.flags.Flags.legacyLeAudioSharing import com.android.settingslib.media.InfoMediaManager.SuggestedDeviceState import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice Loading @@ -44,6 +45,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.shared.MediaControlDrawables import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.shared.model.SuggestedMediaDeviceData import com.android.systemui.media.controls.util.LocalMediaManagerFactory import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils Loading Loading @@ -167,12 +169,28 @@ constructor( } } @MainThread private fun processSuggestedDevice( key: String, oldKey: String?, device: SuggestedMediaDeviceData?, ) { listeners.forEach { it.onSuggestedMediaDeviceChanged(key, oldKey, device) } } interface Listener { /** Called when the route has changed for a given notification. */ fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) /** Called when the notification was removed. */ fun onKeyRemoved(key: String, userInitiated: Boolean) /** Called when the suggested route has changed for a given notification. */ fun onSuggestedMediaDeviceChanged( key: String, oldKey: String?, data: SuggestedMediaDeviceData?, ) } private inner class Entry( Loading Loading @@ -201,6 +219,14 @@ constructor( } } private var suggestedDevice: SuggestedMediaDeviceData? = null set(value) { if (field != value) { field = value fgExecutor.execute { processSuggestedDevice(key, oldKey, value) } } } // 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 Loading Loading @@ -283,6 +309,20 @@ constructor( bgExecutor.execute { updateCurrent() } } override fun onSuggestedDeviceUpdated(state: SuggestedDeviceState?) { bgExecutor.execute { suggestedDevice = state?.let { SuggestedMediaDeviceData( name = it.suggestedDeviceInfo.getDeviceDisplayName(), icon = it.getIcon(context), connectionState = it.connectionState, connect = { localMediaManager.connectSuggestedDevice(it) }, ) } } } override fun onAboutToConnectDeviceAdded( deviceAddress: String, deviceName: String, Loading packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt +19 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.graphics.drawable.Icon import android.media.session.MediaSession import android.os.Process import com.android.internal.logging.InstanceId import com.android.settingslib.media.LocalMediaManager.MediaDeviceState import com.android.systemui.res.R /** State of a media view. */ Loading Loading @@ -55,6 +56,8 @@ data class MediaData( val clickIntent: PendingIntent? = null, /** Where the media is playing: phone, headphones, ear buds, remote session. */ val device: MediaDeviceData? = null, /** Where the media is suggested to be played. */ val suggestedDevice: SuggestedMediaDeviceData? = null, /** * When active, a player will be displayed on keyguard and quick-quick settings. This is * unrelated to the stream being playing or not, a player will not be active if timed out, or in Loading Loading @@ -203,3 +206,19 @@ constructor( showBroadcastButton == other.showBroadcastButton } } /** State of the suggested media device. */ data class SuggestedMediaDeviceData constructor( /** Device display name */ val name: String, /** The current state of attempting to transfer to the suggested device. */ @MediaDeviceState val connectionState: Int, /** The device icon. */ val icon: Drawable, /** Action to invoke to transfer media playback to this device. */ val connect: () -> Unit, ) Loading
packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +14 −0 Original line number Diff line number Diff line Loading @@ -317,6 +317,12 @@ public class LocalMediaManager implements BluetoothCallback { } } void dispatchOnSuggestedDeviceUpdated(@Nullable SuggestedDeviceState device) { for (DeviceCallback callback : getCallbacks()) { callback.onSuggestedDeviceUpdated(device); } } /** * Dispatch a change in the about-to-connect device. See * {@link DeviceCallback#onAboutToConnectDeviceAdded} for more information. Loading Loading @@ -746,6 +752,11 @@ public class LocalMediaManager implements BluetoothCallback { } dispatchOnRequestFailed(reason); } @Override public void onSuggestedDeviceUpdated(@Nullable SuggestedDeviceState device) { dispatchOnSuggestedDeviceUpdated(device); } } private void unRegisterDeviceAttributeChangeCallback() { Loading Loading @@ -823,6 +834,9 @@ public class LocalMediaManager implements BluetoothCallback { * Callback for notifying that we no longer have an about-to-connect device. */ default void onAboutToConnectDeviceRemoved() {} /** Callback for notifying that the suggested device has been updated. */ default void onSuggestedDeviceUpdated(@Nullable SuggestedDeviceState device) {} } /** Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java +83 −6 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import com.android.internal.logging.InstanceId; import com.android.systemui.SysuiTestCase; import com.android.systemui.media.controls.shared.model.MediaData; import com.android.systemui.media.controls.shared.model.MediaDeviceData; import com.android.systemui.media.controls.shared.model.SuggestedMediaDeviceData; import org.junit.Before; import org.junit.Rule; Loading Loading @@ -68,17 +69,44 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { private MediaData mMediaData; private MediaDeviceData mDeviceData; @Mock private SuggestedMediaDeviceData mSuggestedDeviceData; @Before public void setUp() { mManager = new MediaDataCombineLatest(); mManager.addListener(mListener); mMediaData = new MediaData( USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L, InstanceId.fakeInstanceId(-1), -1, false, null); mMediaData = new MediaData( USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L, InstanceId.fakeInstanceId(-1), -1, false, null); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false); } Loading Loading @@ -111,7 +139,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { } @Test public void emitEventAfterMediaFirst() { public void emitEventAfterMediaFirstAndMediaDeviceChangedSecond() { // GIVEN that media event has already been received mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */); // WHEN device event is received Loading @@ -122,6 +150,21 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void emitEventAfterMediaFirstAndMediaDeviceChangedSecondAndSuggestionChangedThird() { // GIVEN that media event has already been received mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */); mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); reset(mListener); // WHEN suggestion event is received mManager.onSuggestedMediaDeviceChanged(KEY, null, mSuggestedDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener) .onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean()); assertThat(captor.getValue().getSuggestedDevice()).isNotNull(); } @Test public void migrateKeyMediaFirst() { // GIVEN that media and device info has already been received Loading Loading @@ -150,6 +193,23 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void migrateKeySuggestionFirst() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */); mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); mManager.onSuggestedMediaDeviceChanged(OLD_KEY, null, mSuggestedDeviceData); reset(mListener); // WHEN a key migration event is received mManager.onSuggestedMediaDeviceChanged(KEY, OLD_KEY, mSuggestedDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener) .onMediaDataLoaded( eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean()); assertThat(captor.getValue().getSuggestedDevice()).isNotNull(); } @Test public void migrateKeyMediaAfter() { // GIVEN that media and device info has already been received Loading Loading @@ -180,6 +240,23 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void migrateKeySuggestionAfter() { // GIVEN that media and device info has already been received mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */); mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); mManager.onSuggestedMediaDeviceChanged(OLD_KEY, null, mSuggestedDeviceData); mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */); reset(mListener); // WHEN a second key migration event is received for the device mManager.onSuggestedMediaDeviceChanged(KEY, OLD_KEY, mSuggestedDeviceData); // THEN the key has already be migrated ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener) .onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean()); assertThat(captor.getValue().getSuggestedDevice()).isNotNull(); } @Test public void mediaDataRemoved() { // WHEN media data is removed without first receiving device or data Loading
packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt +42 −8 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.media.controls.domain.pipeline import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.shared.model.SuggestedMediaDeviceData import javax.inject.Inject /** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */ Loading @@ -25,7 +26,9 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, MediaDeviceManager.Listener { private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf() private val entries: MutableMap<String, Triple<MediaData?, MediaDeviceData?, SuggestedMediaDeviceData?>> = mutableMapOf() override fun onMediaDataLoaded( key: String, Loading @@ -34,10 +37,16 @@ class MediaDataCombineLatest @Inject constructor() : immediately: Boolean, ) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = data to entries.remove(oldKey)?.second val previousEntry = entries.remove(oldKey) val (mediaDeviceData, suggestedMediaDeviceData) = previousEntry?.second to previousEntry?.third entries[key] = Triple(data, mediaDeviceData, suggestedMediaDeviceData) update(key, oldKey) } else { entries[key] = data to entries[key]?.second val previousEntry = entries[key] val (mediaDeviceData, suggestedMediaDeviceData) = previousEntry?.second to previousEntry?.third entries[key] = Triple(data, mediaDeviceData, suggestedMediaDeviceData) update(key, key) } } Loading @@ -48,10 +57,32 @@ class MediaDataCombineLatest @Inject constructor() : override fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = entries.remove(oldKey)?.first to data val previousEntry = entries.remove(oldKey) val (mediaData, suggestedMediaDeviceData) = previousEntry?.first to previousEntry?.third entries[key] = Triple(mediaData, data, suggestedMediaDeviceData) update(key, oldKey) } else { entries[key] = entries[key]?.first to data val previousEntry = entries[key] val (mediaData, suggestedMediaDeviceData) = previousEntry?.first to previousEntry?.third entries[key] = Triple(mediaData, data, suggestedMediaDeviceData) update(key, key) } } override fun onSuggestedMediaDeviceChanged( key: String, oldKey: String?, data: SuggestedMediaDeviceData?, ) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { val previousEntry = entries.remove(oldKey) val (mediaData, mediaDeviceData) = previousEntry?.first to previousEntry?.second entries[key] = Triple(mediaData, mediaDeviceData, data) update(key, oldKey) } else { val previousEntry = entries[key] val (mediaData, mediaDeviceData) = previousEntry?.first to previousEntry?.second entries[key] = Triple(mediaData, mediaDeviceData, data) update(key, key) } } Loading @@ -69,9 +100,12 @@ class MediaDataCombineLatest @Inject constructor() : fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) private fun update(key: String, oldKey: String?) { val (entry, device) = entries[key] ?: null to null if (entry != null && device != null) { val data = entry.copy(device = device) val mediaData = entries[key]?.first val mediaDeviceData = entries[key]?.second val suggestedMediaDeviceData = entries[key]?.third if (mediaData != null && mediaDeviceData != null) { val data = mediaData.copy(device = mediaDeviceData, suggestedDevice = suggestedMediaDeviceData) val listenersCopy = listeners.toSet() listenersCopy.forEach { it.onMediaDataLoaded(key, oldKey, data) } } Loading
packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +40 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.flags.Flags.enableLeAudioSharing import com.android.settingslib.flags.Flags.legacyLeAudioSharing import com.android.settingslib.media.InfoMediaManager.SuggestedDeviceState import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice Loading @@ -44,6 +45,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.shared.MediaControlDrawables import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.shared.model.SuggestedMediaDeviceData import com.android.systemui.media.controls.util.LocalMediaManagerFactory import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils Loading Loading @@ -167,12 +169,28 @@ constructor( } } @MainThread private fun processSuggestedDevice( key: String, oldKey: String?, device: SuggestedMediaDeviceData?, ) { listeners.forEach { it.onSuggestedMediaDeviceChanged(key, oldKey, device) } } interface Listener { /** Called when the route has changed for a given notification. */ fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) /** Called when the notification was removed. */ fun onKeyRemoved(key: String, userInitiated: Boolean) /** Called when the suggested route has changed for a given notification. */ fun onSuggestedMediaDeviceChanged( key: String, oldKey: String?, data: SuggestedMediaDeviceData?, ) } private inner class Entry( Loading Loading @@ -201,6 +219,14 @@ constructor( } } private var suggestedDevice: SuggestedMediaDeviceData? = null set(value) { if (field != value) { field = value fgExecutor.execute { processSuggestedDevice(key, oldKey, value) } } } // 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 Loading Loading @@ -283,6 +309,20 @@ constructor( bgExecutor.execute { updateCurrent() } } override fun onSuggestedDeviceUpdated(state: SuggestedDeviceState?) { bgExecutor.execute { suggestedDevice = state?.let { SuggestedMediaDeviceData( name = it.suggestedDeviceInfo.getDeviceDisplayName(), icon = it.getIcon(context), connectionState = it.connectionState, connect = { localMediaManager.connectSuggestedDevice(it) }, ) } } } override fun onAboutToConnectDeviceAdded( deviceAddress: String, deviceName: String, Loading
packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt +19 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.graphics.drawable.Icon import android.media.session.MediaSession import android.os.Process import com.android.internal.logging.InstanceId import com.android.settingslib.media.LocalMediaManager.MediaDeviceState import com.android.systemui.res.R /** State of a media view. */ Loading Loading @@ -55,6 +56,8 @@ data class MediaData( val clickIntent: PendingIntent? = null, /** Where the media is playing: phone, headphones, ear buds, remote session. */ val device: MediaDeviceData? = null, /** Where the media is suggested to be played. */ val suggestedDevice: SuggestedMediaDeviceData? = null, /** * When active, a player will be displayed on keyguard and quick-quick settings. This is * unrelated to the stream being playing or not, a player will not be active if timed out, or in Loading Loading @@ -203,3 +206,19 @@ constructor( showBroadcastButton == other.showBroadcastButton } } /** State of the suggested media device. */ data class SuggestedMediaDeviceData constructor( /** Device display name */ val name: String, /** The current state of attempting to transfer to the suggested device. */ @MediaDeviceState val connectionState: Int, /** The device icon. */ val icon: Drawable, /** Action to invoke to transfer media playback to this device. */ val connect: () -> Unit, )