1 /*
<lambda>null2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.systemui.statusbar.featurepods.media.domain.interactor
18
19 import com.android.systemui.dagger.SysUISingleton
20 import com.android.systemui.dagger.qualifiers.Background
21 import com.android.systemui.media.controls.data.repository.MediaFilterRepository
22 import com.android.systemui.media.controls.shared.model.MediaData
23 import com.android.systemui.res.R
24 import com.android.systemui.scene.shared.flag.SceneContainerFlag
25 import com.android.systemui.statusbar.featurepods.media.shared.model.MediaControlChipModel
26 import javax.inject.Inject
27 import kotlinx.coroutines.CoroutineScope
28 import kotlinx.coroutines.flow.Flow
29 import kotlinx.coroutines.flow.MutableStateFlow
30 import kotlinx.coroutines.flow.SharingStarted
31 import kotlinx.coroutines.flow.StateFlow
32 import kotlinx.coroutines.flow.combine
33 import kotlinx.coroutines.flow.stateIn
34
35 /**
36 * Interactor for managing the state of the media control chip in the status bar.
37 *
38 * Provides a [StateFlow] of [MediaControlChipModel] representing the current state of the media
39 * control chip. Emits a new [MediaControlChipModel] when there is an active media session and the
40 * corresponding user preference is found, otherwise emits null.
41 *
42 * This functionality is only enabled on large screen devices.
43 */
44 @SysUISingleton
45 class MediaControlChipInteractor
46 @Inject
47 constructor(
48 @Background private val backgroundScope: CoroutineScope,
49 mediaFilterRepository: MediaFilterRepository,
50 ) {
51 private val isEnabled = MutableStateFlow(false)
52
53 private val mediaControlChipModelForScene: Flow<MediaControlChipModel?> =
54 combine(mediaFilterRepository.currentMedia, mediaFilterRepository.selectedUserEntries) {
55 mediaList,
56 userEntries ->
57 mediaList
58 .mapNotNull { userEntries[it.mediaLoadedModel.instanceId] }
59 .firstOrNull { it.active }
60 ?.toMediaControlChipModel()
61 }
62
63 /**
64 * A flow of [MediaControlChipModel] representing the current state of the media controls chip.
65 * This flow emits null when no active media is playing or when playback information is
66 * unavailable. This flow is only active when [SceneContainerFlag] is disabled.
67 */
68 private val mediaControlChipModelLegacy = MutableStateFlow<MediaControlChipModel?>(null)
69
70 fun updateMediaControlChipModelLegacy(mediaData: MediaData?) {
71 if (!SceneContainerFlag.isEnabled) {
72 mediaControlChipModelLegacy.value = mediaData?.toMediaControlChipModel()
73 }
74 }
75
76 private val _mediaControlChipModel: Flow<MediaControlChipModel?> =
77 if (SceneContainerFlag.isEnabled) {
78 mediaControlChipModelForScene
79 } else {
80 mediaControlChipModelLegacy
81 }
82
83 /** The currently active [MediaControlChipModel] */
84 val mediaControlChipModel: StateFlow<MediaControlChipModel?> =
85 combine(_mediaControlChipModel, isEnabled) { mediaControlChipModel, isEnabled ->
86 if (isEnabled) {
87 mediaControlChipModel
88 } else {
89 null
90 }
91 }
92 .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), null)
93
94 /**
95 * The media control chip may not be enabled on all form factors, so only the relevant form
96 * factors should initialize the interactor. This must be called from a CoreStartable.
97 */
98 fun initialize() {
99 isEnabled.value = true
100 }
101 }
102
MediaDatanull103 private fun MediaData.toMediaControlChipModel(): MediaControlChipModel {
104 return MediaControlChipModel(
105 appIcon = this.appIcon,
106 appName = this.app,
107 songName = this.song,
108 playOrPause = this.semanticActions?.getActionById(R.id.actionPlayPause),
109 )
110 }
111