Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2db06b41 authored by Michael Mikhail's avatar Michael Mikhail Committed by Android (Google) Code Review
Browse files

Merge "Add non-UI listener to media changes" into main

parents 903a390a 6be4bf45
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
@@ -69,6 +69,10 @@ constructor(
    private val mediaFlags: MediaFlags,
    private val mediaFilterRepository: MediaFilterRepository,
) : MediaDataManager.Listener {
    /** Non-UI listeners to media changes. */
    private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf()
    val listeners: Set<MediaDataProcessor.Listener>
        get() = _listeners.toSet()
    lateinit var mediaDataProcessor: MediaDataProcessor

    // Ensure the field (and associated reference) isn't removed during optimization.
@@ -113,6 +117,9 @@ constructor(
        mediaFilterRepository.addMediaDataLoadingState(
            MediaDataLoadingModel.Loaded(data.instanceId)
        )

        // Notify listeners
        listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
    }

    override fun onSmartspaceMediaDataLoaded(
@@ -171,6 +178,20 @@ constructor(
                mediaFilterRepository.addMediaDataLoadingState(
                    MediaDataLoadingModel.Loaded(lastActiveId)
                )
                listeners.forEach { listener ->
                    getKey(lastActiveId)?.let { lastActiveKey ->
                        listener.onMediaDataLoaded(
                            lastActiveKey,
                            lastActiveKey,
                            mediaData,
                            receivedSmartspaceCardLatency =
                                (systemClock.currentTimeMillis() -
                                        data.headphoneConnectionTimeMillis)
                                    .toInt(),
                            isSsReactivated = true
                        )
                    }
                }
            }
        } else if (data.isActive) {
            // Mark to prioritize Smartspace card if no recent media.
@@ -189,6 +210,7 @@ constructor(
        mediaFilterRepository.setRecommendationsLoadingState(
            SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
        )
        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
    }

    override fun onMediaDataRemoved(key: String) {
@@ -198,6 +220,8 @@ constructor(
                mediaFilterRepository.addMediaDataLoadingState(
                    MediaDataLoadingModel.Removed(instanceId)
                )
                // Only notify listeners if something actually changed
                listeners.forEach { it.onMediaDataRemoved(key) }
            }
        }
    }
@@ -212,6 +236,11 @@ constructor(
                mediaFilterRepository.addMediaDataLoadingState(
                    MediaDataLoadingModel.Loaded(lastActiveId, immediately)
                )
                listeners.forEach { listener ->
                    getKey(lastActiveId)?.let { lastActiveKey ->
                        listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately)
                    }
                }
            }
        }

@@ -227,6 +256,7 @@ constructor(
        mediaFilterRepository.setRecommendationsLoadingState(
            SmartspaceMediaLoadingModel.Removed(key, immediately)
        )
        listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
    }

    @VisibleForTesting
@@ -240,6 +270,7 @@ constructor(
                mediaFilterRepository.addMediaDataLoadingState(
                    MediaDataLoadingModel.Removed(data.instanceId)
                )
                listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
            }
        }
    }
@@ -247,6 +278,7 @@ constructor(
    @VisibleForTesting
    internal fun handleUserSwitched() {
        // If the user changes, remove all current MediaData objects.
        val listenersCopy = listeners
        val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList()
        // Clear the list first and update loading state to remove media from UI.
        mediaFilterRepository.clearSelectedUserMedia()
@@ -255,6 +287,9 @@ constructor(
            mediaFilterRepository.addMediaDataLoadingState(
                MediaDataLoadingModel.Removed(instanceId)
            )
            getKey(instanceId)?.let {
                listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
            }
        }

        mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
@@ -268,6 +303,7 @@ constructor(
                mediaFilterRepository.addMediaDataLoadingState(
                    MediaDataLoadingModel.Loaded(data.instanceId)
                )
                listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
            }
        }
    }
