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

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

Merge "Switch usermap from key to instanceId." into main

parents e3dcebf5 142427e0
Loading
Loading
Loading
Loading
+18 −14
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.media.controls.data.repository

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -44,16 +45,17 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
        testScope.runTest {
            val selectedUserEntries by collectLastValue(underTest.selectedUserEntries)

            val userMedia = MediaData().copy(active = true)
            val instanceId = InstanceId.fakeInstanceId(123)
            val userMedia = MediaData().copy(active = true, instanceId = instanceId)

            underTest.addSelectedUserMediaEntry(KEY, userMedia)
            underTest.addSelectedUserMediaEntry(userMedia)

            assertThat(selectedUserEntries?.get(KEY)).isEqualTo(userMedia)
            assertThat(selectedUserEntries?.get(instanceId)).isEqualTo(userMedia)

            underTest.addSelectedUserMediaEntry(KEY, userMedia.copy(active = false))
            underTest.addSelectedUserMediaEntry(userMedia.copy(active = false))

            assertThat(selectedUserEntries?.get(KEY)).isNotEqualTo(userMedia)
            assertThat(selectedUserEntries?.get(KEY)?.active).isFalse()
            assertThat(selectedUserEntries?.get(instanceId)).isNotEqualTo(userMedia)
            assertThat(selectedUserEntries?.get(instanceId)?.active).isFalse()
        }

    @Test
@@ -61,13 +63,14 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
        testScope.runTest {
            val selectedUserEntries by collectLastValue(underTest.selectedUserEntries)

            val userMedia = MediaData()
            val instanceId = InstanceId.fakeInstanceId(123)
            val userMedia = MediaData().copy(instanceId = instanceId)

            underTest.addSelectedUserMediaEntry(KEY, userMedia)
            underTest.addSelectedUserMediaEntry(userMedia)

            assertThat(selectedUserEntries?.get(KEY)).isEqualTo(userMedia)
            assertThat(selectedUserEntries?.get(instanceId)).isEqualTo(userMedia)

            assertThat(underTest.removeSelectedUserMediaEntry(KEY, userMedia)).isTrue()
            assertThat(underTest.removeSelectedUserMediaEntry(instanceId, userMedia)).isTrue()
        }

    @Test
@@ -75,13 +78,14 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
        testScope.runTest {
            val selectedUserEntries by collectLastValue(underTest.selectedUserEntries)

            val userMedia = MediaData()
            val instanceId = InstanceId.fakeInstanceId(123)
            val userMedia = MediaData().copy(instanceId = instanceId)

            underTest.addSelectedUserMediaEntry(KEY, userMedia)
            underTest.addSelectedUserMediaEntry(userMedia)

            assertThat(selectedUserEntries?.get(KEY)).isEqualTo(userMedia)
            assertThat(selectedUserEntries?.get(instanceId)).isEqualTo(userMedia)

            assertThat(underTest.removeSelectedUserMediaEntry(KEY)).isEqualTo(userMedia)
            assertThat(underTest.removeSelectedUserMediaEntry(instanceId)).isEqualTo(userMedia)
        }

    @Test
+7 −6
Original line number Diff line number Diff line
@@ -56,13 +56,13 @@ class MediaCarouselInteractorTest : SysuiTestCase() {

            val userMedia = MediaData().copy(active = true)

            mediaFilterRepository.addSelectedUserMediaEntry(KEY, userMedia)
            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)

            assertThat(hasActiveMediaOrRecommendation).isTrue()
            assertThat(hasActiveMedia).isTrue()
            assertThat(hasAnyMedia).isTrue()

            mediaFilterRepository.addSelectedUserMediaEntry(KEY, userMedia.copy(active = false))
            mediaFilterRepository.addSelectedUserMediaEntry(userMedia.copy(active = false))

            assertThat(hasActiveMediaOrRecommendation).isFalse()
            assertThat(hasActiveMedia).isFalse()
@@ -78,14 +78,16 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
            val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)

            val userMedia = MediaData().copy(active = false)
            val instanceId = userMedia.instanceId

            mediaFilterRepository.addSelectedUserMediaEntry(KEY, userMedia)
            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)

            assertThat(hasActiveMediaOrRecommendation).isFalse()
            assertThat(hasActiveMedia).isFalse()
            assertThat(hasAnyMedia).isTrue()

            assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(KEY, userMedia)).isTrue()
            assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
                .isTrue()

            assertThat(hasActiveMediaOrRecommendation).isFalse()
            assertThat(hasActiveMedia).isFalse()
