xref: /aosp_15_r20/frameworks/base/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2020 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.media.controls.shared.model
18 
19 import android.app.PendingIntent
20 import android.graphics.drawable.Drawable
21 import android.graphics.drawable.Icon
22 import android.media.session.MediaSession
23 import android.os.Process
24 import com.android.internal.logging.InstanceId
25 import com.android.systemui.res.R
26 
27 /** State of a media view. */
28 data class MediaData(
29     val userId: Int = -1,
30     val initialized: Boolean = false,
31     /** App name that will be displayed on the player. */
32     val app: String? = null,
33     /** App icon shown on player. */
34     val appIcon: Icon? = null,
35     /** Artist name. */
36     val artist: CharSequence? = null,
37     /** Song name. */
38     val song: CharSequence? = null,
39     /** Album artwork. */
40     val artwork: Icon? = null,
41     /** List of generic action buttons for the media player, based on notification actions */
42     val actions: List<MediaNotificationAction> = emptyList(),
43     /** Same as above, but shown on smaller versions of the player, like in QQS or keyguard. */
44     val actionsToShowInCompact: List<Int> = emptyList(),
45     /**
46      * Semantic actions buttons, based on the PlaybackState of the media session. If present, these
47      * actions will be preferred in the UI over [actions]
48      */
49     val semanticActions: MediaButton? = null,
50     /** Package name of the app that's posting the media. */
51     val packageName: String = "INVALID",
52     /** Unique media session identifier. */
53     val token: MediaSession.Token? = null,
54     /** Action to perform when the player is tapped. This is unrelated to {@link #actions}. */
55     val clickIntent: PendingIntent? = null,
56     /** Where the media is playing: phone, headphones, ear buds, remote session. */
57     val device: MediaDeviceData? = null,
58     /**
59      * When active, a player will be displayed on keyguard and quick-quick settings. This is
60      * unrelated to the stream being playing or not, a player will not be active if timed out, or in
61      * resumption mode.
62      */
63     var active: Boolean = true,
64     /** Action that should be performed to restart a non active session. */
65     var resumeAction: Runnable? = null,
66     /** Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE */
67     var playbackLocation: Int = PLAYBACK_LOCAL,
68     /**
69      * Indicates that this player is a resumption player (ie. It only shows a play actions which
70      * will start the app and start playing).
71      */
72     var resumption: Boolean = false,
73     /**
74      * Notification key for cancelling a media player after a timeout (when not using resumption.)
75      */
76     val notificationKey: String? = null,
77     var hasCheckedForResume: Boolean = false,
78 
79     /** If apps do not report PlaybackState, set as null to imply 'undetermined' */
80     val isPlaying: Boolean? = null,
81 
82     /** Set from the notification and used as fallback when PlaybackState cannot be determined */
83     val isClearable: Boolean = true,
84 
85     /** Milliseconds since boot when this player was last active. */
86     var lastActive: Long = 0L,
87 
88     /** Timestamp in milliseconds when this player was created. */
89     var createdTimestampMillis: Long = 0L,
90 
91     /** Instance ID for logging purposes */
92     val instanceId: InstanceId = InstanceId.fakeInstanceId(-1),
93 
94     /** The UID of the app, used for logging */
95     val appUid: Int = Process.INVALID_UID,
96 
97     /** Whether explicit indicator exists */
98     val isExplicit: Boolean = false,
99 
100     /** Track progress (0 - 1) to display for players where [resumption] is true */
101     val resumeProgress: Double? = null,
102 
103     /** Smartspace Id, used for logging. */
104     var smartspaceId: Int = -1,
105 
106     /** If media card was visible to user, used for logging. */
107     var isImpressed: Boolean = false,
108 ) {
109     companion object {
110         /** Media is playing on the local device */
111         const val PLAYBACK_LOCAL = 0
112         /** Media is cast but originated on the local device */
113         const val PLAYBACK_CAST_LOCAL = 1
114         /** Media is from a remote cast notification */
115         const val PLAYBACK_CAST_REMOTE = 2
116     }
117 
isLocalSessionnull118     fun isLocalSession(): Boolean {
119         return playbackLocation == PLAYBACK_LOCAL
120     }
121 }
122 
123 /** Contains [MediaAction] objects which represent specific buttons in the UI */
124 data class MediaButton(
125     /** Play/pause button */
126     val playOrPause: MediaAction? = null,
127     /** Next button, or custom action */
128     val nextOrCustom: MediaAction? = null,
129     /** Previous button, or custom action */
130     val prevOrCustom: MediaAction? = null,
131     /** First custom action space */
132     val custom0: MediaAction? = null,
133     /** Second custom action space */
134     val custom1: MediaAction? = null,
135     /** Whether to reserve the empty space when the nextOrCustom is null */
136     val reserveNext: Boolean = false,
137     /** Whether to reserve the empty space when the prevOrCustom is null */
138     val reservePrev: Boolean = false
139 ) {
getActionByIdnull140     fun getActionById(id: Int): MediaAction? {
141         return when (id) {
142             R.id.actionPlayPause -> playOrPause
143             R.id.actionNext -> nextOrCustom
144             R.id.actionPrev -> prevOrCustom
145             R.id.action0 -> custom0
146             R.id.action1 -> custom1
147             else -> null
148         }
149     }
150 }
151 
152 /** State of a media action. */
153 data class MediaAction(
154     val icon: Drawable?,
155     val action: Runnable?,
156     val contentDescription: CharSequence?,
157     val background: Drawable?,
158 
159     // Rebind Id is used to detect identical rebinds and ignore them. It is intended
160     // to prevent continuously looping animations from restarting due to the arrival
161     // of repeated media notifications that are visually identical.
162     val rebindId: Int? = null
163 )
164 
165 /** State of a media action from notification. */
166 data class MediaNotificationAction(
167     val isAuthenticationRequired: Boolean,
168     val actionIntent: PendingIntent?,
169     val icon: Drawable?,
170     val contentDescription: CharSequence?
171 )
172 
173 /** State of the media device. */
174 data class MediaDeviceData
175 @JvmOverloads
176 constructor(
177     /** Whether or not to enable the chip */
178     val enabled: Boolean,
179 
180     /** Device icon to show in the chip */
181     val icon: Drawable?,
182 
183     /** Device display name */
184     val name: CharSequence?,
185 
186     /** Optional intent to override the default output switcher for this control */
187     val intent: PendingIntent? = null,
188 
189     /** Unique id for this device */
190     val id: String? = null,
191 
192     /** Whether or not to show the broadcast button */
193     val showBroadcastButton: Boolean
194 ) {
195     /**
196      * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon is
197      * ignored because it can change by reference frequently depending on the device type's
198      * implementation, but this is not usually relevant unless other info has changed
199      */
equalsWithoutIconnull200     fun equalsWithoutIcon(other: MediaDeviceData?): Boolean {
201         if (other == null) {
202             return false
203         }
204 
205         return enabled == other.enabled &&
206             name == other.name &&
207             intent == other.intent &&
208             id == other.id &&
209             showBroadcastButton == other.showBroadcastButton
210     }
211 }
212