1 /*
2  * 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.media.controls.domain.pipeline
18 
19 import android.app.smartspace.SmartspaceAction
20 import android.os.Bundle
21 import android.testing.TestableLooper
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import androidx.test.filters.SmallTest
24 import com.android.internal.logging.InstanceId
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.broadcast.BroadcastSender
27 import com.android.systemui.coroutines.collectLastValue
28 import com.android.systemui.media.controls.MediaTestUtils
29 import com.android.systemui.media.controls.data.repository.MediaFilterRepository
30 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
31 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
32 import com.android.systemui.media.controls.shared.model.MediaCommonModel
33 import com.android.systemui.media.controls.shared.model.MediaData
34 import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
35 import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
36 import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
37 import com.android.systemui.media.controls.ui.controller.MediaPlayerData
38 import com.android.systemui.media.controls.util.MediaFlags
39 import com.android.systemui.media.controls.util.MediaUiEventLogger
40 import com.android.systemui.settings.UserTracker
41 import com.android.systemui.statusbar.NotificationLockscreenUserManager
42 import com.android.systemui.testKosmos
43 import com.android.systemui.util.time.FakeSystemClock
44 import com.google.common.truth.Truth.assertThat
45 import java.util.concurrent.Executor
46 import kotlinx.coroutines.ExperimentalCoroutinesApi
47 import kotlinx.coroutines.test.TestScope
48 import kotlinx.coroutines.test.runCurrent
49 import kotlinx.coroutines.test.runTest
50 import org.junit.Before
51 import org.junit.Test
52 import org.junit.runner.RunWith
53 import org.mockito.ArgumentMatchers.anyBoolean
54 import org.mockito.ArgumentMatchers.anyInt
55 import org.mockito.ArgumentMatchers.anyLong
56 import org.mockito.ArgumentMatchers.anyString
57 import org.mockito.Mock
58 import org.mockito.Mockito.never
59 import org.mockito.Mockito.reset
60 import org.mockito.Mockito.verify
61 import org.mockito.MockitoAnnotations
62 import org.mockito.kotlin.any
63 import org.mockito.kotlin.eq
64 import org.mockito.kotlin.whenever
65 
66 private const val KEY = "TEST_KEY"
67 private const val KEY_ALT = "TEST_KEY_2"
68 private const val USER_MAIN = 0
69 private const val USER_GUEST = 10
70 private const val PRIVATE_PROFILE = 12
71 private const val PACKAGE = "PKG"
72 private val INSTANCE_ID = InstanceId.fakeInstanceId(123)!!
73 private val INSTANCE_ID_GUEST = InstanceId.fakeInstanceId(321)!!
74 private const val APP_UID = 99
75 private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
76 private const val SMARTSPACE_PACKAGE = "SMARTSPACE_PKG"
77 private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
78 
79 @ExperimentalCoroutinesApi
80 @SmallTest
81 @RunWith(AndroidJUnit4::class)
82 @TestableLooper.RunWithLooper
83 class MediaDataFilterImplTest : SysuiTestCase() {
84     val kosmos = testKosmos()
85 
86     @Mock private lateinit var listener: MediaDataProcessor.Listener
87     @Mock private lateinit var userTracker: UserTracker
88     @Mock private lateinit var broadcastSender: BroadcastSender
89     @Mock private lateinit var mediaDataProcessor: MediaDataProcessor
90     @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
91     @Mock private lateinit var executor: Executor
92     @Mock private lateinit var smartspaceData: SmartspaceMediaData
93     @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
94     @Mock private lateinit var logger: MediaUiEventLogger
95     @Mock private lateinit var mediaFlags: MediaFlags
96     @Mock private lateinit var cardAction: SmartspaceAction
97 
98     private lateinit var mediaDataFilter: MediaDataFilterImpl
99     private lateinit var testScope: TestScope
100     private lateinit var dataMain: MediaData
101     private lateinit var dataGuest: MediaData
102     private lateinit var dataPrivateProfile: MediaData
103     private val clock = FakeSystemClock()
104     private val repository: MediaFilterRepository = kosmos.mediaFilterRepository
105     private val mediaLoadingLogger = kosmos.mockMediaLoadingLogger
106 
107     @Before
setupnull108     fun setup() {
109         MockitoAnnotations.initMocks(this)
110         MediaPlayerData.clear()
111         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
112         testScope = TestScope()
113         mediaDataFilter =
114             MediaDataFilterImpl(
115                 context,
116                 userTracker,
117                 broadcastSender,
118                 lockscreenUserManager,
119                 executor,
120                 clock,
121                 logger,
122                 mediaFlags,
123                 repository,
124                 mediaLoadingLogger,
125             )
126         mediaDataFilter.mediaDataProcessor = mediaDataProcessor
127         mediaDataFilter.addListener(listener)
128 
129         // Start all tests as main user
130         setUser(USER_MAIN)
131 
132         // Set up test media data
133         dataMain =
134             MediaTestUtils.emptyMediaData.copy(
135                 userId = USER_MAIN,
136                 packageName = PACKAGE,
137                 instanceId = INSTANCE_ID,
138                 appUid = APP_UID
139             )
140         dataGuest = dataMain.copy(userId = USER_GUEST, instanceId = INSTANCE_ID_GUEST)
141         dataPrivateProfile = dataMain.copy(userId = PRIVATE_PROFILE, instanceId = INSTANCE_ID_GUEST)
142 
143         whenever(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
144         whenever(smartspaceData.isActive).thenReturn(true)
145         whenever(smartspaceData.isValid()).thenReturn(true)
146         whenever(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
147         whenever(smartspaceData.recommendations)
148             .thenReturn(listOf(smartspaceMediaRecommendationItem))
149         whenever(smartspaceData.headphoneConnectionTimeMillis)
150             .thenReturn(clock.currentTimeMillis() - 100)
151         whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
152         whenever(smartspaceData.cardAction).thenReturn(cardAction)
153     }
154 
setUsernull155     private fun setUser(id: Int) {
156         whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
157         whenever(lockscreenUserManager.isProfileAvailable(anyInt())).thenReturn(false)
158         whenever(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
159         whenever(lockscreenUserManager.isProfileAvailable(eq(id))).thenReturn(true)
160         whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(true)
161         mediaDataFilter.handleUserSwitched()
162     }
163 
setPrivateProfileUnavailablenull164     private fun setPrivateProfileUnavailable() {
165         whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
166         whenever(lockscreenUserManager.isCurrentProfile(eq(USER_MAIN))).thenReturn(true)
167         whenever(lockscreenUserManager.isCurrentProfile(eq(PRIVATE_PROFILE))).thenReturn(true)
168         whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(false)
169         mediaDataFilter.handleProfileChanged()
170     }
171 
172     @Test
onDataLoadedForCurrentUser_updatesLoadedStatesnull173     fun onDataLoadedForCurrentUser_updatesLoadedStates() =
174         testScope.runTest {
175             val currentMedia by collectLastValue(repository.currentMedia)
176             val mediaCommonModel =
177                 MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId))
178 
179             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
180 
181             verify(listener)
182                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
183             verify(mediaLoadingLogger)
184                 .logMediaLoaded(eq(dataMain.instanceId), eq(dataMain.active), anyString())
185             assertThat(currentMedia).containsExactly(mediaCommonModel)
186         }
187 
188     @Test
onDataLoadedForGuest_doesNotUpdateLoadedStatesnull189     fun onDataLoadedForGuest_doesNotUpdateLoadedStates() =
190         testScope.runTest {
191             val currentMedia by collectLastValue(repository.currentMedia)
192             val mediaCommonModel =
193                 MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId))
194 
195             mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
196 
197             verify(listener, never())
198                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
199             verify(mediaLoadingLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
200             assertThat(currentMedia).doesNotContain(mediaCommonModel)
201         }
202 
203     @Test
onRemovedForCurrent_updatesLoadedStatesnull204     fun onRemovedForCurrent_updatesLoadedStates() =
205         testScope.runTest {
206             val currentMedia by collectLastValue(repository.currentMedia)
207             val mediaCommonModel =
208                 MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId))
209 
210             // GIVEN a media was removed for main user
211             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
212 
213             verify(mediaLoadingLogger)
214                 .logMediaLoaded(eq(dataMain.instanceId), eq(dataMain.active), anyString())
215             assertThat(currentMedia).containsExactly(mediaCommonModel)
216 
217             mediaDataFilter.onMediaDataRemoved(KEY, false)
218 
219             verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
220             verify(mediaLoadingLogger).logMediaRemoved(eq(dataMain.instanceId), anyString())
221             assertThat(currentMedia).doesNotContain(mediaCommonModel)
222         }
223 
224     @Test
onRemovedForGuest_doesNotUpdateLoadedStatesnull225     fun onRemovedForGuest_doesNotUpdateLoadedStates() =
226         testScope.runTest {
227             val currentMedia by collectLastValue(repository.currentMedia)
228 
229             // GIVEN a media was removed for guest user
230             mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
231             mediaDataFilter.onMediaDataRemoved(KEY, false)
232 
233             verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
234             verify(mediaLoadingLogger, never())
235                 .logMediaRemoved(eq(dataGuest.instanceId), anyString())
236             assertThat(currentMedia).isEmpty()
237         }
238 
239     @Test
onUserSwitched_removesOldUserControlsnull240     fun onUserSwitched_removesOldUserControls() =
241         testScope.runTest {
242             val currentMedia by collectLastValue(repository.currentMedia)
243             val mediaLoaded = MediaDataLoadingModel.Loaded(dataMain.instanceId)
244 
245             // GIVEN that we have a media loaded for main user
246             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
247 
248             verify(mediaLoadingLogger)
249                 .logMediaLoaded(eq(dataMain.instanceId), eq(dataMain.active), anyString())
250             assertThat(currentMedia).containsExactly(MediaCommonModel.MediaControl(mediaLoaded))
251 
252             // and we switch to guest user
253             setUser(USER_GUEST)
254 
255             // THEN we should remove the main user's media
256             verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
257             verify(mediaLoadingLogger).logMediaRemoved(eq(dataMain.instanceId), anyString())
258             assertThat(currentMedia).isEmpty()
259         }
260 
261     @Test
onUserSwitched_addsNewUserControlsnull262     fun onUserSwitched_addsNewUserControls() =
263         testScope.runTest {
264             val currentMedia by collectLastValue(repository.currentMedia)
265             val guestLoadedStatesModel = MediaDataLoadingModel.Loaded(dataGuest.instanceId)
266             val mainLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
267 
268             // GIVEN that we had some media for both users
269             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
270             mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
271 
272             // and we switch to guest user
273             setUser(USER_GUEST)
274 
275             // THEN we should add back the guest user media
276             verify(listener)
277                 .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
278             verify(mediaLoadingLogger)
279                 .logMediaLoaded(eq(dataGuest.instanceId), eq(dataGuest.active), anyString())
280 
281             reset(mediaLoadingLogger)
282 
283             // but not the main user's
284             verify(listener, never())
285                 .onMediaDataLoaded(
286                     eq(KEY),
287                     any(),
288                     eq(dataMain),
289                     anyBoolean(),
290                     anyInt(),
291                     anyBoolean()
292                 )
293             verify(mediaLoadingLogger, never())
294                 .logMediaLoaded(eq(dataMain.instanceId), anyBoolean(), anyString())
295             assertThat(currentMedia)
296                 .containsExactly(MediaCommonModel.MediaControl(guestLoadedStatesModel))
297             assertThat(currentMedia)
298                 .doesNotContain(MediaCommonModel.MediaControl(mainLoadedStatesModel))
299         }
300 
301     @Test
onProfileChanged_profileUnavailable_updateStatesnull302     fun onProfileChanged_profileUnavailable_updateStates() =
303         testScope.runTest {
304             val currentMedia by collectLastValue(repository.currentMedia)
305 
306             // GIVEN that we had some media for both profiles
307             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
308             mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile)
309 
310             // and we change profile status
311             setPrivateProfileUnavailable()
312 
313             val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
314             // THEN we should remove the private profile media
315             verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
316             verify(mediaLoadingLogger).logMediaRemoved(eq(dataGuest.instanceId), anyString())
317             assertThat(currentMedia)
318                 .containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel))
319         }
320 
321     @Test
hasAnyMedia_mediaSet_returnsTruenull322     fun hasAnyMedia_mediaSet_returnsTrue() =
323         testScope.runTest {
324             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
325             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
326 
327             assertThat(hasAnyMedia(selectedUserEntries)).isTrue()
328         }
329 
330     @Test
hasAnyMedia_recommendationSet_returnsFalsenull331     fun hasAnyMedia_recommendationSet_returnsFalse() =
332         testScope.runTest {
333             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
334             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
335 
336             assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
337         }
338 
339     @Test
hasAnyMediaOrRecommendation_mediaSet_returnsTruenull340     fun hasAnyMediaOrRecommendation_mediaSet_returnsTrue() =
341         testScope.runTest {
342             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
343             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
344             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
345 
346             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
347                 .isTrue()
348         }
349 
350     @Test
hasAnyMediaOrRecommendation_recommendationSet_returnsTruenull351     fun hasAnyMediaOrRecommendation_recommendationSet_returnsTrue() =
352         testScope.runTest {
353             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
354             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
355             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
356 
357             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
358                 .isTrue()
359         }
360 
361     @Test
hasActiveMedia_inactiveMediaSet_returnsFalsenull362     fun hasActiveMedia_inactiveMediaSet_returnsFalse() =
363         testScope.runTest {
364             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
365 
366             val data = dataMain.copy(active = false)
367             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
368 
369             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
370         }
371 
372     @Test
hasActiveMedia_activeMediaSet_returnsTruenull373     fun hasActiveMedia_activeMediaSet_returnsTrue() =
374         testScope.runTest {
375             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
376             val data = dataMain.copy(active = true)
377             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
378 
379             assertThat(hasActiveMedia(selectedUserEntries)).isTrue()
380         }
381 
382     @Test
hasActiveMediaOrRecommendation_inactiveMediaSet_returnsFalsenull383     fun hasActiveMediaOrRecommendation_inactiveMediaSet_returnsFalse() =
384         testScope.runTest {
385             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
386             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
387             val reactivatedKey by collectLastValue(repository.reactivatedId)
388             val data = dataMain.copy(active = false)
389             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
390 
391             assertThat(
392                     hasActiveMediaOrRecommendation(
393                         selectedUserEntries,
394                         smartspaceMediaData,
395                         reactivatedKey
396                     )
397                 )
398                 .isFalse()
399         }
400 
401     @Test
hasActiveMediaOrRecommendation_activeMediaSet_returnsTruenull402     fun hasActiveMediaOrRecommendation_activeMediaSet_returnsTrue() =
403         testScope.runTest {
404             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
405             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
406             val reactivatedKey by collectLastValue(repository.reactivatedId)
407             val data = dataMain.copy(active = true)
408             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
409 
410             assertThat(
411                     hasActiveMediaOrRecommendation(
412                         selectedUserEntries,
413                         smartspaceMediaData,
414                         reactivatedKey
415                     )
416                 )
417                 .isTrue()
418         }
419 
420     @Test
hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalsenull421     fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() =
422         testScope.runTest {
423             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
424             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
425             val reactivatedKey by collectLastValue(repository.reactivatedId)
426             whenever(smartspaceData.isActive).thenReturn(false)
427             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
428 
429             assertThat(
430                     hasActiveMediaOrRecommendation(
431                         selectedUserEntries,
432                         smartspaceMediaData,
433                         reactivatedKey
434                     )
435                 )
436                 .isFalse()
437         }
438 
439     @Test
hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalsenull440     fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() =
441         testScope.runTest {
442             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
443             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
444             val reactivatedKey by collectLastValue(repository.reactivatedId)
445             whenever(smartspaceData.isValid()).thenReturn(false)
446             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
447 
448             assertThat(
449                     hasActiveMediaOrRecommendation(
450                         selectedUserEntries,
451                         smartspaceMediaData,
452                         reactivatedKey
453                     )
454                 )
455                 .isFalse()
456         }
457 
458     @Test
hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTruenull459     fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() =
460         testScope.runTest {
461             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
462             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
463             val reactivatedKey by collectLastValue(repository.reactivatedId)
464             whenever(smartspaceData.isActive).thenReturn(true)
465             whenever(smartspaceData.isValid()).thenReturn(true)
466             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
467 
468             assertThat(
469                     hasActiveMediaOrRecommendation(
470                         selectedUserEntries,
471                         smartspaceMediaData,
472                         reactivatedKey
473                     )
474                 )
475                 .isTrue()
476         }
477 
478     @Test
hasAnyMediaOrRecommendation_onlyCurrentUsernull479     fun hasAnyMediaOrRecommendation_onlyCurrentUser() =
480         testScope.runTest {
481             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
482             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
483             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
484                 .isFalse()
485 
486             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataGuest)
487             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
488                 .isFalse()
489             assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
490         }
491 
492     @Test
hasActiveMediaOrRecommendation_onlyCurrentUsernull493     fun hasActiveMediaOrRecommendation_onlyCurrentUser() =
494         testScope.runTest {
495             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
496             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
497             val reactivatedKey by collectLastValue(repository.reactivatedId)
498             assertThat(
499                     hasActiveMediaOrRecommendation(
500                         selectedUserEntries,
501                         smartspaceMediaData,
502                         reactivatedKey
503                     )
504                 )
505                 .isFalse()
506             val data = dataGuest.copy(active = true)
507 
508             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
509             assertThat(
510                     hasActiveMediaOrRecommendation(
511                         selectedUserEntries,
512                         smartspaceMediaData,
513                         reactivatedKey
514                     )
515                 )
516                 .isFalse()
517             assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
518         }
519 
520     @Test
onNotificationRemoved_doesNotHaveMedianull521     fun onNotificationRemoved_doesNotHaveMedia() =
522         testScope.runTest {
523             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
524             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
525 
526             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
527             mediaDataFilter.onMediaDataRemoved(KEY, false)
528             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
529                 .isFalse()
530             assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
531         }
532 
533     @Test
onSwipeToDismiss_setsTimedOutnull534     fun onSwipeToDismiss_setsTimedOut() {
535         mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
536         mediaDataFilter.onSwipeToDismiss()
537 
538         verify(mediaDataProcessor).setInactive(eq(KEY), eq(true), eq(true))
539     }
540 
541     @Test
onSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspacenull542     fun onSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() =
543         testScope.runTest {
544             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
545             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
546             val reactivatedKey by collectLastValue(repository.reactivatedId)
547             val currentMedia by collectLastValue(repository.currentMedia)
548             val recommendationsLoadingModel =
549                 SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
550 
551             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
552 
553             assertThat(currentMedia)
554                 .containsExactly(MediaCommonModel.MediaRecommendations(recommendationsLoadingModel))
555             assertThat(
556                     hasActiveMediaOrRecommendation(
557                         selectedUserEntries,
558                         smartspaceMediaData,
559                         reactivatedKey
560                     )
561                 )
562                 .isTrue()
563             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
564             verify(listener)
565                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
566             verify(mediaLoadingLogger)
567                 .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
568             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
569             verify(logger, never()).logRecommendationActivated(any(), any(), any())
570         }
571 
572     @Test
onSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothingnull573     fun onSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() =
574         testScope.runTest {
575             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
576             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
577             val reactivatedKey by collectLastValue(repository.reactivatedId)
578             val currentMedia by collectLastValue(repository.currentMedia)
579 
580             whenever(smartspaceData.isActive).thenReturn(false)
581 
582             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
583 
584             assertThat(currentMedia).isEmpty()
585             assertThat(
586                     hasActiveMediaOrRecommendation(
587                         selectedUserEntries,
588                         smartspaceMediaData,
589                         reactivatedKey
590                     )
591                 )
592                 .isFalse()
593             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
594             verify(listener, never())
595                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
596             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
597             verify(mediaLoadingLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
598             verify(mediaLoadingLogger, never())
599                 .logRecommendationLoaded(any(), anyBoolean(), anyString())
600             verify(logger, never()).logRecommendationAdded(any(), any())
601             verify(logger, never()).logRecommendationActivated(any(), any(), any())
602         }
603 
604     @Test
onSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspacenull605     fun onSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() =
606         testScope.runTest {
607             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
608             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
609             val reactivatedKey by collectLastValue(repository.reactivatedId)
610             val currentMedia by collectLastValue(repository.currentMedia)
611             val recsCommonModel =
612                 MediaCommonModel.MediaRecommendations(
613                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
614                 )
615             val controlCommonModel =
616                 MediaCommonModel.MediaControl(
617                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
618                     true
619                 )
620             val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
621             mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
622             clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
623             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
624 
625             assertThat(currentMedia).containsExactly(recsCommonModel, controlCommonModel)
626             assertThat(
627                     hasActiveMediaOrRecommendation(
628                         selectedUserEntries,
629                         smartspaceMediaData,
630                         reactivatedKey
631                     )
632                 )
633                 .isTrue()
634             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
635             verify(listener)
636                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
637             verify(mediaLoadingLogger)
638                 .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
639             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
640             verify(logger, never()).logRecommendationActivated(any(), any(), any())
641         }
642 
643     @Test
onSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothingnull644     fun onSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() =
645         testScope.runTest {
646             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
647             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
648             val reactivatedKey by collectLastValue(repository.reactivatedId)
649             val currentMedia by collectLastValue(repository.currentMedia)
650             whenever(smartspaceData.isActive).thenReturn(false)
651 
652             val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
653             mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
654             clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
655             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
656 
657             assertThat(currentMedia)
658                 .doesNotContain(
659                     MediaCommonModel.MediaRecommendations(
660                         SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
661                     )
662                 )
663             assertThat(
664                     hasActiveMediaOrRecommendation(
665                         selectedUserEntries,
666                         smartspaceMediaData,
667                         reactivatedKey
668                     )
669                 )
670                 .isFalse()
671             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
672             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
673             verify(mediaLoadingLogger, never())
674                 .logRecommendationLoaded(any(), anyBoolean(), anyString())
675             verify(logger, never()).logRecommendationAdded(any(), any())
676             verify(logger, never()).logRecommendationActivated(any(), any(), any())
677         }
678 
679     @Test
onSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothingnull680     fun onSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() =
681         testScope.runTest {
682             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
683             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
684             val reactivatedKey by collectLastValue(repository.reactivatedId)
685             val currentMedia by collectLastValue(repository.currentMedia)
686 
687             whenever(smartspaceData.isActive).thenReturn(false)
688 
689             // WHEN we have media that was recently played, but not currently active
690             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
691             val controlCommonModel =
692                 MediaCommonModel.MediaControl(
693                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
694                     true
695                 )
696             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
697             repository.setOrderedMedia()
698 
699             assertThat(currentMedia).containsExactly(controlCommonModel)
700             verify(listener)
701                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
702             verify(mediaLoadingLogger)
703                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
704 
705             reset(mediaLoadingLogger)
706 
707             // AND we get a smartspace signal
708             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
709 
710             // THEN we should treat the media as not active instead
711             assertThat(currentMedia).containsExactly(controlCommonModel)
712             assertThat(
713                     hasActiveMediaOrRecommendation(
714                         selectedUserEntries,
715                         smartspaceMediaData,
716                         reactivatedKey
717                     )
718                 )
719                 .isFalse()
720             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
721             verify(listener, never())
722                 .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
723             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
724             verify(mediaLoadingLogger, never())
725                 .logMediaLoaded(eq(dataCurrent.instanceId), anyBoolean(), anyString())
726             verify(mediaLoadingLogger, never())
727                 .logRecommendationLoaded(any(), anyBoolean(), anyString())
728             verify(logger, never()).logRecommendationAdded(any(), any())
729             verify(logger, never()).logRecommendationActivated(any(), any(), any())
730         }
731 
732     @Test
onSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedianull733     fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() =
734         testScope.runTest {
735             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
736             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
737             val reactivatedKey by collectLastValue(repository.reactivatedId)
738             val currentMedia by collectLastValue(repository.currentMedia)
739             whenever(smartspaceData.isValid()).thenReturn(false)
740 
741             // WHEN we have media that was recently played, but not currently active
742             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
743             val controlCommonModel =
744                 MediaCommonModel.MediaControl(
745                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
746                     true
747                 )
748             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
749             repository.setOrderedMedia()
750             assertThat(currentMedia).containsExactly(controlCommonModel)
751             verify(listener)
752                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
753             verify(mediaLoadingLogger)
754                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
755 
756             // AND we get a smartspace signal
757             runCurrent()
758             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
759 
760             // THEN we should treat the media as active instead
761             val dataCurrentAndActive = dataCurrent.copy(active = true)
762             assertThat(currentMedia).containsExactly(controlCommonModel)
763             assertThat(
764                     hasActiveMediaOrRecommendation(
765                         selectedUserEntries,
766                         smartspaceMediaData,
767                         reactivatedKey
768                     )
769                 )
770                 .isTrue()
771             verify(listener)
772                 .onMediaDataLoaded(
773                     eq(KEY),
774                     eq(KEY),
775                     eq(dataCurrentAndActive),
776                     eq(true),
777                     eq(100),
778                     eq(true)
779                 )
780             verify(mediaLoadingLogger)
781                 .logMediaLoaded(
782                     eq(dataCurrentAndActive.instanceId),
783                     eq(dataCurrentAndActive.active),
784                     anyString()
785                 )
786             // Smartspace update shouldn't be propagated for the empty rec list.
787             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
788             verify(mediaLoadingLogger, never())
789                 .logRecommendationLoaded(any(), anyBoolean(), anyString())
790             verify(logger, never()).logRecommendationAdded(any(), any())
791             verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
792         }
793 
794     @Test
onSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBothnull795     fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() =
796         testScope.runTest {
797             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
798             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
799             val reactivatedKey by collectLastValue(repository.reactivatedId)
800             val currentMedia by collectLastValue(repository.currentMedia)
801             // WHEN we have media that was recently played, but not currently active
802             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
803             val controlCommonModel =
804                 MediaCommonModel.MediaControl(
805                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
806                     true
807                 )
808             val recsCommonModel =
809                 MediaCommonModel.MediaRecommendations(
810                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
811                 )
812 
813             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
814             repository.setOrderedMedia()
815 
816             assertThat(currentMedia).containsExactly(controlCommonModel)
817             verify(listener)
818                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
819             verify(mediaLoadingLogger)
820                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
821 
822             // AND we get a smartspace signal
823             runCurrent()
824             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
825 
826             // THEN we should treat the media as active instead
827             val dataCurrentAndActive = dataCurrent.copy(active = true)
828             verify(listener)
829                 .onMediaDataLoaded(
830                     eq(KEY),
831                     eq(KEY),
832                     eq(dataCurrentAndActive),
833                     eq(true),
834                     eq(100),
835                     eq(true)
836                 )
837             verify(mediaLoadingLogger)
838                 .logMediaLoaded(
839                     eq(dataCurrentAndActive.instanceId),
840                     eq(dataCurrentAndActive.active),
841                     anyString()
842                 )
843             assertThat(
844                     hasActiveMediaOrRecommendation(
845                         selectedUserEntries,
846                         smartspaceMediaData,
847                         reactivatedKey
848                     )
849                 )
850                 .isTrue()
851             // Smartspace update should also be propagated but not prioritized.
852             assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
853             verify(listener)
854                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
855             verify(mediaLoadingLogger)
856                 .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
857             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
858             verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
859         }
860 
861     @Test
onSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspacenull862     fun onSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspace() =
863         testScope.runTest {
864             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
865             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
866             val reactivatedKey by collectLastValue(repository.reactivatedId)
867             val currentMedia by collectLastValue(repository.currentMedia)
868 
869             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
870             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
871 
872             verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
873             verify(mediaLoadingLogger)
874                 .logRecommendationRemoved(eq(SMARTSPACE_KEY), eq(true), anyString())
875             assertThat(currentMedia).isEmpty()
876             assertThat(
877                     hasActiveMediaOrRecommendation(
878                         selectedUserEntries,
879                         smartspaceMediaData,
880                         reactivatedKey
881                     )
882                 )
883                 .isFalse()
884             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
885         }
886 
887     @Test
onSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBothnull888     fun onSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() =
889         testScope.runTest {
890             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
891             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
892             val reactivatedKey by collectLastValue(repository.reactivatedId)
893             val currentMedia by collectLastValue(repository.currentMedia)
894             val controlCommonModel =
895                 MediaCommonModel.MediaControl(
896                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
897                     true
898                 )
899             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
900             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
901             repository.setOrderedMedia()
902 
903             assertThat(currentMedia).containsExactly(controlCommonModel)
904             verify(listener)
905                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
906             verify(mediaLoadingLogger)
907                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
908 
909             runCurrent()
910             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
911 
912             val dataCurrentAndActive = dataCurrent.copy(active = true)
913             verify(listener)
914                 .onMediaDataLoaded(
915                     eq(KEY),
916                     eq(KEY),
917                     eq(dataCurrentAndActive),
918                     eq(true),
919                     eq(100),
920                     eq(true)
921                 )
922             verify(mediaLoadingLogger)
923                 .logMediaLoaded(
924                     eq(dataCurrentAndActive.instanceId),
925                     eq(dataCurrentAndActive.active),
926                     anyString()
927                 )
928 
929             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
930 
931             verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
932             verify(mediaLoadingLogger)
933                 .logRecommendationRemoved(eq(SMARTSPACE_KEY), eq(true), anyString())
934             assertThat(currentMedia).containsExactly(controlCommonModel)
935             assertThat(
936                     hasActiveMediaOrRecommendation(
937                         selectedUserEntries,
938                         smartspaceMediaData,
939                         reactivatedKey
940                     )
941                 )
942                 .isFalse()
943             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
944         }
945 
946     @Test
onSmartspaceLoaded_persistentEnabled_isInactivenull947     fun onSmartspaceLoaded_persistentEnabled_isInactive() =
948         testScope.runTest {
949             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
950             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
951             val reactivatedKey by collectLastValue(repository.reactivatedId)
952             val currentMedia by collectLastValue(repository.currentMedia)
953             val recsCommonModel =
954                 MediaCommonModel.MediaRecommendations(
955                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
956                 )
957             whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
958             whenever(smartspaceData.isActive).thenReturn(false)
959 
960             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
961 
962             verify(listener)
963                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
964             verify(mediaLoadingLogger)
965                 .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(false), anyString())
966             assertThat(currentMedia).containsExactly(recsCommonModel)
967             assertThat(
968                     hasActiveMediaOrRecommendation(
969                         selectedUserEntries,
970                         smartspaceMediaData,
971                         reactivatedKey
972                     )
973                 )
974                 .isFalse()
975             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
976                 .isTrue()
977         }
978 
979     @Test
onSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactivenull980     fun onSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() =
981         testScope.runTest {
982             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
983             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
984             val reactivatedKey by collectLastValue(repository.reactivatedId)
985             val currentMedia by collectLastValue(repository.currentMedia)
986             val recsCommonModel =
987                 MediaCommonModel.MediaRecommendations(
988                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
989                 )
990             val controlCommonModel =
991                 MediaCommonModel.MediaControl(
992                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
993                     true
994                 )
995 
996             whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
997             whenever(smartspaceData.isActive).thenReturn(false)
998 
999             // If there is media that was recently played but inactive
1000             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
1001             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
1002             repository.setOrderedMedia()
1003 
1004             verify(listener)
1005                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
1006             verify(mediaLoadingLogger)
1007                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
1008             assertThat(currentMedia).containsExactly(controlCommonModel)
1009 
1010             reset(mediaLoadingLogger)
1011 
1012             // And an inactive recommendation is loaded
1013             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
1014 
1015             // Smartspace is loaded but the media stays inactive
1016             verify(listener)
1017                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
1018             verify(mediaLoadingLogger)
1019                 .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(false), anyString())
1020             verify(listener, never())
1021                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
1022             verify(mediaLoadingLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
1023             assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
1024             assertThat(
1025                     hasActiveMediaOrRecommendation(
1026                         selectedUserEntries,
1027                         smartspaceMediaData,
1028                         reactivatedKey
1029                     )
1030                 )
1031                 .isFalse()
1032             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
1033                 .isTrue()
1034         }
1035 
1036     @Test
onSwipeToDismiss_persistentEnabled_recommendationSetInactivenull1037     fun onSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
1038         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
1039 
1040         val data =
1041             EMPTY_SMARTSPACE_MEDIA_DATA.copy(
1042                 targetId = SMARTSPACE_KEY,
1043                 isActive = true,
1044                 packageName = SMARTSPACE_PACKAGE,
1045                 recommendations = listOf(smartspaceMediaRecommendationItem),
1046             )
1047         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data)
1048         mediaDataFilter.onSwipeToDismiss()
1049 
1050         verify(mediaDataProcessor).setRecommendationInactive(eq(SMARTSPACE_KEY))
1051         verify(mediaDataProcessor, never())
1052             .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
1053     }
1054 
1055     @Test
smartspaceLoaded_shouldTriggerResume_doesTriggernull1056     fun smartspaceLoaded_shouldTriggerResume_doesTrigger() =
1057         testScope.runTest {
1058             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
1059             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
1060             val reactivatedKey by collectLastValue(repository.reactivatedId)
1061             val currentMedia by collectLastValue(repository.currentMedia)
1062             val recsCommonModel =
1063                 MediaCommonModel.MediaRecommendations(
1064                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
1065                 )
1066             val controlCommonModel =
1067                 MediaCommonModel.MediaControl(
1068                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
1069                     true
1070                 )
1071             // WHEN we have media that was recently played, but not currently active
1072             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
1073             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
1074             repository.setOrderedMedia()
1075 
1076             verify(listener)
1077                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
1078             verify(mediaLoadingLogger)
1079                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
1080             assertThat(currentMedia).containsExactly(controlCommonModel)
1081 
1082             // AND we get a smartspace signal with extra to trigger resume
1083             runCurrent()
1084             val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, true) }
1085             whenever(cardAction.extras).thenReturn(extras)
1086             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
1087 
1088             // THEN we should treat the media as active instead
1089             val dataCurrentAndActive = dataCurrent.copy(active = true)
1090             verify(listener)
1091                 .onMediaDataLoaded(
1092                     eq(KEY),
1093                     eq(KEY),
1094                     eq(dataCurrentAndActive),
1095                     eq(true),
1096                     eq(100),
1097                     eq(true)
1098                 )
1099             verify(mediaLoadingLogger)
1100                 .logMediaLoaded(
1101                     eq(dataCurrentAndActive.instanceId),
1102                     eq(dataCurrentAndActive.active),
1103                     anyString()
1104                 )
1105             assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
1106             assertThat(
1107                     hasActiveMediaOrRecommendation(
1108                         selectedUserEntries,
1109                         smartspaceMediaData,
1110                         reactivatedKey
1111                     )
1112                 )
1113                 .isTrue()
1114             // And update the smartspace data state, but not prioritized
1115             verify(listener)
1116                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
1117             verify(mediaLoadingLogger)
1118                 .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
1119         }
1120 
1121     @Test
smartspaceLoaded_notShouldTriggerResume_doesNotTriggernull1122     fun smartspaceLoaded_notShouldTriggerResume_doesNotTrigger() =
1123         testScope.runTest {
1124             val currentMedia by collectLastValue(repository.currentMedia)
1125             val recsCommonModel =
1126                 MediaCommonModel.MediaRecommendations(
1127                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
1128                 )
1129             val controlCommonModel =
1130                 MediaCommonModel.MediaControl(
1131                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
1132                     true
1133                 )
1134 
1135             // WHEN we have media that was recently played, but not currently active
1136             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
1137             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
1138             repository.setOrderedMedia()
1139 
1140             verify(listener)
1141                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
1142             verify(mediaLoadingLogger)
1143                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
1144             assertThat(currentMedia).containsExactly(controlCommonModel)
1145 
1146             reset(mediaLoadingLogger)
1147 
1148             // AND we get a smartspace signal with extra to not trigger resume
1149             val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
1150             whenever(cardAction.extras).thenReturn(extras)
1151             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
1152 
1153             // THEN listeners are not updated to show media
1154             verify(listener, never())
1155                 .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true))
1156             verify(mediaLoadingLogger, never())
1157                 .logMediaLoaded(eq(dataCurrent.instanceId), anyBoolean(), anyString())
1158             // But the smartspace update is still propagated
1159             verify(listener)
1160                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
1161             verify(mediaLoadingLogger)
1162                 .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
1163             assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
1164         }
1165 
hasActiveMediaOrRecommendationnull1166     private fun hasActiveMediaOrRecommendation(
1167         entries: Map<InstanceId, MediaData>?,
1168         smartspaceMediaData: SmartspaceMediaData?,
1169         reactivatedId: InstanceId?
1170     ): Boolean {
1171         if (entries == null || smartspaceMediaData == null) {
1172             return false
1173         }
1174         return entries.any { it.value.active } ||
1175             (smartspaceMediaData.isActive &&
1176                 (smartspaceMediaData.isValid() || reactivatedId != null))
1177     }
1178 
hasActiveMedianull1179     private fun hasActiveMedia(entries: Map<InstanceId, MediaData>?): Boolean {
1180         return entries?.any { it.value.active } ?: false
1181     }
1182 
hasAnyMediaOrRecommendationnull1183     private fun hasAnyMediaOrRecommendation(
1184         entries: Map<InstanceId, MediaData>?,
1185         smartspaceMediaData: SmartspaceMediaData?
1186     ): Boolean {
1187         if (entries == null || smartspaceMediaData == null) {
1188             return false
1189         }
1190         return entries.isNotEmpty() ||
1191             (if (mediaFlags.isPersistentSsCardEnabled()) {
1192                 smartspaceMediaData.isValid()
1193             } else {
1194                 smartspaceMediaData.isActive && smartspaceMediaData.isValid()
1195             })
1196     }
1197 
hasAnyMedianull1198     private fun hasAnyMedia(entries: Map<InstanceId, MediaData>?): Boolean {
1199         return entries?.isNotEmpty() ?: false
1200     }
1201 }
1202