@@ -317,6 +353,12 @@ constructor(
        }
    }

    /** Add a listener for filtered [MediaData] changes */
    fun addListener(listener: MediaDataProcessor.Listener) = _listeners.add(listener)

    /** Remove a listener that was registered with addListener */
    fun removeListener(listener: MediaDataProcessor.Listener) = _listeners.remove(listener)

    /**
     * Return the time since last active for the most-recent media.
     *
@@ -336,6 +378,16 @@ constructor(
        return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE
    }

    private fun getKey(instanceId: InstanceId): String? {
        val allEntries = mediaFilterRepository.allUserEntries.value
        val filteredEntries = allEntries.filter { (_, data) -> data.instanceId == instanceId }
        return if (filteredEntries.isNotEmpty()) {
            filteredEntries.keys.first()
        } else {
            null
        }
    }

    companion object {
        /**
         * Maximum age of a media control to re-activate on smartspace signal. If there is no media
+8 −0
Original line number Diff line number Diff line
@@ -160,6 +160,14 @@ constructor(
        mediaDataFilter.mediaDataProcessor = mediaDataProcessor
    }

    override fun addListener(listener: MediaDataManager.Listener) {
        mediaDataFilter.addListener(listener)
    }

    override fun removeListener(listener: MediaDataManager.Listener) {
        mediaDataFilter.removeListener(listener)
    }

    override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) = unsupported

    override fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
+109 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
@@ -76,6 +77,7 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
@TestableLooper.RunWithLooper
class MediaDataFilterImplTest : SysuiTestCase() {

    @Mock private lateinit var listener: MediaDataProcessor.Listener
    @Mock private lateinit var userTracker: UserTracker
    @Mock private lateinit var broadcastSender: BroadcastSender
    @Mock private lateinit var mediaDataProcessor: MediaDataProcessor
@@ -115,6 +117,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                repository,
            )
        mediaDataFilter.mediaDataProcessor = mediaDataProcessor
        mediaDataFilter.addListener(listener)

        // Start all tests as main user
        setUser(USER_MAIN)
@@ -167,6 +170,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {

            mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)

            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
            assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
        }

@@ -178,6 +183,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {

            mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)

            verify(listener, never())
                .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
            assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel)
        }

@@ -196,6 +203,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId))
            mediaDataFilter.onMediaDataRemoved(KEY)

            verify(listener).onMediaDataRemoved(eq(KEY))
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
        }

@@ -208,6 +216,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
            mediaDataFilter.onMediaDataRemoved(KEY)

            verify(listener, never()).onMediaDataRemoved(eq(KEY))
            assertThat(mediaDataLoadedStates).isEmpty()
        }

@@ -226,6 +235,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            setUser(USER_GUEST)

            // THEN we should remove the main user's media
            verify(listener).onMediaDataRemoved(eq(KEY))
            assertThat(mediaDataLoadedStates).isEmpty()
        }

@@ -243,6 +253,20 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            // and we switch to guest user
            setUser(USER_GUEST)

            // THEN we should add back the guest user media
            verify(listener)
                .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))

            // but not the main user's
            verify(listener, never())
                .onMediaDataLoaded(
                    eq(KEY),
                    any(),
                    eq(dataMain),
                    anyBoolean(),
                    anyInt(),
                    anyBoolean()
                )
            assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel)
            assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel)
        }
@@ -261,6 +285,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {

            val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
            // THEN we should remove the private profile media
            verify(listener).onMediaDataRemoved(eq(KEY_ALT))
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
        }

@@ -507,6 +532,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                )
                .isTrue()
            assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
            verify(listener)
                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
            verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
            verify(logger, never()).logRecommendationActivated(any(), any(), any())
        }
@@ -534,6 +561,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                )
                .isFalse()
            assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
            verify(listener, never())
                .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
            verify(logger, never()).logRecommendationAdded(any(), any())
            verify(logger, never()).logRecommendationActivated(any(), any(), any())
        }
@@ -563,6 +593,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                )
                .isTrue()
            assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
            verify(listener)
                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
            verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
            verify(logger, never()).logRecommendationActivated(any(), any(), any())
        }
@@ -592,6 +624,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                )
                .isFalse()
            assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
            verify(logger, never()).logRecommendationAdded(any(), any())
            verify(logger, never()).logRecommendationActivated(any(), any(), any())
        }
@@ -614,6 +647,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)

            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))

            // AND we get a smartspace signal
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -629,6 +664,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                )
                .isFalse()
            assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
            verify(listener, never())
                .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
            verify(logger, never()).logRecommendationAdded(any(), any())
            verify(logger, never()).logRecommendationActivated(any(), any(), any())
        }
@@ -649,12 +687,15 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))

            // AND we get a smartspace signal
            runCurrent()
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)

            // THEN we should treat the media as active instead
            val dataCurrentAndActive = dataCurrent.copy(active = true)
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
            assertThat(
                    hasActiveMediaOrRecommendation(
@@ -664,8 +705,18 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                    )
                )
                .isTrue()
            verify(listener)
                .onMediaDataLoaded(
                    eq(KEY),
                    eq(KEY),
                    eq(dataCurrentAndActive),
                    eq(true),
                    eq(100),
                    eq(true)
                )
            // Smartspace update shouldn't be propagated for the empty rec list.
            assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
            verify(logger, never()).logRecommendationAdded(any(), any())
            verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
        }
@@ -687,12 +738,24 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)

            assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))

            // AND we get a smartspace signal
            runCurrent()
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)

            // THEN we should treat the media as active instead
            val dataCurrentAndActive = dataCurrent.copy(active = true)
            verify(listener)
                .onMediaDataLoaded(
                    eq(KEY),
                    eq(KEY),
                    eq(dataCurrentAndActive),
                    eq(true),
                    eq(100),
                    eq(true)
                )
            assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
            assertThat(
                    hasActiveMediaOrRecommendation(
@@ -704,6 +767,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                .isTrue()
            // Smartspace update should also be propagated but not prioritized.
            assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
            verify(listener)
                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
            verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
            verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
        }
@@ -721,6 +786,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
            mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)

            verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
            assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
            assertThat(
                    hasActiveMediaOrRecommendation(
@@ -746,15 +812,29 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
            val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)

            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))

            runCurrent()
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)

            val dataCurrentAndActive = dataCurrent.copy(active = true)
            verify(listener)
                .onMediaDataLoaded(
                    eq(KEY),
                    eq(KEY),
                    eq(dataCurrentAndActive),
                    eq(true),
                    eq(100),
                    eq(true)
                )
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)

            mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)

            verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
            assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
            assertThat(
                    hasActiveMediaOrRecommendation(
@@ -781,6 +861,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {

            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)

            verify(listener)
                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
            assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
            assertThat(
                    hasActiveMediaOrRecommendation(
@@ -813,12 +895,18 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)

            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)

            // And an inactive recommendation is loaded
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)

            // Smartspace is loaded but the media stays inactive
            verify(listener)
                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
            verify(listener, never())
                .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
            assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
            assertThat(
                    hasActiveMediaOrRecommendation(
@@ -866,6 +954,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)

            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)

            // AND we get a smartspace signal with extra to trigger resume
@@ -875,6 +965,16 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)

            // THEN we should treat the media as active instead
            val dataCurrentAndActive = dataCurrent.copy(active = true)
            verify(listener)
                .onMediaDataLoaded(
                    eq(KEY),
                    eq(KEY),
                    eq(dataCurrentAndActive),
                    eq(true),
                    eq(100),
                    eq(true)
                )
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
            assertThat(
                    hasActiveMediaOrRecommendation(
@@ -886,6 +986,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
                .isTrue()
            // And update the smartspace data state, but not prioritized
            assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
            verify(listener)
                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
        }

    @Test
@@ -901,6 +1003,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
            mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)

            verify(listener)
                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
            assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)

            // AND we get a smartspace signal with extra to not trigger resume
@@ -908,7 +1012,12 @@ class MediaDataFilterImplTest : SysuiTestCase() {
            whenever(cardAction.extras).thenReturn(extras)
            mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)

            // THEN listeners are not updated to show media
            verify(listener, never())
                .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true))
            // But the smartspace update is still propagated
            verify(listener)
                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
            assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
        }