blob: e56bbabfdc0bffcc09273a262adf972ef930c0ea [file] [log] [blame]
Lucas Dupinae437a12020-06-17 17:20:33 -07001package com.android.systemui.media
2
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -04003import android.app.Notification.MediaStyle
4import android.app.PendingIntent
5import android.media.MediaDescription
6import android.media.MediaMetadata
7import android.media.session.MediaController
8import android.media.session.MediaSession
Lucas Dupinae437a12020-06-17 17:20:33 -07009import android.service.notification.StatusBarNotification
10import android.testing.AndroidTestingRunner
11import android.testing.TestableLooper.RunWithLooper
12import androidx.test.filters.SmallTest
13import com.android.systemui.SysuiTestCase
14import com.android.systemui.broadcast.BroadcastDispatcher
15import com.android.systemui.dump.DumpManager
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040016import com.android.systemui.statusbar.SbnBuilder
17import com.android.systemui.util.concurrency.FakeExecutor
Lucas Dupine267f4d2020-06-22 22:55:23 -070018import com.android.systemui.util.mockito.eq
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040019import com.android.systemui.util.time.FakeSystemClock
Lucas Dupinae437a12020-06-17 17:20:33 -070020import com.google.common.truth.Truth.assertThat
21import org.junit.After
22import org.junit.Before
23import org.junit.Rule
24import org.junit.Test
25import org.junit.runner.RunWith
26import org.mockito.Mock
27import org.mockito.Mockito
28import org.mockito.Mockito.mock
29import org.mockito.Mockito.verify
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040030import org.mockito.junit.MockitoJUnit
Lucas Dupine267f4d2020-06-22 22:55:23 -070031import org.mockito.Mockito.`when` as whenever
Lucas Dupinae437a12020-06-17 17:20:33 -070032
33private const val KEY = "KEY"
34private const val PACKAGE_NAME = "com.android.systemui"
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040035private const val APP_NAME = "SystemUI"
36private const val SESSION_ARTIST = "artist"
37private const val SESSION_TITLE = "title"
Lucas Dupinae437a12020-06-17 17:20:33 -070038
Lucas Dupinae437a12020-06-17 17:20:33 -070039private fun <T> anyObject(): T {
40 return Mockito.anyObject<T>()
41}
42
43@SmallTest
44@RunWithLooper(setAsMainLooper = true)
45@RunWith(AndroidTestingRunner::class)
46class MediaDataManagerTest : SysuiTestCase() {
47
48 @Mock lateinit var mediaControllerFactory: MediaControllerFactory
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040049 @Mock lateinit var controller: MediaController
50 lateinit var session: MediaSession
51 lateinit var metadataBuilder: MediaMetadata.Builder
52 lateinit var backgroundExecutor: FakeExecutor
53 lateinit var foregroundExecutor: FakeExecutor
Lucas Dupinae437a12020-06-17 17:20:33 -070054 @Mock lateinit var dumpManager: DumpManager
55 @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
56 @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
57 @Mock lateinit var mediaResumeListener: MediaResumeListener
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040058 @Mock lateinit var pendingIntent: PendingIntent
Lucas Dupinae437a12020-06-17 17:20:33 -070059 @JvmField @Rule val mockito = MockitoJUnit.rule()
60 lateinit var mediaDataManager: MediaDataManager
61 lateinit var mediaNotification: StatusBarNotification
62
63 @Before
64 fun setup() {
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040065 foregroundExecutor = FakeExecutor(FakeSystemClock())
66 backgroundExecutor = FakeExecutor(FakeSystemClock())
Lucas Dupinae437a12020-06-17 17:20:33 -070067 mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
68 mediaControllerFactory, broadcastDispatcher, dumpManager,
69 mediaTimeoutListener, mediaResumeListener, useMediaResumption = true,
70 useQsMediaPlayer = true)
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040071 session = MediaSession(context, "MediaDataManagerTestSession")
72 mediaNotification = SbnBuilder().run {
73 setPkg(PACKAGE_NAME)
74 modifyNotification(context).also {
75 it.setSmallIcon(android.R.drawable.ic_media_pause)
76 it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
77 }
78 build()
79 }
80 metadataBuilder = MediaMetadata.Builder().apply {
81 putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
82 putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
83 }
84 whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
Lucas Dupinae437a12020-06-17 17:20:33 -070085 }
86
87 @After
88 fun tearDown() {
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -040089 session.release()
Lucas Dupinae437a12020-06-17 17:20:33 -070090 mediaDataManager.destroy()
91 }
92
93 @Test
94 fun testHasActiveMedia() {
95 assertThat(mediaDataManager.hasActiveMedia()).isFalse()
96 val data = mock(MediaData::class.java)
97
98 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
99 mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
100 assertThat(mediaDataManager.hasActiveMedia()).isFalse()
101
102 whenever(data.active).thenReturn(true)
103 assertThat(mediaDataManager.hasActiveMedia()).isTrue()
104 }
105
106 @Test
Lucas Dupine267f4d2020-06-22 22:55:23 -0700107 fun testOnSwipeToDismiss_deactivatesMedia() {
108 val data = MediaData(initialized = true, backgroundColor = 0, app = null, appIcon = null,
109 artist = null, song = null, artwork = null, actions = emptyList(),
110 actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
111 clickIntent = null, device = null, active = true, resumeAction = null)
112 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
113 mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
114
115 mediaDataManager.onSwipeToDismiss()
116 assertThat(data.active).isFalse()
117 }
118
119 @Test
Lucas Dupinae437a12020-06-17 17:20:33 -0700120 fun testLoadsMetadataOnBackground() {
121 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -0400122 assertThat(backgroundExecutor.numPending()).isEqualTo(1)
Lucas Dupinae437a12020-06-17 17:20:33 -0700123 }
124
125 @Test
126 fun testOnMetaDataLoaded_callsListener() {
127 val listener = mock(MediaDataManager.Listener::class.java)
128 mediaDataManager.addListener(listener)
129 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
130 mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
131 verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject())
132 }
133
134 @Test
Lucas Dupine267f4d2020-06-22 22:55:23 -0700135 fun testOnMetaDataLoaded_conservesActiveFlag() {
136 val listener = TestListener()
137 whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
138 whenever(controller.metadata).thenReturn(metadataBuilder.build())
139 mediaDataManager.addListener(listener)
140 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
141 assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
142 assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
143 assertThat(listener.data!!.active).isTrue()
144
145 // Swiping away makes the notification not active
146 mediaDataManager.onSwipeToDismiss()
147 assertThat(mediaDataManager.hasActiveMedia()).isFalse()
148
149 // And when a notification is updated
150 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
151 assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
152 assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
153
154 // MediaData should still be inactive
155 assertThat(mediaDataManager.hasActiveMedia()).isFalse()
156 }
157
158 @Test
Lucas Dupinae437a12020-06-17 17:20:33 -0700159 fun testHasAnyMedia_whenAddingMedia() {
160 assertThat(mediaDataManager.hasAnyMedia()).isFalse()
161 val data = mock(MediaData::class.java)
162
163 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
164 mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
165 assertThat(mediaDataManager.hasAnyMedia()).isTrue()
166 }
167
168 @Test
169 fun testOnNotificationRemoved_doesntHaveMedia() {
170 val data = mock(MediaData::class.java)
171 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
172 mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
173 mediaDataManager.onNotificationRemoved(KEY)
174 assertThat(mediaDataManager.hasAnyMedia()).isFalse()
175 }
176
177 @Test
178 fun testOnNotificationRemoved_callsListener() {
179 val listener = mock(MediaDataManager.Listener::class.java)
180 mediaDataManager.addListener(listener)
181 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
182 mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
183 mediaDataManager.onNotificationRemoved(KEY)
184
185 verify(listener).onMediaDataRemoved(eq(KEY))
186 }
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -0400187
188 @Test
189 fun testOnNotificationRemoved_withResumption() {
190 // GIVEN that the manager has a notification with a resume action
191 val listener = TestListener()
192 mediaDataManager.addListener(listener)
193 whenever(controller.metadata).thenReturn(metadataBuilder.build())
194 mediaDataManager.onNotificationAdded(KEY, mediaNotification)
195 assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
196 assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
197 val data = listener.data!!
198 assertThat(data.resumption).isFalse()
199 mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
200 // WHEN the notification is removed
201 mediaDataManager.onNotificationRemoved(KEY)
202 // THEN the media data indicates that it is
203 assertThat(listener.data!!.resumption).isTrue()
204 }
205
206 @Test
207 fun testAddResumptionControls() {
208 val listener = TestListener()
209 mediaDataManager.addListener(listener)
210 // WHEN resumption controls are added`
211 val desc = MediaDescription.Builder().run {
212 setTitle(SESSION_TITLE)
213 build()
214 }
215 mediaDataManager.addResumptionControls(desc, Runnable {}, session.sessionToken, APP_NAME,
216 pendingIntent, PACKAGE_NAME)
217 assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
218 assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
219 // THEN the media data indicates that it is for resumption
220 val data = listener.data!!
221 assertThat(data.resumption).isTrue()
222 assertThat(data.song).isEqualTo(SESSION_TITLE)
223 assertThat(data.app).isEqualTo(APP_NAME)
224 assertThat(data.actions).hasSize(1)
225 }
226
227 /**
228 * Simple implementation of [MediaDataManager.Listener] for the test.
229 *
230 * Giving up on trying to get a mock Listener and ArgumentCaptor to work.
231 */
232 private class TestListener : MediaDataManager.Listener {
233 var data: MediaData? = null
234 var key: String? = null
235 var oldKey: String? = null
236
237 override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
238 this.key = key
239 this.oldKey = oldKey
240 this.data = data
241 }
242
243 override fun onMediaDataRemoved(key: String) {
244 this.key = key
245 oldKey = null
246 data = null
247 }
248 }
249}