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

Commit 6f0bd31c authored by Lucas Dupin's avatar Lucas Dupin
Browse files

Move media expiration to MediaDataManager

This way it will be easier to migrate to the new resumption model
when it lands.

Test: manual
Fixes: 157587326
Change-Id: I69ee71d21c96a4c6023156f605387cbe8980e706
parent 5feeacaa
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -35,7 +35,8 @@ data class MediaData(
    val packageName: String?,
    val token: MediaSession.Token?,
    val clickIntent: PendingIntent?,
    val device: MediaDeviceData?
    val device: MediaDeviceData?,
    val notificationKey: String = "INVALID"
)

/** State of a media action. */
+21 −1
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ import com.android.internal.graphics.ColorUtils
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
@@ -77,6 +79,8 @@ 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,
    @Background private val backgroundExecutor: Executor,
    @Main private val foregroundExecutor: Executor
) {
@@ -84,6 +88,12 @@ class MediaDataManager @Inject constructor(
    private val listeners: MutableSet<Listener> = mutableSetOf()
    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()

    init {
        mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
            setTimedOut(token, timedOut) }
        addListener(mediaTimeoutListener)
    }

    fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
        if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
            Assert.isMainThread()
@@ -112,6 +122,16 @@ class MediaDataManager @Inject constructor(
     */
    fun removeListener(listener: Listener) = listeners.remove(listener)

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

    private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) {
        val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
                as MediaSession.Token?
@@ -223,7 +243,7 @@ class MediaDataManager @Inject constructor(
        foregroundExecutor.execute {
            onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song,
                    artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
                    notif.contentIntent, null))
                    notif.contentIntent, null, key))
        }
    }

+117 −0
Original line number 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.media.session.MediaController
import android.media.session.PlaybackState
import android.os.SystemProperties
import android.util.Log
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.util.concurrency.DelayableExecutor
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton

private const val DEBUG = true
private const val TAG = "MediaTimeout"
private val PAUSED_MEDIA_TIMEOUT = SystemProperties
        .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))

/**
 * Controller responsible for keeping track of playback states and expiring inactive streams.
 */
@Singleton
class MediaTimeoutListener @Inject constructor(
    private val mediaControllerFactory: MediaControllerFactory,
    @Main private val mainExecutor: DelayableExecutor
) : MediaDataManager.Listener {

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

    lateinit var timeoutCallback: (String, Boolean) -> Unit

    override fun onMediaDataLoaded(key: String, data: MediaData) {
        if (mediaListeners.containsKey(key)) {
            return
        }
        mediaListeners[key] = PlaybackStateListener(key, data)
    }

    override fun onMediaDataRemoved(key: String) {
        mediaListeners.remove(key)?.destroy()
    }

    fun isTimedOut(key: String): Boolean {
        return mediaListeners[key]?.timedOut ?: false
    }

    private inner class PlaybackStateListener(
        private val key: String,
        data: MediaData
    ) : MediaController.Callback() {

        var timedOut = false

        private val mediaController = mediaControllerFactory.create(data.token)
        private var cancellation: Runnable? = null

        init {
            mediaController.registerCallback(this)
        }

        fun destroy() {
            mediaController.unregisterCallback(this)
        }

        override fun onPlaybackStateChanged(state: PlaybackState?) {
            if (DEBUG) {
                Log.v(TAG, "onPlaybackStateChanged: $state")
            }
            expireMediaTimeout(key, "playback state ativity - $state, $key")

            if (state == null || !isPlayingState(state.state)) {
                if (DEBUG) {
                    Log.v(TAG, "schedule timeout for $key")
                }
                expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
                cancellation = mainExecutor.executeDelayed({
                    cancellation = null
                    if (DEBUG) {
                        Log.v(TAG, "Execute timeout for $key")
                    }
                    timedOut = true
                    timeoutCallback(key, timedOut)
                }, PAUSED_MEDIA_TIMEOUT)
            } else {
                timedOut = false
                timeoutCallback(key, timedOut)
            }
        }

        private fun expireMediaTimeout(mediaNotificationKey: String, reason: String) {
            cancellation?.apply {
                if (DEBUG) {
                    Log.v(TAG,
                            "media timeout cancelled for  $mediaNotificationKey, reason: $reason")
                }
                run()
            }
            cancellation = null
        }
    }
}
 No newline at end of file
+0 −42
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar;

import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
@@ -36,7 +35,6 @@ import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.AsyncTask;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
@@ -80,7 +78,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import dagger.Lazy;

@@ -91,8 +88,6 @@ import dagger.Lazy;
public class NotificationMediaManager implements Dumpable {
    private static final String TAG = "NotificationMediaManager";
    public static final boolean DEBUG_MEDIA = false;
    private static final long PAUSED_MEDIA_TIMEOUT = SystemProperties
            .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10));

    private final StatusBarStateController mStatusBarStateController
            = Dependency.get(StatusBarStateController.class);
@@ -134,7 +129,6 @@ public class NotificationMediaManager implements Dumpable {
    private MediaController mMediaController;
    private String mMediaNotificationKey;
    private MediaMetadata mMediaMetadata;
    private Runnable mMediaTimeoutCancellation;

    private BackDropView mBackdrop;
    private ImageView mBackdropFront;
@@ -164,47 +158,11 @@ public class NotificationMediaManager implements Dumpable {
            if (DEBUG_MEDIA) {
                Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
            }
            if (mMediaTimeoutCancellation != null) {
                if (DEBUG_MEDIA) {
                    Log.v(TAG, "DEBUG_MEDIA: media timeout cancelled");
                }
                mMediaTimeoutCancellation.run();
                mMediaTimeoutCancellation = null;
            }
            if (state != null) {
                if (!isPlaybackActive(state.getState())) {
                    clearCurrentMediaNotification();
                }
                findAndUpdateMediaNotifications();
                scheduleMediaTimeout(state);
            }
        }

        private void scheduleMediaTimeout(PlaybackState state) {
            final NotificationEntry entry;
            synchronized (mEntryManager) {
                entry = mEntryManager.getActiveNotificationUnfiltered(mMediaNotificationKey);
            }
            if (entry != null) {
                if (!isPlayingState(state.getState())) {
                    if (DEBUG_MEDIA) {
                        Log.v(TAG, "DEBUG_MEDIA: schedule timeout for "
                                + mMediaNotificationKey);
                    }
                    mMediaTimeoutCancellation = mMainExecutor.executeDelayed(() -> {
                        synchronized (mEntryManager) {
                            if (DEBUG_MEDIA) {
                                Log.v(TAG, "DEBUG_MEDIA: execute timeout for "
                                        + mMediaNotificationKey);
                            }
                            if (mMediaNotificationKey == null) {
                                return;
                            }
                            mEntryManager.removeNotification(mMediaNotificationKey, null,
                                    UNDEFINED_DISMISS_REASON);
                        }
                    }, PAUSED_MEDIA_TIMEOUT);
                }
            }
        }

+1 −1
Original line number Diff line number Diff line
@@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
        mManager.addListener(mListener);

        mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null);
                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, KEY);
        mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
    }

Loading