• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.systemui.media
2 
3 import android.app.Notification.MediaStyle
4 import android.app.PendingIntent
5 import android.app.smartspace.SmartspaceAction
6 import android.app.smartspace.SmartspaceTarget
7 import android.graphics.Bitmap
8 import android.media.MediaDescription
9 import android.media.MediaMetadata
10 import android.media.session.MediaController
11 import android.media.session.MediaSession
12 import android.os.Bundle
13 import android.provider.Settings
14 import android.service.notification.StatusBarNotification
15 import android.testing.AndroidTestingRunner
16 import android.testing.TestableLooper.RunWithLooper
17 import androidx.test.filters.SmallTest
18 import com.android.systemui.SysuiTestCase
19 import com.android.systemui.broadcast.BroadcastDispatcher
20 import com.android.systemui.dump.DumpManager
21 import com.android.systemui.plugins.ActivityStarter
22 import com.android.systemui.statusbar.SbnBuilder
23 import com.android.systemui.tuner.TunerService
24 import com.android.systemui.util.concurrency.FakeExecutor
25 import com.android.systemui.util.mockito.capture
26 import com.android.systemui.util.mockito.eq
27 import com.android.systemui.util.time.FakeSystemClock
28 import com.google.common.truth.Truth.assertThat
29 import org.junit.After
30 import org.junit.Before
31 import org.junit.Rule
32 import org.junit.Test
33 import org.junit.runner.RunWith
34 import org.mockito.ArgumentCaptor
35 import org.mockito.ArgumentMatchers.anyBoolean
36 import org.mockito.Captor
37 import org.mockito.Mock
38 import org.mockito.Mockito
39 import org.mockito.Mockito.mock
40 import org.mockito.Mockito.never
41 import org.mockito.Mockito.reset
42 import org.mockito.Mockito.verify
43 import org.mockito.junit.MockitoJUnit
44 import org.mockito.Mockito.`when` as whenever
45 
46 private const val KEY = "KEY"
47 private const val KEY_2 = "KEY_2"
48 private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
49 private const val PACKAGE_NAME = "com.android.systemui"
50 private const val APP_NAME = "SystemUI"
51 private const val SESSION_ARTIST = "artist"
52 private const val SESSION_TITLE = "title"
53 private const val USER_ID = 0
54 
anyObjectnull55 private fun <T> anyObject(): T {
56     return Mockito.anyObject<T>()
57 }
58 
59 @SmallTest
60 @RunWithLooper(setAsMainLooper = true)
61 @RunWith(AndroidTestingRunner::class)
62 class MediaDataManagerTest : SysuiTestCase() {
63 
64     @JvmField @Rule val mockito = MockitoJUnit.rule()
65     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
66     @Mock lateinit var controller: MediaController
67     @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
68     lateinit var session: MediaSession
69     lateinit var metadataBuilder: MediaMetadata.Builder
70     lateinit var backgroundExecutor: FakeExecutor
71     lateinit var foregroundExecutor: FakeExecutor
72     @Mock lateinit var dumpManager: DumpManager
73     @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
74     @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
75     @Mock lateinit var mediaResumeListener: MediaResumeListener
76     @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
77     @Mock lateinit var mediaDeviceManager: MediaDeviceManager
78     @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
79     @Mock lateinit var mediaDataFilter: MediaDataFilter
80     @Mock lateinit var listener: MediaDataManager.Listener
81     @Mock lateinit var pendingIntent: PendingIntent
82     @Mock lateinit var activityStarter: ActivityStarter
83     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
84     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
85     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
86     lateinit var mediaDataManager: MediaDataManager
87     lateinit var mediaNotification: StatusBarNotification
88     @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
89     private val clock = FakeSystemClock()
90     @Mock private lateinit var tunerService: TunerService
91     @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
92 
93     private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver,
94             Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
95 
96     @Before
setupnull97     fun setup() {
98         foregroundExecutor = FakeExecutor(clock)
99         backgroundExecutor = FakeExecutor(clock)
100         smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
101         Settings.Secure.putInt(context.contentResolver,
102                 Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
103         mediaDataManager = MediaDataManager(
104             context = context,
105             backgroundExecutor = backgroundExecutor,
106             foregroundExecutor = foregroundExecutor,
107             mediaControllerFactory = mediaControllerFactory,
108             broadcastDispatcher = broadcastDispatcher,
109             dumpManager = dumpManager,
110             mediaTimeoutListener = mediaTimeoutListener,
111             mediaResumeListener = mediaResumeListener,
112             mediaSessionBasedFilter = mediaSessionBasedFilter,
113             mediaDeviceManager = mediaDeviceManager,
114             mediaDataCombineLatest = mediaDataCombineLatest,
115             mediaDataFilter = mediaDataFilter,
116             activityStarter = activityStarter,
117             smartspaceMediaDataProvider = smartspaceMediaDataProvider,
118             useMediaResumption = true,
119             useQsMediaPlayer = true,
120             systemClock = clock,
121             tunerService = tunerService
122         )
123         verify(tunerService).addTunable(capture(tunableCaptor),
124                 eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
125         session = MediaSession(context, "MediaDataManagerTestSession")
126         mediaNotification = SbnBuilder().run {
127             setPkg(PACKAGE_NAME)
128             modifyNotification(context).also {
129                 it.setSmallIcon(android.R.drawable.ic_media_pause)
130                 it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
131             }
132             build()
133         }
134         metadataBuilder = MediaMetadata.Builder().apply {
135             putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
136             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
137         }
138         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
139         whenever(controller.playbackInfo).thenReturn(playbackInfo)
140         whenever(playbackInfo.playbackType).thenReturn(
141                 MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
142 
143         // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
144         // listeners in the internal processing pipeline. It receives events, but ince it is a
145         // mock, it doesn't pass those events along the chain to the external listeners. So, just
146         // treat mediaSessionBasedFilter as a listener for testing.
147         listener = mediaSessionBasedFilter
148 
149         val recommendationExtras = Bundle()
150         recommendationExtras.putString("package_name", PACKAGE_NAME)
151         whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
152         whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
153         whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
154         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
155     }
156 
157     @After
tearDownnull158     fun tearDown() {
159         session.release()
160         mediaDataManager.destroy()
161         Settings.Secure.putInt(context.contentResolver,
162                 Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, originalSmartspaceSetting)
163     }
164 
165     @Test
testSetTimedOut_deactivatesMedianull166     fun testSetTimedOut_deactivatesMedia() {
167         val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
168                 appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
169                 actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
170                 clickIntent = null, device = null, active = true, resumeAction = null)
171         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
172         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
173 
174         mediaDataManager.setTimedOut(KEY, timedOut = true)
175         assertThat(data.active).isFalse()
176     }
177 
178     @Test
testLoadsMetadataOnBackgroundnull179     fun testLoadsMetadataOnBackground() {
180         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
181         assertThat(backgroundExecutor.numPending()).isEqualTo(1)
182     }
183 
184     @Test
testOnMetaDataLoaded_callsListenernull185     fun testOnMetaDataLoaded_callsListener() {
186         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
187         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
188         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject(), eq(true),
189                 eq(false))
190     }
191 
192     @Test
testOnMetaDataLoaded_conservesActiveFlagnull193     fun testOnMetaDataLoaded_conservesActiveFlag() {
194         whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
195         whenever(controller.metadata).thenReturn(metadataBuilder.build())
196         mediaDataManager.addListener(listener)
197         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
198         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
199         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
200         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
201                 eq(false))
202         assertThat(mediaDataCaptor.value!!.active).isTrue()
203     }
204 
205     @Test
testOnNotificationRemoved_callsListenernull206     fun testOnNotificationRemoved_callsListener() {
207         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
208         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
209         mediaDataManager.onNotificationRemoved(KEY)
210         verify(listener).onMediaDataRemoved(eq(KEY))
211     }
212 
213     @Test
testOnNotificationRemoved_withResumptionnull214     fun testOnNotificationRemoved_withResumption() {
215         // GIVEN that the manager has a notification with a resume action
216         whenever(controller.metadata).thenReturn(metadataBuilder.build())
217         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
218         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
219         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
220         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
221                 eq(false))
222         val data = mediaDataCaptor.value
223         assertThat(data.resumption).isFalse()
224         mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
225         // WHEN the notification is removed
226         mediaDataManager.onNotificationRemoved(KEY)
227         // THEN the media data indicates that it is for resumption
228         verify(listener)
229             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
230                     eq(false))
231         assertThat(mediaDataCaptor.value.resumption).isTrue()
232     }
233 
234     @Test
testOnNotificationRemoved_twoWithResumptionnull235     fun testOnNotificationRemoved_twoWithResumption() {
236         // GIVEN that the manager has two notifications with resume actions
237         whenever(controller.metadata).thenReturn(metadataBuilder.build())
238         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
239         mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
240         assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
241         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
242         verify(listener)
243             .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
244                     eq(false))
245         val data = mediaDataCaptor.value
246         assertThat(data.resumption).isFalse()
247         val resumableData = data.copy(resumeAction = Runnable {})
248         mediaDataManager.onMediaDataLoaded(KEY, null, resumableData)
249         mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData)
250         reset(listener)
251         // WHEN the first is removed
252         mediaDataManager.onNotificationRemoved(KEY)
253         // THEN the data is for resumption and the key is migrated to the package name
254         verify(listener)
255             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
256                     eq(false))
257         assertThat(mediaDataCaptor.value.resumption).isTrue()
258         verify(listener, never()).onMediaDataRemoved(eq(KEY))
259         // WHEN the second is removed
260         mediaDataManager.onNotificationRemoved(KEY_2)
261         // THEN the data is for resumption and the second key is removed
262         verify(listener)
263             .onMediaDataLoaded(
264                 eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true),
265                     eq(false))
266         assertThat(mediaDataCaptor.value.resumption).isTrue()
267         verify(listener).onMediaDataRemoved(eq(KEY_2))
268     }
269 
270     @Test
testOnNotificationRemoved_withResumption_butNotLocalnull271     fun testOnNotificationRemoved_withResumption_butNotLocal() {
272         // GIVEN that the manager has a notification with a resume action, but is not local
273         whenever(controller.metadata).thenReturn(metadataBuilder.build())
274         whenever(playbackInfo.playbackType).thenReturn(
275                 MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
276         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
277         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
278         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
279         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
280                 eq(false))
281         val data = mediaDataCaptor.value
282         val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, isLocalSession = false)
283         mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
284 
285         // WHEN the notification is removed
286         mediaDataManager.onNotificationRemoved(KEY)
287 
288         // THEN the media data is removed
289         verify(listener).onMediaDataRemoved(eq(KEY))
290     }
291 
292     @Test
testAddResumptionControlsnull293     fun testAddResumptionControls() {
294         // WHEN resumption controls are added
295         val desc = MediaDescription.Builder().run {
296             setTitle(SESSION_TITLE)
297             build()
298         }
299         val currentTime = clock.elapsedRealtime()
300         mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
301                 APP_NAME, pendingIntent, PACKAGE_NAME)
302         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
303         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
304         // THEN the media data indicates that it is for resumption
305         verify(listener)
306             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true),
307                     eq(false))
308         val data = mediaDataCaptor.value
309         assertThat(data.resumption).isTrue()
310         assertThat(data.song).isEqualTo(SESSION_TITLE)
311         assertThat(data.app).isEqualTo(APP_NAME)
312         assertThat(data.actions).hasSize(1)
313         assertThat(data.lastActive).isAtLeast(currentTime)
314     }
315 
316     @Test
testDismissMedia_listenerCallednull317     fun testDismissMedia_listenerCalled() {
318         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
319         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
320         val removed = mediaDataManager.dismissMediaData(KEY, 0L)
321         assertThat(removed).isTrue()
322 
323         foregroundExecutor.advanceClockToLast()
324         foregroundExecutor.runAllReady()
325 
326         verify(listener).onMediaDataRemoved(eq(KEY))
327     }
328 
329     @Test
testDismissMedia_keyDoesNotExist_returnsFalsenull330     fun testDismissMedia_keyDoesNotExist_returnsFalse() {
331         val removed = mediaDataManager.dismissMediaData(KEY, 0L)
332         assertThat(removed).isFalse()
333     }
334 
335     @Test
testBadArtwork_doesNotUsenull336     fun testBadArtwork_doesNotUse() {
337         // WHEN notification has a too-small artwork
338         val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
339         val notif = SbnBuilder().run {
340             setPkg(PACKAGE_NAME)
341             modifyNotification(context).also {
342                 it.setSmallIcon(android.R.drawable.ic_media_pause)
343                 it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
344                 it.setLargeIcon(artwork)
345             }
346             build()
347         }
348         mediaDataManager.onNotificationAdded(KEY, notif)
349 
350         // THEN it still loads
351         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
352         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
353         verify(listener)
354             .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
355                     eq(false))
356     }
357 
358     @Test
testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListenernull359     fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
360         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
361         verify(listener).onSmartspaceMediaDataLoaded(
362             eq(KEY_MEDIA_SMARTSPACE),
363             eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */,
364             PACKAGE_NAME, null, listOf(mediaRecommendationItem), 0)),
365             eq(false))
366     }
367 
368     @Test
testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListenernull369     fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
370         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
371         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
372         verify(listener).onSmartspaceMediaDataLoaded(
373             eq(KEY_MEDIA_SMARTSPACE),
374             eq(EMPTY_SMARTSPACE_MEDIA_DATA
375                 .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, isValid = false)),
376             eq(false))
377     }
378 
379     @Test
testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListenernull380     fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
381         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
382         verify(listener, never())
383                 .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
384     }
385 
386     @Test
testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListenernull387     fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
388         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
389         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
390         foregroundExecutor.advanceClockToLast()
391         foregroundExecutor.runAllReady()
392 
393         verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
394     }
395 
396     @Test
testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothingnull397     fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
398         // WHEN media recommendation setting is off
399         Settings.Secure.putInt(context.contentResolver,
400                 Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
401         tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
402 
403         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
404 
405         // THEN smartspace signal is ignored
406         verify(listener, never())
407                 .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
408     }
409 
410     @Test
testMediaRecommendationDisabled_removesSmartspaceDatanull411     fun testMediaRecommendationDisabled_removesSmartspaceData() {
412         // GIVEN a media recommendation card is present
413         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
414         verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(),
415                 anyBoolean())
416 
417         // WHEN the media recommendation setting is turned off
418         Settings.Secure.putInt(context.contentResolver,
419                 Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
420         tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
421 
422         // THEN listeners are notified
423         foregroundExecutor.advanceClockToLast()
424         foregroundExecutor.runAllReady()
425         verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
426     }
427 
428     @Test
testOnMediaDataChanged_updatesLastActiveTimenull429     fun testOnMediaDataChanged_updatesLastActiveTime() {
430         val currentTime = clock.elapsedRealtime()
431         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
432         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
433         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
434         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
435                 eq(false))
436         assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTime)
437     }
438 
439     @Test
testOnMediaDataTimedOut_doesNotUpdateLastActiveTimenull440     fun testOnMediaDataTimedOut_doesNotUpdateLastActiveTime() {
441         // GIVEN that the manager has a notification
442         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
443         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
444         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
445 
446         // WHEN the notification times out
447         clock.advanceTime(100)
448         val currentTime = clock.elapsedRealtime()
449         mediaDataManager.setTimedOut(KEY, true, true)
450 
451         // THEN the last active time is not changed
452         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true),
453                 eq(false))
454         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
455     }
456 
457     @Test
testOnActiveMediaConverted_doesNotUpdateLastActiveTimenull458     fun testOnActiveMediaConverted_doesNotUpdateLastActiveTime() {
459         // GIVEN that the manager has a notification with a resume action
460         whenever(controller.metadata).thenReturn(metadataBuilder.build())
461         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
462         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
463         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
464         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
465                 eq(false))
466         val data = mediaDataCaptor.value
467         assertThat(data.resumption).isFalse()
468         mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
469 
470         // WHEN the notification is removed
471         clock.advanceTime(100)
472         val currentTime = clock.elapsedRealtime()
473         mediaDataManager.onNotificationRemoved(KEY)
474 
475         // THEN the last active time is not changed
476         verify(listener)
477             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
478                     eq(false))
479         assertThat(mediaDataCaptor.value.resumption).isTrue()
480         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
481     }
482 
483     @Test
testTooManyCompactActions_isTruncatednull484     fun testTooManyCompactActions_isTruncated() {
485         // GIVEN a notification where too many compact actions were specified
486         val notif = SbnBuilder().run {
487             setPkg(PACKAGE_NAME)
488             modifyNotification(context).also {
489                 it.setSmallIcon(android.R.drawable.ic_media_pause)
490                 it.setStyle(MediaStyle().apply {
491                     setMediaSession(session.sessionToken)
492                     setShowActionsInCompactView(0, 1, 2, 3, 4)
493                 })
494             }
495             build()
496         }
497 
498         // WHEN the notification is loaded
499         mediaDataManager.onNotificationAdded(KEY, notif)
500         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
501         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
502 
503         // THEN only the first MAX_COMPACT_ACTIONS are actually set
504         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
505                 eq(false))
506         assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
507                 MediaDataManager.MAX_COMPACT_ACTIONS)
508     }
509 }
510