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.privacy
18 
19 import android.app.ActivityManager
20 import android.os.UserHandle
21 import android.testing.TestableLooper.RunWithLooper
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import androidx.test.filters.SmallTest
24 import com.android.systemui.SysuiTestCase
25 import com.android.systemui.dump.DumpManager
26 import com.android.systemui.privacy.logging.PrivacyLogger
27 import com.android.systemui.util.DeviceConfigProxy
28 import com.android.systemui.util.DeviceConfigProxyFake
29 import com.android.systemui.util.concurrency.FakeExecutor
30 import com.android.systemui.util.mockito.argumentCaptor
31 import com.android.systemui.util.time.FakeSystemClock
32 import org.junit.Assert.assertEquals
33 import org.junit.Assert.assertTrue
34 import org.junit.Before
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 import org.mockito.ArgumentCaptor
38 import org.mockito.ArgumentMatchers.anyList
39 import org.mockito.Captor
40 import org.mockito.Mock
41 import org.mockito.Mockito
42 import org.mockito.Mockito.`when`
43 import org.mockito.Mockito.atLeastOnce
44 import org.mockito.Mockito.doReturn
45 import org.mockito.Mockito.mock
46 import org.mockito.Mockito.never
47 import org.mockito.Mockito.reset
48 import org.mockito.Mockito.verify
49 import org.mockito.Mockito.verifyNoMoreInteractions
50 import org.mockito.MockitoAnnotations
51 
52 @RunWith(AndroidJUnit4::class)
53 @SmallTest
54 @RunWithLooper
55 class PrivacyItemControllerTest : SysuiTestCase() {
56 
57     companion object {
58         val CURRENT_USER_ID = ActivityManager.getCurrentUser()
59         val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
60         const val TEST_PACKAGE_NAME = "test"
61 
capturenull62         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
63         fun <T> any(): T = Mockito.any<T>()
64     }
65 
66     @Mock
67     private lateinit var callback: PrivacyItemController.Callback
68     @Mock
69     private lateinit var privacyConfig: PrivacyConfig
70     @Mock
71     private lateinit var privacyItemMonitor: PrivacyItemMonitor
72     @Mock
73     private lateinit var privacyItemMonitor2: PrivacyItemMonitor
74     @Mock
75     private lateinit var dumpManager: DumpManager
76     @Mock
77     private lateinit var logger: PrivacyLogger
78     @Captor
79     private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
80     @Captor
81     private lateinit var argCaptorCallback: ArgumentCaptor<PrivacyItemMonitor.Callback>
82     @Captor
83     private lateinit var argCaptorConfigCallback: ArgumentCaptor<PrivacyConfig.Callback>
84 
85     private lateinit var privacyItemController: PrivacyItemController
86     private lateinit var executor: FakeExecutor
87     private lateinit var fakeClock: FakeSystemClock
88     private lateinit var deviceConfigProxy: DeviceConfigProxy
89 
90     fun createPrivacyItemController(): PrivacyItemController {
91         return PrivacyItemController(
92                 executor,
93                 executor,
94                 privacyConfig,
95                 setOf(privacyItemMonitor, privacyItemMonitor2),
96                 logger,
97                 fakeClock,
98                 dumpManager)
99     }
100 
101     @Before
setupnull102     fun setup() {
103         MockitoAnnotations.initMocks(this)
104         fakeClock = FakeSystemClock()
105         executor = FakeExecutor(fakeClock)
106         deviceConfigProxy = DeviceConfigProxyFake()
107         privacyItemController = createPrivacyItemController()
108     }
109 
110     @Test
testStartListeningByAddingCallbacknull111     fun testStartListeningByAddingCallback() {
112         privacyItemController.addCallback(callback)
113         executor.runAllReady()
114         verify(privacyItemMonitor).startListening(any())
115         verify(privacyItemMonitor2).startListening(any())
116         verify(callback).onPrivacyItemsChanged(anyList())
117     }
118 
119     @Test
testStopListeningByRemovingLastCallbacknull120     fun testStopListeningByRemovingLastCallback() {
121         privacyItemController.addCallback(callback)
122         executor.runAllReady()
123         verify(privacyItemMonitor, never()).stopListening()
124         privacyItemController.removeCallback(callback)
125         executor.runAllReady()
126         verify(privacyItemMonitor).stopListening()
127         verify(privacyItemMonitor2).stopListening()
128         verify(callback).onPrivacyItemsChanged(emptyList())
129     }
130 
131     @Test
testPrivacyItemsAggregatednull132     fun testPrivacyItemsAggregated() {
133         val item1 = PrivacyItem(PrivacyType.TYPE_CAMERA,
134                 PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0)
135         val item2 = PrivacyItem(PrivacyType.TYPE_MICROPHONE,
136                 PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 1)
137         doReturn(listOf(item1))
138                 .`when`(privacyItemMonitor).getActivePrivacyItems()
139         doReturn(listOf(item2))
140                 .`when`(privacyItemMonitor2).getActivePrivacyItems()
141 
142         privacyItemController.addCallback(callback)
143         executor.runAllReady()
144         verify(callback).onPrivacyItemsChanged(capture(argCaptor))
145         assertEquals(2, argCaptor.value.size)
146         assertTrue(argCaptor.value.contains(item1))
147         assertTrue(argCaptor.value.contains(item2))
148     }
149 
150     @Test
testDistinctItemsnull151     fun testDistinctItems() {
152         doReturn(listOf(
153                 PrivacyItem(PrivacyType.TYPE_CAMERA,
154                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0),
155                 PrivacyItem(PrivacyType.TYPE_CAMERA,
156                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0)))
157                 .`when`(privacyItemMonitor).getActivePrivacyItems()
158         doReturn(listOf(
159                 PrivacyItem(PrivacyType.TYPE_CAMERA,
160                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0)))
161                 .`when`(privacyItemMonitor2).getActivePrivacyItems()
162 
163         privacyItemController.addCallback(callback)
164         executor.runAllReady()
165         verify(callback).onPrivacyItemsChanged(capture(argCaptor))
166         assertEquals(1, argCaptor.value.size)
167     }
168 
169     @Test
testSimilarItemsDifferentTimeStampnull170     fun testSimilarItemsDifferentTimeStamp() {
171         doReturn(listOf(
172                 PrivacyItem(PrivacyType.TYPE_CAMERA,
173                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0),
174                 PrivacyItem(PrivacyType.TYPE_CAMERA,
175                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 1)))
176                 .`when`(privacyItemMonitor).getActivePrivacyItems()
177 
178         privacyItemController.addCallback(callback)
179         executor.runAllReady()
180         verify(callback).onPrivacyItemsChanged(capture(argCaptor))
181         assertEquals(2, argCaptor.value.size)
182     }
183 
184     @Test
testAddMultipleCallbacksnull185     fun testAddMultipleCallbacks() {
186         val otherCallback = mock(PrivacyItemController.Callback::class.java)
187         privacyItemController.addCallback(callback)
188         executor.runAllReady()
189         verify(callback).onPrivacyItemsChanged(anyList())
190 
191         privacyItemController.addCallback(otherCallback)
192         executor.runAllReady()
193         verify(otherCallback).onPrivacyItemsChanged(anyList())
194         // Adding a callback should not unnecessarily call previous ones
195         verifyNoMoreInteractions(callback)
196     }
197 
198     @Test
testMultipleCallbacksAreUpdatednull199     fun testMultipleCallbacksAreUpdated() {
200         doReturn(emptyList<PrivacyItem>()).`when`(privacyItemMonitor).getActivePrivacyItems()
201 
202         val otherCallback = mock(PrivacyItemController.Callback::class.java)
203         privacyItemController.addCallback(callback)
204         privacyItemController.addCallback(otherCallback)
205         executor.runAllReady()
206         reset(callback)
207         reset(otherCallback)
208 
209         verify(privacyItemMonitor).startListening(capture(argCaptorCallback))
210         argCaptorCallback.value.onPrivacyItemsChanged()
211         executor.runAllReady()
212         verify(callback).onPrivacyItemsChanged(anyList())
213         verify(otherCallback).onPrivacyItemsChanged(anyList())
214     }
215 
216     @Test
testRemoveCallbacknull217     fun testRemoveCallback() {
218         doReturn(emptyList<PrivacyItem>()).`when`(privacyItemMonitor).getActivePrivacyItems()
219         val otherCallback = mock(PrivacyItemController.Callback::class.java)
220         privacyItemController.addCallback(callback)
221         privacyItemController.addCallback(otherCallback)
222         executor.runAllReady()
223         executor.runAllReady()
224         reset(callback)
225         reset(otherCallback)
226 
227         verify(privacyItemMonitor).startListening(capture(argCaptorCallback))
228         privacyItemController.removeCallback(callback)
229         argCaptorCallback.value.onPrivacyItemsChanged()
230         executor.runAllReady()
231         verify(callback, never()).onPrivacyItemsChanged(anyList())
232         verify(otherCallback).onPrivacyItemsChanged(anyList())
233     }
234 
235     @Test
testListShouldBeCopynull236     fun testListShouldBeCopy() {
237         val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA,
238                 PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0))
239         privacyItemController.privacyList = list
240         val privacyList = privacyItemController.privacyList
241         assertEquals(list, privacyList)
242         assertTrue(list !== privacyList)
243     }
244 
245     @Test
testLogListUpdatednull246     fun testLogListUpdated() {
247         val privacyItem = PrivacyItem(
248                 PrivacyType.TYPE_LOCATION,
249                 PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID),
250                 0
251         )
252 
253         doReturn(listOf(privacyItem)).`when`(privacyItemMonitor).getActivePrivacyItems()
254 
255         privacyItemController.addCallback(callback)
256         executor.runAllReady()
257 
258         verify(privacyItemMonitor).startListening(capture(argCaptorCallback))
259         argCaptorCallback.value.onPrivacyItemsChanged()
260         executor.runAllReady()
261 
262         val captor = argumentCaptor<List<PrivacyItem>>()
263         verify(logger, atLeastOnce()).logRetrievedPrivacyItemsList(capture(captor))
264         // Let's look at the last log
265         val values = captor.allValues
266         assertTrue(values[values.size - 1].contains(privacyItem))
267     }
268 
269     @Test
testPassageOfTimeDoesNotRemoveIndicatorsnull270     fun testPassageOfTimeDoesNotRemoveIndicators() {
271         doReturn(listOf(
272                 PrivacyItem(PrivacyType.TYPE_CAMERA,
273                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0)
274         )).`when`(privacyItemMonitor).getActivePrivacyItems()
275 
276         privacyItemController.addCallback(callback)
277 
278         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS * 10)
279         executor.runAllReady()
280 
281         verify(callback, never()).onPrivacyItemsChanged(emptyList())
282         assertTrue(privacyItemController.privacyList.isNotEmpty())
283     }
284 
285     @Test
testNotHeldAfterTimeIsOffnull286     fun testNotHeldAfterTimeIsOff() {
287         // Start with some element at time 0
288         doReturn(listOf(
289                 PrivacyItem(PrivacyType.TYPE_CAMERA,
290                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0)
291         )).`when`(privacyItemMonitor).getActivePrivacyItems()
292         privacyItemController.addCallback(callback)
293         executor.runAllReady()
294 
295         // Then remove it at time HOLD + 1
296         doReturn(emptyList<PrivacyItem>()).`when`(privacyItemMonitor).getActivePrivacyItems()
297         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1)
298 
299         verify(privacyItemMonitor).startListening(capture(argCaptorCallback))
300         argCaptorCallback.value.onPrivacyItemsChanged()
301         executor.runAllReady()
302 
303         // See it's not there
304         verify(callback).onPrivacyItemsChanged(emptyList())
305         assertTrue(privacyItemController.privacyList.isEmpty())
306     }
307 
308     @Test
testElementNotRemovedBeforeHoldTimenull309     fun testElementNotRemovedBeforeHoldTime() {
310         // Start with some element at current time
311         doReturn(listOf(
312                 PrivacyItem(PrivacyType.TYPE_CAMERA,
313                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID),
314                         fakeClock.elapsedRealtime())
315         )).`when`(privacyItemMonitor).getActivePrivacyItems()
316         privacyItemController.addCallback(callback)
317         executor.runAllReady()
318 
319         // Then remove it at time HOLD - 1
320         doReturn(emptyList<PrivacyItem>()).`when`(privacyItemMonitor).getActivePrivacyItems()
321         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
322 
323         verify(privacyItemMonitor).startListening(capture(argCaptorCallback))
324         argCaptorCallback.value.onPrivacyItemsChanged()
325         executor.runAllReady()
326 
327         // See it's still there
328         verify(callback, never()).onPrivacyItemsChanged(emptyList())
329         assertTrue(privacyItemController.privacyList.isNotEmpty())
330     }
331 
332     @Test
testElementAutoRemovedAfterHoldTimenull333     fun testElementAutoRemovedAfterHoldTime() {
334         // Start with some element at time 0
335         doReturn(listOf(
336                 PrivacyItem(PrivacyType.TYPE_CAMERA,
337                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0)
338         )).`when`(privacyItemMonitor).getActivePrivacyItems()
339         privacyItemController.addCallback(callback)
340         executor.runAllReady()
341 
342         // Then remove it at time HOLD - 1
343         doReturn(emptyList<PrivacyItem>()).`when`(privacyItemMonitor).getActivePrivacyItems()
344         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
345 
346         verify(privacyItemMonitor).startListening(capture(argCaptorCallback))
347         argCaptorCallback.value.onPrivacyItemsChanged()
348         executor.runAllReady()
349 
350         fakeClock.advanceTime(2L)
351         executor.runAllReady()
352 
353         // See it was auto-removed
354         verify(callback).onPrivacyItemsChanged(emptyList())
355         assertTrue(privacyItemController.privacyList.isEmpty())
356     }
357 
358     @Test
testFlagsAll_listeningToAllnull359     fun testFlagsAll_listeningToAll() {
360         verify(privacyConfig).addCallback(capture(argCaptorConfigCallback))
361         privacyItemController.addCallback(callback)
362         `when`(privacyConfig.micCameraAvailable).thenReturn(true)
363         `when`(privacyConfig.locationAvailable).thenReturn(true)
364         `when`(privacyConfig.mediaProjectionAvailable).thenReturn(true)
365         argCaptorConfigCallback.value.onFlagMicCameraChanged(true)
366         argCaptorConfigCallback.value.onFlagLocationChanged(true)
367         argCaptorConfigCallback.value.onFlagMediaProjectionChanged(true)
368         executor.runAllReady()
369 
370         assertTrue(privacyItemController.allIndicatorsAvailable)
371     }
372 
373     @Test
testFlags_onFlagMicCameraChangednull374     fun testFlags_onFlagMicCameraChanged() {
375         verify(privacyConfig).addCallback(capture(argCaptorConfigCallback))
376         privacyItemController.addCallback(callback)
377         `when`(privacyConfig.micCameraAvailable).thenReturn(true)
378         argCaptorConfigCallback.value.onFlagMicCameraChanged(true)
379         executor.runAllReady()
380 
381         assertTrue(privacyItemController.micCameraAvailable)
382         verify(callback).onFlagMicCameraChanged(true)
383     }
384 
385     @Test
testFlags_onFlagLocationChangednull386     fun testFlags_onFlagLocationChanged() {
387         verify(privacyConfig).addCallback(capture(argCaptorConfigCallback))
388         privacyItemController.addCallback(callback)
389         `when`(privacyConfig.locationAvailable).thenReturn(true)
390         argCaptorConfigCallback.value.onFlagLocationChanged(true)
391         executor.runAllReady()
392 
393         assertTrue(privacyItemController.locationAvailable)
394         verify(callback).onFlagLocationChanged(true)
395     }
396 
397     @Test
testFlags_onFlagMediaProjectionChangednull398     fun testFlags_onFlagMediaProjectionChanged() {
399         verify(privacyConfig).addCallback(capture(argCaptorConfigCallback))
400         privacyItemController.addCallback(callback)
401         `when`(privacyConfig.mediaProjectionAvailable).thenReturn(true)
402         argCaptorConfigCallback.value.onFlagMediaProjectionChanged(true)
403         executor.runAllReady()
404 
405         verify(callback).onFlagMediaProjectionChanged(true)
406     }
407 
408     @Test
testPausedElementsAreRemovednull409     fun testPausedElementsAreRemoved() {
410         doReturn(listOf(
411                 PrivacyItem(PrivacyType.TYPE_MICROPHONE,
412                         PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0, true)))
413                 .`when`(privacyItemMonitor).getActivePrivacyItems()
414 
415         privacyItemController.addCallback(callback)
416         executor.runAllReady()
417 
418         assertTrue(privacyItemController.privacyList.isEmpty())
419     }
420 }
421