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