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

Commit 1a285cd5 authored by Michael Mikhail's avatar Michael Mikhail Committed by Android (Google) Code Review
Browse files

Merge "Apply media mutex to avoid race condition" into main

parents 12dc2abb 9d79df7a
Loading
Loading
Loading
Loading
+103 −70
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.graphics.Color
import com.android.internal.annotations.GuardedBy
import com.android.internal.logging.InstanceId
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -56,6 +57,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext

/** A repository that holds the state of current media on the device. */
@@ -97,6 +100,7 @@ constructor(

    override var shouldScrollToFirst by mutableStateOf(false)

    @GuardedBy("mediaMutex")
    private var sortedMedia = TreeMap<MediaSortKeyModel, MediaDataModel>(comparator)

    // To store active controllers and their callbacks
@@ -104,21 +108,28 @@ constructor(
    private val mediaCallbacks = mutableMapOf<InstanceId, MediaController.Callback>()
    // To store active polling jobs
    private val positionPollers = mutableMapOf<InstanceId, Job>()
    private val mediaMutex = Mutex()

    override fun addCurrentUserMediaEntry(data: MediaData): UpdateArtInfoModel? {
        return super.addCurrentUserMediaEntry(data).also { updateModel ->
            addToSortedMedia(data, updateModel)
            applicationScope.launch {
                mediaMutex.withLock { addToSortedMediaLocked(data, updateModel) }
            }
        }
    }

    override fun removeCurrentUserMediaEntry(key: InstanceId): MediaData? {
        return super.removeCurrentUserMediaEntry(key)?.also { removeFromSortedMedia(it) }
        return super.removeCurrentUserMediaEntry(key)?.also {
            applicationScope.launch { mediaMutex.withLock { removeFromSortedMediaLocked(it) } }
        }
    }

    override fun removeCurrentUserMediaEntry(key: InstanceId, data: MediaData): Boolean {
        return super.removeCurrentUserMediaEntry(key, data).also {
            if (it) {
                removeFromSortedMedia(data)
                applicationScope.launch {
                    mediaMutex.withLock { removeFromSortedMediaLocked(data) }
                }
            }
        }
    }
@@ -126,23 +137,33 @@ constructor(
    override fun clearCurrentUserMedia() {
        val userEntries = LinkedHashMap<InstanceId, MediaData>(mutableUserEntries.value)
        mutableUserEntries.value = LinkedHashMap()
        userEntries.forEach { removeFromSortedMedia(it.value) }
        applicationScope.launch {
            mediaMutex.withLock { userEntries.forEach { removeFromSortedMediaLocked(it.value) } }
        }
    }

    override fun seek(sessionKey: InstanceId, to: Long) {
        activeControllers[sessionKey]?.let { controller ->
            controller.transportControls.seekTo(to)
            applicationScope.launch {
                mediaMutex.withLock {
                    currentMedia
                        .find { it.instanceId == sessionKey }
                        ?.let { latestModel ->
                    updateMediaModelInState(latestModel) { it.copy(positionMs = to) }
                            updateMediaModelInStateLocked(latestModel) { it.copy(positionMs = to) }
                        }
                }
            }
        }
    }

    override fun reorderMedia() {
        applicationScope.launch {
            mediaMutex.withLock {
                currentMedia.clear()
                currentMedia.addAll(sortedMedia.values.toList())
            }
        }
        currentCarouselIndex = 0
    }

@@ -154,7 +175,8 @@ constructor(
        shouldScrollToFirst = false
    }

    private fun addToSortedMedia(data: MediaData, updateModel: UpdateArtInfoModel?) {
    @GuardedBy("mediaMutex")
    private suspend fun addToSortedMediaLocked(data: MediaData, updateModel: UpdateArtInfoModel?) {
        val sortedMap = TreeMap<MediaSortKeyModel, MediaDataModel>(comparator)
        val currentModel = sortedMedia.values.find { it.instanceId == data.instanceId }

@@ -173,8 +195,6 @@ constructor(
                        systemClock.currentTimeMillis(),
                        instanceId,
                    )

                applicationScope.launch {
                val controller =
                    if (currentModel != null && currentModel.token == token) {
                        activeControllers[currentModel.instanceId]
@@ -183,8 +203,7 @@ constructor(
                        currentModel?.instanceId?.let { clearControllerState(it) }
                        token?.let { MediaController(applicationContext, it) }
                    }
                    val (icon, background) =
                        getIconAndBackground(mediaData, currentModel, updateModel)
                val (icon, background) = getIconAndBackground(mediaData, currentModel, updateModel)
                val mediaModel = toDataModel(controller, icon, background)
                sortedMap[sortKey] = mediaModel
                controller?.let { setupController(mediaModel, it) }
@@ -215,9 +234,9 @@ constructor(
            }
        }
    }
    }

    private fun removeFromSortedMedia(data: MediaData) {
    @GuardedBy("mediaMutex")
    private fun removeFromSortedMediaLocked(data: MediaData) {
        currentMedia.removeIf { model -> data.instanceId == model.instanceId }
        sortedMedia =
            TreeMap<MediaSortKeyModel, MediaDataModel>(comparator).apply {
@@ -390,15 +409,23 @@ constructor(
                }

                override fun onMetadataChanged(metadata: MediaMetadata?) {
                    val duration = metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION) ?: 0L
                    applicationScope.launch {
                        mediaMutex.withLock {
                            val duration =
                                metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION) ?: 0L
                            currentMedia
                                .find { it.instanceId == dataModel.instanceId }
                                ?.let { latestModel ->
                            updateMediaModelInState(latestModel) { model ->
                                    updateMediaModelInStateLocked(latestModel) { model ->
                                        val canBeScrubbed =
                                    controller.playbackState?.state != PlaybackState.STATE_NONE &&
                                        duration > 0L
                                model.copy(canBeScrubbed = canBeScrubbed, durationMs = duration)
                                            controller.playbackState?.state !=
                                                PlaybackState.STATE_NONE && duration > 0L
                                        model.copy(
                                            canBeScrubbed = canBeScrubbed,
                                            durationMs = duration,
                                        )
                                    }
                                }
                        }
                    }
                }
@@ -465,11 +492,14 @@ constructor(
    }

    private fun checkPlaybackPosition(instanceId: InstanceId, playbackState: PlaybackState?) {
        applicationScope.launch {
            mediaMutex.withLock {
                currentMedia
                    .find { it.instanceId == instanceId }
                    ?.let { latestModel ->
                val newPosition = playbackState?.computeActualPosition(latestModel.durationMs)
                updateMediaModelInState(latestModel) {
                        val newPosition =
                            playbackState?.computeActualPosition(latestModel.durationMs)
                        updateMediaModelInStateLocked(latestModel) {
                            if (newPosition != null && newPosition <= latestModel.durationMs) {
                                it.copy(positionMs = newPosition)
                            } else {
@@ -478,6 +508,8 @@ constructor(
                        }
                    }
            }
        }
    }

    private fun clearControllerState(instanceId: InstanceId) {
        positionPollers[instanceId]?.cancel()
@@ -487,7 +519,8 @@ constructor(
        mediaCallbacks.remove(instanceId)
    }

    private fun updateMediaModelInState(
    @GuardedBy("mediaMutex")
    private fun updateMediaModelInStateLocked(
        oldModel: MediaDataModel,
        updateBlock: (MediaDataModel) -> MediaDataModel,
    ) {