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

Commit 6212919f authored by Beth Thibodeau's avatar Beth Thibodeau Committed by Automerger Merge Worker
Browse files

Merge "Filter media controls by user" into rvc-dev am: c781b2e4

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/12037671

Change-Id: Ife21e7b8baa5a6dfb8b4ae8fc74a7b86472014b3
parents 4a127ead c781b2e4
Loading
Loading
Loading
Loading
+2 −3
Original line number Original line Diff line number Diff line
@@ -39,9 +39,8 @@ class MediaCarouselController @Inject constructor(
    private val mediaHostStatesManager: MediaHostStatesManager,
    private val mediaHostStatesManager: MediaHostStatesManager,
    private val activityStarter: ActivityStarter,
    private val activityStarter: ActivityStarter,
    @Main executor: DelayableExecutor,
    @Main executor: DelayableExecutor,
    mediaManager: MediaDataCombineLatest,
    mediaManager: MediaDataFilter,
    configurationController: ConfigurationController,
    configurationController: ConfigurationController,
    mediaDataManager: MediaDataManager,
    falsingManager: FalsingManager
    falsingManager: FalsingManager
) {
) {
    /**
    /**
@@ -148,7 +147,7 @@ class MediaCarouselController @Inject constructor(
        mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
        mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
        pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
        pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
        mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
        mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
                executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
                executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
                falsingManager)
                falsingManager)
        isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
        isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
        inflateSettingsButton()
        inflateSettingsButton()
+1 −0
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import android.media.session.MediaSession


/** State of a media view. */
/** State of a media view. */
data class MediaData(
data class MediaData(
    val userId: Int,
    val initialized: Boolean = false,
    val initialized: Boolean = false,
    val backgroundColor: Int,
    val backgroundColor: Int,
    /**
    /**
+11 −0
Original line number Original line Diff line number Diff line
@@ -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].
     * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
     */
     */
+153 −0
Original line number Original line Diff line number Diff line
/*
 * 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)
}
 No newline at end of file
+13 −58
Original line number Original line Diff line number Diff line
@@ -67,7 +67,7 @@ private const val DEFAULT_LUMINOSITY = 0.25f
private const val LUMINOSITY_THRESHOLD = 0.05f
private const val LUMINOSITY_THRESHOLD = 0.05f
private const val SATURATION_MULTIPLIER = 0.8f
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)
        emptyList(), emptyList(), "INVALID", null, null, null, true, null)


fun isMediaNotification(sbn: StatusBarNotification): Boolean {
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
@@ -116,15 +116,6 @@ class MediaDataManager(
            broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
            broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
            Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
            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() {
    private val appChangeReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
        override fun onReceive(context: Context, intent: Intent) {
            when (intent.action) {
            when (intent.action) {
@@ -152,9 +143,6 @@ class MediaDataManager(
        mediaResumeListener.setManager(this)
        mediaResumeListener.setManager(this)
        addListener(mediaResumeListener)
        addListener(mediaResumeListener)


        val userFilter = IntentFilter(Intent.ACTION_USER_SWITCHED)
        broadcastDispatcher.registerReceiver(userChangeReceiver, userFilter, null, UserHandle.ALL)

        val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
        val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
        broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
        broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)


@@ -169,7 +157,6 @@ class MediaDataManager(


    fun destroy() {
    fun destroy() {
        context.unregisterReceiver(appChangeReceiver)
        context.unregisterReceiver(appChangeReceiver)
        broadcastDispatcher.unregisterReceiver(userChangeReceiver)
    }
    }


    fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
    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) {
    private fun removeAllForPackage(packageName: String) {
        Assert.isMainThread()
        Assert.isMainThread()
        val listenersCopy = listeners.toSet()
        val listenersCopy = listeners.toSet()
@@ -224,6 +197,7 @@ class MediaDataManager(
    }
    }


    fun addResumptionControls(
    fun addResumptionControls(
        userId: Int,
        desc: MediaDescription,
        desc: MediaDescription,
        action: Runnable,
        action: Runnable,
        token: MediaSession.Token,
        token: MediaSession.Token,
@@ -238,7 +212,8 @@ class MediaDataManager(
            mediaEntries.put(packageName, resumeData)
            mediaEntries.put(packageName, resumeData)
        }
        }
        backgroundExecutor.execute {
        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.
     * This will make the player not active anymore, hiding it from QQS and Keyguard.
     * @see MediaData.active
     * @see MediaData.active
     */
     */
    private fun setTimedOut(token: String, timedOut: Boolean) {
    internal fun setTimedOut(token: String, timedOut: Boolean) {
        mediaEntries[token]?.let {
        mediaEntries[token]?.let {
            if (it.active == !timedOut) {
            if (it.active == !timedOut) {
                return
                return
@@ -293,6 +268,7 @@ class MediaDataManager(
    }
    }


    private fun loadMediaDataInBgForResumption(
    private fun loadMediaDataInBgForResumption(
        userId: Int,
        desc: MediaDescription,
        desc: MediaDescription,
        resumeAction: Runnable,
        resumeAction: Runnable,
        token: MediaSession.Token,
        token: MediaSession.Token,
@@ -307,7 +283,7 @@ class MediaDataManager(
            return
            return
        }
        }


        Log.d(TAG, "adding track from browser: $desc")
        Log.d(TAG, "adding track for $userId from browser: $desc")


        // Album art
        // Album art
        var artworkBitmap = desc.iconBitmap
        var artworkBitmap = desc.iconBitmap
@@ -323,7 +299,7 @@ class MediaDataManager(


        val mediaAction = getResumeMediaAction(resumeAction)
        val mediaAction = getResumeMediaAction(resumeAction)
        foregroundExecutor.execute {
        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),
                    null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
                    packageName, token, appIntent, device = null, active = false,
                    packageName, token, appIntent, device = null, active = false,
                    resumeAction = resumeAction, resumption = true, notificationKey = packageName,
                    resumeAction = resumeAction, resumption = true, notificationKey = packageName,
@@ -439,10 +415,11 @@ class MediaDataManager(
            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
            val active = mediaEntries[key]?.active ?: true
            val active = mediaEntries[key]?.active ?: true
            onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
                    song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
                    smallIconDrawable, artist, song, artWorkIcon, actionIcons,
                    notif.contentIntent, null, active, resumeAction = resumeAction,
                    actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
                    notificationKey = key, hasCheckedForResume = hasCheckedForResume))
                    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) {
    fun setMediaResumptionEnabled(isEnabled: Boolean) {
        if (useMediaResumption == isEnabled) {
        if (useMediaResumption == isEnabled) {
            return
            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 {
    interface Listener {


        /**
        /**
Loading