@@ -114,7 +116,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
            assertThat(hasActiveMediaOrRecommendation).isTrue()
            assertThat(hasAnyMediaOrRecommendation).isTrue()

            mediaFilterRepository.addSelectedUserMediaEntry(KEY, userMedia)
            mediaFilterRepository.addSelectedUserMediaEntry(userMedia)

            assertThat(hasActiveMediaOrRecommendation).isTrue()
            assertThat(hasAnyMediaOrRecommendation).isTrue()
@@ -193,7 +195,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
        testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() }

    companion object {
        private const val KEY = "KEY"
        private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
    }
}
+16 −14
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.media.controls.data.repository

import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -28,17 +29,18 @@ import kotlinx.coroutines.flow.asStateFlow
@SysUISingleton
class MediaFilterRepository @Inject constructor() {

    /** Key of media control that recommendations card reactivated. */
    private val _reactivatedKey: MutableStateFlow<String?> = MutableStateFlow(null)
    val reactivatedKey: StateFlow<String?> = _reactivatedKey.asStateFlow()
    /** Instance id of media control that recommendations card reactivated. */
    private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
    val reactivatedId: StateFlow<InstanceId?> = _reactivatedId.asStateFlow()

    private val _smartspaceMediaData: MutableStateFlow<SmartspaceMediaData> =
        MutableStateFlow(SmartspaceMediaData())
    val smartspaceMediaData: StateFlow<SmartspaceMediaData> = _smartspaceMediaData.asStateFlow()

    private val _selectedUserEntries: MutableStateFlow<Map<String, MediaData>> =
    private val _selectedUserEntries: MutableStateFlow<Map<InstanceId, MediaData>> =
        MutableStateFlow(LinkedHashMap())
    val selectedUserEntries: StateFlow<Map<String, MediaData>> = _selectedUserEntries.asStateFlow()
    val selectedUserEntries: StateFlow<Map<InstanceId, MediaData>> =
        _selectedUserEntries.asStateFlow()

    private val _allUserEntries: MutableStateFlow<Map<String, MediaData>> =
        MutableStateFlow(LinkedHashMap())
@@ -62,9 +64,9 @@ class MediaFilterRepository @Inject constructor() {
        return mediaData
    }

