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.TestableLooper.RunWithLooper
23 import androidx.test.ext.junit.runners.AndroidJUnit4
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(AndroidJUnit4::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 
capturenull65         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
setupnull104     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
testStartListeningAddsAppOpsCallbacknull119     fun testStartListeningAddsAppOpsCallback() {
120         appOpsPrivacyItemMonitor.startListening(callback)
121         executor.runAllReady()
122         verify(appOpsController).addCallback(eq(AppOpsPrivacyItemMonitor.OPS), any())
123     }
124 
125     @Test
testStopListeningRemovesAppOpsCallbacknull126     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
testDistinctItemsnull137     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
testVoiceActivationPrivacyItemsnull146     fun testVoiceActivationPrivacyItems() {
147         doReturn(listOf(AppOpItem(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID,
148                 TEST_PACKAGE_NAME, 0)))
149                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
150         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
151         assertEquals(1, privacyItems.size)
152         assertEquals(PrivacyType.TYPE_MICROPHONE, privacyItems[0].privacyType)
153     }
154 
155     @Test
testSimilarItemsDifferentTimeStampnull156     fun testSimilarItemsDifferentTimeStamp() {
157         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
158                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 1)))
159                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
160 
161         assertEquals(2, appOpsPrivacyItemMonitor.getActivePrivacyItems().size)
162     }
163 
164     @Test
testRegisterUserTrackerCallbacknull165     fun testRegisterUserTrackerCallback() {
166         appOpsPrivacyItemMonitor.startListening(callback)
167         executor.runAllReady()
168         verify(userTracker, atLeastOnce()).addCallback(
169                 eq(appOpsPrivacyItemMonitor.userTrackerCallback), any())
170         verify(userTracker, never()).removeCallback(
171                 eq(appOpsPrivacyItemMonitor.userTrackerCallback))
172     }
173 
174     @Test
testUserTrackerCallback_userChangednull175     fun testUserTrackerCallback_userChanged() {
176         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(0, mContext)
177         executor.runAllReady()
178         verify(userTracker).userProfiles
179     }
180 
181     @Test
testUserTrackerCallback_profilesChangednull182     fun testUserTrackerCallback_profilesChanged() {
183         appOpsPrivacyItemMonitor.userTrackerCallback.onProfilesChanged(emptyList())
184         executor.runAllReady()
185         verify(userTracker).userProfiles
186     }
187 
188     @Test
testCallbackIsUpdatednull189     fun testCallbackIsUpdated() {
190         doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
191         appOpsPrivacyItemMonitor.startListening(callback)
192         executor.runAllReady()
193         reset(callback)
194 
195         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
196         argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, TEST_PACKAGE_NAME, true)
197         executor.runAllReady()
198         verify(callback).onPrivacyItemsChanged()
199     }
200 
201     @Test
testRemoveCallbacknull202     fun testRemoveCallback() {
203         doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
204         appOpsPrivacyItemMonitor.startListening(callback)
205         executor.runAllReady()
206         reset(callback)
207 
208         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
209         appOpsPrivacyItemMonitor.stopListening()
210         argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, TEST_PACKAGE_NAME, true)
211         executor.runAllReady()
212         verify(callback, never()).onPrivacyItemsChanged()
213     }
214 
215     @Test
testListShouldNotHaveNullnull216     fun testListShouldNotHaveNull() {
217         doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, TEST_PACKAGE_NAME, 0),
218                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)))
219                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
220 
221         assertThat(appOpsPrivacyItemMonitor.getActivePrivacyItems(), not(hasItem(nullValue())))
222     }
223 
224     @Test
testNotListeningWhenIndicatorsDisablednull225     fun testNotListeningWhenIndicatorsDisabled() {
226         changeMicCamera(false)
227         changeLocation(false)
228 
229         appOpsPrivacyItemMonitor.startListening(callback)
230         executor.runAllReady()
231         verify(appOpsController, never()).addCallback(eq(AppOpsPrivacyItemMonitor.OPS), any())
232     }
233 
234     @Test
testNotSendingLocationWhenLocationDisablednull235     fun testNotSendingLocationWhenLocationDisabled() {
236         changeLocation(false)
237         executor.runAllReady()
238 
239         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
240                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)))
241                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
242 
243         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
244         assertEquals(1, privacyItems.size)
245         assertEquals(PrivacyType.TYPE_CAMERA, privacyItems[0].privacyType)
246     }
247 
248     @Test
testNotUpdated_LocationChangeWhenLocationDisablednull249     fun testNotUpdated_LocationChangeWhenLocationDisabled() {
250         doReturn(listOf(
251                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)))
252                 .`when`(appOpsController).getActiveAppOps(anyBoolean())
253 
254         appOpsPrivacyItemMonitor.startListening(callback)
255         changeLocation(false)
256         executor.runAllReady()
257         reset(callback) // Clean callback
258 
259         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
260         argCaptorCallback.value.onActiveStateChanged(
261                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
262 
263         verify(callback, never()).onPrivacyItemsChanged()
264     }
265 
266     @Test
testLogActiveChangednull267     fun testLogActiveChanged() {
268         appOpsPrivacyItemMonitor.startListening(callback)
269         executor.runAllReady()
270 
271         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
272         argCaptorCallback.value.onActiveStateChanged(
273                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
274 
275         verify(logger).logUpdatedItemFromAppOps(
276                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
277     }
278 
279     @Test
testListRequestedShowPausednull280     fun testListRequestedShowPaused() {
281         appOpsPrivacyItemMonitor.getActivePrivacyItems()
282         verify(appOpsController).getActiveAppOps(true)
283     }
284 
285     @Test
testListFilterCurrentUsernull286     fun testListFilterCurrentUser() {
287         val otherUser = CURRENT_USER_ID + 1
288         val otherUserUid = otherUser * UserHandle.PER_USER_RANGE
289         `when`(userTracker.userProfiles)
290                 .thenReturn(listOf(UserInfo(otherUser, TEST_PACKAGE_NAME, 0)))
291 
292         doReturn(listOf(
293                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
294                 AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0))
295         ).`when`(appOpsController).getActiveAppOps(anyBoolean())
296 
297         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(otherUser, mContext)
298         executor.runAllReady()
299 
300         appOpsPrivacyItemMonitor.startListening(callback)
301         executor.runAllReady()
302 
303         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
304 
305         assertEquals(1, privacyItems.size)
306         assertEquals(PrivacyType.TYPE_CAMERA, privacyItems[0].privacyType)
307         assertEquals(otherUserUid, privacyItems[0].application.uid)
308     }
309 
310     @Test
testAlwaysGetPhoneCameraOpsnull311     fun testAlwaysGetPhoneCameraOps() {
312         val otherUser = CURRENT_USER_ID + 1
313         `when`(userTracker.userProfiles)
314                 .thenReturn(listOf(UserInfo(otherUser, TEST_PACKAGE_NAME, 0)))
315 
316         doReturn(listOf(
317                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
318                 AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0),
319                 AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0))
320         ).`when`(appOpsController).getActiveAppOps(anyBoolean())
321 
322         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(otherUser, mContext)
323         executor.runAllReady()
324 
325         appOpsPrivacyItemMonitor.startListening(callback)
326         executor.runAllReady()
327 
328         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
329 
330         assertEquals(1, privacyItems.size)
331         assertEquals(PrivacyType.TYPE_CAMERA, privacyItems[0].privacyType)
332     }
333 
334     @Test
testAlwaysGetPhoneMicOpsnull335     fun testAlwaysGetPhoneMicOps() {
336         val otherUser = CURRENT_USER_ID + 1
337         `when`(userTracker.userProfiles)
338                 .thenReturn(listOf(UserInfo(otherUser, TEST_PACKAGE_NAME, 0)))
339 
340         doReturn(listOf(
341                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
342                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
343                 AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0))
344         ).`when`(appOpsController).getActiveAppOps(anyBoolean())
345 
346         appOpsPrivacyItemMonitor.userTrackerCallback.onUserChanged(otherUser, mContext)
347         executor.runAllReady()
348 
349         appOpsPrivacyItemMonitor.startListening(callback)
350         executor.runAllReady()
351 
352         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
353 
354         assertEquals(1, privacyItems.size)
355         assertEquals(PrivacyType.TYPE_MICROPHONE, privacyItems[0].privacyType)
356     }
357 
358     @Test
testDisabledAppOpIsPausednull359     fun testDisabledAppOpIsPaused() {
360         val item = AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0)
361         item.isDisabled = true
362         `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item))
363 
364         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
365         assertEquals(1, privacyItems.size)
366         assertTrue(privacyItems[0].paused)
367     }
368 
369     @Test
testEnabledAppOpIsNotPausednull370     fun testEnabledAppOpIsNotPaused() {
371         val item = AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0)
372         `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item))
373 
374         val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
375         assertEquals(1, privacyItems.size)
376         assertFalse(privacyItems[0].paused)
377     }
378 
changeMicCameranull379     private fun changeMicCamera(value: Boolean) {
380         `when`(privacyConfig.micCameraAvailable).thenReturn(value)
381         argCaptorConfigCallback.value.onFlagMicCameraChanged(value)
382     }
383 
changeLocationnull384     private fun changeLocation(value: Boolean) {
385         `when`(privacyConfig.locationAvailable).thenReturn(value)
386         argCaptorConfigCallback.value.onFlagLocationChanged(value)
387     }
388 }
389