1 /*
2  * Copyright (C) 2023 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 
18 package com.android.systemui.controls.start
19 
20 import android.content.BroadcastReceiver
21 import android.content.ComponentName
22 import android.content.Context
23 import android.content.Intent
24 import android.content.IntentFilter
25 import android.content.pm.ApplicationInfo
26 import android.content.pm.ServiceInfo
27 import android.os.UserHandle
28 import android.os.UserManager
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.broadcast.BroadcastDispatcher
33 import com.android.systemui.common.data.repository.fakePackageChangeRepository
34 import com.android.systemui.common.domain.interactor.packageChangeInteractor
35 import com.android.systemui.common.shared.model.PackageChangeModel
36 import com.android.systemui.controls.ControlsServiceInfo
37 import com.android.systemui.controls.controller.ControlsController
38 import com.android.systemui.controls.dagger.ControlsComponent
39 import com.android.systemui.controls.management.ControlsListingController
40 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
41 import com.android.systemui.controls.panels.SelectedComponentRepository
42 import com.android.systemui.controls.panels.selectedComponentRepository
43 import com.android.systemui.controls.ui.SelectedItem
44 import com.android.systemui.kosmos.applicationCoroutineScope
45 import com.android.systemui.kosmos.testDispatcher
46 import com.android.systemui.kosmos.testScope
47 import com.android.systemui.settings.UserTracker
48 import com.android.systemui.testKosmos
49 import com.android.systemui.util.concurrency.FakeExecutor
50 import com.android.systemui.util.mockito.any
51 import com.android.systemui.util.mockito.argumentCaptor
52 import com.android.systemui.util.mockito.capture
53 import com.android.systemui.util.mockito.eq
54 import com.android.systemui.util.mockito.mock
55 import com.android.systemui.util.mockito.nullable
56 import com.android.systemui.util.mockito.whenever
57 import com.android.systemui.util.time.FakeSystemClock
58 import com.google.common.truth.Truth.assertThat
59 import java.util.Optional
60 import kotlinx.coroutines.ExperimentalCoroutinesApi
61 import kotlinx.coroutines.test.runCurrent
62 import kotlinx.coroutines.test.runTest
63 import org.junit.Before
64 import org.junit.Test
65 import org.junit.runner.RunWith
66 import org.mockito.Mock
67 import org.mockito.Mockito.anyInt
68 import org.mockito.Mockito.doAnswer
69 import org.mockito.Mockito.doReturn
70 import org.mockito.Mockito.never
71 import org.mockito.Mockito.verify
72 import org.mockito.Mockito.verifyZeroInteractions
73 import org.mockito.Mockito.`when`
74 import org.mockito.MockitoAnnotations
75 
76 @OptIn(ExperimentalCoroutinesApi::class)
77 @SmallTest
78 @RunWith(AndroidJUnit4::class)
79 class ControlsStartableTest : SysuiTestCase() {
80 
81     private val kosmos = testKosmos()
82 
83     @Mock private lateinit var controlsController: ControlsController
84     @Mock private lateinit var controlsListingController: ControlsListingController
85     @Mock private lateinit var userTracker: UserTracker
86     @Mock private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
87     @Mock private lateinit var userManager: UserManager
88     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
89 
90     private lateinit var preferredPanelsRepository: SelectedComponentRepository
91 
92     private lateinit var fakeExecutor: FakeExecutor
93 
94     @Before
setUpnull95     fun setUp() {
96         MockitoAnnotations.initMocks(this)
97         whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
98         whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true)
99         whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
100 
101         fakeExecutor = FakeExecutor(FakeSystemClock())
102         preferredPanelsRepository = kosmos.selectedComponentRepository
103     }
104 
105     @Test
testDisabledNothingIsCallednull106     fun testDisabledNothingIsCalled() {
107         createStartable(enabled = false).apply {
108             start()
109             onBootCompleted()
110         }
111 
112         verifyZeroInteractions(controlsController, controlsListingController, userTracker)
113     }
114 
115     @Test
testNothingCalledOnStartnull116     fun testNothingCalledOnStart() {
117         createStartable(enabled = true).start()
118 
119         fakeExecutor.advanceClockToLast()
120         fakeExecutor.runAllReady()
121 
122         verifyZeroInteractions(controlsController, controlsListingController, userTracker)
123     }
124 
125     @Test
testNoPreferredPackagesNoDefaultSelected_noNewSelectionnull126     fun testNoPreferredPackagesNoDefaultSelected_noNewSelection() {
127         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
128         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
129         setUpControlsListingControls(listings)
130 
131         createStartable(enabled = true).onBootCompleted()
132         fakeExecutor.runAllReady()
133 
134         verify(controlsController, never()).setPreferredSelection(any())
135     }
136 
137     @Test
testPreferredPackagesNotInstalled_noNewSelectionnull138     fun testPreferredPackagesNotInstalled_noNewSelection() {
139         whenever(authorizedPanelsRepository.getPreferredPackages())
140             .thenReturn(setOf(TEST_PACKAGE_PANEL))
141         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
142         setUpControlsListingControls(emptyList())
143 
144         createStartable(enabled = true).onBootCompleted()
145         fakeExecutor.runAllReady()
146 
147         verify(controlsController, never()).setPreferredSelection(any())
148     }
149 
150     @Test
testPreferredPackageNotPanel_noNewSelectionnull151     fun testPreferredPackageNotPanel_noNewSelection() {
152         whenever(authorizedPanelsRepository.getPreferredPackages())
153             .thenReturn(setOf(TEST_PACKAGE_PANEL))
154         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
155         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
156         setUpControlsListingControls(listings)
157 
158         createStartable(enabled = true).onBootCompleted()
159         fakeExecutor.runAllReady()
160 
161         verify(controlsController, never()).setPreferredSelection(any())
162     }
163 
164     @Test
testExistingSelection_noNewSelectionnull165     fun testExistingSelection_noNewSelection() {
166         whenever(authorizedPanelsRepository.getPreferredPackages())
167             .thenReturn(setOf(TEST_PACKAGE_PANEL))
168         `when`(controlsController.getPreferredSelection())
169             .thenReturn(mock<SelectedItem.PanelItem>())
170         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
171         setUpControlsListingControls(listings)
172 
173         createStartable(enabled = true).onBootCompleted()
174         fakeExecutor.runAllReady()
175 
176         verify(controlsController, never()).setPreferredSelection(any())
177     }
178 
179     @Test
testPanelAddednull180     fun testPanelAdded() {
181         whenever(authorizedPanelsRepository.getPreferredPackages())
182             .thenReturn(setOf(TEST_PACKAGE_PANEL))
183         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
184         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
185         setUpControlsListingControls(listings)
186 
187         createStartable(enabled = true).onBootCompleted()
188         fakeExecutor.runAllReady()
189 
190         verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
191     }
192 
193     @Test
testMultiplePreferredOnlyOnePanel_panelAddednull194     fun testMultiplePreferredOnlyOnePanel_panelAdded() {
195         whenever(authorizedPanelsRepository.getPreferredPackages())
196             .thenReturn(setOf(TEST_PACKAGE_PANEL))
197         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
198         val listings =
199             listOf(
200                 ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true),
201                 ControlsServiceInfo(ComponentName("other_package", "cls"), "non panel", false)
202             )
203         setUpControlsListingControls(listings)
204 
205         createStartable(enabled = true).onBootCompleted()
206         fakeExecutor.runAllReady()
207 
208         verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
209     }
210 
211     @Test
testMultiplePreferredMultiplePanels_firstPreferredAddednull212     fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
213         whenever(authorizedPanelsRepository.getPreferredPackages())
214             .thenReturn(setOf(TEST_PACKAGE_PANEL))
215         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
216         val listings =
217             listOf(
218                 ControlsServiceInfo(ComponentName("other_package", "cls"), "panel", true),
219                 ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)
220             )
221         setUpControlsListingControls(listings)
222 
223         createStartable(enabled = true).onBootCompleted()
224         fakeExecutor.runAllReady()
225 
226         verify(controlsController).setPreferredSelection(listings[1].toPanelItem())
227     }
228 
229     @Test
testPreferredSelectionIsPanel_bindOnBootnull230     fun testPreferredSelectionIsPanel_bindOnBoot() {
231         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
232         setUpControlsListingControls(listings)
233         `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
234 
235         createStartable(enabled = true).onBootCompleted()
236         fakeExecutor.runAllReady()
237 
238         verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
239     }
240 
241     @Test
testPreferredSelectionIsPanel_userNotUnlocked_notBindnull242     fun testPreferredSelectionIsPanel_userNotUnlocked_notBind() {
243         whenever(userManager.isUserUnlocked(anyInt())).thenReturn(false)
244 
245         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
246         setUpControlsListingControls(listings)
247         `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
248 
249         createStartable(enabled = true).onBootCompleted()
250         fakeExecutor.runAllReady()
251 
252         verify(controlsController, never()).bindComponentForPanel(TEST_COMPONENT_PANEL)
253     }
254 
255     @Test
testPreferredSelectionIsPanel_userNotUnlocked_broadcastRegistered_broadcastSentBindsnull256     fun testPreferredSelectionIsPanel_userNotUnlocked_broadcastRegistered_broadcastSentBinds() {
257         whenever(userManager.isUserUnlocked(anyInt())).thenReturn(false)
258 
259         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
260         setUpControlsListingControls(listings)
261         `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
262 
263         createStartable(enabled = true).onBootCompleted()
264         fakeExecutor.runAllReady()
265 
266         val intentFilterCaptor = argumentCaptor<IntentFilter>()
267         val receiverCaptor = argumentCaptor<BroadcastReceiver>()
268 
269         verify(broadcastDispatcher)
270             .registerReceiver(
271                 capture(receiverCaptor),
272                 capture(intentFilterCaptor),
273                 eq(fakeExecutor),
274                 nullable(),
275                 anyInt(),
276                 nullable()
277             )
278         assertThat(intentFilterCaptor.value.matchAction(Intent.ACTION_USER_UNLOCKED)).isTrue()
279 
280         // User is unlocked
281         whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true)
282         receiverCaptor.value.onReceive(mock(), Intent(Intent.ACTION_USER_UNLOCKED))
283 
284         verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
285     }
286 
287     @Test
testPreferredSelectionPanel_listingNoPanel_notBindnull288     fun testPreferredSelectionPanel_listingNoPanel_notBind() {
289         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
290         setUpControlsListingControls(listings)
291         `when`(controlsController.getPreferredSelection())
292             .thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL))
293 
294         createStartable(enabled = true).onBootCompleted()
295         fakeExecutor.runAllReady()
296 
297         verify(controlsController, never()).bindComponentForPanel(any())
298     }
299 
300     @Test
testNotPanelSelection_noBindnull301     fun testNotPanelSelection_noBind() {
302         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
303         setUpControlsListingControls(listings)
304         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
305 
306         createStartable(enabled = true).onBootCompleted()
307         fakeExecutor.runAllReady()
308 
309         verify(controlsController, never()).bindComponentForPanel(any())
310     }
311 
312     @Test
testAlreadyAddedPanel_noNewSelectionnull313     fun testAlreadyAddedPanel_noNewSelection() {
314         preferredPanelsRepository.setShouldAddDefaultComponent(false)
315         whenever(authorizedPanelsRepository.getPreferredPackages())
316             .thenReturn(setOf(TEST_PACKAGE_PANEL))
317         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
318         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
319         `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
320 
321         createStartable(enabled = true).onBootCompleted()
322 
323         verify(controlsController, never()).setPreferredSelection(any())
324     }
325 
326     @Test
testSelectedComponentIsUninstallednull327     fun testSelectedComponentIsUninstalled() =
328         with(kosmos) {
329             testScope.runTest {
330                 val selectedComponent =
331                     SelectedComponentRepository.SelectedComponent(
332                         "panel",
333                         TEST_COMPONENT_PANEL,
334                         isPanel = true
335                     )
336                 preferredPanelsRepository.setSelectedComponent(selectedComponent)
337                 val activeUser = UserHandle.of(100)
338                 whenever(userTracker.userHandle).thenReturn(activeUser)
339 
340                 createStartable(enabled = true).onBootCompleted()
341                 fakeExecutor.runAllReady()
342                 runCurrent()
343 
344                 assertThat(preferredPanelsRepository.getSelectedComponent())
345                     .isEqualTo(selectedComponent)
346                 fakePackageChangeRepository.notifyChange(
347                     PackageChangeModel.Uninstalled(
348                         packageName = TEST_PACKAGE_PANEL,
349                         packageUid = UserHandle.getUid(100, 1)
350                     )
351                 )
352                 runCurrent()
353 
354                 assertThat(preferredPanelsRepository.getSelectedComponent()).isNull()
355             }
356         }
357 
358     @Test
testSelectedComponentIsChangednull359     fun testSelectedComponentIsChanged() =
360         with(kosmos) {
361             testScope.runTest {
362                 val selectedComponent =
363                     SelectedComponentRepository.SelectedComponent(
364                         "panel",
365                         TEST_COMPONENT_PANEL,
366                         isPanel = true
367                     )
368                 preferredPanelsRepository.setSelectedComponent(selectedComponent)
369                 val activeUser = UserHandle.of(100)
370                 whenever(userTracker.userHandle).thenReturn(activeUser)
371 
372                 createStartable(enabled = true).onBootCompleted()
373                 fakeExecutor.runAllReady()
374                 runCurrent()
375 
376                 fakePackageChangeRepository.notifyChange(
377                     PackageChangeModel.Changed(
378                         packageName = TEST_PACKAGE_PANEL,
379                         packageUid = UserHandle.getUid(100, 1)
380                     )
381                 )
382                 runCurrent()
383 
384                 assertThat(preferredPanelsRepository.getSelectedComponent())
385                     .isEqualTo(selectedComponent)
386             }
387         }
388 
389     @Test
testOtherPackageIsUninstallednull390     fun testOtherPackageIsUninstalled() =
391         with(kosmos) {
392             testScope.runTest {
393                 val selectedComponent =
394                     SelectedComponentRepository.SelectedComponent(
395                         "panel",
396                         TEST_COMPONENT_PANEL,
397                         isPanel = true
398                     )
399                 preferredPanelsRepository.setSelectedComponent(selectedComponent)
400                 val activeUser = UserHandle.of(100)
401                 whenever(userTracker.userHandle).thenReturn(activeUser)
402 
403                 createStartable(enabled = true).onBootCompleted()
404                 fakeExecutor.runAllReady()
405                 runCurrent()
406 
407                 fakePackageChangeRepository.notifyChange(
408                     PackageChangeModel.Uninstalled(
409                         packageName = TEST_PACKAGE,
410                         packageUid = UserHandle.getUid(100, 1)
411                     )
412                 )
413                 runCurrent()
414 
415                 assertThat(preferredPanelsRepository.getSelectedComponent())
416                     .isEqualTo(selectedComponent)
417             }
418         }
419 
setUpControlsListingControlsnull420     private fun setUpControlsListingControls(listings: List<ControlsServiceInfo>) {
421         doAnswer { doReturn(listings).`when`(controlsListingController).getCurrentServices() }
422             .`when`(controlsListingController)
423             .forceReload()
424     }
425 
createStartablenull426     private fun createStartable(enabled: Boolean): ControlsStartable {
427         val component: ControlsComponent =
428             mock() {
429                 `when`(isEnabled()).thenReturn(enabled)
430                 if (enabled) {
431                     `when`(getControlsController()).thenReturn(Optional.of(controlsController))
432                     `when`(getControlsListingController())
433                         .thenReturn(Optional.of(controlsListingController))
434                 } else {
435                     `when`(getControlsController()).thenReturn(Optional.empty())
436                     `when`(getControlsListingController()).thenReturn(Optional.empty())
437                 }
438             }
439         return ControlsStartable(
440             kosmos.applicationCoroutineScope,
441             kosmos.testDispatcher,
442             fakeExecutor,
443             component,
444             userTracker,
445             authorizedPanelsRepository,
446             preferredPanelsRepository,
447             kosmos.packageChangeInteractor,
448             userManager,
449             broadcastDispatcher,
450         )
451     }
452 
ControlsServiceInfonull453     private fun ControlsServiceInfo(
454         componentName: ComponentName,
455         label: CharSequence,
456         hasPanel: Boolean
457     ): ControlsServiceInfo {
458         val serviceInfo =
459             ServiceInfo().apply {
460                 applicationInfo = ApplicationInfo()
461                 packageName = componentName.packageName
462                 name = componentName.className
463             }
464         return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
465     }
466 
467     private class FakeControlsServiceInfo(
468         context: Context,
469         serviceInfo: ServiceInfo,
470         private val label: CharSequence,
471         hasPanel: Boolean
472     ) : ControlsServiceInfo(context, serviceInfo) {
473 
474         init {
475             if (hasPanel) {
476                 panelActivity = serviceInfo.componentName
477             }
478         }
479 
loadLabelnull480         override fun loadLabel(): CharSequence {
481             return label
482         }
483     }
484 
485     companion object {
toPanelItemnull486         private fun ControlsServiceInfo.toPanelItem(): SelectedItem.PanelItem {
487             if (panelActivity == null) {
488                 throw IllegalArgumentException("$this is not a panel")
489             }
490             return SelectedItem.PanelItem(loadLabel(), componentName)
491         }
492 
493         private const val TEST_PACKAGE = "pkg"
494         private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
495         private const val TEST_PACKAGE_PANEL = "pkg.panel"
496         private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
497     }
498 }
499