1 /*
2  * Copyright (C) 2022 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.AppOpsManager
20 import android.content.pm.UserInfo
21 import android.os.UserHandle
22 import android.testing.AndroidTestingRunner
23 import android.testing.TestableLooper.RunWithLooper
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.appops.AppOpItem
27 import com.android.systemui.appops.AppOpsController
28 import com.android.systemui.privacy.logging.PrivacyLogger
29 import com.android.systemui.settings.UserTracker
30 import com.android.systemui.util.concurrency.FakeExecutor
31 import com.android.systemui.util.time.FakeSystemClock
32 import org.hamcrest.Matchers.hasItem
33 import org.hamcrest.Matchers.not
34 import org.hamcrest.Matchers.nullValue
35 import org.junit.Assert.assertEquals
36 import org.junit.Assert.assertThat
37 import org.junit.Assert.assertTrue
38 import org.junit.Assert.assertFalse
39 import org.junit.Before
40 import org.junit.Test
41 import org.junit.runner.RunWith
42 import org.mockito.ArgumentCaptor
43 import org.mockito.ArgumentMatchers.anyBoolean
44 import org.mockito.Captor
45 import org.mockito.Mock
46 import org.mockito.Mockito
47 import org.mockito.Mockito.`when`
48 import org.mockito.Mockito.atLeastOnce
49 import org.mockito.Mockito.doReturn
50 import org.mockito.Mockito.never
51 import org.mockito.Mockito.reset
52 import org.mockito.Mockito.verify
53 import org.mockito.MockitoAnnotations
54 
55 @RunWith(AndroidTestingRunner::class)
56 @SmallTest
57 @RunWithLooper
58 class AppOpsPrivacyItemMonitorTest : SysuiTestCase() {
59 
60     companion object {
61         val CURRENT_USER_ID = 1
62         val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
63         const val TEST_PACKAGE_NAME = "test"
64 
65         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
66         fun <T> eq(value: T): T = Mockito.eq(value) ?: value
67         fun <T> any(): T = Mockito.any<T>()
68     }
69 
70     @Mock
71     private lateinit var appOpsController: AppOpsController
72 
73     @Mock
74     private lateinit var callback: PrivacyItemMonitor.Callback
75 
76     @Mock
77     private lateinit var userTracker: UserTracker
78 
79     @Mock
80     private lateinit var privacyConfig: PrivacyConfig
81 
82     @Mock
83     private lateinit var logger: PrivacyLogger
84 
85     @Captor
86     private lateinit var argCaptorConfigCallback: ArgumentCaptor<PrivacyConfig.Callback>
87 
88     @Captor
89     private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback>
90 
91     private lateinit var appOpsPrivacyItemMonitor: AppOpsPrivacyItemMonitor
92     private lateinit var executor: FakeExecutor
93 
94     fun createAppOpsPrivacyItemMonitor(): AppOpsPrivacyItemMonitor {
95         return AppOpsPrivacyItemMonitor(
96                 appOpsController,
97                 userTracker,
98                 privacyConfig,
99                 executor,
100                 logger)
101     }
102 
103     @Before
104     fun setup() {
105         MockitoAnnotations.initMocks(this)
106         executor = FakeExecutor(FakeSystemClock())
107 
108         // Listen to everything by default
109         `when`(privacyConfig.micCameraAvailable).thenReturn(true)
110         `when`(privacyConfig.locationAvailable).thenReturn(true)
111         `when`(userTracker.userProfiles).thenReturn(
112                 listOf(UserInfo(CURRENT_USER_ID, TEST_PACKAGE_NAME, 0)))
113 
114         appOpsPrivacyItemMonitor = createAppOpsPrivacyItemMonitor()
115         verify(privacyConfig).addCallback(capture(argCaptorConfigCallback))
116     }
117 
118     @Test
119     fun testStartListeningAddsAppOpsCallback() {
120         appOpsPrivacyItemMonitor.startListening(callback)
121         executor.runAllReady()
122         verify(appOpsController).addCallback(eq(AppOpsPrivacyItemMonitor.OPS), any())
123     }
124 
125     @Test
126     fun testStopListeningRemovesAppOpsCallback() {
127         appOpsPrivacyItemMonitor.startListening(callback)
128         executor.runAllReady()
129         verify(appOpsController, never()).removeCallback(any(), any())
130 
131         appOpsPrivacyItemMonitor.stopListening()
132         executor.runAllReady()
133         verify(appOpsController).removeCallback(eq(AppOpsPrivacyItemMonitor.OPS), any())
134     }
135 
136     @Test
137     fun testDistinctItems() {
138         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
139                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)))
140                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
141 
142         assertEquals(1, appOpsPrivacyItemMonitor.getActivePrivacyItems().size)
143     }
144 
145     @Test
146     fun testSimilarItemsDifferentTimeStamp() {
147         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
148                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 1)))
149                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
150 
151         assertEquals(2, appOpsPrivacyItemMonitor.getActivePrivacyItems().size)
152     }
153 
154     @Test
155     fun testRegisterUserTrackerCallback() {
156         appOpsPrivacyItemMonitor.startListening(callback)
157         executor.runAllReady()
158         verify(userTracker, atLeastOnce()).addCallback(
159                 eq(appOpsPrivacyItemMonitor.userTrackerCallback), any())
160         verify(userTracker, never()).removeCallback(
161                 eq(appOpsPrivacyItemMonitor.userTrackerCallback))
162     }
163 
164     @Test
165     fun testUserTrackerCallback_userChanged() {
166         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(0, mContext)
167         executor.runAllReady()
168         verify(userTracker).userProfiles
169     }
170 
171     @Test
172     fun testUserTrackerCallback_profilesChanged() {
173         appOpsPrivacyItemMonitor.userTrackerCallback.onProfilesChanged(emptyList())
174         executor.runAllReady()
175         verify(userTracker).userProfiles
176     }
177 
178     @Test
179     fun testCallbackIsUpdated() {
180         doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
181         appOpsPrivacyItemMonitor.startListening(callback)
182         executor.runAllReady()
183         reset(callback)
184 
185         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
186         argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, TEST_PACKAGE_NAME, true)
187         executor.runAllReady()
188         verify(callback).onPrivacyItemsChanged()
189     }
190 
191     @Test
192     fun testRemoveCallback() {
193         doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
194         appOpsPrivacyItemMonitor.startListening(callback)
195         executor.runAllReady()
196         reset(callback)
197 
198         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
199         appOpsPrivacyItemMonitor.stopListening()
200         argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, TEST_PACKAGE_NAME, true)
201         executor.runAllReady()
202         verify(callback, never()).onPrivacyItemsChanged()
203     }
204 
205     @Test
206     fun testListShouldNotHaveNull() {
207         doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, TEST_PACKAGE_NAME, 0),
208                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)))
209                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
210 
211         assertThat(appOpsPrivacyItemMonitor.getActivePrivacyItems(), not(hasItem(nullValue())))
212     }
213 
214     @Test
215     fun testNotListeningWhenIndicatorsDisabled() {
216         changeMicCamera(false)
217         changeLocation(false)
218 
219         appOpsPrivacyItemMonitor.startListening(callback)
220         executor.runAllReady()
221         verify(appOpsController, never()).addCallback(eq(AppOpsPrivacyItemMonitor.OPS), any())
222     }
223 
224     @Test
225     fun testNotSendingLocationWhenLocationDisabled() {
226         changeLocation(false)
227         executor.runAllReady()
228 
229         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
230                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)))
231                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
232 
233         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
234         assertEquals(1, privacyItems.size)
235         assertEquals(PrivacyType.TYPE_CAMERA, privacyItems[0].privacyType)
236     }
237 
238     @Test
239     fun testNotUpdated_LocationChangeWhenLocationDisabled() {
240         doReturn(listOf(
241                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)))
242                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
243 
244         appOpsPrivacyItemMonitor.startListening(callback)
245         changeLocation(false)
246         executor.runAllReady()
247         reset(callback) // Clean callback
248 
249         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
250         argCaptorCallback.value.onActiveStateChanged(
251                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
252 
253         verify(callback, never()).onPrivacyItemsChanged()
254     }
255 
256     @Test
257     fun testLogActiveChanged() {
258         appOpsPrivacyItemMonitor.startListening(callback)
259         executor.runAllReady()
260 
261         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
262         argCaptorCallback.value.onActiveStateChanged(
263                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
264 
265         verify(logger).logUpdatedItemFromAppOps(
266                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
267     }
268 
269     @Test
270     fun testListRequestedShowPaused() {
271         appOpsPrivacyItemMonitor.getActivePrivacyItems()
272         verify(appOpsController).getActiveAppOps(true)
273     }
274 
275     @Test
276     fun testListFilterCurrentUser() {
277         val otherUser = CURRENT_USER_ID + 1
278         val otherUserUid = otherUser * UserHandle.PER_USER_RANGE
279         `when`(userTracker.userProfiles)
280                 .thenReturn(listOf(UserInfo(otherUser, TEST_PACKAGE_NAME, 0)))
281 
282         doReturn(listOf(
283                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
284                 AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0))
285         ).`when`(appOpsController).getActiveAppOps(anyBoolean())
286 
287         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(otherUser, mContext)
288         executor.runAllReady()
289 
290         appOpsPrivacyItemMonitor.startListening(callback)
291         executor.runAllReady()
292 
293         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
294 
295         assertEquals(1, privacyItems.size)
296         assertEquals(PrivacyType.TYPE_CAMERA, privacyItems[0].privacyType)
297         assertEquals(otherUserUid, privacyItems[0].application.uid)
298     }
299 
300     @Test
301     fun testAlwaysGetPhoneCameraOps() {
302         val otherUser = CURRENT_USER_ID + 1
303         `when`(userTracker.userProfiles)
304                 .thenReturn(listOf(UserInfo(otherUser, TEST_PACKAGE_NAME, 0)))
305 
306         doReturn(listOf(
307                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
308                 AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0),
309                 AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0))
310         ).`when`(appOpsController).getActiveAppOps(anyBoolean())
311 
312         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(otherUser, mContext)
313         executor.runAllReady()
314 
315         appOpsPrivacyItemMonitor.startListening(callback)
316         executor.runAllReady()
317 
318         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
319 
320         assertEquals(1, privacyItems.size)
321         assertEquals(PrivacyType.TYPE_CAMERA, privacyItems[0].privacyType)
322     }
323 
324     @Test
325     fun testAlwaysGetPhoneMicOps() {
326         val otherUser = CURRENT_USER_ID + 1
327         `when`(userTracker.userProfiles)
328                 .thenReturn(listOf(UserInfo(otherUser, TEST_PACKAGE_NAME, 0)))
329 
330         doReturn(listOf(
331                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
332                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
333                 AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0))
334         ).`when`(appOpsController).getActiveAppOps(anyBoolean())
335 
336         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(otherUser, mContext)
337         executor.runAllReady()
338 
339         appOpsPrivacyItemMonitor.startListening(callback)
340         executor.runAllReady()
341 
342         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
343 
344         assertEquals(1, privacyItems.size)
345         assertEquals(PrivacyType.TYPE_MICROPHONE, privacyItems[0].privacyType)
346     }
347 
348     @Test
349     fun testDisabledAppOpIsPaused() {
350         val item = AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0)
351         item.isDisabled = true
352         `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item))
353 
354         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
355         assertEquals(1, privacyItems.size)
356         assertTrue(privacyItems[0].paused)
357     }
358 
359     @Test
360     fun testEnabledAppOpIsNotPaused() {
361         val item = AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0)
362         `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item))
363 
364         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
365         assertEquals(1, privacyItems.size)
366         assertFalse(privacyItems[0].paused)
367     }
368 
369     private fun changeMicCamera(value: Boolean) {
370         `when`(privacyConfig.micCameraAvailable).thenReturn(value)
371         argCaptorConfigCallback.value.onFlagMicCameraChanged(value)
372     }
373 
374     private fun changeLocation(value: Boolean) {
375         `when`(privacyConfig.locationAvailable).thenReturn(value)
376         argCaptorConfigCallback.value.onFlagLocationChanged(value)
377     }
378 }