1 /*
2  * 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.management
18 
19 import android.Manifest
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.ActivityInfo
25 import android.content.pm.PackageManager
26 import android.content.pm.ResolveInfo
27 import android.content.pm.ServiceInfo
28 import android.os.Bundle
29 import android.os.UserHandle
30 import android.service.controls.ControlsProviderService
31 import android.testing.AndroidTestingRunner
32 import androidx.test.filters.SmallTest
33 import com.android.settingslib.applications.ServiceListing
34 import com.android.systemui.R
35 import com.android.systemui.SysuiTestCase
36 import com.android.systemui.controls.ControlsServiceInfo
37 import com.android.systemui.dump.DumpManager
38 import com.android.systemui.flags.FeatureFlags
39 import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
40 import com.android.systemui.settings.UserTracker
41 import com.android.systemui.util.ActivityTaskManagerProxy
42 import com.android.systemui.util.concurrency.FakeExecutor
43 import com.android.systemui.util.mockito.any
44 import com.android.systemui.util.mockito.argThat
45 import com.android.systemui.util.mockito.capture
46 import com.android.systemui.util.mockito.eq
47 import com.android.systemui.util.time.FakeSystemClock
48 import com.google.common.truth.Truth.assertThat
49 import java.util.concurrent.Executor
50 import org.junit.After
51 import org.junit.Assert.assertEquals
52 import org.junit.Assert.assertNull
53 import org.junit.Assert.assertTrue
54 import org.junit.Before
55 import org.junit.Test
56 import org.junit.runner.RunWith
57 import org.mockito.ArgumentCaptor
58 import org.mockito.ArgumentMatcher
59 import org.mockito.Mock
60 import org.mockito.Mockito.`when`
61 import org.mockito.Mockito.inOrder
62 import org.mockito.Mockito.mock
63 import org.mockito.Mockito.never
64 import org.mockito.Mockito.reset
65 import org.mockito.Mockito.verify
66 import org.mockito.MockitoAnnotations
67 
68 @SmallTest
69 @RunWith(AndroidTestingRunner::class)
70 class ControlsListingControllerImplTest : SysuiTestCase() {
71 
72     companion object {
73         private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
74                 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
75     }
76 
77     @Mock
78     private lateinit var mockSL: ServiceListing
79     @Mock
80     private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
81     @Mock
82     private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
83     @Mock(stubOnly = true)
84     private lateinit var userTracker: UserTracker
85     @Mock(stubOnly = true)
86     private lateinit var dumpManager: DumpManager
87     @Mock
88     private lateinit var packageManager: PackageManager
89     @Mock
90     private lateinit var featureFlags: FeatureFlags
91     @Mock
92     private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy
93 
94     private var componentName = ComponentName("pkg", "class1")
95     private var activityName = ComponentName("pkg", "activity")
96 
97     private val executor = FakeExecutor(FakeSystemClock())
98 
99     private lateinit var controller: ControlsListingControllerImpl
100 
101     private var serviceListingCallbackCaptor =
102             ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
103 
104     private val user = mContext.userId
105     private val otherUser = user + 1
106 
107     @Before
108     fun setUp() {
109         MockitoAnnotations.initMocks(this)
110 
111         `when`(userTracker.userId).thenReturn(user)
112         `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
113         `when`(userTracker.userContext).thenReturn(context)
114         // Return disabled by default
115         `when`(packageManager.getComponentEnabledSetting(any()))
116                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
117         `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true)
118         mContext.setMockPackageManager(packageManager)
119 
120         mContext.orCreateTestableResources
121                 .addOverride(
122                         R.array.config_controlsPreferredPackages,
123                         arrayOf(componentName.packageName)
124                 )
125 
126         // Return false by default, we'll test the true path
127         `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false)
128 
129         val wrapper = object : ContextWrapper(mContext) {
130             override fun createContextAsUser(user: UserHandle, flags: Int): Context {
131                 return baseContext
132             }
133         }
134 
135         controller = ControlsListingControllerImpl(
136                 wrapper,
137                 executor,
138                 { mockSL },
139                 userTracker,
140                 activityTaskManagerProxy,
141                 dumpManager,
142                 featureFlags
143         )
144         verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
145     }
146 
147     @After
148     fun tearDown() {
149         executor.advanceClockToLast()
150         executor.runAllReady()
151     }
152 
153     @Test
154     fun testInitialStateListening() {
155         verify(mockSL).setListening(true)
156         verify(mockSL).reload()
157     }
158 
159     @Test
160     fun testImmediateListingReload_doesNotCrash() {
161         val exec = Executor { it.run() }
162         val mockServiceListing = mock(ServiceListing::class.java)
163         var callback: ServiceListing.Callback? = null
164         `when`(mockServiceListing.addCallback(any<ServiceListing.Callback>())).then {
165             callback = it.getArgument(0)
166             Unit
167         }
168         `when`(mockServiceListing.reload()).then {
169             callback?.onServicesReloaded(listOf(ServiceInfo(componentName)))
170         }
171         ControlsListingControllerImpl(
172                 mContext,
173                 exec,
174                 { mockServiceListing },
175                 userTracker,
176                 activityTaskManagerProxy,
177                 dumpManager,
178                 featureFlags
179         )
180     }
181 
182     @Test
183     fun testStartsOnUser() {
184         assertEquals(user, controller.currentUserId)
185     }
186 
187     @Test
188     fun testCallbackCalledWhenAdded() {
189         controller.addCallback(mockCallback)
190         executor.runAllReady()
191         verify(mockCallback).onServicesUpdated(any())
192         reset(mockCallback)
193 
194         controller.addCallback(mockCallbackOther)
195         executor.runAllReady()
196         verify(mockCallbackOther).onServicesUpdated(any())
197         verify(mockCallback, never()).onServicesUpdated(any())
198     }
199 
200     @Test
201     fun testCallbackGetsList() {
202         val list = listOf(ServiceInfo(componentName))
203         controller.addCallback(mockCallback)
204         controller.addCallback(mockCallbackOther)
205 
206         @Suppress("unchecked_cast")
207         val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
208                 ArgumentCaptor.forClass(List::class.java)
209                         as ArgumentCaptor<List<ControlsServiceInfo>>
210 
211         executor.runAllReady()
212         reset(mockCallback)
213         reset(mockCallbackOther)
214 
215         serviceListingCallbackCaptor.value.onServicesReloaded(list)
216 
217         executor.runAllReady()
218         verify(mockCallback).onServicesUpdated(capture(captor))
219         assertEquals(1, captor.value.size)
220         assertEquals(componentName.flattenToString(), captor.value[0].key)
221 
222         verify(mockCallbackOther).onServicesUpdated(capture(captor))
223         assertEquals(1, captor.value.size)
224         assertEquals(componentName.flattenToString(), captor.value[0].key)
225     }
226 
227     @Test
228     fun testChangeUser() {
229         controller.changeUser(UserHandle.of(otherUser))
230         executor.runAllReady()
231         assertEquals(otherUser, controller.currentUserId)
232 
233         val inOrder = inOrder(mockSL)
234         inOrder.verify(mockSL).setListening(false)
235         inOrder.verify(mockSL).addCallback(any()) // We add a callback because we replaced the SL
236         inOrder.verify(mockSL).setListening(true)
237         inOrder.verify(mockSL).reload()
238     }
239 
240     @Test
241     fun testChangeUserSendsCorrectServiceUpdate() {
242         val serviceInfo = ServiceInfo(componentName)
243 
244         val list = listOf(serviceInfo)
245         controller.addCallback(mockCallback)
246 
247         @Suppress("unchecked_cast")
248         val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
249                 ArgumentCaptor.forClass(List::class.java)
250                         as ArgumentCaptor<List<ControlsServiceInfo>>
251         executor.runAllReady()
252         reset(mockCallback)
253 
254         serviceListingCallbackCaptor.value.onServicesReloaded(list)
255 
256         executor.runAllReady()
257         verify(mockCallback).onServicesUpdated(capture(captor))
258         assertEquals(1, captor.value.size)
259 
260         reset(mockCallback)
261         reset(mockSL)
262 
263         val updatedList = listOf(serviceInfo)
264         serviceListingCallbackCaptor.value.onServicesReloaded(updatedList)
265         controller.changeUser(UserHandle.of(otherUser))
266         executor.runAllReady()
267         assertEquals(otherUser, controller.currentUserId)
268 
269         // this event should was triggered just before the user change, and should
270         // be ignored
271         verify(mockCallback, never()).onServicesUpdated(any())
272 
273         serviceListingCallbackCaptor.value.onServicesReloaded(emptyList<ServiceInfo>())
274         executor.runAllReady()
275 
276         verify(mockCallback).onServicesUpdated(capture(captor))
277         assertEquals(0, captor.value.size)
278     }
279 
280     @Test
281     fun test_nullPanelActivity() {
282         val list = listOf(ServiceInfo(componentName))
283         serviceListingCallbackCaptor.value.onServicesReloaded(list)
284 
285         executor.runAllReady()
286 
287         assertNull(controller.getCurrentServices()[0].panelActivity)
288     }
289 
290     @Test
291     fun testNoActivity_nullPanel() {
292         val serviceInfo = ServiceInfo(
293                 componentName,
294                 activityName
295         )
296 
297         val list = listOf(serviceInfo)
298         serviceListingCallbackCaptor.value.onServicesReloaded(list)
299 
300         executor.runAllReady()
301 
302         assertNull(controller.getCurrentServices()[0].panelActivity)
303     }
304 
305     @Test
306     fun testActivityWithoutPermission_nullPanel() {
307         val serviceInfo = ServiceInfo(
308                 componentName,
309                 activityName
310         )
311 
312         setUpQueryResult(listOf(ActivityInfo(activityName)))
313 
314         val list = listOf(serviceInfo)
315         serviceListingCallbackCaptor.value.onServicesReloaded(list)
316 
317         executor.runAllReady()
318 
319         assertNull(controller.getCurrentServices()[0].panelActivity)
320     }
321 
322     @Test
323     fun testActivityPermissionNotExported_nullPanel() {
324         val serviceInfo = ServiceInfo(
325                 componentName,
326                 activityName
327         )
328 
329         setUpQueryResult(listOf(
330                 ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS)
331         ))
332 
333         val list = listOf(serviceInfo)
334         serviceListingCallbackCaptor.value.onServicesReloaded(list)
335 
336         executor.runAllReady()
337 
338         assertNull(controller.getCurrentServices()[0].panelActivity)
339     }
340 
341     @Test
342     fun testActivityDisabled_nullPanel() {
343         val serviceInfo = ServiceInfo(
344                 componentName,
345                 activityName
346         )
347 
348         setUpQueryResult(listOf(
349                 ActivityInfo(
350                         activityName,
351                         exported = true,
352                         permission = Manifest.permission.BIND_CONTROLS
353                 )
354         ))
355 
356         val list = listOf(serviceInfo)
357         serviceListingCallbackCaptor.value.onServicesReloaded(list)
358 
359         executor.runAllReady()
360 
361         assertNull(controller.getCurrentServices()[0].panelActivity)
362     }
363 
364     @Test
365     fun testActivityEnabled_correctPanel() {
366         val serviceInfo = ServiceInfo(
367                 componentName,
368                 activityName
369         )
370 
371         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
372                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
373 
374         setUpQueryResult(listOf(
375                 ActivityInfo(
376                         activityName,
377                         exported = true,
378                         permission = Manifest.permission.BIND_CONTROLS
379                 )
380         ))
381 
382         val list = listOf(serviceInfo)
383         serviceListingCallbackCaptor.value.onServicesReloaded(list)
384 
385         executor.runAllReady()
386 
387         assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
388     }
389 
390     @Test
391     fun testActivityDefaultEnabled_correctPanel() {
392         val serviceInfo = ServiceInfo(
393                 componentName,
394                 activityName
395         )
396 
397         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
398                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
399 
400         setUpQueryResult(listOf(
401                 ActivityInfo(
402                         activityName,
403                         enabled = true,
404                         exported = true,
405                         permission = Manifest.permission.BIND_CONTROLS
406                 )
407         ))
408 
409         val list = listOf(serviceInfo)
410         serviceListingCallbackCaptor.value.onServicesReloaded(list)
411 
412         executor.runAllReady()
413 
414         assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
415     }
416 
417     @Test
418     fun testActivityDefaultDisabled_nullPanel() {
419         val serviceInfo = ServiceInfo(
420                 componentName,
421                 activityName
422         )
423 
424         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
425                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
426 
427         setUpQueryResult(listOf(
428                 ActivityInfo(
429                         activityName,
430                         enabled = false,
431                         exported = true,
432                         permission = Manifest.permission.BIND_CONTROLS
433                 )
434         ))
435 
436         val list = listOf(serviceInfo)
437         serviceListingCallbackCaptor.value.onServicesReloaded(list)
438 
439         executor.runAllReady()
440 
441         assertNull(controller.getCurrentServices()[0].panelActivity)
442     }
443 
444     @Test
445     fun testActivityDifferentPackage_nullPanel() {
446         val serviceInfo = ServiceInfo(
447                 componentName,
448                 ComponentName("other_package", "cls")
449         )
450 
451         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
452                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
453 
454         setUpQueryResult(listOf(
455                 ActivityInfo(
456                         activityName,
457                         enabled = true,
458                         exported = true,
459                         permission = Manifest.permission.BIND_CONTROLS
460                 )
461         ))
462 
463         val list = listOf(serviceInfo)
464         serviceListingCallbackCaptor.value.onServicesReloaded(list)
465 
466         executor.runAllReady()
467 
468         assertNull(controller.getCurrentServices()[0].panelActivity)
469     }
470 
471     @Test
472     fun testPackageNotPreferred_nullPanel() {
473         mContext.orCreateTestableResources
474                 .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
475 
476         val serviceInfo = ServiceInfo(
477                 componentName,
478                 activityName
479         )
480 
481         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
482                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
483 
484         setUpQueryResult(listOf(
485                 ActivityInfo(
486                         activityName,
487                         exported = true,
488                         permission = Manifest.permission.BIND_CONTROLS
489                 )
490         ))
491 
492         val list = listOf(serviceInfo)
493         serviceListingCallbackCaptor.value.onServicesReloaded(list)
494 
495         executor.runAllReady()
496 
497         assertNull(controller.getCurrentServices()[0].panelActivity)
498     }
499 
500     @Test
501     fun testPackageNotPreferred_allowAllApps_correctPanel() {
502         `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(true)
503 
504         mContext.orCreateTestableResources
505                 .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
506 
507         val serviceInfo = ServiceInfo(
508                 componentName,
509                 activityName
510         )
511 
512         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
513                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
514 
515         setUpQueryResult(listOf(
516                 ActivityInfo(
517                         activityName,
518                         exported = true,
519                         permission = Manifest.permission.BIND_CONTROLS
520                 )
521         ))
522 
523         val list = listOf(serviceInfo)
524         serviceListingCallbackCaptor.value.onServicesReloaded(list)
525 
526         executor.runAllReady()
527 
528         assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
529     }
530 
531     @Test
532     fun testListingsNotModifiedByCallback() {
533         // This test checks that if the list passed to the callback is modified, it has no effect
534         // in the resulting services
535         val list = mutableListOf<ServiceInfo>()
536         serviceListingCallbackCaptor.value.onServicesReloaded(list)
537 
538         list.add(ServiceInfo(ComponentName("a", "b")))
539         executor.runAllReady()
540 
541         assertTrue(controller.getCurrentServices().isEmpty())
542     }
543 
544     @Test
545     fun testForceReloadQueriesPackageManager() {
546         val user = 10
547         `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
548 
549         controller.forceReload()
550         verify(packageManager).queryIntentServicesAsUser(
551                 argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
552                 argThat(FlagsMatcher(
553                         PackageManager.GET_META_DATA.toLong() or
554                                 PackageManager.GET_SERVICES.toLong() or
555                                 PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
556                                 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
557                 )),
558                 eq(UserHandle.of(user))
559         )
560     }
561 
562     @Test
563     fun testForceReloadUpdatesList() {
564         val resolveInfo = ResolveInfo()
565         resolveInfo.serviceInfo = ServiceInfo(componentName)
566 
567         `when`(packageManager.queryIntentServicesAsUser(
568                 argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
569                 argThat(FlagsMatcher(
570                         PackageManager.GET_META_DATA.toLong() or
571                                 PackageManager.GET_SERVICES.toLong() or
572                                 PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
573                                 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
574                 )),
575                 any<UserHandle>()
576         )).thenReturn(listOf(resolveInfo))
577 
578         controller.forceReload()
579 
580         val services = controller.getCurrentServices()
581         assertThat(services.size).isEqualTo(1)
582         assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName)
583     }
584 
585     @Test
586     fun testForceReloadCallsListeners() {
587         controller.addCallback(mockCallback)
588         executor.runAllReady()
589 
590         @Suppress("unchecked_cast")
591         val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
592                 ArgumentCaptor.forClass(List::class.java)
593                         as ArgumentCaptor<List<ControlsServiceInfo>>
594 
595         val resolveInfo = ResolveInfo()
596         resolveInfo.serviceInfo = ServiceInfo(componentName)
597 
598         `when`(packageManager.queryIntentServicesAsUser(
599                 any(),
600                 any<PackageManager.ResolveInfoFlags>(),
601                 any<UserHandle>()
602         )).thenReturn(listOf(resolveInfo))
603 
604         reset(mockCallback)
605         controller.forceReload()
606 
607         verify(mockCallback).onServicesUpdated(capture(captor))
608 
609         val services = captor.value
610 
611         assertThat(services.size).isEqualTo(1)
612         assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName)
613     }
614 
615     @Test
616     fun testNoPanelIfMultiWindowNotSupported() {
617         `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false)
618 
619         val serviceInfo = ServiceInfo(
620             componentName,
621             activityName
622         )
623 
624         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
625             .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
626 
627         setUpQueryResult(listOf(
628             ActivityInfo(
629                 activityName,
630                 enabled = true,
631                 exported = true,
632                 permission = Manifest.permission.BIND_CONTROLS
633             )
634         ))
635 
636         val list = listOf(serviceInfo)
637         serviceListingCallbackCaptor.value.onServicesReloaded(list)
638 
639         executor.runAllReady()
640 
641         assertNull(controller.getCurrentServices()[0].panelActivity)
642     }
643 
644     private fun ServiceInfo(
645             componentName: ComponentName,
646             panelActivityComponentName: ComponentName? = null
647     ): ServiceInfo {
648         return ServiceInfo().apply {
649             packageName = componentName.packageName
650             name = componentName.className
651             panelActivityComponentName?.let {
652                 metaData = Bundle().apply {
653                     putString(
654                             ControlsProviderService.META_DATA_PANEL_ACTIVITY,
655                             it.flattenToShortString()
656                     )
657                 }
658             }
659         }
660     }
661 
662     private fun ActivityInfo(
663         componentName: ComponentName,
664         exported: Boolean = false,
665         enabled: Boolean = true,
666         permission: String? = null
667     ): ActivityInfo {
668         return ActivityInfo().apply {
669             packageName = componentName.packageName
670             name = componentName.className
671             this.permission = permission
672             this.exported = exported
673             this.enabled = enabled
674         }
675     }
676 
677     private fun setUpQueryResult(infos: List<ActivityInfo>) {
678         `when`(
679                 packageManager.queryIntentActivitiesAsUser(
680                         argThat(IntentMatcherComponent(activityName)),
681                         argThat(FlagsMatcher(FLAGS)),
682                         eq(UserHandle.of(user))
683                 )
684         ).thenReturn(infos.map {
685             ResolveInfo().apply { activityInfo = it }
686         })
687     }
688 
689     private class IntentMatcherComponent(
690             private val componentName: ComponentName
691     ) : ArgumentMatcher<Intent> {
692         override fun matches(argument: Intent?): Boolean {
693             return argument?.component == componentName
694         }
695     }
696 
697     private class IntentMatcherAction(
698             private val action: String
699     ) : ArgumentMatcher<Intent> {
700         override fun matches(argument: Intent?): Boolean {
701             return argument?.action == action
702         }
703     }
704 
705     private class FlagsMatcher(
706             private val flags: Long
707     ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
708         override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean {
709             return flags == argument?.value
710         }
711     }
712 }
713