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 }