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

Commit 9cbca1a6 authored by cecilia's avatar cecilia Committed by Cecilia Hong
Browse files

Always show media recommendation on headphones connection.

- If all the resumable media age > MAX_AGE, show the media recommendations as the first card. All the existing resumable media remains as is;
- Otherwise, reactivate all the resumable media with age <= MAX_AGE to be shown, followed by the media recommendations as the last card.

Also, shorten the maximum media age from 3 hours to 30 minutes.

Fixes: 187999902
Fixes: 187943295
Test: Local builds
Test: MediaDataFilterTest
Change-Id: I40bdbb2a172de1c110edfd9a8e3a1f6521438ba3
parent 6eb35894
Loading
Loading
Loading
Loading
+19 −8
Original line number Diff line number Diff line
@@ -208,9 +208,13 @@ class MediaCarouselController @Inject constructor(
                }
            }

            override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
            override fun onSmartspaceMediaDataLoaded(
                key: String,
                data: SmartspaceTarget,
                shouldPrioritize: Boolean
            ) {
                Log.d(TAG, "My Smartspace media update is here")
                addSmartspaceMediaRecommendations(key, data)
                addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
                MediaPlayerData.getMediaPlayer(key, null)?.let {
                    logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                            it.mInstanceId,
@@ -327,7 +331,11 @@ class MediaCarouselController @Inject constructor(
        return existingPlayer == null
    }

    private fun addSmartspaceMediaRecommendations(key: String, data: SmartspaceTarget) {
    private fun addSmartspaceMediaRecommendations(
        key: String,
        data: SmartspaceTarget,
        shouldPrioritize: Boolean
    ) {
        Log.d(TAG, "Updating smartspace target in carousel")
        if (MediaPlayerData.getMediaPlayer(key, null) != null) {
            Log.w(TAG, "Skip adding smartspace target in carousel")
@@ -342,7 +350,7 @@ class MediaCarouselController @Inject constructor(
            ViewGroup.LayoutParams.WRAP_CONTENT)
        newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
        newRecs.bindRecommendation(data, bgColor)
        MediaPlayerData.addMediaRecommendation(key, newRecs)
        MediaPlayerData.addMediaRecommendation(key, newRecs, shouldPrioritize)
        updatePlayerToState(newRecs, noAnimation = true)
        reorderAllPlayers()
        updatePageIndicator()
@@ -671,17 +679,19 @@ class MediaCarouselController @Inject constructor(
internal object MediaPlayerData {
    private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
        emptyList(), emptyList(), "INVALID", null, null, null, true, null)
    // Whether should prioritize Smartspace card.
    private var shouldPrioritizeSs: Boolean = false

    data class MediaSortKey(
        // Is Smartspace media recommendation. When the Smartspace media is present, it should
        // always be the first card in carousel.
        // Whether the item represents a Smartspace media recommendation.
        val isSsMediaRec: Boolean,
        val data: MediaData,
        val updateTime: Long = 0
    )

    private val comparator =
        compareByDescending<MediaSortKey> { it.isSsMediaRec }
        compareByDescending<MediaSortKey>
            { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
            .thenByDescending { it.data.isPlaying }
            .thenByDescending { it.data.isLocalSession }
            .thenByDescending { !it.data.resumption }
@@ -697,7 +707,8 @@ internal object MediaPlayerData {
        mediaPlayers.put(sortKey, player)
    }

    fun addMediaRecommendation(key: String, player: MediaControlPanel) {
    fun addMediaRecommendation(key: String, player: MediaControlPanel, shouldPrioritize: Boolean) {
        shouldPrioritizeSs = shouldPrioritize
        removeMediaPlayer(key)
        val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, System.currentTimeMillis())
        mediaData.put(key, sortKey)
+5 −1
Original line number Diff line number Diff line
@@ -38,7 +38,11 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
        }
    }

    override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
    override fun onSmartspaceMediaDataLoaded(
        key: String,
        data: SmartspaceTarget,
        shouldPrioritize: Boolean
    ) {
        listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
    }

+46 −22
Original line number Diff line number Diff line
@@ -26,9 +26,11 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.time.SystemClock
import java.util.SortedMap
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.LinkedHashMap

private const val TAG = "MediaDataFilter"
private const val DEBUG = true
@@ -39,7 +41,7 @@ private const val DEBUG = true
 */
@VisibleForTesting
internal val SMARTSPACE_MAX_AGE = SystemProperties
        .getLong("debug.sysui.smartspace_max_age", TimeUnit.HOURS.toMillis(3))
        .getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30))

/**
 * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
@@ -65,7 +67,8 @@ class MediaDataFilter @Inject constructor(
    private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
    private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    private var hasSmartspace: Boolean = false
    var hasSmartspace: Boolean = false
        private set
    private var reactivatedKey: String? = null

    init {
@@ -99,20 +102,21 @@ class MediaDataFilter @Inject constructor(
        }
    }

    override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
    override fun onSmartspaceMediaDataLoaded(
        key: String,
        data: SmartspaceTarget,
        shouldPrioritize: Boolean
    ) {
        var shouldPrioritizeMutable = shouldPrioritize
        hasSmartspace = true

        // Before forwarding the smartspace target, first check if we have recently inactive media
        val now = systemClock.elapsedRealtime()
        val sorted = userEntries.toSortedMap(compareBy {
            userEntries.get(it)?.lastActive ?: -1
        })
        if (sorted.size > 0) {
            val lastActiveKey = sorted.lastKey() // most recently active
            val timeSinceActive = sorted.get(lastActiveKey)?.let {
                now - it.lastActive
            } ?: Long.MAX_VALUE
        val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
        if (timeSinceActive < SMARTSPACE_MAX_AGE) {
            val lastActiveKey = sorted.lastKey() // most recently active
            // Notify listeners to consider this media active
            Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
            reactivatedKey = lastActiveKey
@@ -120,18 +124,17 @@ class MediaDataFilter @Inject constructor(
            listeners.forEach {
                it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData)
            }
                return
            }
        } else {
            // Mark to prioritize Smartspace card if no recent media.
            shouldPrioritizeMutable = true
        }

        // If no recent media, continue with smartspace update
        // Only proceed with the Smartspace update if the recommendation is not empty.
        if (isMediaRecommendationEmpty(data)) {
            Log.d(TAG, "Empty media recommendations. Skip showing the card")
            return
        }

        // Proceed only if the Smartspace recommendation is not empty.
        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data) }
        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
    }

    override fun onMediaDataRemoved(key: String) {
@@ -158,7 +161,6 @@ class MediaDataFilter @Inject constructor(
                    it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData)
                }
            }
            return
        }

        listeners.forEach { it.onSmartspaceMediaDataRemoved(key) }
@@ -230,4 +232,26 @@ class MediaDataFilter @Inject constructor(
        val mediaRecommendationList: List<SmartspaceAction> = data.getIconGrid()
        return mediaRecommendationList == null || mediaRecommendationList.isEmpty()
    }

    /**
     * Return the time since last active for the most-recent media.
     *
     * @param sortedEntries userEntries sorted from the earliest to the most-recent.
     *
     * @return The duration in milliseconds from the most-recent media's last active timestamp to
     * the present. MAX_VALUE will be returned if there is no media.
     */
    private fun timeSinceActiveForMostRecentMedia(
        sortedEntries: SortedMap<String, MediaData>
    ): Long {
        if (sortedEntries.isEmpty()) {
            return Long.MAX_VALUE
        }

        val now = systemClock.elapsedRealtime()
        val lastActiveKey = sortedEntries.lastKey() // most recently active
        return sortedEntries.get(lastActiveKey)?.let {
            now - it.lastActive
        } ?: Long.MAX_VALUE
    }
}
+11 −2
Original line number Diff line number Diff line
@@ -817,8 +817,17 @@ class MediaDataManager(
         */
        fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {}

        /** Called whenever there's new Smartspace media data loaded. */
        fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {}
        /**
         * Called whenever there's new Smartspace media data loaded.
         *
         * 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,
            data: SmartspaceTarget,
            shouldPrioritize: Boolean = false
        ) {}

        /**
         * Called whenever a previously existing Media notification was removed
+5 −1
Original line number Diff line number Diff line
@@ -56,7 +56,11 @@ class MediaHost constructor(
            updateViewVisibility()
        }

        override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
        override fun onSmartspaceMediaDataLoaded(
            key: String,
            data: SmartspaceTarget,
            shouldPrioritize: Boolean
        ) {
            updateViewVisibility()
        }

Loading