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

Commit bb9ecdd2 authored by Beth Thibodeau's avatar Beth Thibodeau Committed by Android (Google) Code Review
Browse files

Merge "Fix sorting issues with remote and resume controls" into sc-v2-dev

parents d29e0cdf 8340c2b0
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -833,11 +833,12 @@ internal object MediaPlayerData {
    )

    private val comparator =
            compareByDescending<MediaSortKey> { it.data.isPlaying }
            compareByDescending<MediaSortKey> { it.data.isPlaying == true && it.data.isLocalSession }
                    .thenByDescending { it.data.isPlaying }
                    .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
                    .thenByDescending { it.data.isLocalSession }
                    .thenByDescending { !it.data.resumption }
                    .thenByDescending { it.updateTime }
                    .thenByDescending { !it.data.isLocalSession }

    private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
    private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+1 −1
Original line number Diff line number Diff line
@@ -768,7 +768,7 @@ class MediaDataManager(
            val resumeAction = getResumeMediaAction(removed.resumeAction!!)
            val updated = removed.copy(token = null, actions = listOf(resumeAction),
                    actionsToShowInCompact = listOf(0), active = false, resumption = true,
                    isClearable = true)
                    isPlaying = false, isClearable = true)
            val pkg = removed.packageName
            val migrate = mediaEntries.put(pkg, updated) == null
            // Notify listeners of "new" controls when migrating or removed and update when not
+174 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.media

import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import javax.inject.Provider

private val DATA = MediaData(
    userId = -1,
    initialized = false,
    backgroundColor = 0,
    app = null,
    appIcon = null,
    artist = null,
    song = null,
    artwork = null,
    actions = emptyList(),
    actionsToShowInCompact = emptyList(),
    packageName = "INVALID",
    token = null,
    clickIntent = null,
    device = null,
    active = true,
    resumeAction = null)

private val SMARTSPACE_KEY = "smartspace"

@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
class MediaCarouselControllerTest : SysuiTestCase() {

    @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
    @Mock lateinit var panel: MediaControlPanel
    @Mock lateinit var visualStabilityManager: VisualStabilityManager
    @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
    @Mock lateinit var activityStarter: ActivityStarter
    @Mock @Main private lateinit var executor: DelayableExecutor
    @Mock lateinit var mediaDataManager: MediaDataManager
    @Mock lateinit var configurationController: ConfigurationController
    @Mock lateinit var falsingCollector: FalsingCollector
    @Mock lateinit var falsingManager: FalsingManager
    @Mock lateinit var dumpManager: DumpManager

    private val clock = FakeSystemClock()
    private lateinit var mediaCarouselController: MediaCarouselController

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)

        mediaCarouselController = MediaCarouselController(
            context,
            mediaControlPanelFactory,
            visualStabilityManager,
            mediaHostStatesManager,
            activityStarter,
            clock,
            executor,
            mediaDataManager,
            configurationController,
            falsingCollector,
            falsingManager,
            dumpManager
        )

        MediaPlayerData.clear()
    }

    @Test
    fun testPlayerOrdering() {
        // Test values: key, data, last active time
        val playingLocal = Triple("playing local",
            DATA.copy(active = true, isPlaying = true, isLocalSession = true, resumption = false),
            4500L)

        val playingRemote = Triple("playing remote",
            DATA.copy(active = true, isPlaying = true, isLocalSession = false, resumption = false),
            5000L)

        val pausedLocal = Triple("paused local",
            DATA.copy(active = true, isPlaying = false, isLocalSession = true, resumption = false),
            1000L)

        val pausedRemote = Triple("paused remote",
            DATA.copy(active = true, isPlaying = false, isLocalSession = false, resumption = false),
            2000L)

        val resume1 = Triple("resume 1",
            DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
            500L)

        val resume2 = Triple("resume 2",
            DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
            1000L)

        // Expected ordering for media players:
        // Actively playing local sessions
        // Actively playing remote sessions
        // Paused sessions, by last active
        // Resume controls, by last active

        val expected = listOf(playingLocal, playingRemote, pausedRemote, pausedLocal, resume2,
            resume1)

        expected.forEach {
            clock.setCurrentTimeMillis(it.third)
            MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
                panel, clock)
        }

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

    @Test
    fun testOrderWithSmartspace_prioritized() {
        testPlayerOrdering()

        // If smartspace is prioritized
        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
            true, clock)

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

    @Test
    fun testOrderWithSmartspace_notPrioritized() {
        testPlayerOrdering()

        // If smartspace is not prioritized
        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
            false, clock)

        // Then it should be shown at the end of the carousel
        val size = MediaPlayerData.playerKeys().size
        assertTrue(MediaPlayerData.playerKeys().elementAt(size - 1).isSsMediaRec)
    }
}
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ class MediaDataFilterTest : SysuiTestCase() {
    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        MediaPlayerData.clear()
        mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
                lockscreenUserManager, executor, clock)
        mediaDataFilter.mediaDataManager = mediaDataManager
+1 −0
Original line number Diff line number Diff line
@@ -255,6 +255,7 @@ class MediaDataManagerTest : SysuiTestCase() {
            .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
                    eq(false))
        assertThat(mediaDataCaptor.value.resumption).isTrue()
        assertThat(mediaDataCaptor.value.isPlaying).isFalse()
    }

    @Test
Loading