Merge "Filter media controls by user" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c781b2e4b0
@@ -39,9 +39,8 @@ class MediaCarouselController @Inject constructor(
|
||||
private val mediaHostStatesManager: MediaHostStatesManager,
|
||||
private val activityStarter: ActivityStarter,
|
||||
@Main executor: DelayableExecutor,
|
||||
mediaManager: MediaDataCombineLatest,
|
||||
mediaManager: MediaDataFilter,
|
||||
configurationController: ConfigurationController,
|
||||
mediaDataManager: MediaDataManager,
|
||||
falsingManager: FalsingManager
|
||||
) {
|
||||
/**
|
||||
@@ -148,7 +147,7 @@ class MediaCarouselController @Inject constructor(
|
||||
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
|
||||
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
|
||||
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
|
||||
executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
|
||||
executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
|
||||
falsingManager)
|
||||
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
inflateSettingsButton()
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.media.session.MediaSession
|
||||
|
||||
/** State of a media view. */
|
||||
data class MediaData(
|
||||
val userId: Int,
|
||||
val initialized: Boolean = false,
|
||||
val backgroundColor: Int,
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ class MediaDataCombineLatest @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of all non-null data entries
|
||||
*/
|
||||
fun getData(): Map<String, MediaData> {
|
||||
return entries.filter {
|
||||
(key, pair) -> pair.first != null
|
||||
}.mapValues {
|
||||
(key, pair) -> pair.first!!
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.util.Log
|
||||
import com.android.internal.annotations.VisibleForTesting
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.settings.CurrentUserTracker
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val TAG = "MediaDataFilter"
|
||||
|
||||
/**
|
||||
* Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
|
||||
* switches (removing entries for the previous user, adding back entries for the current user)
|
||||
*
|
||||
* This is added downstream of [MediaDataManager] since we may still need to handle callbacks from
|
||||
* background users (e.g. timeouts) that UI classes should ignore.
|
||||
* Instead, UI classes should listen to this so they can stay in sync with the current user.
|
||||
*/
|
||||
@Singleton
|
||||
class MediaDataFilter @Inject constructor(
|
||||
private val dataSource: MediaDataCombineLatest,
|
||||
private val broadcastDispatcher: BroadcastDispatcher,
|
||||
private val mediaResumeListener: MediaResumeListener,
|
||||
private val mediaDataManager: MediaDataManager,
|
||||
private val lockscreenUserManager: NotificationLockscreenUserManager,
|
||||
@Main private val executor: Executor
|
||||
) : MediaDataManager.Listener {
|
||||
private val userTracker: CurrentUserTracker
|
||||
private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
|
||||
|
||||
// The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
|
||||
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
|
||||
|
||||
init {
|
||||
userTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
||||
override fun onUserSwitched(newUserId: Int) {
|
||||
// Post this so we can be sure lockscreenUserManager already got the broadcast
|
||||
executor.execute { handleUserSwitched(newUserId) }
|
||||
}
|
||||
}
|
||||
userTracker.startTracking()
|
||||
dataSource.addListener(this)
|
||||
}
|
||||
|
||||
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
|
||||
if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (oldKey != null) {
|
||||
mediaEntries.remove(oldKey)
|
||||
}
|
||||
mediaEntries.put(key, data)
|
||||
|
||||
// Notify listeners
|
||||
val listenersCopy = listeners.toSet()
|
||||
listenersCopy.forEach {
|
||||
it.onMediaDataLoaded(key, oldKey, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMediaDataRemoved(key: String) {
|
||||
mediaEntries.remove(key)?.let {
|
||||
// Only notify listeners if something actually changed
|
||||
val listenersCopy = listeners.toSet()
|
||||
listenersCopy.forEach {
|
||||
it.onMediaDataRemoved(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun handleUserSwitched(id: Int) {
|
||||
// If the user changes, remove all current MediaData objects and inform listeners
|
||||
val listenersCopy = listeners.toSet()
|
||||
val keyCopy = mediaEntries.keys.toMutableList()
|
||||
// Clear the list first, to make sure callbacks from listeners if we have any entries
|
||||
// are up to date
|
||||
mediaEntries.clear()
|
||||
keyCopy.forEach {
|
||||
Log.d(TAG, "Removing $it after user change")
|
||||
listenersCopy.forEach { listener ->
|
||||
listener.onMediaDataRemoved(it)
|
||||
}
|
||||
}
|
||||
|
||||
dataSource.getData().forEach { (key, data) ->
|
||||
if (lockscreenUserManager.isCurrentProfile(data.userId)) {
|
||||
Log.d(TAG, "Re-adding $key after user change")
|
||||
mediaEntries.put(key, data)
|
||||
listenersCopy.forEach { listener ->
|
||||
listener.onMediaDataLoaded(key, null, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user has dismissed the media carousel
|
||||
*/
|
||||
fun onSwipeToDismiss() {
|
||||
val mediaKeys = mediaEntries.keys.toSet()
|
||||
mediaKeys.forEach {
|
||||
mediaDataManager.setTimedOut(it, timedOut = true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are there any media notifications active?
|
||||
*/
|
||||
fun hasActiveMedia() = mediaEntries.any { it.value.active }
|
||||
|
||||
/**
|
||||
* Are there any media entries we should display?
|
||||
* If resumption is enabled, this will include inactive players
|
||||
* If resumption is disabled, we only want to show active players
|
||||
*/
|
||||
fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
|
||||
mediaEntries.isNotEmpty()
|
||||
} else {
|
||||
hasActiveMedia()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for filtered [MediaData] changes
|
||||
*/
|
||||
fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
|
||||
|
||||
/**
|
||||
* Remove a listener that was registered with addListener
|
||||
*/
|
||||
fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
|
||||
}
|
||||
@@ -67,7 +67,7 @@ private const val DEFAULT_LUMINOSITY = 0.25f
|
||||
private const val LUMINOSITY_THRESHOLD = 0.05f
|
||||
private const val SATURATION_MULTIPLIER = 0.8f
|
||||
|
||||
private val LOADING = MediaData(false, 0, null, null, null, null, null,
|
||||
private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
|
||||
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
|
||||
|
||||
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
||||
@@ -116,15 +116,6 @@ class MediaDataManager(
|
||||
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
|
||||
Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
|
||||
|
||||
private val userChangeReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (Intent.ACTION_USER_SWITCHED == intent.action) {
|
||||
// Remove all controls, regardless of state
|
||||
clearData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val appChangeReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
@@ -152,9 +143,6 @@ class MediaDataManager(
|
||||
mediaResumeListener.setManager(this)
|
||||
addListener(mediaResumeListener)
|
||||
|
||||
val userFilter = IntentFilter(Intent.ACTION_USER_SWITCHED)
|
||||
broadcastDispatcher.registerReceiver(userChangeReceiver, userFilter, null, UserHandle.ALL)
|
||||
|
||||
val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
|
||||
broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
|
||||
|
||||
@@ -169,7 +157,6 @@ class MediaDataManager(
|
||||
|
||||
fun destroy() {
|
||||
context.unregisterReceiver(appChangeReceiver)
|
||||
broadcastDispatcher.unregisterReceiver(userChangeReceiver)
|
||||
}
|
||||
|
||||
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
||||
@@ -190,20 +177,6 @@ class MediaDataManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearData() {
|
||||
// Called on user change. Remove all current MediaData objects and inform listeners
|
||||
val listenersCopy = listeners.toSet()
|
||||
val keyCopy = mediaEntries.keys.toMutableList()
|
||||
// Clear the list first, to make sure callbacks from listeners if we have any entries
|
||||
// are up to date
|
||||
mediaEntries.clear()
|
||||
keyCopy.forEach {
|
||||
listenersCopy.forEach { listener ->
|
||||
listener.onMediaDataRemoved(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeAllForPackage(packageName: String) {
|
||||
Assert.isMainThread()
|
||||
val listenersCopy = listeners.toSet()
|
||||
@@ -224,6 +197,7 @@ class MediaDataManager(
|
||||
}
|
||||
|
||||
fun addResumptionControls(
|
||||
userId: Int,
|
||||
desc: MediaDescription,
|
||||
action: Runnable,
|
||||
token: MediaSession.Token,
|
||||
@@ -238,7 +212,8 @@ class MediaDataManager(
|
||||
mediaEntries.put(packageName, resumeData)
|
||||
}
|
||||
backgroundExecutor.execute {
|
||||
loadMediaDataInBgForResumption(desc, action, token, appName, appIntent, packageName)
|
||||
loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent,
|
||||
packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +257,7 @@ class MediaDataManager(
|
||||
* This will make the player not active anymore, hiding it from QQS and Keyguard.
|
||||
* @see MediaData.active
|
||||
*/
|
||||
private fun setTimedOut(token: String, timedOut: Boolean) {
|
||||
internal fun setTimedOut(token: String, timedOut: Boolean) {
|
||||
mediaEntries[token]?.let {
|
||||
if (it.active == !timedOut) {
|
||||
return
|
||||
@@ -293,6 +268,7 @@ class MediaDataManager(
|
||||
}
|
||||
|
||||
private fun loadMediaDataInBgForResumption(
|
||||
userId: Int,
|
||||
desc: MediaDescription,
|
||||
resumeAction: Runnable,
|
||||
token: MediaSession.Token,
|
||||
@@ -307,7 +283,7 @@ class MediaDataManager(
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "adding track from browser: $desc")
|
||||
Log.d(TAG, "adding track for $userId from browser: $desc")
|
||||
|
||||
// Album art
|
||||
var artworkBitmap = desc.iconBitmap
|
||||
@@ -323,7 +299,7 @@ class MediaDataManager(
|
||||
|
||||
val mediaAction = getResumeMediaAction(resumeAction)
|
||||
foregroundExecutor.execute {
|
||||
onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName,
|
||||
onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
|
||||
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
|
||||
packageName, token, appIntent, device = null, active = false,
|
||||
resumeAction = resumeAction, resumption = true, notificationKey = packageName,
|
||||
@@ -439,10 +415,11 @@ class MediaDataManager(
|
||||
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
|
||||
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
|
||||
val active = mediaEntries[key]?.active ?: true
|
||||
onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
|
||||
song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
|
||||
notif.contentIntent, null, active, resumeAction = resumeAction,
|
||||
notificationKey = key, hasCheckedForResume = hasCheckedForResume))
|
||||
onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
|
||||
smallIconDrawable, artist, song, artWorkIcon, actionIcons,
|
||||
actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
|
||||
active, resumeAction = resumeAction, notificationKey = key,
|
||||
hasCheckedForResume = hasCheckedForResume))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,18 +541,6 @@ class MediaDataManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are there any media notifications active?
|
||||
*/
|
||||
fun hasActiveMedia() = mediaEntries.any { it.value.active }
|
||||
|
||||
/**
|
||||
* Are there any media entries we should display?
|
||||
* If resumption is enabled, this will include inactive players
|
||||
* If resumption is disabled, we only want to show active players
|
||||
*/
|
||||
fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia()
|
||||
|
||||
fun setMediaResumptionEnabled(isEnabled: Boolean) {
|
||||
if (useMediaResumption == isEnabled) {
|
||||
return
|
||||
@@ -596,16 +561,6 @@ class MediaDataManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user has dismissed the media carousel
|
||||
*/
|
||||
fun onSwipeToDismiss() {
|
||||
val mediaKeys = mediaEntries.keys.toSet()
|
||||
mediaKeys.forEach {
|
||||
setTimedOut(it, timedOut = true)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,8 +14,7 @@ import javax.inject.Inject
|
||||
class MediaHost @Inject constructor(
|
||||
private val state: MediaHostStateHolder,
|
||||
private val mediaHierarchyManager: MediaHierarchyManager,
|
||||
private val mediaDataManager: MediaDataManager,
|
||||
private val mediaDataManagerCombineLatest: MediaDataCombineLatest,
|
||||
private val mediaDataFilter: MediaDataFilter,
|
||||
private val mediaHostStatesManager: MediaHostStatesManager
|
||||
) : MediaHostState by state {
|
||||
lateinit var hostView: UniqueObjectHostView
|
||||
@@ -80,12 +79,12 @@ class MediaHost @Inject constructor(
|
||||
// be a delay until the views and the controllers are initialized, leaving us
|
||||
// with either a blank view or the controllers not yet initialized and the
|
||||
// measuring wrong
|
||||
mediaDataManagerCombineLatest.addListener(listener)
|
||||
mediaDataFilter.addListener(listener)
|
||||
updateViewVisibility()
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(v: View?) {
|
||||
mediaDataManagerCombineLatest.removeListener(listener)
|
||||
mediaDataFilter.removeListener(listener)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -114,9 +113,9 @@ class MediaHost @Inject constructor(
|
||||
|
||||
private fun updateViewVisibility() {
|
||||
visible = if (showsOnlyActiveMedia) {
|
||||
mediaDataManager.hasActiveMedia()
|
||||
mediaDataFilter.hasActiveMedia()
|
||||
} else {
|
||||
mediaDataManager.hasAnyMedia()
|
||||
mediaDataFilter.hasAnyMedia()
|
||||
}
|
||||
val newVisibility = if (visible) View.VISIBLE else View.GONE
|
||||
if (newVisibility != hostView.visibility) {
|
||||
|
||||
@@ -56,7 +56,7 @@ class MediaResumeListener @Inject constructor(
|
||||
private lateinit var mediaDataManager: MediaDataManager
|
||||
|
||||
private var mediaBrowser: ResumeMediaBrowser? = null
|
||||
private var currentUserId: Int
|
||||
private var currentUserId: Int = context.userId
|
||||
|
||||
private val userChangeReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
@@ -65,7 +65,6 @@ class MediaResumeListener @Inject constructor(
|
||||
} else if (Intent.ACTION_USER_SWITCHED == intent.action) {
|
||||
currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
|
||||
loadSavedComponents()
|
||||
loadMediaResumptionControls()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,13 +88,12 @@ class MediaResumeListener @Inject constructor(
|
||||
}
|
||||
|
||||
Log.d(TAG, "Adding resume controls $desc")
|
||||
mediaDataManager.addResumptionControls(desc, resumeAction, token, appName.toString(),
|
||||
appIntent, component.packageName)
|
||||
mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token,
|
||||
appName.toString(), appIntent, component.packageName)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
currentUserId = context.userId
|
||||
if (useMediaResumption) {
|
||||
val unlockFilter = IntentFilter()
|
||||
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
|
||||
@@ -118,6 +116,8 @@ class MediaResumeListener @Inject constructor(
|
||||
}, Settings.Secure.MEDIA_CONTROLS_RESUME)
|
||||
}
|
||||
|
||||
fun isResumptionEnabled() = useMediaResumption
|
||||
|
||||
private fun loadSavedComponents() {
|
||||
// Make sure list is empty (if we switched users)
|
||||
resumeComponents.clear()
|
||||
|
||||
@@ -64,6 +64,7 @@ private const val DEVICE_NAME = "DEVICE_NAME"
|
||||
private const val SESSION_KEY = "SESSION_KEY"
|
||||
private const val SESSION_ARTIST = "SESSION_ARTIST"
|
||||
private const val SESSION_TITLE = "SESSION_TITLE"
|
||||
private const val USER_ID = 0
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -180,7 +181,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun bindWhenUnattached() {
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, null, null, device, true, null)
|
||||
player.bind(state)
|
||||
assertThat(player.isPlaying()).isFalse()
|
||||
@@ -189,7 +190,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun bindText() {
|
||||
player.attach(holder)
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
|
||||
player.bind(state)
|
||||
assertThat(appName.getText()).isEqualTo(APP)
|
||||
@@ -200,7 +201,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun bindBackgroundColor() {
|
||||
player.attach(holder)
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
|
||||
player.bind(state)
|
||||
val list = ArgumentCaptor.forClass(ColorStateList::class.java)
|
||||
@@ -211,7 +212,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun bindDevice() {
|
||||
player.attach(holder)
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
|
||||
player.bind(state)
|
||||
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
||||
@@ -223,7 +224,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
seamless.id = 1
|
||||
seamlessFallback.id = 2
|
||||
player.attach(holder)
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
|
||||
player.bind(state)
|
||||
verify(expandedSet).setVisibility(seamless.id, View.GONE)
|
||||
@@ -235,7 +236,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun bindNullDevice() {
|
||||
player.attach(holder)
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
|
||||
player.bind(state)
|
||||
assertThat(seamless.isEnabled()).isTrue()
|
||||
@@ -246,7 +247,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun bindDeviceResumptionPlayer() {
|
||||
player.attach(holder)
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
|
||||
resumption = true)
|
||||
player.bind(state)
|
||||
|
||||
@@ -52,6 +52,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
|
||||
private static final String ARTIST = "ARTIST";
|
||||
private static final String TITLE = "TITLE";
|
||||
private static final String DEVICE_NAME = "DEVICE_NAME";
|
||||
private static final int USER_ID = 0;
|
||||
|
||||
private MediaDataCombineLatest mManager;
|
||||
|
||||
@@ -78,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
|
||||
|
||||
mManager.addListener(mListener);
|
||||
|
||||
mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
|
||||
mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
|
||||
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
|
||||
KEY, false);
|
||||
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.graphics.Color
|
||||
import androidx.test.filters.SmallTest
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
private const val KEY = "TEST_KEY"
|
||||
private const val KEY_ALT = "TEST_KEY_2"
|
||||
private const val USER_MAIN = 0
|
||||
private const val USER_GUEST = 10
|
||||
private const val APP = "APP"
|
||||
private const val BG_COLOR = Color.RED
|
||||
private const val PACKAGE = "PKG"
|
||||
private const val ARTIST = "ARTIST"
|
||||
private const val TITLE = "TITLE"
|
||||
private const val DEVICE_NAME = "DEVICE_NAME"
|
||||
|
||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||
private fun <T> any(): T = Mockito.any()
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class MediaDataFilterTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var combineLatest: MediaDataCombineLatest
|
||||
@Mock
|
||||
private lateinit var listener: MediaDataManager.Listener
|
||||
@Mock
|
||||
private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||
@Mock
|
||||
private lateinit var mediaResumeListener: MediaResumeListener
|
||||
@Mock
|
||||
private lateinit var mediaDataManager: MediaDataManager
|
||||
@Mock
|
||||
private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
|
||||
@Mock
|
||||
private lateinit var executor: Executor
|
||||
|
||||
private lateinit var mediaDataFilter: MediaDataFilter
|
||||
private lateinit var dataMain: MediaData
|
||||
private lateinit var dataGuest: MediaData
|
||||
private val device = MediaDeviceData(true, null, DEVICE_NAME)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
mediaDataFilter = MediaDataFilter(combineLatest, broadcastDispatcher, mediaResumeListener,
|
||||
mediaDataManager, lockscreenUserManager, executor)
|
||||
mediaDataFilter.addListener(listener)
|
||||
|
||||
// Start all tests as main user
|
||||
setUser(USER_MAIN)
|
||||
|
||||
// Set up test media data
|
||||
dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, null, null, device, true, null)
|
||||
|
||||
dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
|
||||
emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
|
||||
}
|
||||
|
||||
private fun setUser(id: Int) {
|
||||
`when`(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
|
||||
`when`(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
|
||||
mediaDataFilter.handleUserSwitched(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDataLoadedForCurrentUser_callsListener() {
|
||||
// GIVEN a media for main user
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
|
||||
|
||||
// THEN we should tell the listener
|
||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDataLoadedForGuest_doesNotCallListener() {
|
||||
// GIVEN a media for guest user
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
|
||||
|
||||
// THEN we should NOT tell the listener
|
||||
verify(listener, never()).onMediaDataLoaded(any(), any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnRemovedForCurrent_callsListener() {
|
||||
// GIVEN a media was removed for main user
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
|
||||
mediaDataFilter.onMediaDataRemoved(KEY)
|
||||
|
||||
// THEN we should tell the listener
|
||||
verify(listener).onMediaDataRemoved(eq(KEY))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnRemovedForGuest_doesNotCallListener() {
|
||||
// GIVEN a media was removed for guest user
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
|
||||
mediaDataFilter.onMediaDataRemoved(KEY)
|
||||
|
||||
// THEN we should NOT tell the listener
|
||||
verify(listener, never()).onMediaDataRemoved(eq(KEY))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnUserSwitched_removesOldUserControls() {
|
||||
// GIVEN that we have a media loaded for main user
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
|
||||
|
||||
// and we switch to guest user
|
||||
setUser(USER_GUEST)
|
||||
|
||||
// THEN we should remove the main user's media
|
||||
verify(listener).onMediaDataRemoved(eq(KEY))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnUserSwitched_addsNewUserControls() {
|
||||
// GIVEN that we had some media for both users
|
||||
val dataMap = mapOf(KEY to dataMain, KEY_ALT to dataGuest)
|
||||
`when`(combineLatest.getData()).thenReturn(dataMap)
|
||||
|
||||
// and we switch to guest user
|
||||
setUser(USER_GUEST)
|
||||
|
||||
// THEN we should add back the guest user media
|
||||
verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest))
|
||||
|
||||
// but not the main user's
|
||||
verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasAnyMedia() {
|
||||
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
|
||||
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
|
||||
assertThat(mediaDataFilter.hasAnyMedia()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasActiveMedia() {
|
||||
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
||||
val data = dataMain.copy(active = true)
|
||||
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasAnyMedia_onlyCurrentUser() {
|
||||
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
|
||||
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataGuest)
|
||||
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasActiveMedia_onlyCurrentUser() {
|
||||
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
||||
val data = dataGuest.copy(active = true)
|
||||
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationRemoved_doesntHaveMedia() {
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
|
||||
mediaDataFilter.onMediaDataRemoved(KEY)
|
||||
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnSwipeToDismiss_setsTimedOut() {
|
||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
|
||||
mediaDataFilter.onSwipeToDismiss()
|
||||
|
||||
verify(mediaDataManager).setTimedOut(eq(KEY), eq(true))
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ private const val PACKAGE_NAME = "com.android.systemui"
|
||||
private const val APP_NAME = "SystemUI"
|
||||
private const val SESSION_ARTIST = "artist"
|
||||
private const val SESSION_TITLE = "title"
|
||||
private const val USER_ID = 0
|
||||
|
||||
private fun <T> anyObject(): T {
|
||||
return Mockito.anyObject<T>()
|
||||
@@ -91,28 +92,15 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasActiveMedia() {
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isFalse()
|
||||
val data = mock(MediaData::class.java)
|
||||
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isFalse()
|
||||
|
||||
whenever(data.active).thenReturn(true)
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnSwipeToDismiss_deactivatesMedia() {
|
||||
val data = MediaData(initialized = true, backgroundColor = 0, app = null, appIcon = null,
|
||||
artist = null, song = null, artwork = null, actions = emptyList(),
|
||||
fun testSetTimedOut_deactivatesMedia() {
|
||||
val data = MediaData(userId = USER_ID, initialized = true, 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)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
|
||||
mediaDataManager.onSwipeToDismiss()
|
||||
mediaDataManager.setTimedOut(KEY, timedOut = true)
|
||||
assertThat(data.active).isFalse()
|
||||
}
|
||||
|
||||
@@ -141,37 +129,6 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(listener.data!!.active).isTrue()
|
||||
|
||||
// Swiping away makes the notification not active
|
||||
mediaDataManager.onSwipeToDismiss()
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isFalse()
|
||||
|
||||
// And when a notification is updated
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
|
||||
// MediaData should still be inactive
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasAnyMedia_whenAddingMedia() {
|
||||
assertThat(mediaDataManager.hasAnyMedia()).isFalse()
|
||||
val data = mock(MediaData::class.java)
|
||||
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
assertThat(mediaDataManager.hasAnyMedia()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationRemoved_doesntHaveMedia() {
|
||||
val data = mock(MediaData::class.java)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
mediaDataManager.onNotificationRemoved(KEY)
|
||||
assertThat(mediaDataManager.hasAnyMedia()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -212,8 +169,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
setTitle(SESSION_TITLE)
|
||||
build()
|
||||
}
|
||||
mediaDataManager.addResumptionControls(desc, Runnable {}, session.sessionToken, APP_NAME,
|
||||
pendingIntent, PACKAGE_NAME)
|
||||
mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
|
||||
APP_NAME, pendingIntent, PACKAGE_NAME)
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
// THEN the media data indicates that it is for resumption
|
||||
|
||||
@@ -58,6 +58,7 @@ private const val SESSION_KEY = "SESSION_KEY"
|
||||
private const val SESSION_ARTIST = "SESSION_ARTIST"
|
||||
private const val SESSION_TITLE = "SESSION_TITLE"
|
||||
private const val DEVICE_NAME = "DEVICE_NAME"
|
||||
private const val USER_ID = 0
|
||||
|
||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||
|
||||
@@ -118,7 +119,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
||||
setSmallIcon(android.R.drawable.ic_media_pause)
|
||||
setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
|
||||
}
|
||||
mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
||||
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
||||
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
|
||||
device = null, active = true, resumeAction = null)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ private const val PACKAGE = "PKG"
|
||||
private const val SESSION_KEY = "SESSION_KEY"
|
||||
private const val SESSION_ARTIST = "SESSION_ARTIST"
|
||||
private const val SESSION_TITLE = "SESSION_TITLE"
|
||||
private const val USER_ID = 0
|
||||
|
||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||
private fun <T> anyObject(): T {
|
||||
@@ -95,7 +96,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
||||
setPlaybackState(playbackBuilder.build())
|
||||
}
|
||||
session.setActive(true)
|
||||
mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
||||
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
||||
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
|
||||
device = null, active = true, resumeAction = null)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user