    fun addSelectedUserMediaEntry(key: String, data: MediaData) {
        val entries = LinkedHashMap<String, MediaData>(_selectedUserEntries.value)
        entries[key] = data
    fun addSelectedUserMediaEntry(data: MediaData) {
        val entries = LinkedHashMap<InstanceId, MediaData>(_selectedUserEntries.value)
        entries[data.instanceId] = data
        _selectedUserEntries.value = entries
    }

@@ -73,8 +75,8 @@ class MediaFilterRepository @Inject constructor() {
     *
     * @return media data if an entry is actually removed, `null` otherwise.
     */
    fun removeSelectedUserMediaEntry(key: String): MediaData? {
        val entries = LinkedHashMap<String, MediaData>(_selectedUserEntries.value)
    fun removeSelectedUserMediaEntry(key: InstanceId): MediaData? {
        val entries = LinkedHashMap<InstanceId, MediaData>(_selectedUserEntries.value)
        val mediaData = entries.remove(key)
        _selectedUserEntries.value = entries
        return mediaData
@@ -85,8 +87,8 @@ class MediaFilterRepository @Inject constructor() {
     *
     * @return true if media data is removed, false otherwise.
     */
    fun removeSelectedUserMediaEntry(key: String, data: MediaData): Boolean {
        val entries = LinkedHashMap<String, MediaData>(_selectedUserEntries.value)
    fun removeSelectedUserMediaEntry(key: InstanceId, data: MediaData): Boolean {
        val entries = LinkedHashMap<InstanceId, MediaData>(_selectedUserEntries.value)
        val succeed = entries.remove(key, data)
        if (!succeed) {
            return false
@@ -105,7 +107,7 @@ class MediaFilterRepository @Inject constructor() {
    }

    /** Updates media control key that recommendations card reactivated. */
    fun setReactivatedKey(key: String?) {
        _reactivatedKey.value = key
    fun setReactivatedId(instanceId: InstanceId?) {
        _reactivatedId.value = instanceId
    }
}
+82 −41
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.KeepForWeakReference
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.InstanceId
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
@@ -66,8 +67,8 @@ constructor(
    private val mediaFlags: MediaFlags,
    private val mediaFilterRepository: MediaFilterRepository,
) : MediaDataManager.Listener {
    private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
    val listeners: Set<MediaDataManager.Listener>
    private val _listeners: MutableSet<Listener> = mutableSetOf()
    val listeners: Set<Listener>
        get() = _listeners.toSet()
    lateinit var mediaDataManager: MediaDataManager

@@ -108,13 +109,10 @@ constructor(
            return
        }

        if (oldKey != null && oldKey != key) {
            mediaFilterRepository.removeSelectedUserMediaEntry(oldKey)
        }
        mediaFilterRepository.addSelectedUserMediaEntry(key, data)
        mediaFilterRepository.addSelectedUserMediaEntry(data)

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

    override fun onSmartspaceMediaDataLoaded(
@@ -160,11 +158,11 @@ constructor(
            // It could happen there are existing active media resume cards, then we don't need to
            // reactivate.
            if (shouldReactivate) {
                val lastActiveKey = sorted.lastKey() // most recently active
                val lastActiveId = sorted.lastKey() // most recently active id
                // Notify listeners to consider this media active
                Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
                mediaFilterRepository.setReactivatedKey(lastActiveKey)
                val mediaData = sorted[lastActiveKey]!!.copy(active = true)
                Log.d(TAG, "reactivating $lastActiveId instead of smartspace")
                mediaFilterRepository.setReactivatedId(lastActiveId)
                val mediaData = sorted[lastActiveId]!!.copy(active = true)
                logger.logRecommendationActivated(
                    mediaData.appUid,
                    mediaData.packageName,
@@ -172,9 +170,7 @@ constructor(
                )
                listeners.forEach {
                    it.onMediaDataLoaded(
                        lastActiveKey,
                        lastActiveKey,
                        mediaData,
                        lastActiveId,
                        receivedSmartspaceCardLatency =
                            (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
                                .toInt(),
@@ -196,27 +192,28 @@ constructor(
            smartspaceMediaData.packageName,
            smartspaceMediaData.instanceId
        )
        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, shouldPrioritizeMutable) }
    }

    override fun onMediaDataRemoved(key: String) {
        mediaFilterRepository.removeMediaEntry(key)
        mediaFilterRepository.removeSelectedUserMediaEntry(key)?.let {
        mediaFilterRepository.removeMediaEntry(key)?.let { mediaData ->
            val instanceId = mediaData.instanceId
            mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let {
                // Only notify listeners if something actually changed
            listeners.forEach { it.onMediaDataRemoved(key) }
                listeners.forEach { it.onMediaDataRemoved(instanceId) }
            }
        }
    }

    override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
        // First check if we had reactivated media instead of forwarding smartspace
        mediaFilterRepository.reactivatedKey.value?.let {
            val lastActiveKey = it
            mediaFilterRepository.setReactivatedKey(null)
            Log.d(TAG, "expiring reactivated key $lastActiveKey")
        mediaFilterRepository.reactivatedId.value?.let { lastActiveId ->
            mediaFilterRepository.setReactivatedId(null)
            Log.d(TAG, "expiring reactivated key $lastActiveId")
            // Notify listeners to update with actual active value
            mediaFilterRepository.selectedUserEntries.value[lastActiveKey]?.let { mediaData ->
            mediaFilterRepository.selectedUserEntries.value[lastActiveId]?.let {
                listeners.forEach { listener ->
                    listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, immediately)
                    listener.onMediaDataLoaded(lastActiveId, immediately)
                }
            }
        }
@@ -240,8 +237,8 @@ constructor(
            if (!lockscreenUserManager.isProfileAvailable(data.userId)) {
                // Only remove media when the profile is unavailable.
                if (DEBUG) Log.d(TAG, "Removing $key after profile change")
                mediaFilterRepository.removeSelectedUserMediaEntry(key, data)
                listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
                mediaFilterRepository.removeSelectedUserMediaEntry(data.instanceId, data)
                listeners.forEach { listener -> listener.onMediaDataRemoved(data.instanceId) }
            }
        }
    }
@@ -254,16 +251,16 @@ constructor(
        // Clear the list first, to make sure callbacks from listeners if we have any entries
        // are up to date
        mediaFilterRepository.clearSelectedUserMedia()
        keyCopy.forEach {
            if (DEBUG) Log.d(TAG, "Removing $it after user change")
            listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
        keyCopy.forEach { instanceId ->
            if (DEBUG) Log.d(TAG, "Removing $instanceId after user change")
            listenersCopy.forEach { listener -> listener.onMediaDataRemoved(instanceId) }
        }

        mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
            if (lockscreenUserManager.isCurrentProfile(data.userId)) {
                if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
                mediaFilterRepository.addSelectedUserMediaEntry(key, data)
                listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
                mediaFilterRepository.addSelectedUserMediaEntry(data)
                listenersCopy.forEach { listener -> listener.onMediaDataLoaded(data.instanceId) }
            }
        }
    }
@@ -271,10 +268,12 @@ constructor(
    /** Invoked when the user has dismissed the media carousel */
    fun onSwipeToDismiss() {
        if (DEBUG) Log.d(TAG, "Media carousel swiped away")
        val mediaKeys = mediaFilterRepository.selectedUserEntries.value.keys.toSet()
        mediaKeys.forEach {
        val mediaEntries = mediaFilterRepository.allUserEntries.value.entries
        mediaEntries.forEach { (key, data) ->
            if (mediaFilterRepository.selectedUserEntries.value.containsKey(data.instanceId)) {
                // Force updates to listeners, needed for re-activated card
            mediaDataManager.setInactive(it, timedOut = true, forceUpdate = true)
                mediaDataManager.setInactive(key, timedOut = true, forceUpdate = true)
            }
        }
        val smartspaceMediaData = mediaFilterRepository.smartspaceMediaData.value
        if (smartspaceMediaData.isActive) {
@@ -312,10 +311,10 @@ constructor(
    }

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

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

    /**
     * Return the time since last active for the most-recent media.
@@ -325,15 +324,57 @@ constructor(
     *   the present. MAX_VALUE will be returned if there is no media.
     */
    private fun timeSinceActiveForMostRecentMedia(
        sortedEntries: SortedMap<String, MediaData>
        sortedEntries: SortedMap<InstanceId, MediaData>
    ): Long {
        if (sortedEntries.isEmpty()) {
            return Long.MAX_VALUE
        }

        val now = systemClock.elapsedRealtime()
        val lastActiveKey = sortedEntries.lastKey() // most recently active
        return sortedEntries[lastActiveKey]?.let { now - it.lastActive } ?: Long.MAX_VALUE
        val lastActiveInstanceId = sortedEntries.lastKey() // most recently active
        return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE
    }

    interface Listener {
        /**
         * Called whenever there's new MediaData Loaded for the consumption in views.
         *
         * @param immediately indicates should apply the UI changes immediately, otherwise wait
         *   until the next refresh-round before UI becomes visible. True by default to take in
         *   place immediately.
         * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI
         *   displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace
         *   signal.
         * @param isSsReactivated indicates resume media card is reactivated by Smartspace
         *   recommendation signal
         */
        fun onMediaDataLoaded(
            instanceId: InstanceId,
            immediately: Boolean = true,
            receivedSmartspaceCardLatency: Int = 0,
            isSsReactivated: Boolean = false,
        )

        /**
         * Called whenever there's new Smartspace media data loaded.
         *
         * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true,
         *   it will be prioritized as the first card. Otherwise, it will show up as the last card
         *   as default.
         */
        fun onSmartspaceMediaDataLoaded(key: String, shouldPrioritize: Boolean = false)

        /** Called whenever a previously existing Media notification was removed. */
        fun onMediaDataRemoved(instanceId: InstanceId)

        /**
         * Called whenever a previously existing Smartspace media data was removed.
         *
         * @param immediately indicates should apply the UI changes immediately, otherwise wait
         *   until the next refresh-round before UI becomes visible. True by default to take in
         *   place immediately.
         */
        fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean = true)
    }

    companion object {
+2 −2
Original line number Diff line number Diff line
@@ -27,10 +27,10 @@ import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
interface MediaDataManager {

    /** Add a listener for changes in this class */
    fun addListener(listener: Listener)
    fun addListener(listener: Listener) {}

    /** Remove a listener for changes in this class */
    fun removeListener(listener: Listener)
    fun removeListener(listener: Listener) {}

    /**
     * Called whenever the player has been paused or stopped for a while, or swiped from QQS. This
Loading