Merge "Fix sorting issues with remote and resume controls" into sc-v2-dev am: bb9ecdd2af
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15985269 Change-Id: I250c418b7f56122ebccccd55bb15e52041f40bd0
This commit is contained in:
@@ -833,11 +833,12 @@ internal object MediaPlayerData {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val comparator =
|
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 { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
|
||||||
.thenByDescending { it.data.isLocalSession }
|
|
||||||
.thenByDescending { !it.data.resumption }
|
.thenByDescending { !it.data.resumption }
|
||||||
.thenByDescending { it.updateTime }
|
.thenByDescending { it.updateTime }
|
||||||
|
.thenByDescending { !it.data.isLocalSession }
|
||||||
|
|
||||||
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
|
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
|
||||||
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
|
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
|
||||||
|
|||||||
@@ -768,7 +768,7 @@ class MediaDataManager(
|
|||||||
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
|
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
|
||||||
val updated = removed.copy(token = null, actions = listOf(resumeAction),
|
val updated = removed.copy(token = null, actions = listOf(resumeAction),
|
||||||
actionsToShowInCompact = listOf(0), active = false, resumption = true,
|
actionsToShowInCompact = listOf(0), active = false, resumption = true,
|
||||||
isClearable = true)
|
isPlaying = false, isClearable = true)
|
||||||
val pkg = removed.packageName
|
val pkg = removed.packageName
|
||||||
val migrate = mediaEntries.put(pkg, updated) == null
|
val migrate = mediaEntries.put(pkg, updated) == null
|
||||||
// Notify listeners of "new" controls when migrating or removed and update when not
|
// Notify listeners of "new" controls when migrating or removed and update when not
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,7 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
MediaPlayerData.clear()
|
||||||
mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
|
mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
|
||||||
lockscreenUserManager, executor, clock)
|
lockscreenUserManager, executor, clock)
|
||||||
mediaDataFilter.mediaDataManager = mediaDataManager
|
mediaDataFilter.mediaDataManager = mediaDataManager
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
.onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
|
.onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
|
||||||
eq(false))
|
eq(false))
|
||||||
assertThat(mediaDataCaptor.value.resumption).isTrue()
|
assertThat(mediaDataCaptor.value.resumption).isTrue()
|
||||||
|
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ public class MediaPlayerDataTest : SysuiTestCase() {
|
|||||||
val players = MediaPlayerData.players()
|
val players = MediaPlayerData.players()
|
||||||
assertThat(players).hasSize(6)
|
assertThat(players).hasSize(6)
|
||||||
assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
|
assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
|
||||||
playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote,
|
playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerCanResume,
|
||||||
playerUndetermined).inOrder()
|
playerUndetermined).inOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user