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

Commit 790c621d authored by Lucas Dupin's avatar Lucas Dupin Committed by Automerger Merge Worker
Browse files

Merge "Hook up media player resumption to UI" into rvc-dev am: df9b6978

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

Change-Id: I25f8ecd16b07e0a281476ff7890b9953b86d7cac
parents 79f1fc32 df9b6978
Loading
Loading
Loading
Loading
+2 −29
Original line number Diff line number Diff line
@@ -96,13 +96,13 @@ public class MediaControlPanel {
     */
    @Inject
    public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
            ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager,
            ActivityStarter activityStarter, MediaViewController mediaViewController,
            SeekBarViewModel seekBarViewModel) {
        mContext = context;
        mBackgroundExecutor = backgroundExecutor;
        mActivityStarter = activityStarter;
        mSeekBarViewModel = seekBarViewModel;
        mMediaViewController = new MediaViewController(context, mediaHostStatesManager);
        mMediaViewController = mediaViewController;
        loadDimens();
    }

@@ -365,14 +365,6 @@ public class MediaControlPanel {
        return artwork;
    }

    /**
     * Return the token for the current media session
     * @return the token
     */
    public MediaSession.Token getMediaSessionToken() {
        return mToken;
    }

    /**
     * Get the current media controller
     * @return the controller
@@ -381,25 +373,6 @@ public class MediaControlPanel {
        return mController;
    }

    /**
     * Get the name of the package associated with the current media controller
     * @return the package name, or null if no controller
     */
    public String getMediaPlayerPackage() {
        if (mController == null) {
            return null;
        }
        return mController.getPackageName();
    }

    /**
     * Check whether this player has an attached media session.
     * @return whether there is a controller with a current media session.
     */
    public boolean hasMediaSession() {
        return mController != null && mController.getPlaybackState() != null;
    }

    /**
     * Check whether the media controlled by this player is currently playing
     * @return whether it is playing, or false if no controller information
+47 −1
Original line number Diff line number Diff line
@@ -25,19 +25,65 @@ import android.media.session.MediaSession
data class MediaData(
    val initialized: Boolean = false,
    val backgroundColor: Int,
    /**
     * App name that will be displayed on the player.
     */
    val app: String?,
    /**
     * Icon shown on player, close to app name.
     */
    val appIcon: Drawable?,
    /**
     * Artist name.
     */
    val artist: CharSequence?,
    /**
     * Song name.
     */
    val song: CharSequence?,
    /**
     * Album artwork.
     */
    val artwork: Icon?,
    /**
     * List of actions that can be performed on the player: prev, next, play, pause, etc.
     */
    val actions: List<MediaAction>,
    /**
     * Same as above, but shown on smaller versions of the player, like in QQS or keyguard.
     */
    val actionsToShowInCompact: List<Int>,
    /**
     * Package name of the app that's posting the media.
     */
    val packageName: String,
    /**
     * Unique media session identifier.
     */
    val token: MediaSession.Token?,
    /**
     * Action to perform when the player is tapped.
     * This is unrelated to {@link #actions}.
     */
    val clickIntent: PendingIntent?,
    /**
     * Where the media is playing: phone, headphones, ear buds, remote session.
     */
    val device: MediaDeviceData?,
    /**
     * When active, a player will be displayed on keyguard and quick-quick settings.
     * This is unrelated to the stream being playing or not, a player will not be active if
     * timed out, or in resumption mode.
     */
    var active: Boolean,
    /**
     * Action that should be performed to restart a non active session.
     */
    var resumeAction: Runnable?,
    val notificationKey: String = "INVALID",
    /**
     * Notification key for cancelling a media player after a timeout (when not using resumption.)
     */
    val notificationKey: String? = null,
    var hasCheckedForResume: Boolean = false
)

+26 −25
Original line number Diff line number Diff line
@@ -67,7 +67,7 @@ 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,
        emptyList(), emptyList(), "INVALID", null, null, null, null)
        emptyList(), emptyList(), "INVALID", null, null, null, true, null)

