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<MediaAction> = 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     companion object {
104         /** Media is playing on the local device */
105         const val PLAYBACK_LOCAL = 0
106         /** Media is cast but originated on the local device */
107         const val PLAYBACK_CAST_LOCAL = 1
108         /** Media is from a remote cast notification */
109         const val PLAYBACK_CAST_REMOTE = 2
110     }
111 
isLocalSessionnull112     fun isLocalSession(): Boolean {
113         return playbackLocation == PLAYBACK_LOCAL
114     }
115 }
116 
117 /** Contains [MediaAction] objects which represent specific buttons in the UI */
118 data class MediaButton(
119     /** Play/pause button */
120     val playOrPause: MediaAction? = null,
121     /** Next button, or custom action */
122     val nextOrCustom: MediaAction? = null,
123     /** Previous button, or custom action */
124     val prevOrCustom: MediaAction? = null,
125     /** First custom action space */
126     val custom0: MediaAction? = null,
127     /** Second custom action space */
128     val custom1: MediaAction? = null,
129     /** Whether to reserve the empty space when the nextOrCustom is null */
130     val reserveNext: Boolean = false,
131     /** Whether to reserve the empty space when the prevOrCustom is null */
132     val reservePrev: Boolean = false
133 ) {
getActionByIdnull134     fun getActionById(id: Int): MediaAction? {
135         return when (id) {
136             R.id.actionPlayPause -> playOrPause
137             R.id.actionNext -> nextOrCustom
138             R.id.actionPrev -> prevOrCustom
139             R.id.action0 -> custom0
140             R.id.action1 -> custom1
141             else -> null
142         }
143     }
144 }
145 
146 /** State of a media action. */
147 data class MediaAction(
148     val icon: Drawable?,
149     val action: Runnable?,
150     val contentDescription: CharSequence?,
151     val background: Drawable?,
152 
153     // Rebind Id is used to detect identical rebinds and ignore them. It is intended
154     // to prevent continuously looping animations from restarting due to the arrival
155     // of repeated media notifications that are visually identical.
156     val rebindId: Int? = null
157 )
158 
159 /** State of the media device. */
160 data class MediaDeviceData
161 @JvmOverloads
162 constructor(
163     /** Whether or not to enable the chip */
164     val enabled: Boolean,
165 
166     /** Device icon to show in the chip */
167     val icon: Drawable?,
168 
169     /** Device display name */
170     val name: CharSequence?,
171 
172     /** Optional intent to override the default output switcher for this control */
173     val intent: PendingIntent? = null,
174 
175     /** Unique id for this device */
176     val id: String? = null,
177 
178     /** Whether or not to show the broadcast button */
179     val showBroadcastButton: Boolean
180 ) {
181     /**
182      * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon is
183      * ignored because it can change by reference frequently depending on the device type's
184      * implementation, but this is not usually relevant unless other info has changed
185      */
equalsWithoutIconnull186     fun equalsWithoutIcon(other: MediaDeviceData?): Boolean {
187         if (other == null) {
188             return false
189         }
190 
191         return enabled == other.enabled &&
192             name == other.name &&
193             intent == other.intent &&
194             id == other.id &&
195             showBroadcastButton == other.showBroadcastButton
196     }
197 }
198