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

Commit a0ecdaaf authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "On Smartspace removal update, only dismiss media recommendation/player...

Merge "On Smartspace removal update, only dismiss media recommendation/player when it's invisible to users." into sc-dev
parents 7d83e6a2 38c6f2bf
Loading
Loading
Loading
Loading
+48 −21
Original line number Diff line number Diff line
package com.android.systemui.media

import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -184,7 +183,12 @@ class MediaCarouselController @Inject constructor(
        visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback,
                true /* persistent */)
        mediaManager.addListener(object : MediaDataManager.Listener {
            override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
            override fun onMediaDataLoaded(
                key: String,
                oldKey: String?,
                data: MediaData,
                immediately: Boolean
            ) {
                if (addOrUpdatePlayer(key, oldKey, data)) {
                    MediaPlayerData.getMediaPlayer(key, null)?.let {
                        logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
@@ -210,10 +214,11 @@ class MediaCarouselController @Inject constructor(

            override fun onSmartspaceMediaDataLoaded(
                key: String,
                data: SmartspaceTarget,
                data: SmartspaceMediaData,
                shouldPrioritize: Boolean
            ) {
                Log.d(TAG, "My Smartspace media update is here")
                if (data.isActive) {
                    addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
                    MediaPlayerData.getMediaPlayer(key, null)?.let {
                        logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
@@ -224,15 +229,22 @@ class MediaCarouselController @Inject constructor(
                    if (mediaCarouselScrollHandler.visibleToUser) {
                        logSmartspaceImpression()
                    }
                } else {
                    onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
                }
            }

            override fun onMediaDataRemoved(key: String) {
                removePlayer(key)
            }

            override fun onSmartspaceMediaDataRemoved(key: String) {
            override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
                Log.d(TAG, "My Smartspace media removal request is received")
                removePlayer(key)
                if (immediately || visualStabilityManager.isReorderingAllowed) {
                    onMediaDataRemoved(key)
                } else {
                    keysNeedRemoval.add(key)
                }
            }
        })
        mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
@@ -287,7 +299,7 @@ class MediaCarouselController @Inject constructor(
        // Automatically scroll to the active player if needed
        if (shouldScrollToActivePlayer) {
            shouldScrollToActivePlayer = false
            val activeMediaIndex = MediaPlayerData.getActiveMediaIndex()
            val activeMediaIndex = MediaPlayerData.activeMediaIndex()
            if (activeMediaIndex != -1) {
                mediaCarouselScrollHandler.scrollToActivePlayer(activeMediaIndex)
            }
@@ -333,7 +345,7 @@ class MediaCarouselController @Inject constructor(

    private fun addSmartspaceMediaRecommendations(
        key: String,
        data: SmartspaceTarget,
        data: SmartspaceMediaData,
        shouldPrioritize: Boolean
    ) {
        Log.d(TAG, "Updating smartspace target in carousel")
@@ -342,6 +354,11 @@ class MediaCarouselController @Inject constructor(
            return
        }

        val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
        existingSmartspaceMediaKey?.let {
            MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey)
        }

        var newRecs = mediaControlPanelFactory.get()
        newRecs.attachRecommendation(
            RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
@@ -349,7 +366,7 @@ class MediaCarouselController @Inject constructor(
        val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT)
        newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
        newRecs.bindRecommendation(data, bgColor)
        newRecs.bindRecommendation(data.copy(backgroundColor = bgColor))
        MediaPlayerData.addMediaRecommendation(key, newRecs, shouldPrioritize)
        updatePlayerToState(newRecs, noAnimation = true)
        reorderAllPlayers()
@@ -378,11 +395,11 @@ class MediaCarouselController @Inject constructor(

            if (dismissMediaData) {
                // Inform the media manager of a potentially late dismissal
                mediaManager.dismissMediaData(key, 0L /* delaye */)
                mediaManager.dismissMediaData(key, delay = 0L)
            }
            if (dismissRecommendation) {
                // Inform the media manager of a potentially late dismissal
                mediaManager.dismissSmartspaceRecommendation(0L /* delay */)
                mediaManager.dismissSmartspaceRecommendation(key, delay = 0L)
            }
        }
    }
@@ -392,7 +409,7 @@ class MediaCarouselController @Inject constructor(
        pageIndicator.tintList = ColorStateList.valueOf(getForegroundColor())

        MediaPlayerData.mediaData().forEach { (key, data) ->
            removePlayer(key, dismissMediaData = false)
            removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
            addOrUpdatePlayer(key = key, oldKey = null, data = data)
        }
    }
@@ -732,7 +749,7 @@ internal object MediaPlayerData {
    fun players() = mediaPlayers.values

    /** Returns the index of the first non-timeout media. */
    fun getActiveMediaIndex(): Int {
    fun activeMediaIndex(): Int {
        mediaPlayers.entries.forEachIndexed { index, e ->
            if (!e.key.isSsMediaRec && e.key.data.active) {
                return index
@@ -741,6 +758,16 @@ internal object MediaPlayerData {
        return -1
    }

    /** Returns the existing Smartspace target id. */
    fun smartspaceMediaKey(): String? {
        mediaData.entries.forEach { e ->
            if (e.value.isSsMediaRec) {
                return e.key
            }
        }
        return null
    }

    fun playerKeys() = mediaPlayers.keys

    @VisibleForTesting
+1 −3
Original line number Diff line number Diff line
@@ -560,12 +560,10 @@ class MediaCarouselScrollHandler(
    }

    fun scrollToActivePlayer(activePlayerIndex: Int) {
        var destIndex = activePlayerIndex
        destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
        val destIndex = Math.min(mediaContent.getChildCount() - 1, activePlayerIndex)
        val view = mediaContent.getChildAt(destIndex)
        // We need to post this to wait for the active player becomes visible.
        mainExecutor.executeDelayed({
            visibleMediaIndex = activePlayerIndex
            scrollView.smoothScrollTo(view.left, scrollView.scrollY)
        }, SCROLL_DELAY)
    }
+14 −46
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;

import android.app.PendingIntent;
import android.app.smartspace.SmartspaceAction;
import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -34,7 +33,6 @@ import android.graphics.drawable.Icon;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.text.Layout;
import android.util.Log;
import android.view.View;
@@ -74,7 +72,6 @@ import kotlin.Unit;
public class MediaControlPanel {
    private static final String TAG = "MediaControlPanel";
    private static final float DISABLED_ALPHA = 0.38f;
    private static final String EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name";
    private static final String EXTRAS_SMARTSPACE_INTENT =
            "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
    private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
@@ -493,27 +490,30 @@ public class MediaControlPanel {
        };
    }

    /** Bind this recommendation view based on the data given. */
    public void bindRecommendation(@NonNull SmartspaceTarget target, @NonNull int backgroundColor) {
    /** Bind this recommendation view based on the given data. */
    public void bindRecommendation(@NonNull SmartspaceMediaData data) {
        if (mRecommendationViewHolder == null) {
            return;
        }

        mInstanceId = target.getSmartspaceTargetId().hashCode();
        mInstanceId = data.getTargetId().hashCode();
        mBackgroundColor = data.getBackgroundColor();
        mRecommendationViewHolder.getRecommendations()
                .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
        mBackgroundColor = backgroundColor;
                .setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));

        List<SmartspaceAction> mediaRecommendationList = target.getIconGrid();
        List<SmartspaceAction> mediaRecommendationList = data.getRecommendations();
        if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) {
            Log.w(TAG, "Empty media recommendations");
            return;
        }

        // Set up recommendation card's header.
        ApplicationInfo applicationInfo = getApplicationInfo(target);
        if (applicationInfo == null) {
            Log.w(TAG, "No valid application info is found for media recommendations");
        ApplicationInfo applicationInfo = null;
        try {
            applicationInfo = mContext.getPackageManager()
                    .getApplicationInfo(data.getPackageName(), 0 /* flags */);
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Fail to get media recommendation's app info", e);
            return;
        }

@@ -531,7 +531,7 @@ public class MediaControlPanel {
        }
        // Set up media card's tap action if applicable.
        setSmartspaceRecItemOnClickListener(
                mRecommendationViewHolder.getRecommendations(), target.getBaseAction());
                mRecommendationViewHolder.getRecommendations(), data.getCardAction());

        List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
        List<Integer> mediaCoverItemsResIds = mRecommendationViewHolder.getMediaCoverItemsResIds();
@@ -574,7 +574,7 @@ public class MediaControlPanel {
                    /* isRecommendationCard */ true);
            closeGuts();
            mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
                    MediaViewController.GUTS_ANIMATION_DURATION + 100L);
                    data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
        });

        mController = null;
@@ -752,38 +752,6 @@ public class MediaControlPanel {
        return false;
    }

    /**
     * Returns the application info for the media recommendation's source app.
     *
     * @param target Smartspace target contains a list of media recommendations. Each item should
     *               contain the same source app's info.
     *
     * @return The source app's application info. This value can be null if no valid application
     * info can be obtained.
     */
    private ApplicationInfo getApplicationInfo(@NonNull SmartspaceTarget target) {
        List<SmartspaceAction> mediaRecommendationList = target.getIconGrid();
        if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) {
            return null;
        }

        for (SmartspaceAction recommendation: mediaRecommendationList) {
            Bundle extras = recommendation.getExtras();
            if (extras != null && extras.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME) != null) {
                // Get the logo from app's package name when applicable.
                String packageName = extras.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME);
                try {
                    return mContext.getPackageManager()
                            .getApplicationInfo(packageName, 0 /* flags */);
                } catch (PackageManager.NameNotFoundException e) {
                    Log.w(TAG, "Fail to get media recommendation's app info", e);
                }
            }
        }

        return null;
    }

    /**
     * Get the surface given the current end location for MediaViewController
     * @return surface used for Smartspace logging
+9 −5
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.systemui.media

import android.app.smartspace.SmartspaceTarget
import javax.inject.Inject

/**
@@ -28,7 +27,12 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
    private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()

    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
    override fun onMediaDataLoaded(
        key: String,
        oldKey: String?,
        data: MediaData,
        immediately: Boolean
    ) {
        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
            entries[key] = data to entries.remove(oldKey)?.second
            update(key, oldKey)
@@ -40,7 +44,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,

    override fun onSmartspaceMediaDataLoaded(
        key: String,
        data: SmartspaceTarget,
        data: SmartspaceMediaData,
        shouldPrioritize: Boolean
    ) {
        listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
@@ -50,8 +54,8 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
        remove(key)
    }

    override fun onSmartspaceMediaDataRemoved(key: String) {
        listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key) }
    override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
        listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
    }

    override fun onMediaDeviceChanged(
+42 −27
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.systemui.media

import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceTarget
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
@@ -34,6 +32,7 @@ import kotlin.collections.LinkedHashMap

private const val TAG = "MediaDataFilter"
private const val DEBUG = true
private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"

/**
 * Maximum age of a media control to re-activate on smartspace signal. If there is no media control
@@ -67,8 +66,7 @@ class MediaDataFilter @Inject constructor(
    private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
    private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    var hasSmartspace: Boolean = false
        private set
    private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
    private var reactivatedKey: String? = null

    init {
@@ -81,7 +79,12 @@ class MediaDataFilter @Inject constructor(
        userTracker.startTracking()
    }

    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
    override fun onMediaDataLoaded(
        key: String,
        oldKey: String?,
        data: MediaData,
        immediately: Boolean
    ) {
        if (oldKey != null && oldKey != key) {
            allEntries.remove(oldKey)
        }
@@ -104,18 +107,32 @@ class MediaDataFilter @Inject constructor(

    override fun onSmartspaceMediaDataLoaded(
        key: String,
        data: SmartspaceTarget,
        data: SmartspaceMediaData,
        shouldPrioritize: Boolean
    ) {
        var shouldPrioritizeMutable = shouldPrioritize
        hasSmartspace = true
        if (!data.isActive) {
            Log.d(TAG, "Inactive recommendation data. Skip triggering.")
            return
        }

        // Override the pass-in value here, as the order of Smartspace card is only determined here.
        var shouldPrioritizeMutable = false
        smartspaceMediaData = data

        // Before forwarding the smartspace target, first check if we have recently inactive media
        val sorted = userEntries.toSortedMap(compareBy {
            userEntries.get(it)?.lastActive ?: -1
        })
        val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
        if (timeSinceActive < SMARTSPACE_MAX_AGE) {
        var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE
        data.cardAction?.let {
            val smartspaceMaxAgeSeconds =
                it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
            if (smartspaceMaxAgeSeconds > 0) {
                smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds)
            }
        }
        if (timeSinceActive < smartspaceMaxAgeMillis) {
            val lastActiveKey = sorted.lastKey() // most recently active
            // Notify listeners to consider this media active
            Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
@@ -129,9 +146,8 @@ class MediaDataFilter @Inject constructor(
            shouldPrioritizeMutable = true
        }

        // Only proceed with the Smartspace update if the recommendation is not empty.
        if (isMediaRecommendationEmpty(data)) {
            Log.d(TAG, "Empty media recommendations. Skip showing the card")
        if (!data.isValid) {
            Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
            return
        }
        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
@@ -147,9 +163,7 @@ class MediaDataFilter @Inject constructor(
        }
    }

    override fun onSmartspaceMediaDataRemoved(key: String) {
        hasSmartspace = false

    override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
        // First check if we had reactivated media instead of forwarding smartspace
        reactivatedKey?.let {
            val lastActiveKey = it
@@ -158,12 +172,17 @@ class MediaDataFilter @Inject constructor(
            // Notify listeners to update with actual active value
            userEntries.get(lastActiveKey)?.let { mediaData ->
                listeners.forEach {
                    it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData)
                    it.onMediaDataLoaded(
                            lastActiveKey, lastActiveKey, mediaData, immediately)
                }
            }
        }

        listeners.forEach { it.onSmartspaceMediaDataRemoved(key) }
        if (smartspaceMediaData.isActive) {
            smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
                targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
        }
        listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
    }

    @VisibleForTesting
@@ -202,20 +221,22 @@ class MediaDataFilter @Inject constructor(
            // Force updates to listeners, needed for re-activated card
            mediaDataManager.setTimedOut(it, timedOut = true, forceUpdate = true)
        }
        if (hasSmartspace) {
            mediaDataManager.dismissSmartspaceRecommendation(0L /* delay */)
        if (smartspaceMediaData.isActive) {
            smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
                targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
        }
        mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId, delay = 0L)
    }

    /**
     * Are there any media notifications active?
     */
    fun hasActiveMedia() = userEntries.any { it.value.active } || hasSmartspace
    fun hasActiveMedia() = userEntries.any { it.value.active } || smartspaceMediaData.isActive

    /**
     * Are there any media entries we should display?
     */
    fun hasAnyMedia() = userEntries.isNotEmpty() || hasSmartspace
    fun hasAnyMedia() = userEntries.isNotEmpty() || smartspaceMediaData.isActive

    /**
     * Add a listener for filtered [MediaData] changes
@@ -227,12 +248,6 @@ class MediaDataFilter @Inject constructor(
     */
    fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)

    /** Check if the Smartspace sends an empty update. */
    private fun isMediaRecommendationEmpty(data: SmartspaceTarget): Boolean {
        val mediaRecommendationList: List<SmartspaceAction> = data.getIconGrid()
        return mediaRecommendationList == null || mediaRecommendationList.isEmpty()
    }

    /**
     * Return the time since last active for the most-recent media.
     *
Loading