fun isMediaNotification(sbn: StatusBarNotification): Boolean {
    if (!sbn.notification.hasMediaSession()) {
@@ -88,12 +88,12 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean {
class MediaDataManager @Inject constructor(
    private val context: Context,
    private val mediaControllerFactory: MediaControllerFactory,
    private val mediaTimeoutListener: MediaTimeoutListener,
    private val notificationEntryManager: NotificationEntryManager,
    private val mediaResumeListener: MediaResumeListener,
    @Background private val backgroundExecutor: Executor,
    @Main private val foregroundExecutor: Executor,
    private val broadcastDispatcher: BroadcastDispatcher
    broadcastDispatcher: BroadcastDispatcher,
    mediaTimeoutListener: MediaTimeoutListener,
    mediaResumeListener: MediaResumeListener
) {

    private val listeners: MutableSet<Listener> = mutableSetOf()
@@ -131,7 +131,6 @@ class MediaDataManager @Inject constructor(
        mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
            setTimedOut(token, timedOut) }
        addListener(mediaTimeoutListener)

        if (useMediaResumption) {
            mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription,
                resumeAction: Runnable, token: MediaSession.Token, appName: String,
@@ -215,7 +214,7 @@ class MediaDataManager @Inject constructor(
            mediaEntries.put(packageName, resumeData)
        }
        backgroundExecutor.execute {
            loadMediaDataInBg(desc, action, token, appName, appIntent, packageName)
            loadMediaDataInBgForResumption(desc, action, token, appName, appIntent, packageName)
        }
    }

@@ -255,16 +254,21 @@ class MediaDataManager @Inject constructor(
    fun removeListener(listener: Listener) = listeners.remove(listener)

    private fun setTimedOut(token: String, timedOut: Boolean) {
        if (!timedOut) {
        mediaEntries[token]?.let {
            if (Utils.useMediaResumption(context)) {
                if (it.active == !timedOut) {
                    return
                }
        mediaEntries[token]?.let {
                it.active = !timedOut
                onMediaDataLoaded(token, token, it)
            } else {
                notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */,
                        UNDEFINED_DISMISS_REASON)
            }
        }
    }

    private fun loadMediaDataInBg(
    private fun loadMediaDataInBgForResumption(
        desc: MediaDescription,
        resumeAction: Runnable,
        token: MediaSession.Token,
@@ -272,11 +276,6 @@ class MediaDataManager @Inject constructor(
        appIntent: PendingIntent,
        packageName: String
    ) {
        if (resumeAction == null) {
            Log.e(TAG, "Resume action cannot be null")
            return
        }

        if (TextUtils.isEmpty(desc.title)) {
            Log.e(TAG, "Description incomplete")
            return
@@ -299,7 +298,8 @@ class MediaDataManager @Inject constructor(
        foregroundExecutor.execute {
            onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName,
                    null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
                packageName, token, appIntent, null, resumeAction, packageName))
                    packageName, token, appIntent, device = null, active = false,
                    resumeAction = resumeAction))
        }
    }

@@ -430,7 +430,8 @@ class MediaDataManager @Inject constructor(
        foregroundExecutor.execute {
            onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
                    song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
                    notif.contentIntent, null, resumeAction, key))
                    notif.contentIntent, null, active = true, resumeAction = resumeAction,
                    notificationKey = key))
        }
    }

@@ -528,13 +529,13 @@ class MediaDataManager @Inject constructor(
    /**
     * Are there any media notifications active?
     */
    fun hasActiveMedia() = mediaEntries.any({ isActive(it.value) })
    fun hasActiveMedia() = mediaEntries.any { it.value.active }

    fun isActive(data: MediaData): Boolean {
        if (data.token == null) {
    fun isActive(token: MediaSession.Token?): Boolean {
        if (token == null) {
            return false
        }
        val controller = mediaControllerFactory.create(data.token)
        val controller = mediaControllerFactory.create(token)
        val state = controller?.playbackState?.state
        return state != null && NotificationMediaManager.isActiveState(state)
    }
@@ -542,7 +543,7 @@ class MediaDataManager @Inject constructor(
    /**
     * Are there any media entries, including resume controls?
     */
    fun hasAnyMedia() = mediaEntries.isNotEmpty()
    fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia()

    interface Listener {

+60 −6
Original line number Diff line number Diff line
package com.android.systemui.media

import android.graphics.PointF
import android.graphics.Rect
import android.view.View
import android.view.View.OnAttachStateChangeListener
@@ -20,8 +21,6 @@ class MediaHost @Inject constructor(
    var location: Int = -1
        private set
    var visibleChangedListener: ((Boolean) -> Unit)? = null
    var visible: Boolean = false
        private set

    private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)

@@ -109,16 +108,17 @@ class MediaHost @Inject constructor(
    }

    private fun updateViewVisibility() {
        if (showsOnlyActiveMedia) {
            visible = mediaDataManager.hasActiveMedia()
        visible = if (showsOnlyActiveMedia) {
            mediaDataManager.hasActiveMedia()
        } else {
            visible = mediaDataManager.hasAnyMedia()
            mediaDataManager.hasAnyMedia()
        }
        hostView.visibility = if (visible) View.VISIBLE else View.GONE
        visibleChangedListener?.invoke(visible)
    }

    class MediaHostStateHolder @Inject constructor() : MediaHostState {
        private var gonePivot: PointF = PointF()

        override var measurementInput: MeasurementInput? = null
            set(value) {
@@ -144,6 +144,25 @@ class MediaHost @Inject constructor(
                }
            }

        override var visible: Boolean = true
            set(value) {
                if (field == value) {
                    return
                }
                field = value
                changedListener?.invoke()
            }

        override fun getPivotX(): Float = gonePivot.x
        override fun getPivotY(): Float = gonePivot.y
        override fun setGonePivot(x: Float, y: Float) {
            if (gonePivot.equals(x, y)) {
                return
            }
            gonePivot.set(x, y)
            changedListener?.invoke()
        }

        /**
         * A listener for all changes. This won't be copied over when invoking [copy]
         */
@@ -157,6 +176,8 @@ class MediaHost @Inject constructor(
            mediaHostState.expansion = expansion
            mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
            mediaHostState.measurementInput = measurementInput?.copy()
            mediaHostState.visible = visible
            mediaHostState.gonePivot.set(gonePivot)
            return mediaHostState
        }

@@ -173,6 +194,12 @@ class MediaHost @Inject constructor(
            if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
                return false
            }
            if (visible != other.visible) {
                return false
            }
            if (!gonePivot.equals(other.getPivotX(), other.getPivotY())) {
                return false
            }
            return true
        }

@@ -180,6 +207,8 @@ class MediaHost @Inject constructor(
            var result = measurementInput?.hashCode() ?: 0
            result = 31 * result + expansion.hashCode()
            result = 31 * result + showsOnlyActiveMedia.hashCode()
            result = 31 * result + if (visible) 1 else 2
            result = 31 * result + gonePivot.hashCode()
            return result
        }
    }
@@ -194,7 +223,8 @@ interface MediaHostState {
    var measurementInput: MeasurementInput?

    /**
     * The expansion of the player, 0 for fully collapsed, 1 for fully expanded
     * The expansion of the player, 0 for fully collapsed (up to 3 actions), 1 for fully expanded
     * (up to 5 actions.)
     */
    var expansion: Float

@@ -203,6 +233,30 @@ interface MediaHostState {
     */
    var showsOnlyActiveMedia: Boolean

    /**
     * If the view should be VISIBLE or GONE.
     */
    var visible: Boolean

    /**
     * Sets the pivot point when clipping the height or width.
     * Clipping happens when animating visibility when we're visible in QS but not on QQS,
     * for example.
     */
    fun setGonePivot(x: Float, y: Float)

    /**
     * x position of pivot, from 0 to 1
     * @see [setGonePivot]
     */
    fun getPivotX(): Float

    /**
     * y position of pivot, from 0 to 1
     * @see [setGonePivot]
     */
    fun getPivotY(): Float

    /**
     * Get a copy of this view state, deepcopying all appropriate members
     */
+7 −2
Original line number Diff line number Diff line
@@ -43,6 +43,11 @@ class MediaTimeoutListener @Inject constructor(

    private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()

    /**
     * Callback representing that a media object is now expired:
     * @param token Media session unique identifier
     * @param pauseTimeuot True when expired for {@code PAUSED_MEDIA_TIMEOUT}
     */
    lateinit var timeoutCallback: (String, Boolean) -> Unit

    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
@@ -112,11 +117,11 @@ class MediaTimeoutListener @Inject constructor(
            }
        }

        private fun expireMediaTimeout(mediaNotificationKey: String, reason: String) {
        private fun expireMediaTimeout(mediaKey: String, reason: String) {
            cancellation?.apply {
                if (DEBUG) {
                    Log.v(TAG,
                            "media timeout cancelled for  $mediaNotificationKey, reason: $reason")
                            "media timeout cancelled for  $mediaKey, reason: $reason")
                }
                run()
            }
Loading