1 /* <lambda>null2 * Copyright (C) 2021 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 android.sensorprivacy.cts 18 19 import android.app.KeyguardManager 20 import android.app.AppOpsManager 21 import android.content.Intent 22 import android.content.pm.PackageManager 23 import android.hardware.SensorPrivacyManager 24 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener 25 import android.os.PowerManager 26 import android.platform.test.annotations.AppModeFull 27 import android.hardware.SensorPrivacyManager.Sensors.CAMERA 28 import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE 29 import android.hardware.SensorPrivacyManager.Sources.OTHER 30 import android.support.test.uiautomator.By 31 import android.view.KeyEvent 32 import androidx.test.platform.app.InstrumentationRegistry 33 import androidx.test.uiautomator.UiDevice 34 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity 35 import com.android.compatibility.common.util.SystemUtil.eventually 36 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow 37 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity 38 import com.android.compatibility.common.util.UiAutomatorUtils 39 import org.junit.After 40 import org.junit.Assert.assertEquals 41 import org.junit.Assert.assertFalse 42 import org.junit.Assert.assertNull 43 import org.junit.Assert.assertTrue 44 import org.junit.Assume 45 import org.junit.Before 46 import org.junit.Test 47 import java.util.concurrent.CountDownLatch 48 import java.util.concurrent.Executors 49 import java.util.concurrent.TimeUnit 50 import java.util.regex.Pattern 51 52 abstract class SensorPrivacyBaseTest( 53 val sensor: Int, 54 vararg val extras: String 55 ) { 56 57 companion object { 58 const val MIC_CAM_ACTIVITY_ACTION = 59 "android.sensorprivacy.cts.usemiccamera.action.USE_MIC_CAM" 60 const val FINISH_MIC_CAM_ACTIVITY_ACTION = 61 "android.sensorprivacy.cts.usemiccamera.action.FINISH_USE_MIC_CAM" 62 const val USE_MIC_EXTRA = 63 "android.sensorprivacy.cts.usemiccamera.extra.USE_MICROPHONE" 64 const val USE_CAM_EXTRA = 65 "android.sensorprivacy.cts.usemiccamera.extra.USE_CAMERA" 66 const val DELAYED_ACTIVITY_EXTRA = 67 "android.sensorprivacy.cts.usemiccamera.extra.DELAYED_ACTIVITY" 68 const val DELAYED_ACTIVITY_NEW_TASK_EXTRA = 69 "android.sensorprivacy.cts.usemiccamera.extra.DELAYED_ACTIVITY_NEW_TASK" 70 const val PKG_NAME = "android.sensorprivacy.cts.usemiccamera" 71 const val RECORDING_FILE_NAME = "${PKG_NAME}_record.mp4" 72 const val ACTIVITY_TITLE_SNIP = "CtsUseMic" 73 const val SENSOR_USE_TIME_MS = 5L 74 } 75 76 protected val instrumentation = InstrumentationRegistry.getInstrumentation()!! 77 protected val uiAutomation = instrumentation.uiAutomation!! 78 protected val uiDevice = UiDevice.getInstance(instrumentation)!! 79 protected val context = instrumentation.targetContext!! 80 protected val spm = context.getSystemService(SensorPrivacyManager::class.java)!! 81 protected val packageManager = context.packageManager!! 82 protected val op = getOpForSensor(sensor) 83 84 var oldState: Boolean = false 85 86 @Before 87 fun init() { 88 oldState = isSensorPrivacyEnabled() 89 setSensor(false) 90 Assume.assumeTrue(spm.supportsSensorToggle(sensor)) 91 uiDevice.wakeUp() 92 runShellCommandOrThrow("wm dismiss-keyguard") 93 uiDevice.waitForIdle() 94 } 95 96 @After 97 fun tearDown() { 98 finishTestApp() 99 Thread.sleep(3000) 100 setSensor(oldState) 101 } 102 103 @Test 104 fun testSetSensor() { 105 setSensor(true) 106 assertTrue(isSensorPrivacyEnabled()) 107 108 setSensor(false) 109 assertFalse(isSensorPrivacyEnabled()) 110 } 111 112 @Test 113 fun testDialog() { 114 testDialog(delayedActivity = false, delayedActivityNewTask = false) 115 } 116 117 @Test 118 fun testDialog_remainsOnTop() { 119 testDialog(delayedActivity = true, delayedActivityNewTask = false) 120 } 121 122 @Test 123 fun testDialog_remainsOnTop_newTask() { 124 testDialog(delayedActivity = true, delayedActivityNewTask = true) 125 } 126 127 fun testDialog(delayedActivity: Boolean = false, delayedActivityNewTask: Boolean = false) { 128 try { 129 setSensor(true) 130 val intent = Intent(MIC_CAM_ACTIVITY_ACTION) 131 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 132 .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) 133 for (extra in extras) { 134 intent.putExtra(extra, true) 135 } 136 intent.putExtra(DELAYED_ACTIVITY_EXTRA, delayedActivity) 137 intent.putExtra(DELAYED_ACTIVITY_NEW_TASK_EXTRA, delayedActivityNewTask) 138 context.startActivity(intent) 139 if (delayedActivity || delayedActivityNewTask) { 140 Thread.sleep(3000) 141 } 142 unblockSensorWithDialogAndAssert() 143 } finally { 144 runShellCommandOrThrow("am broadcast" + 145 " --user ${context.userId}" + 146 " -a $FINISH_MIC_CAM_ACTIVITY_ACTION" + 147 " -f ${Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS}") 148 } 149 } 150 151 @Test 152 fun testListener() { 153 val executor = Executors.newSingleThreadExecutor() 154 setSensor(false) 155 val latchEnabled = CountDownLatch(1) 156 var listener = 157 OnSensorPrivacyChangedListener { _, enabled: Boolean -> 158 if (enabled) { 159 latchEnabled.countDown() 160 } 161 } 162 runWithShellPermissionIdentity { 163 spm.addSensorPrivacyListener(sensor, executor, listener) 164 } 165 setSensor(true) 166 latchEnabled.await(100, TimeUnit.MILLISECONDS) 167 runWithShellPermissionIdentity { 168 spm.removeSensorPrivacyListener(sensor, listener) 169 } 170 171 val latchDisabled = CountDownLatch(1) 172 listener = OnSensorPrivacyChangedListener { _, enabled: Boolean -> 173 if (!enabled) { 174 latchDisabled.countDown() 175 } 176 } 177 runWithShellPermissionIdentity { 178 spm.addSensorPrivacyListener(sensor, executor, listener) 179 } 180 setSensor(false) 181 latchEnabled.await(100, TimeUnit.MILLISECONDS) 182 runWithShellPermissionIdentity { 183 spm.removeSensorPrivacyListener(sensor, listener) 184 } 185 } 186 187 @Test 188 @AppModeFull(reason = "Instant apps can't manage keyguard") 189 fun testCantChangeWhenLocked() { 190 setSensor(false) 191 assertFalse(isSensorPrivacyEnabled()) 192 runWhileLocked { 193 setSensor(true) 194 assertFalse("State was changed while device is locked", 195 isSensorPrivacyEnabled()) 196 } 197 198 setSensor(true) 199 assertTrue(isSensorPrivacyEnabled()) 200 runWhileLocked { 201 setSensor(false) 202 assertTrue("State was changed while device is locked", 203 isSensorPrivacyEnabled()) 204 } 205 } 206 207 fun unblockSensorWithDialogAndAssert() { 208 UiAutomatorUtils.waitFindObject(By.text( 209 Pattern.compile("Unblock", Pattern.CASE_INSENSITIVE))).click() 210 eventually { 211 assertFalse(isSensorPrivacyEnabled()) 212 } 213 } 214 215 @Test 216 @AppModeFull(reason = "Uses secondary app, instant apps have no visibility") 217 fun testOpNotRunningWhileSensorPrivacyEnabled() { 218 setSensor(false) 219 val before = System.currentTimeMillis() 220 startTestApp() 221 eventually { 222 assertOpRunning(true) 223 } 224 Thread.sleep(SENSOR_USE_TIME_MS) 225 setSensor(true) 226 eventually { 227 val after = System.currentTimeMillis() 228 assertOpRunning(false) 229 assertLastAccessTimeAndDuration(before, after) 230 } 231 } 232 233 @Test 234 @AppModeFull(reason = "Uses secondary app, instant apps have no visibility") 235 fun testOpStartsRunningAfterStartedWithSensoryPrivacyEnabled() { 236 setSensor(true) 237 startTestApp() 238 UiAutomatorUtils.waitFindObject(By.text( 239 Pattern.compile("Cancel", Pattern.CASE_INSENSITIVE))).click() 240 assertOpRunning(false) 241 setSensor(false) 242 eventually { 243 assertOpRunning(true) 244 } 245 } 246 247 @Test 248 @AppModeFull(reason = "Uses secondary app, instant apps have no visibility") 249 fun testOpGetsRecordedAfterStartedWithSensorPrivacyEnabled() { 250 setSensor(true) 251 startTestApp() 252 UiAutomatorUtils.waitFindObject(By.text( 253 Pattern.compile("Cancel", Pattern.CASE_INSENSITIVE))).click() 254 val before = System.currentTimeMillis() 255 setSensor(false) 256 eventually { 257 assertOpRunning(true) 258 } 259 setSensor(true) 260 eventually { 261 val after = System.currentTimeMillis() 262 assertLastAccessTimeAndDuration(before, after) 263 } 264 } 265 266 @Test 267 @AppModeFull(reason = "Uses secondary app, instant apps have no visibility") 268 fun testOpLastAccessUpdatesAfterToggleSensorPrivacy() { 269 setSensor(false) 270 val before = System.currentTimeMillis() 271 startTestApp() 272 eventually { 273 assertOpRunning(true) 274 } 275 Thread.sleep(SENSOR_USE_TIME_MS) 276 setSensor(true) 277 eventually { 278 val after = System.currentTimeMillis() 279 assertOpRunning(false) 280 assertLastAccessTimeAndDuration(before, after) 281 } 282 283 val before2 = System.currentTimeMillis() 284 setSensor(false) 285 eventually { 286 assertOpRunning(true) 287 } 288 Thread.sleep(SENSOR_USE_TIME_MS) 289 setSensor(true) 290 eventually { 291 val after = System.currentTimeMillis() 292 assertOpRunning(false) 293 assertLastAccessTimeAndDuration(before2, after) 294 } 295 } 296 297 @Test 298 @AppModeFull(reason = "Uses secondary app, instant apps have no visibility") 299 fun testOpFinishedWhileToggleOn() { 300 setSensor(false) 301 startTestApp() 302 eventually { 303 assertOpRunning(true) 304 } 305 setSensor(true) 306 Thread.sleep(5000) 307 eventually { 308 assertOpRunning(false) 309 } 310 finishTestApp() 311 Thread.sleep(1000) 312 setSensor(false) 313 Thread.sleep(1000) 314 assertOpRunning(false) 315 } 316 317 private fun startTestApp() { 318 val intent = Intent(MIC_CAM_ACTIVITY_ACTION) 319 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 320 .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) 321 for (extra in extras) { 322 intent.putExtra(extra, true) 323 } 324 context.startActivity(intent) 325 // Wait for app to open 326 UiAutomatorUtils.waitFindObject(By.textContains(ACTIVITY_TITLE_SNIP)) 327 } 328 329 private fun finishTestApp() { 330 // instant apps can't broadcast to other instant apps; use the shell 331 runShellCommandOrThrow("am broadcast" + 332 " --user ${context.userId}" + 333 " -a $FINISH_MIC_CAM_ACTIVITY_ACTION" + 334 " -f ${Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS}") 335 } 336 337 protected fun setSensor(enable: Boolean) { 338 runWithShellPermissionIdentity { 339 spm.setSensorPrivacy(OTHER, sensor, enable) 340 } 341 } 342 343 private fun isSensorPrivacyEnabled(): Boolean { 344 return callWithShellPermissionIdentity { 345 spm.isSensorPrivacyEnabled(sensor) 346 } 347 } 348 349 private fun getOpForSensor(sensor: Int): String? { 350 return when (sensor) { 351 CAMERA -> AppOpsManager.OPSTR_CAMERA 352 MICROPHONE -> AppOpsManager.OPSTR_RECORD_AUDIO 353 else -> null 354 } 355 } 356 357 private fun getOpForPackage(): AppOpsManager.PackageOps { 358 return callWithShellPermissionIdentity { 359 val uid = try { 360 packageManager.getPackageUid(PKG_NAME, 0) 361 } catch (e: PackageManager.NameNotFoundException) { 362 // fail test 363 assertNull(e) 364 -1 365 } 366 val appOpsManager: AppOpsManager = 367 context.getSystemService(AppOpsManager::class.java)!! 368 val pkgOps = appOpsManager.getOpsForPackage(uid, PKG_NAME, op) 369 assertFalse("expected non empty app op list", pkgOps.isEmpty()) 370 pkgOps[0] 371 } 372 } 373 374 private fun assertOpRunning(isRunning: Boolean) { 375 val pkgOp = getOpForPackage() 376 for (op in pkgOp.ops) { 377 for ((_, attrOp) in op.attributedOpEntries) { 378 assertEquals("Unexpected op running state", isRunning, attrOp.isRunning) 379 } 380 } 381 } 382 383 private fun assertLastAccessTimeAndDuration(before: Long, after: Long) { 384 val pkgOp = getOpForPackage() 385 for (op in pkgOp.ops) { 386 for ((_, attrOp) in op.attributedOpEntries) { 387 val lastAccess = attrOp.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED) 388 val lastDuration = attrOp.getLastDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED) 389 assertTrue("lastAccess was $lastAccess, not between $before and $after", 390 lastAccess in before..after) 391 assertTrue("lastAccess had duration $lastDuration, greater than ${after - before}", 392 lastDuration <= (after - before)) 393 } 394 } 395 } 396 397 fun runWhileLocked(r: () -> Unit) { 398 val km = context.getSystemService(KeyguardManager::class.java)!! 399 val pm = context.getSystemService(PowerManager::class.java)!! 400 val password = byteArrayOf(1, 2, 3, 4) 401 try { 402 runWithShellPermissionIdentity { 403 km!!.setLock(KeyguardManager.PIN, password, KeyguardManager.PIN, null) 404 } 405 eventually { 406 uiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP) 407 assertFalse("Device never slept.", pm.isInteractive) 408 } 409 eventually { 410 uiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP) 411 assertTrue("Device never woke up.", pm.isInteractive) 412 } 413 eventually { 414 assertTrue("Device isn't locked", km.isDeviceLocked) 415 } 416 417 r.invoke() 418 } finally { 419 runWithShellPermissionIdentity { 420 km!!.setLock(KeyguardManager.PIN, null, KeyguardManager.PIN, password) 421 } 422 423 // Recycle the screen power in case the keyguard is stuck open 424 eventually { 425 uiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP) 426 assertFalse("Device never slept.", pm.isInteractive) 427 } 428 eventually { 429 uiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP) 430 assertTrue("Device never woke up.", pm.isInteractive) 431 } 432 433 eventually { 434 assertFalse("Device isn't unlocked", km.isDeviceLocked) 435 } 436 } 437 } 438 } 439