1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.controls.controller
18 
19 import android.app.PendingIntent
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.ContextWrapper
23 import android.content.Intent
24 import android.content.pm.ServiceInfo
25 import android.os.UserHandle
26 import android.service.controls.Control
27 import android.service.controls.DeviceTypes
28 import android.service.controls.actions.ControlAction
29 import androidx.test.ext.junit.runners.AndroidJUnit4
30 import androidx.test.filters.SmallTest
31 import com.android.systemui.SysuiTestCase
32 import com.android.systemui.backup.BackupHelper
33 import com.android.systemui.controls.ControlStatus
34 import com.android.systemui.controls.ControlsServiceInfo
35 import com.android.systemui.controls.management.ControlsListingController
36 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
37 import com.android.systemui.controls.panels.selectedComponentRepository
38 import com.android.systemui.controls.ui.ControlsUiController
39 import com.android.systemui.dump.DumpManager
40 import com.android.systemui.settings.UserFileManager
41 import com.android.systemui.settings.UserTracker
42 import com.android.systemui.testKosmos
43 import com.android.systemui.util.concurrency.FakeExecutor
44 import com.android.systemui.util.mockito.whenever
45 import com.android.systemui.util.time.FakeSystemClock
46 import com.google.common.truth.Truth.assertThat
47 import org.junit.After
48 import org.junit.Assert.assertEquals
49 import org.junit.Assert.assertFalse
50 import org.junit.Assert.assertNotEquals
51 import org.junit.Assert.assertTrue
52 import org.junit.Before
53 import org.junit.Test
54 import org.junit.runner.RunWith
55 import org.mockito.ArgumentCaptor
56 import org.mockito.ArgumentMatchers
57 import org.mockito.ArgumentMatchers.anyString
58 import org.mockito.Captor
59 import org.mockito.Mock
60 import org.mockito.Mockito
61 import org.mockito.Mockito.anyInt
62 import org.mockito.Mockito.clearInvocations
63 import org.mockito.Mockito.inOrder
64 import org.mockito.Mockito.mock
65 import org.mockito.Mockito.never
66 import org.mockito.Mockito.reset
67 import org.mockito.Mockito.times
68 import org.mockito.Mockito.verify
69 import org.mockito.Mockito.verifyNoMoreInteractions
70 import org.mockito.Mockito.`when`
71 import org.mockito.MockitoAnnotations
72 import java.io.File
73 import java.util.Optional
74 import java.util.function.Consumer
75 
76 @SmallTest
77 @RunWith(AndroidJUnit4::class)
78 class ControlsControllerImplTest : SysuiTestCase() {
79     private val kosmos = testKosmos()
80 
81     @Mock
82     private lateinit var uiController: ControlsUiController
83     @Mock
84     private lateinit var bindingController: ControlsBindingController
85     @Mock
86     private lateinit var pendingIntent: PendingIntent
87     @Mock
88     private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
89     @Mock
90     private lateinit var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
91     @Mock
92     private lateinit var listingController: ControlsListingController
93     @Mock
94     private lateinit var userTracker: UserTracker
95     @Mock
96     private lateinit var userFileManager: UserFileManager
97     @Mock
98     private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
99 
100     @Captor
101     private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
102 
103     @Captor
104     private lateinit var controlLoadCallbackCaptor:
105             ArgumentCaptor<ControlsBindingController.LoadCallback>
106     @Captor
107     private lateinit var controlLoadCallbackCaptor2:
108             ArgumentCaptor<ControlsBindingController.LoadCallback>
109 
110     @Captor
111     private lateinit var listingCallbackCaptor:
112             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
113 
114     private lateinit var delayableExecutor: FakeExecutor
115     private lateinit var controller: ControlsControllerImpl
116     private lateinit var canceller: DidRunRunnable
117 
118     companion object {
119         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
120         fun <T> eq(value: T): T = Mockito.eq(value) ?: value
121         fun <T> any(): T = Mockito.any<T>()
122 
123         private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
124         private const val TEST_CONTROL_ID = "control1"
125         private const val TEST_CONTROL_TITLE = "Test"
126         private const val TEST_CONTROL_SUBTITLE = "TestSub"
127         private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER
128         private const val TEST_STRUCTURE = ""
129         private val TEST_CONTROL_INFO = ControlInfo(TEST_CONTROL_ID,
130                 TEST_CONTROL_TITLE, TEST_CONTROL_SUBTITLE, TEST_DEVICE_TYPE)
131         private val TEST_STRUCTURE_INFO = StructureInfo(TEST_COMPONENT,
132                 TEST_STRUCTURE, listOf(TEST_CONTROL_INFO))
133 
134         private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2")
135         private const val TEST_CONTROL_ID_2 = "control2"
136         private const val TEST_CONTROL_TITLE_2 = "Test 2"
137         private const val TEST_CONTROL_SUBTITLE_2 = "TestSub 2"
138         private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA
139         private const val TEST_STRUCTURE_2 = "My House"
140         private val TEST_CONTROL_INFO_2 = ControlInfo(TEST_CONTROL_ID_2,
141                 TEST_CONTROL_TITLE_2, TEST_CONTROL_SUBTITLE_2, TEST_DEVICE_TYPE_2)
142         private val TEST_STRUCTURE_INFO_2 = StructureInfo(TEST_COMPONENT_2,
143                 TEST_STRUCTURE_2, listOf(TEST_CONTROL_INFO_2))
144     }
145 
146     private val user = mContext.userId
147     private val otherUser = user + 1
148 
149     @Before
150     fun setUp() {
151         MockitoAnnotations.initMocks(this)
152 
153         whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
154         `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
155 
156         delayableExecutor = FakeExecutor(FakeSystemClock())
157 
158         val wrapper = object : ContextWrapper(mContext) {
159             override fun createContextAsUser(user: UserHandle, flags: Int): Context {
160                 return baseContext
161             }
162         }
163 
164         canceller = DidRunRunnable()
165         `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
166         `when`(userFileManager.getFile(anyString(), anyInt())).thenReturn(mock(File::class.java))
167         `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
168             .thenReturn(context.getSharedPreferences("test", Context.MODE_PRIVATE))
169 
170         controller = ControlsControllerImpl(
171                 wrapper,
172                 delayableExecutor,
173                 uiController,
174                 kosmos.selectedComponentRepository,
175                 bindingController,
176                 listingController,
177                 userFileManager,
178                 userTracker,
179                 authorizedPanelsRepository,
180                 Optional.of(persistenceWrapper),
181                 mock(DumpManager::class.java)
182         )
183         controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
184 
185         verify(listingController).addCallback(capture(listingCallbackCaptor))
186     }
187 
188     @After
189     fun tearDown() {
190         controller.destroy()
191     }
192 
193     private fun statelessBuilderFromInfo(
194         controlInfo: ControlInfo,
195         structure: CharSequence = ""
196     ): Control.StatelessBuilder {
197         return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
198                 .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
199                 .setSubtitle(controlInfo.controlSubtitle).setStructure(structure)
200     }
201 
202     private fun statefulBuilderFromInfo(
203         controlInfo: ControlInfo,
204         structure: CharSequence = ""
205     ): Control.StatefulBuilder {
206         return Control.StatefulBuilder(controlInfo.controlId, pendingIntent)
207                 .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
208                 .setSubtitle(controlInfo.controlSubtitle).setStructure(structure)
209     }
210 
211     @Test
212     fun testStartOnUser() {
213         assertEquals(user, controller.currentUserId)
214     }
215 
216     @Test
217     fun testStartWithoutFavorites() {
218         assertTrue(controller.getFavorites().isEmpty())
219     }
220 
221     @Test
222     fun testStartWithSavedFavorites() {
223         `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_STRUCTURE_INFO))
224         val controller_other = ControlsControllerImpl(
225                 mContext,
226                 delayableExecutor,
227                 uiController,
228                 kosmos.selectedComponentRepository,
229                 bindingController,
230                 listingController,
231                 userFileManager,
232                 userTracker,
233                 authorizedPanelsRepository,
234                 Optional.of(persistenceWrapper),
235                 mock(DumpManager::class.java)
236         )
237         assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
238     }
239 
240     @Test
241     fun testAddAuthorizedPackagesFromSavedFavoritesOnStart() {
242         clearInvocations(authorizedPanelsRepository)
243         `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_STRUCTURE_INFO))
244         ControlsControllerImpl(
245                 mContext,
246                 delayableExecutor,
247                 uiController,
248                 kosmos.selectedComponentRepository,
249                 bindingController,
250                 listingController,
251                 userFileManager,
252                 userTracker,
253                 authorizedPanelsRepository,
254                 Optional.of(persistenceWrapper),
255                 mock(DumpManager::class.java)
256         )
257         verify(authorizedPanelsRepository)
258                 .addAuthorizedPanels(setOf(TEST_STRUCTURE_INFO.componentName.packageName))
259     }
260 
261     @Test
262     fun testOnActionResponse() {
263         controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
264 
265         verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID,
266                 ControlAction.RESPONSE_OK)
267     }
268 
269     @Test
270     fun testRefreshStatus() {
271         val control = Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build()
272         val list = listOf(control)
273         controller.refreshStatus(TEST_COMPONENT, control)
274 
275         verify(uiController).onRefreshState(TEST_COMPONENT, list)
276     }
277 
278     @Test
279     fun testUnsubscribe() {
280         controller.unsubscribe()
281         verify(bindingController).unsubscribe()
282     }
283 
284     @Test
285     fun testSubscribeFavorites() {
286         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
287         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO_2)
288         delayableExecutor.runAllReady()
289 
290         controller.subscribeToFavorites(TEST_STRUCTURE_INFO)
291 
292         verify(bindingController).subscribe(capture(structureInfoCaptor))
293 
294         assertTrue(TEST_CONTROL_INFO in structureInfoCaptor.value.controls)
295         assertFalse(TEST_CONTROL_INFO_2 in structureInfoCaptor.value.controls)
296     }
297 
298     @Test
299     fun testLoadForComponent_noFavorites() {
300         var loaded = false
301         val control = statelessBuilderFromInfo(TEST_CONTROL_INFO).build()
302 
303         controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
304             val controls = data.allControls
305             val favorites = data.favoritesIds
306             loaded = true
307             assertEquals(1, controls.size)
308             val controlStatus = controls[0]
309             assertEquals(ControlStatus(control, TEST_COMPONENT, false), controlStatus)
310 
311             assertTrue(favorites.isEmpty())
312             assertFalse(data.errorOnLoad)
313         }, Consumer {})
314 
315         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
316                 capture(controlLoadCallbackCaptor))
317 
318         controlLoadCallbackCaptor.value.accept(listOf(control))
319 
320         delayableExecutor.runAllReady()
321 
322         assertTrue(loaded)
323     }
324 
325     @Test
326     fun testLoadForComponent_favorites() {
327         var loaded = false
328         val control = statelessBuilderFromInfo(TEST_CONTROL_INFO).build()
329         val control2 = statelessBuilderFromInfo(TEST_CONTROL_INFO_2).build()
330         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
331         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO_2)
332         delayableExecutor.runAllReady()
333 
334         controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
335             val controls = data.allControls
336             val favorites = data.favoritesIds
337             loaded = true
338             assertEquals(2, controls.size)
339             val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID }
340             assertEquals(ControlStatus(control, TEST_COMPONENT, true), controlStatus)
341 
342             val controlStatus2 = controls.first { it.control.controlId == TEST_CONTROL_ID_2 }
343             assertEquals(ControlStatus(control2, TEST_COMPONENT, false), controlStatus2)
344 
345             assertEquals(1, favorites.size)
346             assertEquals(TEST_CONTROL_ID, favorites[0])
347             assertFalse(data.errorOnLoad)
348         }, Consumer {})
349 
350         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
351                 capture(controlLoadCallbackCaptor))
352 
353         controlLoadCallbackCaptor.value.accept(listOf(control, control2))
354         delayableExecutor.runAllReady()
355 
356         assertTrue(loaded)
357     }
358 
359     @Test
360     fun testLoadForComponent_removed() {
361         var loaded = false
362         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
363         delayableExecutor.runAllReady()
364 
365         controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
366             val controls = data.allControls
367             val favorites = data.favoritesIds
368             loaded = true
369             assertEquals(1, controls.size)
370             val controlStatus = controls[0]
371             assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
372             assertEquals(TEST_STRUCTURE_INFO.structure, controlStatus.control.structure)
373             assertTrue(controlStatus.favorite)
374             assertTrue(controlStatus.removed)
375 
376             assertEquals(1, favorites.size)
377             assertEquals(TEST_CONTROL_ID, favorites[0])
378             assertFalse(data.errorOnLoad)
379         }, Consumer {})
380 
381         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
382                 capture(controlLoadCallbackCaptor))
383 
384         controlLoadCallbackCaptor.value.accept(emptyList())
385         delayableExecutor.runAllReady()
386 
387         assertTrue(loaded)
388     }
389 
390     @Test
391     fun testErrorOnLoad_notRemoved() {
392         var loaded = false
393         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
394         delayableExecutor.runAllReady()
395 
396         controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
397             val controls = data.allControls
398             val favorites = data.favoritesIds
399             loaded = true
400             assertEquals(1, controls.size)
401             val controlStatus = controls[0]
402             assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
403             assertEquals(TEST_STRUCTURE_INFO.structure, controlStatus.control.structure)
404             assertTrue(controlStatus.favorite)
405             assertFalse(controlStatus.removed)
406 
407             assertEquals(1, favorites.size)
408             assertEquals(TEST_CONTROL_ID, favorites[0])
409             assertTrue(data.errorOnLoad)
410         }, Consumer {})
411 
412         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
413                 capture(controlLoadCallbackCaptor))
414 
415         controlLoadCallbackCaptor.value.error("")
416 
417         delayableExecutor.runAllReady()
418 
419         assertTrue(loaded)
420     }
421 
422     @Test
423     fun testCancelLoad() {
424         var loaded = false
425         var cancelRunnable: Runnable? = null
426         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
427         delayableExecutor.runAllReady()
428         controller.loadForComponent(TEST_COMPONENT, Consumer {
429             loaded = true
430         }, Consumer { runnable -> cancelRunnable = runnable })
431 
432         cancelRunnable?.run()
433         delayableExecutor.runAllReady()
434 
435         assertFalse(loaded)
436         assertTrue(canceller.ran)
437     }
438 
439     @Test
440     fun testCancelLoad_afterSuccessfulLoad() {
441         var loaded = false
442         var cancelRunnable: Runnable? = null
443         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
444         delayableExecutor.runAllReady()
445         controller.loadForComponent(TEST_COMPONENT, Consumer {
446             loaded = true
447         }, Consumer { runnable -> cancelRunnable = runnable })
448 
449         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
450             capture(controlLoadCallbackCaptor))
451 
452         controlLoadCallbackCaptor.value.accept(emptyList())
453 
454         cancelRunnable?.run()
455         delayableExecutor.runAllReady()
456 
457         assertTrue(loaded)
458         assertTrue(canceller.ran)
459     }
460 
461     @Test
462     fun testCancelLoad_afterErrorLoad() {
463         var loaded = false
464         var cancelRunnable: Runnable? = null
465         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
466         delayableExecutor.runAllReady()
467         controller.loadForComponent(TEST_COMPONENT, Consumer {
468             loaded = true
469         }, Consumer { runnable -> cancelRunnable = runnable })
470 
471         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
472             capture(controlLoadCallbackCaptor))
473 
474         controlLoadCallbackCaptor.value.error("")
475 
476         cancelRunnable?.run()
477         delayableExecutor.runAllReady()
478 
479         assertTrue(loaded)
480         assertTrue(canceller.ran)
481     }
482 
483     @Test
484     fun testFavoriteInformationModifiedOnLoad() {
485         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
486         delayableExecutor.runAllReady()
487 
488         val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
489         val control = statelessBuilderFromInfo(newControlInfo).build()
490 
491         controller.loadForComponent(TEST_COMPONENT, Consumer {}, Consumer {})
492 
493         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
494                 capture(controlLoadCallbackCaptor))
495 
496         controlLoadCallbackCaptor.value.accept(listOf(control))
497         delayableExecutor.runAllReady()
498 
499         val favorites = controller.getFavorites().flatMap { it.controls }
500         assertEquals(1, favorites.size)
501         assertEquals(newControlInfo, favorites[0])
502     }
503 
504     @Test
505     fun testFavoriteInformationModifiedOnRefreshWithOkStatus() {
506         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
507 
508         val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
509         val control = statefulBuilderFromInfo(newControlInfo).setStatus(Control.STATUS_OK).build()
510 
511         controller.refreshStatus(TEST_COMPONENT, control)
512 
513         delayableExecutor.runAllReady()
514 
515         val favorites = controller.getFavorites().flatMap { it.controls }
516         assertEquals(1, favorites.size)
517         assertEquals(newControlInfo, favorites[0])
518     }
519 
520     @Test
521     fun testFavoriteInformationNotModifiedOnRefreshWithNonOkStatus() {
522         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
523 
524         val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
525         val control = statefulBuilderFromInfo(newControlInfo).setStatus(Control.STATUS_ERROR)
526             .build()
527 
528         controller.refreshStatus(TEST_COMPONENT, control)
529 
530         delayableExecutor.runAllReady()
531 
532         val favorites = controller.getFavorites().flatMap { it.controls }
533         assertEquals(1, favorites.size)
534         assertEquals(TEST_CONTROL_INFO, favorites[0])
535     }
536 
537     @Test
538     fun testSwitchUsers() {
539         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
540         delayableExecutor.runAllReady()
541 
542         reset(persistenceWrapper)
543 
544         controller.changeUser(UserHandle.of(otherUser))
545 
546         verify(persistenceWrapper).changeFileAndBackupManager(any(), any())
547         verify(persistenceWrapper).readFavorites()
548         verify(auxiliaryPersistenceWrapper).changeFile(any())
549         verify(bindingController).changeUser(UserHandle.of(otherUser))
550         verify(listingController).changeUser(UserHandle.of(otherUser))
551         assertTrue(controller.getFavorites().isEmpty())
552         assertEquals(otherUser, controller.currentUserId)
553     }
554 
555     @Test
556     fun testCountFavoritesForComponent_singleComponent() {
557         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
558         delayableExecutor.runAllReady()
559 
560         assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
561         assertEquals(0, controller.countFavoritesForComponent(TEST_COMPONENT_2))
562     }
563 
564     @Test
565     fun testCountFavoritesForComponent_multipleComponents() {
566         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
567         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO_2)
568         delayableExecutor.runAllReady()
569 
570         assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
571         assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2))
572     }
573 
574     @Test
575     fun testGetFavoritesForComponent() {
576         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
577         delayableExecutor.runAllReady()
578 
579         assertEquals(listOf(TEST_STRUCTURE_INFO),
580             controller.getFavoritesForComponent(TEST_COMPONENT))
581     }
582 
583     @Test
584     fun testGetFavoritesForComponent_otherComponent() {
585         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO_2)
586         delayableExecutor.runAllReady()
587 
588         assertTrue(controller.getFavoritesForComponent(TEST_COMPONENT).isEmpty())
589     }
590 
591     @Test
592     fun testGetFavoritesForComponent_multipleInOrder() {
593         val controlInfo = ControlInfo("id", "title", "subtitle", 0)
594 
595         controller.replaceFavoritesForStructure(
596             StructureInfo(
597                 TEST_COMPONENT,
598                 "Home",
599                 listOf(TEST_CONTROL_INFO, controlInfo)
600         ))
601         delayableExecutor.runAllReady()
602 
603         assertEquals(listOf(TEST_CONTROL_INFO, controlInfo),
604             controller.getFavoritesForComponent(TEST_COMPONENT).flatMap { it.controls })
605 
606         controller.replaceFavoritesForStructure(
607             StructureInfo(
608                 TEST_COMPONENT,
609                 "Home",
610                 listOf(controlInfo, TEST_CONTROL_INFO)
611         ))
612         delayableExecutor.runAllReady()
613 
614         assertEquals(listOf(controlInfo, TEST_CONTROL_INFO),
615             controller.getFavoritesForComponent(TEST_COMPONENT).flatMap { it.controls })
616     }
617 
618     @Test
619     fun testReplaceFavoritesForStructure_noExistingFavorites() {
620         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
621         delayableExecutor.runAllReady()
622 
623         assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
624         assertEquals(listOf(TEST_STRUCTURE_INFO),
625             controller.getFavoritesForComponent(TEST_COMPONENT))
626     }
627 
628     @Test
629     fun testReplaceFavoritesForStructure_doNotStoreEmptyStructure() {
630         controller.replaceFavoritesForStructure(
631             StructureInfo(TEST_COMPONENT, "Home", emptyList<ControlInfo>()))
632         delayableExecutor.runAllReady()
633 
634         assertEquals(0, controller.countFavoritesForComponent(TEST_COMPONENT))
635         assertEquals(emptyList<ControlInfo>(), controller.getFavoritesForComponent(TEST_COMPONENT))
636     }
637 
638     @Test
639     fun testReplaceFavoritesForStructure_differentComponentsAreFilteredOut() {
640         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
641         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO_2)
642         delayableExecutor.runAllReady()
643 
644         assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
645         assertEquals(listOf(TEST_CONTROL_INFO),
646             controller.getFavoritesForComponent(TEST_COMPONENT).flatMap { it.controls })
647     }
648 
649     @Test
650     fun testReplaceFavoritesForStructure_oldFavoritesRemoved() {
651         val controlInfo = ControlInfo("id", "title", "subtitle", 0)
652         assertNotEquals(TEST_CONTROL_INFO, controlInfo)
653 
654         val newComponent = ComponentName("test.pkg", "test.class.3")
655 
656         controller.replaceFavoritesForStructure(
657             StructureInfo(
658                 newComponent,
659                 "Home",
660                 listOf(controlInfo)
661         ))
662         controller.replaceFavoritesForStructure(
663             StructureInfo(
664                 newComponent,
665                 "Home",
666                 listOf(TEST_CONTROL_INFO)
667         ))
668         delayableExecutor.runAllReady()
669 
670         assertEquals(1, controller.countFavoritesForComponent(newComponent))
671         assertEquals(listOf(TEST_CONTROL_INFO), controller
672             .getFavoritesForComponent(newComponent).flatMap { it.controls })
673     }
674 
675     @Test
676     fun testReplaceFavoritesForStructure_favoritesInOrder() {
677         val controlInfo = ControlInfo("id", "title", "subtitle", 0)
678 
679         val listOrder1 = listOf(TEST_CONTROL_INFO, controlInfo)
680         val structure1 = StructureInfo(TEST_COMPONENT, "Home", listOrder1)
681         controller.replaceFavoritesForStructure(structure1)
682         delayableExecutor.runAllReady()
683 
684         assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT))
685         assertEquals(listOrder1, controller.getFavoritesForComponent(TEST_COMPONENT)
686             .flatMap { it.controls })
687 
688         val listOrder2 = listOf(controlInfo, TEST_CONTROL_INFO)
689         val structure2 = StructureInfo(TEST_COMPONENT, "Home", listOrder2)
690 
691         controller.replaceFavoritesForStructure(structure2)
692         delayableExecutor.runAllReady()
693 
694         assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT))
695         assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT)
696             .flatMap { it.controls })
697     }
698 
699     @Test
700     fun testPackageRemoved_noFavorites_noRemovals() {
701         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
702         delayableExecutor.runAllReady()
703 
704         val serviceInfo = mock(ServiceInfo::class.java)
705         `when`(serviceInfo.componentName).thenReturn(TEST_COMPONENT)
706         val info = ControlsServiceInfo(mContext, serviceInfo)
707 
708         // Don't want to check what happens before this call
709         reset(persistenceWrapper)
710         listingCallbackCaptor.value.onServicesUpdated(listOf(info))
711         delayableExecutor.runAllReady()
712 
713         verify(bindingController, never()).onComponentRemoved(any())
714 
715         assertEquals(1, controller.getFavorites().size)
716         assertEquals(TEST_STRUCTURE_INFO, controller.getFavorites()[0])
717 
718         verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
719     }
720 
721     @Test
722     fun testPackageRemoved_hasFavorites() {
723         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
724         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO_2)
725         delayableExecutor.runAllReady()
726 
727         val serviceInfo = mock(ServiceInfo::class.java)
728         `when`(serviceInfo.componentName).thenReturn(TEST_COMPONENT)
729         val info = ControlsServiceInfo(mContext, serviceInfo)
730 
731         // Don't want to check what happens before this call
732         reset(persistenceWrapper)
733 
734         listingCallbackCaptor.value.onServicesUpdated(listOf(info))
735         delayableExecutor.runAllReady()
736 
737         verify(bindingController).onComponentRemoved(TEST_COMPONENT_2)
738 
739         assertEquals(1, controller.getFavorites().size)
740         assertEquals(TEST_STRUCTURE_INFO, controller.getFavorites()[0])
741 
742         verify(persistenceWrapper).storeFavorites(ArgumentMatchers.anyList())
743     }
744 
745     @Test
746     fun testExistingPackage_removedFromCache() {
747         `when`(auxiliaryPersistenceWrapper.favorites).thenReturn(
748             listOf(TEST_STRUCTURE_INFO, TEST_STRUCTURE_INFO_2))
749 
750         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
751         delayableExecutor.runAllReady()
752 
753         val serviceInfo = mock(ServiceInfo::class.java)
754         `when`(serviceInfo.componentName).thenReturn(TEST_COMPONENT)
755         val info = ControlsServiceInfo(mContext, serviceInfo)
756 
757         listingCallbackCaptor.value.onServicesUpdated(listOf(info))
758         delayableExecutor.runAllReady()
759 
760         verify(auxiliaryPersistenceWrapper).getCachedFavoritesAndRemoveFor(TEST_COMPONENT)
761     }
762 
763     @Test
764     fun testAddedPackage_requestedFromCache() {
765         `when`(auxiliaryPersistenceWrapper.favorites).thenReturn(
766             listOf(TEST_STRUCTURE_INFO, TEST_STRUCTURE_INFO_2))
767 
768         val serviceInfo = mock(ServiceInfo::class.java)
769         `when`(serviceInfo.componentName).thenReturn(TEST_COMPONENT)
770         val info = ControlsServiceInfo(mContext, serviceInfo)
771 
772         listingCallbackCaptor.value.onServicesUpdated(listOf(info))
773         delayableExecutor.runAllReady()
774 
775         verify(auxiliaryPersistenceWrapper).getCachedFavoritesAndRemoveFor(TEST_COMPONENT)
776         verify(auxiliaryPersistenceWrapper, never())
777                 .getCachedFavoritesAndRemoveFor(TEST_COMPONENT_2)
778     }
779 
780     @Test
781     fun testSeedFavoritesForComponentsWithLimit() {
782         var responses = mutableListOf<SeedResponse>()
783 
784         val controls1 = mutableListOf<Control>()
785         for (i in 1..10) {
786             controls1.add(statelessBuilderFromInfo(ControlInfo("id1:$i", TEST_CONTROL_TITLE,
787                 TEST_CONTROL_SUBTITLE, TEST_DEVICE_TYPE), "testStructure").build())
788         }
789         val controls2 = mutableListOf<Control>()
790         for (i in 1..3) {
791             controls2.add(statelessBuilderFromInfo(ControlInfo("id2:$i", TEST_CONTROL_TITLE,
792                 TEST_CONTROL_SUBTITLE, TEST_DEVICE_TYPE), "testStructure2").build())
793         }
794         controller.seedFavoritesForComponents(listOf(TEST_COMPONENT, TEST_COMPONENT_2), Consumer {
795             resp -> responses.add(resp)
796         })
797 
798         verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
799                 capture(controlLoadCallbackCaptor))
800         controlLoadCallbackCaptor.value.accept(controls1)
801         delayableExecutor.runAllReady()
802 
803         verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT_2),
804                 capture(controlLoadCallbackCaptor2))
805         controlLoadCallbackCaptor2.value.accept(controls2)
806         delayableExecutor.runAllReady()
807 
808         // COMPONENT 1
809         val structureInfo = controller.getFavoritesForComponent(TEST_COMPONENT)[0]
810         assertEquals(structureInfo.controls.size,
811             ControlsControllerImpl.SUGGESTED_CONTROLS_PER_STRUCTURE)
812 
813         var i = 1
814         structureInfo.controls.forEach {
815             assertEquals(it.controlId, "id1:$i")
816             i++
817         }
818         assertEquals(SeedResponse(TEST_COMPONENT.packageName, true), responses[0])
819 
820         // COMPONENT 2
821         val structureInfo2 = controller.getFavoritesForComponent(TEST_COMPONENT_2)[0]
822         assertEquals(structureInfo2.controls.size, 3)
823 
824         i = 1
825         structureInfo2.controls.forEach {
826             assertEquals(it.controlId, "id2:$i")
827             i++
828         }
829         assertEquals(SeedResponse(TEST_COMPONENT.packageName, true), responses[1])
830     }
831 
832     @Test
833     fun testSeedFavoritesForComponents_error() {
834         var response: SeedResponse? = null
835 
836         controller.seedFavoritesForComponents(listOf(TEST_COMPONENT), Consumer { resp ->
837             response = resp
838         })
839 
840         verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
841                 capture(controlLoadCallbackCaptor))
842 
843         controlLoadCallbackCaptor.value.error("Error loading")
844 
845         delayableExecutor.runAllReady()
846 
847         assertEquals(listOf<StructureInfo>(), controller.getFavoritesForComponent(TEST_COMPONENT))
848         assertEquals(SeedResponse(TEST_COMPONENT.packageName, false), response)
849     }
850 
851     @Test
852     fun testSeedFavoritesForComponents_inProgressCallback() {
853         var response: SeedResponse? = null
854         var seeded = false
855         val control = statelessBuilderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure)
856             .build()
857 
858         controller.seedFavoritesForComponents(listOf(TEST_COMPONENT), Consumer { resp ->
859             response = resp
860         })
861 
862         verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
863                 capture(controlLoadCallbackCaptor))
864 
865         controller.addSeedingFavoritesCallback(Consumer { accepted ->
866             seeded = accepted
867         })
868         controlLoadCallbackCaptor.value.accept(listOf(control))
869 
870         delayableExecutor.runAllReady()
871 
872         assertEquals(listOf(TEST_STRUCTURE_INFO),
873             controller.getFavoritesForComponent(TEST_COMPONENT))
874         assertEquals(SeedResponse(TEST_COMPONENT.packageName, true), response)
875         assertTrue(seeded)
876     }
877 
878     @Test
879     fun testRestoreReceiver_loadsAuxiliaryData() {
880         val receiver = controller.restoreFinishedReceiver
881 
882         val structure1 = mock(StructureInfo::class.java)
883         val structure2 = mock(StructureInfo::class.java)
884         val listOfStructureInfo = listOf(structure1, structure2)
885         `when`(auxiliaryPersistenceWrapper.favorites).thenReturn(listOfStructureInfo)
886 
887         val intent = Intent(BackupHelper.ACTION_RESTORE_FINISHED)
888         intent.putExtra(Intent.EXTRA_USER_ID, context.userId)
889         receiver.onReceive(context, intent)
890         delayableExecutor.runAllReady()
891 
892         val inOrder = inOrder(auxiliaryPersistenceWrapper, persistenceWrapper)
893         inOrder.verify(auxiliaryPersistenceWrapper).initialize()
894         inOrder.verify(auxiliaryPersistenceWrapper).favorites
895         inOrder.verify(persistenceWrapper).storeFavorites(listOfStructureInfo)
896         inOrder.verify(persistenceWrapper).readFavorites()
897     }
898 
899     @Test
900     fun testRestoreReceiver_noActionOnWrongUser() {
901         val receiver = controller.restoreFinishedReceiver
902 
903         reset(persistenceWrapper)
904         reset(auxiliaryPersistenceWrapper)
905         val intent = Intent(BackupHelper.ACTION_RESTORE_FINISHED)
906         intent.putExtra(Intent.EXTRA_USER_ID, context.userId + 1)
907         receiver.onReceive(context, intent)
908         delayableExecutor.runAllReady()
909 
910         verifyNoMoreInteractions(persistenceWrapper)
911         verifyNoMoreInteractions(auxiliaryPersistenceWrapper)
912     }
913 
914     @Test
915     fun testGetFavoritesForStructure() {
916         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
917         controller.replaceFavoritesForStructure(
918                 TEST_STRUCTURE_INFO_2.copy(componentName = TEST_COMPONENT))
919         delayableExecutor.runAllReady()
920 
921         assertEquals(TEST_STRUCTURE_INFO.controls,
922                 controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE))
923         assertEquals(TEST_STRUCTURE_INFO_2.controls,
924                 controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE_2))
925     }
926 
927     @Test
928     fun testGetFavoritesForStructure_wrongStructure() {
929         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
930         delayableExecutor.runAllReady()
931 
932         assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE_2).isEmpty())
933     }
934 
935     @Test
936     fun testGetFavoritesForStructure_wrongComponent() {
937         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
938         delayableExecutor.runAllReady()
939 
940         assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
941     }
942 
943     @Test
944     fun testUserStructure() {
945         val userStructure = UserStructure(context, context.user, userFileManager)
946         verify(userFileManager, times(2))
947             .getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier)
948         assertThat(userStructure.file).isNotNull()
949     }
950 
951     @Test
952     fun testBindForPanel() {
953         controller.bindComponentForPanel(TEST_COMPONENT)
954         verify(bindingController).bindServiceForPanel(TEST_COMPONENT)
955     }
956 
957     @Test
958     fun testRemoveFavoriteRemovesFavorite() {
959         val componentName = ComponentName(context, "test.Cls")
960         controller.addFavorite(
961                 componentName,
962                 "test structure",
963                 ControlInfo(
964                         controlId = "testId",
965                         controlTitle = "Test Control",
966                         controlSubtitle = "test control subtitle",
967                         deviceType = DeviceTypes.TYPE_LIGHT,
968                 ),
969         )
970 
971         controller.removeFavorites(componentName)
972         delayableExecutor.runAllReady()
973 
974         verify(authorizedPanelsRepository)
975                 .removeAuthorizedPanels(eq(setOf(componentName.packageName)))
976         assertThat(controller.getFavorites()).isEmpty()
977     }
978 }
979 
980 private class DidRunRunnable() : Runnable {
981     var ran = false
runnull982     override fun run() {
983         ran = true
984     }
985 }
986