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