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