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.AppOpsManager
20 import android.app.KeyguardManager
21 import android.content.Context
22 import android.content.Intent
23 import android.content.pm.PackageManager
24 import android.content.res.Resources.NotFoundException
25 import android.hardware.SensorPrivacyManager
26 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener
27 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams
28 import android.hardware.SensorPrivacyManager.Sensors.CAMERA
29 import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
30 import android.hardware.SensorPrivacyManager.Sources.OTHER
31 import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE
32 import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE
33 import android.hardware.camera2.CameraCharacteristics
34 import android.hardware.camera2.CameraManager
35 import android.hardware.camera2.CameraMetadata
36 import android.os.PowerManager
37 import android.platform.test.annotations.AppModeFull
38 import android.platform.test.annotations.AsbSecurityTest
39 import android.support.test.uiautomator.By
40 import android.util.Log
41 import android.view.KeyEvent
42 import androidx.test.platform.app.InstrumentationRegistry
43 import androidx.test.uiautomator.UiDevice
44 import androidx.test.uiautomator.Until
45 import com.android.compatibility.common.util.SystemUtil
46 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
47 import com.android.compatibility.common.util.SystemUtil.eventually
48 import com.android.compatibility.common.util.SystemUtil.getEventually
49 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
50 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
51 import com.android.compatibility.common.util.UiAutomatorUtils
52 import java.nio.charset.StandardCharsets
53 import java.util.concurrent.CountDownLatch
54 import java.util.concurrent.Executors
55 import java.util.concurrent.TimeUnit
56 import java.util.regex.Pattern
57 import org.junit.After
58 import org.junit.Assert.assertEquals
59 import org.junit.Assert.assertFalse
60 import org.junit.Assert.assertNotNull
61 import org.junit.Assert.assertNull
62 import org.junit.Assert.assertTrue
63 import org.junit.Assume.assumeFalse
64 import org.junit.Assume.assumeTrue
65 import org.junit.Before
66 import org.junit.Test
67 
68 abstract class SensorPrivacyBaseTest(
69     val sensor: Int,
70     vararg val extras: String
71 ) {
72 
73     companion object {
74         val TAG = this::class.simpleName
75         const val MIC_CAM_ACTIVITY_ACTION =
76                 "android.sensorprivacy.cts.usemiccamera.action.USE_MIC_CAM"
77         const val MIC_CAM_OVERLAY_ACTIVITY_ACTION =
78                 "android.sensorprivacy.cts.usemiccamera.overlay.action.USE_MIC_CAM"
79         const val SHOW_OVERLAY_ACTION =
80                 "android.sensorprivacy.cts.usemiccamera.action.SHOW_OVERLAY_ACTION"
81         const val FINISH_MIC_CAM_ACTIVITY_ACTION =
82                 "android.sensorprivacy.cts.usemiccamera.action.FINISH_USE_MIC_CAM"
83         const val USE_MIC_EXTRA =
84                 "android.sensorprivacy.cts.usemiccamera.extra.USE_MICROPHONE"
85         const val USE_CAM_EXTRA =
86                 "android.sensorprivacy.cts.usemiccamera.extra.USE_CAMERA"
87         const val DELAYED_ACTIVITY_EXTRA =
88                 "android.sensorprivacy.cts.usemiccamera.extra.DELAYED_ACTIVITY"
89         const val DELAYED_ACTIVITY_NEW_TASK_EXTRA =
90                 "android.sensorprivacy.cts.usemiccamera.extra.DELAYED_ACTIVITY_NEW_TASK"
91         const val RETRY_CAM_EXTRA =
92                 "android.sensorprivacy.cts.usemiccamera.extra.RETRY_CAM_EXTRA"
93         const val PKG_NAME = "android.sensorprivacy.cts.usemiccamera"
94         const val RECORDING_FILE_NAME = "${PKG_NAME}_record.mp4"
95         const val ACTIVITY_TITLE_SNIP = "CtsUseMic"
96         const val SENSOR_USE_TIME_MS = 5L
97         const val NEW_WINDOW_TIMEOUT_MILLIS = 5_000L
98     }
99 
100     protected val instrumentation = InstrumentationRegistry.getInstrumentation()!!
101     protected val uiAutomation = instrumentation.uiAutomation!!
102     protected val uiDevice = UiDevice.getInstance(instrumentation)!!
103     protected val context = instrumentation.targetContext!!
104     protected val spm = context.getSystemService(SensorPrivacyManager::class.java)!!
105     protected val packageManager = context.packageManager!!
106     protected val op = getOpForSensor(sensor)
107 
108     var oldState: Boolean = false
109 
110     @Before
111     open fun init() {
112         oldState = isSensorPrivacyEnabled()
113         setSensor(false)
114         uiDevice.wakeUp()
115         runShellCommandOrThrow("wm dismiss-keyguard")
116         uiDevice.waitForIdle()
117         SystemUtil.waitForBroadcastDispatch(FINISH_MIC_CAM_ACTIVITY_ACTION)
118     }
119 
120     @After
121     open fun tearDown() {
122         finishTestApp()
123         Thread.sleep(3000)
124         setSensor(oldState)
125     }
126 
127     @Test
128     fun testSetSensor() {
129         assumeSensorToggleSupport()
130         setSensor(true)
131         assertTrue(isSensorPrivacyEnabled())
132 
133         setSensor(false)
134         assertFalse(isSensorPrivacyEnabled())
135     }
136 
137     @Test
138     fun testSensorPrivacy_softwareToggle() {
139         assumeSensorToggleSupport()
140         setSensor(true)
141         assertTrue(isToggleSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE))
142 
143         setSensor(false)
144         assertFalse(isToggleSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE))
145     }
146 
147     @Test
148     fun testSensorPrivacy_hardwareToggle() {
149         assumeSensorToggleSupport()
150         // Default value should be false weather HW toggles
151         // are supported or not
152         assertFalse(isToggleSensorPrivacyEnabled(TOGGLE_TYPE_HARDWARE))
153     }
154 
155     @Test
156     fun testSensorPrivacy_comboToggle() {
157         assumeSensorToggleSupport()
158         setSensor(sensor, true)
159         assertTrue(isCombinedSensorPrivacyEnabled())
160 
161         setSensor(sensor, false)
162         assertFalse(isCombinedSensorPrivacyEnabled())
163     }
164 
165     @Test
166     fun testDialog() {
167         assumeSensorToggleSupport()
168         testDialog(delayedActivity = false, delayedActivityNewTask = false)
169     }
170 
171     @Test
172     fun testDialog_remainsOnTop() {
173         assumeSensorToggleSupport()
174         testDialog(delayedActivity = true, delayedActivityNewTask = false)
175     }
176 
177     @Test
178     fun testDialog_remainsOnTop_newTask() {
179         assumeSensorToggleSupport()
180         testDialog(delayedActivity = true, delayedActivityNewTask = true)
181     }
182 
183     fun testDialog(delayedActivity: Boolean = false, delayedActivityNewTask: Boolean = false) {
184         try {
185             setSensor(true)
186             val intent = Intent(MIC_CAM_ACTIVITY_ACTION)
187                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
188                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
189                     .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
190             for (extra in extras) {
191                 intent.putExtra(extra, true)
192             }
193             intent.putExtra(DELAYED_ACTIVITY_EXTRA, delayedActivity)
194             intent.putExtra(DELAYED_ACTIVITY_NEW_TASK_EXTRA, delayedActivityNewTask)
195             doAndWaitForWindowTransition {
196                 context.startActivity(intent)
197             }
198             Thread.sleep(3000)
199             unblockSensorWithDialogAndAssert()
200         } finally {
201             broadcastAndWait(FINISH_MIC_CAM_ACTIVITY_ACTION)
202         }
203     }
204 
205     @Test
206     fun testListener() {
207         assumeSensorToggleSupport()
208         val executor = Executors.newSingleThreadExecutor()
209         setSensor(false)
210         val latchEnabled = CountDownLatch(1)
211         var listener =
212                 OnSensorPrivacyChangedListener { _, enabled: Boolean ->
213                     if (enabled) {
214                         latchEnabled.countDown()
215                     }
216                 }
217         runWithShellPermissionIdentity {
218             spm.addSensorPrivacyListener(sensor, executor, listener)
219         }
220         setSensor(true)
221         latchEnabled.await(100, TimeUnit.MILLISECONDS)
222         runWithShellPermissionIdentity {
223             spm.removeSensorPrivacyListener(sensor, listener)
224         }
225 
226         val latchDisabled = CountDownLatch(1)
227         listener = OnSensorPrivacyChangedListener { _, enabled: Boolean ->
228             if (!enabled) {
229                 latchDisabled.countDown()
230             }
231         }
232         runWithShellPermissionIdentity {
233             spm.addSensorPrivacyListener(sensor, executor, listener)
234         }
235         setSensor(false)
236         latchEnabled.await(100, TimeUnit.MILLISECONDS)
237         runWithShellPermissionIdentity {
238             spm.removeSensorPrivacyListener(sensor, listener)
239         }
240     }
241 
242     @Test
243     fun testToggleListener() {
244         assumeSensorToggleSupport()
245         val executor = Executors.newSingleThreadExecutor()
246         setSensor(false)
247         val latchEnabled = CountDownLatch(1)
248         val listenerSensorEnabled = object : OnSensorPrivacyChangedListener {
249             override fun onSensorPrivacyChanged(params: SensorPrivacyChangedParams) {
250                 if (params.isEnabled &&
251                         params.sensor == sensor &&
252                         params.toggleType == TOGGLE_TYPE_SOFTWARE) {
253                     latchEnabled.countDown()
254                 }
255             }
256 
257             override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) {
258             }
259         }
260         runWithShellPermissionIdentity {
261             spm.addSensorPrivacyListener(executor, listenerSensorEnabled)
262         }
263         setSensor(true)
264         latchEnabled.await(100, TimeUnit.MILLISECONDS)
265         runWithShellPermissionIdentity {
266             spm.removeSensorPrivacyListener(listenerSensorEnabled)
267         }
268 
269         val latchDisabled = CountDownLatch(1)
270         val listenerSensorDisabled = object : OnSensorPrivacyChangedListener {
271             override fun onSensorPrivacyChanged(params: SensorPrivacyChangedParams) {
272                 if (!params.isEnabled &&
273                         params.sensor == sensor &&
274                         params.toggleType == TOGGLE_TYPE_SOFTWARE) {
275                     latchDisabled.countDown()
276                 }
277             }
278 
279             override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) {
280             }
281         }
282         runWithShellPermissionIdentity {
283             spm.addSensorPrivacyListener(executor, listenerSensorDisabled)
284         }
285         setSensor(false)
286         latchEnabled.await(100, TimeUnit.MILLISECONDS)
287         runWithShellPermissionIdentity {
288             spm.removeSensorPrivacyListener(listenerSensorDisabled)
289         }
290     }
291 
292     @Test
293     fun testToggleListener_defaultExecutor() {
294         assumeSensorToggleSupport()
295         setSensor(false)
296         val latchEnabled = CountDownLatch(1)
297         var listenerSensorEnabled = object : OnSensorPrivacyChangedListener {
298             override fun onSensorPrivacyChanged(params: SensorPrivacyChangedParams) {
299                 if (params.isEnabled && params.sensor == sensor) {
300                     latchEnabled.countDown()
301                 }
302             }
303 
304             override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) {
305             }
306         }
307         runWithShellPermissionIdentity {
308             spm.addSensorPrivacyListener(listenerSensorEnabled)
309         }
310         setSensor(true)
311         latchEnabled.await(100, TimeUnit.MILLISECONDS)
312         runWithShellPermissionIdentity {
313             spm.removeSensorPrivacyListener(listenerSensorEnabled)
314         }
315 
316         val latchDisabled = CountDownLatch(1)
317         val listenerSensorDisabled = object : OnSensorPrivacyChangedListener {
318             override fun onSensorPrivacyChanged(params: SensorPrivacyChangedParams) {
319                 if (!params.isEnabled && params.sensor == sensor) {
320                     latchDisabled.countDown()
321                 }
322             }
323 
324             override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) {
325             }
326         }
327         runWithShellPermissionIdentity {
328             spm.addSensorPrivacyListener(listenerSensorDisabled)
329         }
330         setSensor(false)
331         latchEnabled.await(100, TimeUnit.MILLISECONDS)
332         runWithShellPermissionIdentity {
333             spm.removeSensorPrivacyListener(listenerSensorDisabled)
334         }
335     }
336 
337     @Test
338     @AppModeFull(reason = "Instant apps can't manage keyguard")
339     fun testCantChangeWhenLocked() {
340         assumeSensorToggleSupport()
341         assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN))
342 
343 //      TODO use actual test api when it can be added
344 //      assumeTrue(callWithShellPermissionIdentity { spm.requiresAuthentication() })
345         val packageContext: Context = context.createPackageContext("android", 0)
346         try {
347             assumeTrue(
348                 packageContext.resources.getBoolean(
349                     packageContext.resources
350                     .getIdentifier("config_sensorPrivacyRequiresAuthentication", "bool", "android")
351                 )
352             )
353         } catch (e: NotFoundException) {
354         // Since by default we want authentication to be required we
355         // continue the test if the OEM has removed this resource.
356         }
357 
358         setSensor(false)
359         assertFalse(isSensorPrivacyEnabled())
360         runWhileLocked {
361             setSensor(true)
362             assertFalse(
363                 "State was changed while device is locked",
364                     isSensorPrivacyEnabled()
365             )
366         }
367 
368         setSensor(true)
369         assertTrue(isSensorPrivacyEnabled())
370         runWhileLocked {
371             setSensor(false)
372             assertTrue(
373                 "State was changed while device is locked",
374                     isSensorPrivacyEnabled()
375             )
376         }
377     }
378 
379     fun unblockSensorWithDialogAndAssert() {
380         val buttonResId = getDialogPositiveButtonId()
381         UiAutomatorUtils.waitFindObject(By.res(buttonResId)).click()
382         eventually {
383             assertFalse(isSensorPrivacyEnabled())
384         }
385     }
386 
387     private fun getDialogPositiveButtonId() =
388             if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
389                 "com.android.systemui:id/bottom_sheet_positive_button"
390             } else {
391                 "android:id/button1"
392             }
393 
394     @Test
395     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
396     fun testOpNotRunningWhileSensorPrivacyEnabled() {
397         assumeSensorToggleSupport()
398         setSensor(false)
399         val before = System.currentTimeMillis()
400         startTestApp()
401         eventually {
402             assertOpRunning(true)
403         }
404         Thread.sleep(SENSOR_USE_TIME_MS)
405         setSensor(true)
406         eventually {
407             val after = System.currentTimeMillis()
408             assertOpRunning(false)
409             assertLastAccessTimeAndDuration(before, after)
410         }
411     }
412 
413     @Test
414     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
415     fun testOpStartsRunningAfterStartedWithSensoryPrivacyEnabled() {
416         assumeSensorToggleSupport()
417         setSensor(true)
418         // Retry camera connection because external cameras are disconnected
419         // if sensor privacy is enabled (b/182204067)
420         startTestApp(true)
421         UiAutomatorUtils.waitFindObject(By.text(
422                 Pattern.compile("Cancel", Pattern.CASE_INSENSITIVE)
423         )).click()
424         assertOpRunning(false)
425         setSensor(false)
426         eventually {
427             assertOpRunning(true)
428         }
429     }
430 
431     @Test
432     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
433     fun testOpGetsRecordedAfterStartedWithSensorPrivacyEnabled() {
434         assumeSensorToggleSupport()
435         if (sensor == CAMERA) {
436             assumeTrue(supportsCameraMute())
437         }
438         setSensor(true)
439         // Retry camera connection because external cameras are disconnected
440         // if sensor privacy is enabled (b/182204067)
441         startTestApp(true)
442         UiAutomatorUtils.waitFindObject(By.text(
443                 Pattern.compile("Cancel", Pattern.CASE_INSENSITIVE)
444         )).click()
445         val before = System.currentTimeMillis()
446         setSensor(false)
447         eventually {
448             assertOpRunning(true)
449         }
450         setSensor(true)
451         eventually {
452             val after = System.currentTimeMillis()
453             assertLastAccessTimeAndDuration(before, after)
454         }
455     }
456 
457     @Test
458     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
459     fun testOpLastAccessUpdatesAfterToggleSensorPrivacy() {
460         assumeSensorToggleSupport()
461         setSensor(false)
462         val before = System.currentTimeMillis()
463         startTestApp()
464         eventually {
465             assertOpRunning(true)
466         }
467         Thread.sleep(SENSOR_USE_TIME_MS)
468         setSensor(true)
469         eventually {
470             val after = System.currentTimeMillis()
471             assertOpRunning(false)
472             assertLastAccessTimeAndDuration(before, after)
473         }
474 
475         val before2 = System.currentTimeMillis()
476         setSensor(false)
477         eventually {
478             assertOpRunning(true)
479         }
480         Thread.sleep(SENSOR_USE_TIME_MS)
481         setSensor(true)
482         eventually {
483             val after = System.currentTimeMillis()
484             assertOpRunning(false)
485             assertLastAccessTimeAndDuration(before2, after)
486         }
487     }
488 
489     @Test
490     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
491     fun testOpFinishedWhileToggleOn() {
492         assumeSensorToggleSupport()
493         setSensor(false)
494         startTestApp()
495         eventually {
496             assertOpRunning(true)
497         }
498         setSensor(true)
499         Thread.sleep(5000)
500         eventually {
501             assertOpRunning(false)
502         }
503         finishTestApp()
504         Thread.sleep(1000)
505         setSensor(false)
506         Thread.sleep(1000)
507         assertOpRunning(false)
508     }
509 
510     @Test
511     @AsbSecurityTest(cveBugId = [199550934])
512     fun testTapjacking() {
513         assumeSensorToggleSupport()
514         setSensor(true)
515         startTestOverlayApp(false)
516         assertNotNull(
517             "Dialog never showed",
518                 UiAutomatorUtils.waitFindObject(By.res(getDialogPositiveButtonId()))
519         )
520         val view = UiAutomatorUtils.waitFindObjectOrNull(By.text("This Should Be Hidden"), 10_000)
521         assertNull("Overlay should not have shown.", view)
522     }
523 
524     @Test
525     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
526     fun testCantEnablePrivacyIfNotSupported() {
527         assumeFalse(spm.supportsSensorToggle(sensor))
528         assumeFalse(spm.supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor))
529         setSensor(true)
530         assertFalse(isSensorPrivacyEnabled())
531     }
532 
533     protected fun assumeSensorToggleSupport() {
534         assumeTrue(spm.supportsSensorToggle(sensor))
535         assumeTrue(spm.supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor))
536     }
537 
538     private fun startTestApp() {
539         startTestApp(false)
540     }
541 
542     private fun startTestApp(retryCameraOnError: Boolean) {
543         val intent = Intent(MIC_CAM_ACTIVITY_ACTION)
544                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
545                 .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
546         for (extra in extras) {
547             intent.putExtra(extra, true)
548         }
549         intent.putExtra(RETRY_CAM_EXTRA, retryCameraOnError)
550         context.startActivity(intent)
551         // Wait for app to open
552         if (!isWear()) {
553             UiAutomatorUtils.waitFindObject(By.textContains(ACTIVITY_TITLE_SNIP))
554         }
555     }
556 
557     private fun startTestOverlayApp(retryCameraOnError: Boolean) {
558         val intent = Intent(MIC_CAM_OVERLAY_ACTIVITY_ACTION)
559                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
560                 .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
561         for (extra in extras) {
562             intent.putExtra(extra, true)
563         }
564         intent.putExtra(RETRY_CAM_EXTRA, retryCameraOnError)
565         context.startActivity(intent)
566         // Wait for app to open
567         if (!isWear()) {
568             eventually {
569                 UiAutomatorUtils.waitFindObject(By.textContains(ACTIVITY_TITLE_SNIP))
570             }
571         }
572 
573         context.sendBroadcast(Intent(SHOW_OVERLAY_ACTION))
574     }
575 
576     private fun finishTestApp() {
577         // instant apps can't broadcast to other instant apps; use the shell
578         broadcastAndWait(FINISH_MIC_CAM_ACTIVITY_ACTION)
579     }
580 
581     private fun broadcastAndWait(action: String) {
582         Log.i(TAG, "Broadcasting action '$action'")
583         runShellCommandOrThrow(
584             "am broadcast" +
585                 " --user ${context.userId}" +
586                 " -a $action" +
587                 " -f ${Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS}"
588         )
589         SystemUtil.waitForBroadcastDispatch(FINISH_MIC_CAM_ACTIVITY_ACTION)
590         Log.i(TAG, "Finished broadcasting action '$action'")
591     }
592 
593     protected fun setSensor(enable: Boolean) {
594         runWithShellPermissionIdentity {
595             spm.setSensorPrivacy(OTHER, sensor, enable)
596         }
597     }
598 
599     protected fun setSensor(sensor: Int, enable: Boolean) {
600         runWithShellPermissionIdentity {
601             spm.setSensorPrivacy(sensor, enable)
602         }
603     }
604 
605     private fun isSensorPrivacyEnabled(): Boolean {
606         return callWithShellPermissionIdentity {
607             spm.isSensorPrivacyEnabled(sensor)
608         }
609     }
610 
611     private fun isToggleSensorPrivacyEnabled(toggleType: Int): Boolean {
612         return callWithShellPermissionIdentity {
613             spm.isSensorPrivacyEnabled(toggleType, sensor)
614         }
615     }
616 
617     private fun isCombinedSensorPrivacyEnabled(): Boolean {
618         return callWithShellPermissionIdentity {
619             spm.areAnySensorPrivacyTogglesEnabled(sensor)
620         }
621     }
622 
623     private fun supportsCameraMute(): Boolean {
624         val cameraManager = context.getSystemService(CameraManager::class.java)!!
625         val cameraIdList = cameraManager.cameraIdList
626         assumeFalse(cameraIdList.isEmpty())
627 
628         val cameraId = cameraManager.cameraIdList[0]
629         val availableTestPatternModes = cameraManager.getCameraCharacteristics(cameraId)
630                 .get(CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES) ?: return false
631         for (mode in availableTestPatternModes) {
632             if ((mode == CameraMetadata.SENSOR_TEST_PATTERN_MODE_SOLID_COLOR) ||
633                     (mode == CameraMetadata.SENSOR_TEST_PATTERN_MODE_BLACK)) {
634                 return true
635             }
636         }
637         return false
638     }
639 
640     private fun getOpForSensor(sensor: Int): String? {
641         return when (sensor) {
642             CAMERA -> AppOpsManager.OPSTR_CAMERA
643             MICROPHONE -> AppOpsManager.OPSTR_RECORD_AUDIO
644             else -> null
645         }
646     }
647 
648     private fun getOpForPackage(): AppOpsManager.PackageOps {
649         return callWithShellPermissionIdentity {
650             val uid = try {
651                 packageManager.getPackageUid(PKG_NAME, 0)
652             } catch (e: PackageManager.NameNotFoundException) {
653                 // fail test
654                 assertNull(e)
655                 -1
656             }
657             val appOpsManager: AppOpsManager =
658                     context.getSystemService(AppOpsManager::class.java)!!
659             val pkgOps = appOpsManager.getOpsForPackage(uid, PKG_NAME, op)
660             assertFalse("expected non empty app op list", pkgOps.isEmpty())
661             pkgOps[0]
662         }
663     }
664 
665     private fun assertOpRunning(isRunning: Boolean) {
666         val pkgOp = getOpForPackage()
667         for (op in pkgOp.ops) {
668             for ((_, attrOp) in op.attributedOpEntries) {
669                 assertEquals("Unexpected op running state", isRunning, attrOp.isRunning)
670             }
671         }
672     }
673 
674     private fun isWear(): Boolean = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
675 
676     private fun assertLastAccessTimeAndDuration(before: Long, after: Long) {
677         val pkgOp = getOpForPackage()
678         for (op in pkgOp.ops) {
679             for ((_, attrOp) in op.attributedOpEntries) {
680                 val lastAccess = attrOp.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
681                 val lastDuration = attrOp.getLastDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
682                 assertTrue(
683                     "lastAccess was $lastAccess, not between $before and $after",
684                         lastAccess in before..after
685                 )
686                 assertTrue(
687                     "lastAccess had duration $lastDuration, greater than ${after - before}",
688                 lastDuration <= (after - before)
689                 )
690             }
691         }
692     }
693 
694     fun runWhileLocked(r: () -> Unit) {
695         val km = context.getSystemService(KeyguardManager::class.java)!!
696         val pm = context.getSystemService(PowerManager::class.java)!!
697         val pin = "1234".toByteArray(StandardCharsets.UTF_8)
698         try {
699             runWithShellPermissionIdentity {
700                 assumeTrue(
701                     "Could not set lock.",
702                         km.setLock(KeyguardManager.PIN, pin, KeyguardManager.PIN, null)
703                 )
704             }
705             getEventually {
706                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP)
707                 assumeFalse("Device never slept.", pm.isInteractive)
708             }
709             getEventually {
710                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP)
711                 assumeTrue("Device never woke up.", pm.isInteractive)
712             }
713             getEventually {
714                 assumeTrue("Device isn't locked", km.isDeviceLocked)
715             }
716 
717             r.invoke()
718         } finally {
719             runWithShellPermissionIdentity {
720                 assumeTrue(
721                     "Could not remove lock.",
722                         km.setLock(KeyguardManager.PIN, null, KeyguardManager.PIN, pin)
723                 )
724             }
725 
726             // Recycle the screen power in case the keyguard is stuck open
727             getEventually {
728                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP)
729                 assumeFalse("Device never slept.", pm.isInteractive)
730             }
731             getEventually {
732                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP)
733                 assumeTrue("Device never woke up.", pm.isInteractive)
734             }
735 
736             getEventually {
737                 assumeFalse("Device isn't unlocked", km.isDeviceLocked)
738             }
739         }
740     }
741 
742     /**
743      * Perform the requested action, then wait both for the action to complete, and for at least
744      * one window transition to occur since the moment the action begins executing.
745      */
746     private inline fun doAndWaitForWindowTransition(
747             crossinline block: () -> Unit
748     ) {
749         val timeoutOccurred = !uiDevice.performActionAndWait({
750             block()
751         }, Until.newWindow(), NEW_WINDOW_TIMEOUT_MILLIS)
752 
753         if (timeoutOccurred) {
754             throw RuntimeException("Timed out waiting for window transition.")
755         }
756     }
757 }
758