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

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

Merge "Track order of visible media players" into tm-qpr-dev

parents 3dd646cf 49f68af1
Loading
Loading
Loading
Loading
+83 −33
Original line number Diff line number Diff line
@@ -133,7 +133,7 @@ class MediaCarouselController @Inject constructor(
    private val visualStabilityCallback: OnReorderingAllowedListener
    private var needsReordering: Boolean = false
    private var keysNeedRemoval = mutableSetOf<String>()
    var shouldScrollToActivePlayer: Boolean = false
    var shouldScrollToKey: Boolean = false
    private var isRtl: Boolean = false
        set(value) {
            if (value != field) {
@@ -436,7 +436,10 @@ class MediaCarouselController @Inject constructor(
        return mediaCarousel
    }

    private fun reorderAllPlayers(previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?) {
    private fun reorderAllPlayers(
            previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
            key: String? = null
    ) {
        mediaContent.removeAllViews()
        for (mediaPlayer in MediaPlayerData.players()) {
            mediaPlayer.mediaViewHolder?.let {
@@ -446,18 +449,18 @@ class MediaCarouselController @Inject constructor(
            }
        }
        mediaCarouselScrollHandler.onPlayersChanged()

        MediaPlayerData.updateVisibleMediaPlayers()
        // Automatically scroll to the active player if needed
        if (shouldScrollToActivePlayer) {
            shouldScrollToActivePlayer = false
            val activeMediaIndex = MediaPlayerData.firstActiveMediaIndex()
            if (activeMediaIndex != -1) {
        if (shouldScrollToKey) {
            shouldScrollToKey = false
            val mediaIndex = key?.let { MediaPlayerData.getMediaPlayerIndex(it) } ?: -1
            if (mediaIndex != -1) {
                previousVisiblePlayerKey?.let {
                    val previousVisibleIndex = MediaPlayerData.playerKeys()
                            .indexOfFirst { key -> it == key }
                    mediaCarouselScrollHandler
                            .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
                } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
                            .scrollToPlayer(previousVisibleIndex, mediaIndex)
                } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
            }
        }
    }
@@ -471,9 +474,8 @@ class MediaCarouselController @Inject constructor(
    ): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") {
        MediaPlayerData.moveIfExists(oldKey, key)
        val existingPlayer = MediaPlayerData.getMediaPlayer(key)
        val curVisibleMediaKey = MediaPlayerData.playerKeys()
        val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys()
                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
        val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
        if (existingPlayer == null) {
            val newPlayer = mediaControlPanelFactory.get()
            newPlayer.attachPlayer(MediaViewHolder.create(
@@ -488,8 +490,10 @@ class MediaCarouselController @Inject constructor(
                key, data, newPlayer, systemClock, isSsReactivated, debugLogger
            )
            updatePlayerToState(newPlayer, noAnimation = true)
            if (data.active) {
                reorderAllPlayers(curVisibleMediaKey)
            // Media data added from a recommendation card should starts playing.
            if ((shouldScrollToKey && data.isPlaying == true) ||
                    (!shouldScrollToKey && data.active)) {
                reorderAllPlayers(curVisibleMediaKey, key)
            } else {
                needsReordering = true
            }
@@ -498,14 +502,16 @@ class MediaCarouselController @Inject constructor(
            MediaPlayerData.addMediaPlayer(
                key, data, existingPlayer, systemClock, isSsReactivated, debugLogger
            )
            // Check the playing status of both current visible and new media players
            // To make sure we scroll to the active playing media card.
            val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
            // In case of recommendations hits.
            // Check the playing status of media player and the package name.
            // To make sure we scroll to the right app's media player.
            if (isReorderingAllowed ||
                    shouldScrollToActivePlayer &&
                    shouldScrollToKey &&
                    data.isPlaying == true &&
                    isCurVisibleMediaPlaying == false
                    packageName == data.packageName
            ) {
                reorderAllPlayers(curVisibleMediaKey)
                reorderAllPlayers(curVisibleMediaKey, key)
            } else {
                needsReordering = true
            }
@@ -534,7 +540,7 @@ class MediaCarouselController @Inject constructor(

        val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
        existingSmartspaceMediaKey?.let {
            val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey)
            val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true)
            removedPlayer?.run { debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) }
        }

@@ -546,7 +552,7 @@ class MediaCarouselController @Inject constructor(
                ViewGroup.LayoutParams.WRAP_CONTENT)
        newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
        newRecs.bindRecommendation(data)
        val curVisibleMediaKey = MediaPlayerData.playerKeys()
        val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys()
                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
        MediaPlayerData.addMediaRecommendation(
            key, data, newRecs, shouldPrioritize, systemClock, debugLogger
@@ -572,7 +578,10 @@ class MediaCarouselController @Inject constructor(
                logger.logRecommendationRemoved(it.packageName, it.instanceId)
            }
        }
        val removed = MediaPlayerData.removeMediaPlayer(key)
        val removed = MediaPlayerData.removeMediaPlayer(
                key,
                dismissMediaData || dismissRecommendation
        )
        removed?.apply {
            mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
            mediaContent.removeView(removed.mediaViewHolder?.player)
@@ -835,18 +844,20 @@ class MediaCarouselController @Inject constructor(
    fun logSmartspaceImpression(qsExpanded: Boolean) {
        val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
        if (MediaPlayerData.players().size > visibleMediaIndex) {
            val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
            val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex)
            val hasActiveMediaOrRecommendationCard =
                    MediaPlayerData.hasActiveMediaOrRecommendationCard()
            if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
                // Skip logging if on LS or QQS, and there is no active media card
                return
            }
            mediaControlPanel?.let {
                logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
                    mediaControlPanel.mSmartspaceId,
                    mediaControlPanel.mUid,
                    intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
            mediaControlPanel.mIsImpressed = true
                        it.mSmartspaceId,
                        it.mUid,
                        intArrayOf(it.surfaceForSmartspaceLogging))
                it.mIsImpressed = true
            }
        }
    }

@@ -885,7 +896,7 @@ class MediaCarouselController @Inject constructor(
            return
        }

        val mediaControlKey = MediaPlayerData.playerKeys().elementAt(rank)
        val mediaControlKey = MediaPlayerData.visiblePlayerKeys().elementAt(rank)
        // Only log media resume card when Smartspace data is available
        if (!mediaControlKey.isSsMediaRec &&
                !mediaManager.smartspaceMediaData.isActive &&
@@ -960,7 +971,8 @@ class MediaCarouselController @Inject constructor(
        pw.apply {
            println("keysNeedRemoval: $keysNeedRemoval")
            println("dataKeys: ${MediaPlayerData.dataKeys()}")
            println("playerSortKeys: ${MediaPlayerData.playerKeys()}")
            println("orderedPlayerSortKeys: ${MediaPlayerData.playerKeys()}")
            println("visiblePlayerSortKeys: ${MediaPlayerData.visiblePlayerKeys()}")
            println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
            println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
            println("current size: $currentCarouselWidth x $currentCarouselHeight")
@@ -1000,6 +1012,7 @@ internal object MediaPlayerData {
    data class MediaSortKey(
        val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
        val data: MediaData,
        val key: String,
        val updateTime: Long = 0,
        val isSsReactivated: Boolean = false
    )
@@ -1018,6 +1031,8 @@ internal object MediaPlayerData {

    private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
    private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
    // A map that tracks order of visible media players before they get reordered.
    private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()

    fun addMediaPlayer(
        key: String,
@@ -1032,9 +1047,10 @@ internal object MediaPlayerData {
            debugLogger?.logPotentialMemoryLeak(key)
        }
        val sortKey = MediaSortKey(isSsMediaRec = false,
                data, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
                data, key, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
        mediaData.put(key, sortKey)
        mediaPlayers.put(sortKey, player)
        visibleMediaPlayers.put(key, sortKey)
    }

    fun addMediaRecommendation(
@@ -1050,10 +1066,16 @@ internal object MediaPlayerData {
        if (removedPlayer != null && removedPlayer != player) {
            debugLogger?.logPotentialMemoryLeak(key)
        }
        val sortKey = MediaSortKey(isSsMediaRec = true,
            EMPTY.copy(isPlaying = false), clock.currentTimeMillis(), isSsReactivated = true)
        val sortKey = MediaSortKey(
            isSsMediaRec = true,
            EMPTY.copy(isPlaying = false),
            key,
            clock.currentTimeMillis(),
            isSsReactivated = true
        )
        mediaData.put(key, sortKey)
        mediaPlayers.put(sortKey, player)
        visibleMediaPlayers.put(key, sortKey)
        smartspaceMediaData = data
    }

@@ -1067,12 +1089,18 @@ internal object MediaPlayerData {
        }

        mediaData.remove(oldKey)?.let {
            // MediaPlayer should not be visible
            // no need to set isDismissed flag.
            val removedPlayer = removeMediaPlayer(newKey)
            removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
            mediaData.put(newKey, it)
        }
    }

    fun getMediaControlPanel(visibleIndex: Int): MediaControlPanel? {
        return mediaPlayers.get(visiblePlayerKeys().elementAt(visibleIndex))
    }

    fun getMediaPlayer(key: String): MediaControlPanel? {
        return mediaData.get(key)?.let { mediaPlayers.get(it) }
    }
@@ -1087,10 +1115,17 @@ internal object MediaPlayerData {
        return -1
    }

    fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let {
    /**
     * Removes media player given the key.
     * @param isDismissed determines whether the media player is removed from the carousel.
     */
    fun removeMediaPlayer(key: String, isDismissed: Boolean = false) = mediaData.remove(key)?.let {
        if (it.isSsMediaRec) {
            smartspaceMediaData = null
        }
        if (isDismissed) {
            visibleMediaPlayers.remove(key)
        }
        mediaPlayers.remove(it)
    }

@@ -1102,6 +1137,8 @@ internal object MediaPlayerData {

    fun playerKeys() = mediaPlayers.keys

    fun visiblePlayerKeys() = visibleMediaPlayers.values

    /** Returns the index of the first non-timeout media. */
    fun firstActiveMediaIndex(): Int {
        mediaPlayers.entries.forEachIndexed { index, e ->
@@ -1126,6 +1163,7 @@ internal object MediaPlayerData {
    fun clear() {
        mediaData.clear()
        mediaPlayers.clear()
        visibleMediaPlayers.clear()
    }

    /* Returns true if there is active media player card or recommendation card */
@@ -1140,4 +1178,16 @@ internal object MediaPlayerData {
    }

    fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false

    /**
     * This method is called when media players are reordered.
     * To make sure we have the new version of the order of
     * media players visible to user.
     */
    fun updateVisibleMediaPlayers() {
        visibleMediaPlayers.clear()
        playerKeys().forEach {
            visibleMediaPlayers.put(it.key, it)
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1441,7 +1441,7 @@ public class MediaControlPanel {
            }

            // Automatically scroll to the active player once the media is loaded.
            mMediaCarouselController.setShouldScrollToActivePlayer(true);
            mMediaCarouselController.setShouldScrollToKey(true);
        });
    }

+56 −7
Original line number Diff line number Diff line
@@ -76,7 +76,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
    @Mock lateinit var dumpManager: DumpManager
    @Mock lateinit var logger: MediaUiEventLogger
    @Mock lateinit var debugLogger: MediaCarouselControllerLogger
    @Mock lateinit var mediaPlayer: MediaControlPanel
    @Mock lateinit var mediaViewController: MediaViewController
    @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
    @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@@ -107,8 +106,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
        verify(mediaDataManager).addListener(capture(listener))
        verify(visualStabilityProvider)
            .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
        whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer)
        whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController)
        whenever(mediaControlPanelFactory.get()).thenReturn(panel)
        whenever(panel.mediaViewController).thenReturn(mediaViewController)
        whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
        MediaPlayerData.clear()
    }
@@ -189,6 +188,10 @@ class MediaCarouselControllerTest : SysuiTestCase() {
        for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
            assertEquals(expected.get(index).first, key.data.notificationKey)
        }

        for ((index, key) in MediaPlayerData.visiblePlayerKeys().withIndex()) {
            assertEquals(expected.get(index).first, key.data.notificationKey)
        }
    }

    @Test
@@ -203,6 +206,22 @@ class MediaCarouselControllerTest : SysuiTestCase() {
        assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
    }

    @Test
    fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
        testPlayerOrdering()

        // If smartspace is prioritized
        listener.value.onSmartspaceMediaDataLoaded(
                SMARTSPACE_KEY,
                EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
                true
        )

        // Then it should be shown immediately after any actively playing controls
        assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
        assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
    }

    @Test
    fun testOrderWithSmartspace_notPrioritized() {
        testPlayerOrdering()
@@ -216,6 +235,31 @@ class MediaCarouselControllerTest : SysuiTestCase() {
        assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
    }

    @Test
    fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
        testPlayerOrdering()
        // playing paused player
        listener.value.onMediaDataLoaded("paused local",
                "paused local",
                DATA.copy(active = true, isPlaying = true,
                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
        listener.value.onMediaDataLoaded("playing local",
                "playing local",
                DATA.copy(active = true, isPlaying = false,
                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true)
        )

        assertEquals(
                MediaPlayerData.getMediaPlayerIndex("paused local"),
                mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
        )
        // paused player order should stays the same in visibleMediaPLayer map.
        // paused player order should be first in mediaPlayer map.
        assertEquals(
                MediaPlayerData.visiblePlayerKeys().elementAt(3),
                MediaPlayerData.playerKeys().elementAt(0)
        )
    }
    @Test
    fun testSwipeDismiss_logged() {
        mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
@@ -295,7 +339,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
        // adding a media recommendation card.
        listener.value.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA,
                false)
        mediaCarouselController.shouldScrollToActivePlayer = true
        mediaCarouselController.shouldScrollToKey = true
        // switching between media players.
        listener.value.onMediaDataLoaded("playing local",
        "playing local",
@@ -315,8 +359,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {

    @Test
    fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
                false, clock)
        listener.value.onSmartspaceMediaDataLoaded(
                SMARTSPACE_KEY,
                EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
                false
        )
        listener.value.onMediaDataLoaded("playing local",
                null,
                DATA.copy(active = true, isPlaying = true,
@@ -332,10 +379,12 @@ class MediaCarouselControllerTest : SysuiTestCase() {

        // Replaying the same media player one more time.
        // And check that the card stays in its position.
        mediaCarouselController.shouldScrollToKey = true
        listener.value.onMediaDataLoaded("playing local",
                null,
                DATA.copy(active = true, isPlaying = true,
                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false,
                        packageName = "PACKAGE_NAME")
        )
        playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
        assertEquals(playerIndex, 0)