• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.security.cts
18 
19 import android.Manifest
20 import android.app.Activity
21 import android.app.ActivityManager
22 import android.app.AppOpsManager
23 import android.app.Instrumentation
24 import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT
25 import android.content.AttributionSourceState
26 import android.content.BroadcastReceiver
27 import android.content.ComponentName
28 import android.content.Context
29 import android.content.Intent
30 import android.content.IntentFilter
31 import android.hardware.Camera
32 import android.hardware.ICameraClient
33 import android.hardware.ICameraService
34 import android.hardware.SensorPrivacyManager
35 import android.hardware.camera2.CameraCharacteristics
36 import android.hardware.camera2.CameraDevice
37 import android.hardware.camera2.CameraManager
38 import android.hardware.camera2.CameraMetadata
39 import android.hardware.camera2.CameraMetadataInfo
40 import android.hardware.camera2.ICameraDeviceCallbacks
41 import android.hardware.camera2.impl.CameraMetadataNative
42 import android.hardware.camera2.impl.CaptureResultExtras
43 import android.hardware.camera2.impl.PhysicalCaptureResultInfo
44 import android.os.Binder
45 import android.os.IBinder
46 import android.os.Process
47 import android.os.ServiceManager
48 import android.os.ServiceSpecificException
49 import android.platform.test.annotations.RequiresFlagsDisabled
50 import android.platform.test.annotations.RequiresFlagsEnabled
51 import android.platform.test.flag.junit.DeviceFlagsValueProvider
52 import android.provider.Settings
53 import android.security.cts.camera.open.lib.ICameraOpener
54 import android.security.cts.camera.open.lib.IntentKeys
55 import android.util.Log
56 import androidx.test.core.app.ActivityScenario
57 import androidx.test.filters.LargeTest
58 import androidx.test.platform.app.InstrumentationRegistry
59 import androidx.test.runner.AndroidJUnit4
60 import com.android.bedstead.nene.TestApis
61 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
62 import com.android.cts.install.lib.Install
63 import com.android.cts.install.lib.TestApp
64 import com.android.cts.install.lib.Uninstall
65 import com.android.internal.camera.flags.Flags
66 import java.util.concurrent.CompletableFuture
67 import java.util.concurrent.TimeUnit
68 import java.util.concurrent.TimeoutException
69 import org.junit.After
70 import org.junit.AfterClass
71 import org.junit.Assert.assertEquals
72 import org.junit.Assert.assertNotNull
73 import org.junit.Assert.assertTrue
74 import org.junit.Assume.assumeFalse
75 import org.junit.Assume.assumeTrue
76 import org.junit.Before
77 import org.junit.BeforeClass
78 import org.junit.Rule
79 import org.junit.Test
80 import org.junit.runner.RunWith
81 
82 /** Tests that cameraserver checks for permissions correctly. */
83 @LargeTest
84 @RunWith(AndroidJUnit4::class)
85 class CameraPermissionTest {
86   @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
87 
88   private val onTearDown = mutableListOf<() -> Unit>()
89 
90   class DummyCameraDeviceCallbacks : ICameraDeviceCallbacks.Stub() {
91     override fun onDeviceError(errorCode: Int, resultExtras: CaptureResultExtras) {
92       Log.i(TAG, "onDeviceError($errorCode)")
93     }
94 
95     override fun onCaptureStarted(resultExtras: CaptureResultExtras, timestamp: Long) {
96       Log.i(TAG, "onCaptureStated($timestamp)")
97     }
98 
99     override fun onResultReceived(
100         result: CameraMetadataInfo,
101         resultExtras: CaptureResultExtras,
102         physicalResults: Array<PhysicalCaptureResultInfo>
103     ) {
104       Log.i(TAG, "onResultReceived()")
105     }
106 
107     override fun onDeviceIdle() {
108       Log.i(TAG, "onDeviceIdle()")
109     }
110 
111     override fun onPrepared(streamId: Int) {
112       Log.i(TAG, "onPrepared()")
113     }
114 
115     override fun onRequestQueueEmpty() {
116       Log.i(TAG, "onRequestQueueEmpty()")
117     }
118 
119     override fun onRepeatingRequestError(lastFrameNumber: Long, repeatingRequestId: Int) {
120       Log.i(TAG, "onRepeatingRequestError($lastFrameNumber, $repeatingRequestId)")
121     }
122 
123     override fun onClientSharedAccessPriorityChanged(primaryClient: Boolean) {
124       Log.i(TAG, "onClientSharedAccessPriorityChanged($primaryClient)")
125     }
126   }
127 
128   abstract class DummyBase : Binder(), android.os.IInterface {
129     override fun asBinder(): IBinder {
130       return this
131     }
132   }
133 
134   class DummyCameraClient : DummyBase(), ICameraClient
135 
136   private lateinit var broadcastReceiver: BroadcastReceiver
137   private lateinit var cameraOpener: ICameraOpener
138   private lateinit var appOpsManager: AppOpsManager
139   private var oldAppOpsSettings: String? = null
140   private var shouldRestoreAppOpsSettings: Boolean = false
141   private val onResumeFuture = CompletableFuture<Intent>()
142   private val streamOpenedFuture = CompletableFuture<Intent>()
143   private var openCameraResultFuture: CompletableFuture<Instrumentation.ActivityResult>? = null
144   private var restoreSensorPrivacy: (() -> Unit)? = null
145 
146   private lateinit var cameraManager: CameraManager
147 
148   private val context: Context
149     get() = instrumentation.context
150 
151   @Before
152   fun setUp() {
153     TestApis.packages()
154         .find(OPEN_CAMERA_APP.packageName)
155         .grantPermission(Manifest.permission.CAMERA)
156 
157     TestApis.packages()
158         .find(CAMERA_PROXY_APP.packageName)
159         .grantPermission(Manifest.permission.CAMERA)
160 
161     cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
162     assumeTrue(cameraManager.getCameraIdList().size > 0)
163 
164     appOpsManager = context.getSystemService(AppOpsManager::class.java)
165 
166     runWithShellPermissionIdentity {
167       oldAppOpsSettings =
168           Settings.Global.getString(context.contentResolver, Settings.Global.APP_OPS_CONSTANTS)
169       Settings.Global.putString(
170           context.contentResolver,
171           Settings.Global.APP_OPS_CONSTANTS,
172           "top_state_settle_time=0,fg_service_state_settle_time=0,bg_state_settle_time=0")
173       shouldRestoreAppOpsSettings = true
174       appOpsManager.clearHistory()
175       appOpsManager.resetHistoryParameters()
176     }
177 
178     broadcastReceiver =
179         object : BroadcastReceiver() {
180           override fun onReceive(context: Context, intent: Intent) {
181             Log.i(TAG, "onReceive")
182             when (intent.action) {
183               OPEN_CAMERA_APP.keys.onResume -> {
184                 onResumeFuture.complete(intent)
185               }
186 
187               OPEN_CAMERA_APP.keys.streamOpened -> {
188                 streamOpenedFuture.complete(intent)
189               }
190             }
191           }
192         }
193 
194     val filter =
195         IntentFilter().apply {
196           addAction(OPEN_CAMERA_APP.keys.onResume)
197           addAction(OPEN_CAMERA_APP.keys.streamOpened)
198         }
199     context.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_EXPORTED)
200   }
201 
202   @After
203   fun tearDown() {
204     finishActivity()
205 
206     if (this::broadcastReceiver.isInitialized) {
207       context.unregisterReceiver(broadcastReceiver)
208     }
209 
210     if (shouldRestoreAppOpsSettings) {
211       runWithShellPermissionIdentity {
212         // restore old AppOps settings.
213         Settings.Global.putString(
214             context.contentResolver, Settings.Global.APP_OPS_CONSTANTS, oldAppOpsSettings)
215         appOpsManager.clearHistory()
216         appOpsManager.resetHistoryParameters()
217       }
218     }
219 
220     for (callback in onTearDown) {
221       callback()
222     }
223 
224     restoreSensorPrivacy?.let { it() }
225   }
226 
227   @Test
228   @RequiresFlagsDisabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
229   fun testConnectDevice_dataDeliveryPermissionChecks_off() {
230     testConnectDevice(expectDenial = true)
231   }
232 
233   @Test
234   @RequiresFlagsEnabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
235   fun testConnectDevice_dataDeliveryPermissionChecks_on() {
236     testConnectDevice(expectDenial = false)
237   }
238 
239   @Test
240   @RequiresFlagsDisabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
241   fun testConnect_dataDeliveryPermissionChecks_off() {
242     testConnect(expectDenial = true)
243   }
244 
245   @Test
246   @RequiresFlagsEnabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
247   fun testConnect_dataDeliveryPermissionChecks_on() {
248     testConnect(expectDenial = false)
249   }
250 
251   @Test fun testAppConnectDevice() = testAppOpenCamera(Api.API_2)
252 
253   @Test fun testAppConnect() = testAppOpenCamera(Api.API_1)
254 
255   @Test fun testAppNdkConnect() = testAppOpenCamera(Api.NDK)
256 
257   private fun testAppOpenCamera(api: Api) {
258     openCameraResultFuture = startOpenCameraActivityForOpenCamera(api)
259     checkAppOpenedCamera(cameraOpenedKey(api))
260   }
261 
262   @Test fun testAppConnectDevice_noPermission() = testAppOpenCamera_noPermission(Api.API_2)
263 
264   @Test fun testAppConnect_noPermission() = testAppOpenCamera_noPermission(Api.API_1)
265 
266   private fun testAppOpenCamera_noPermission(api: Api) {
267     denyAppPermission(OPEN_CAMERA_APP)
268     openCameraResultFuture = startOpenCameraActivityForOpenCamera(api)
269     checkAppFailedToOpenCamera(cameraOpenedKey(api))
270   }
271 
272   @Test
273   fun testAppNdkConnect_noPermission() {
274     denyAppPermission(OPEN_CAMERA_APP)
275     openCameraResultFuture = startOpenCameraActivityForOpenCamera(Api.NDK)
276     checkAppFailedToOpenCameraNdk()
277   }
278 
279   @Test fun testAppStreaming2() = testAppStreaming(Api.API_2)
280 
281   @Test fun testAppStreaming1() = testAppStreaming(Api.API_1)
282 
283   @Test fun testAppStreamingNdk() = testAppStreaming(Api.NDK)
284 
285   private fun testAppStreaming(api: Api, shouldRestoreSensorPrivacy: Boolean = false) {
286     openCameraResultFuture = startOpenCameraActivityForOpenCamera(api, shouldStream = true)
287     assertStreamOpened()
288     if (shouldRestoreSensorPrivacy) {
289       restoreSensorPrivacy!!()
290     }
291     checkAppOpenedCamera(cameraOpenedKey(api), expectStreamOpened = true)
292   }
293 
294   @Test
295   fun testAppStreaming2_softDenial_cameraMute() = testAppStreaming_softDenial_cameraMute(Api.API_2)
296 
297   @Test
298   fun testAppStreaming1_softDenial_cameraMute() = testAppStreaming_softDenial_cameraMute(Api.API_1)
299 
300   @Test
301   fun testAppStreamingNdk_softDenial_cameraMute() = testAppStreaming_softDenial_cameraMute(Api.NDK)
302 
303   private fun testAppStreaming_softDenial_cameraMute(api: Api) {
304     assumeTrue(supportsCameraMute())
305     setSensorPrivacy(enabled = true)
306     testAppStreaming(api, shouldRestoreSensorPrivacy = true)
307   }
308 
309   @Test
310   fun testAppStreaming2_opChanged_softDenial_block() =
311       testAppStreaming_opChanged_softDenial_block(Api.API_2)
312 
313   @Test
314   fun testAppStreaming1_opChanged_softDenial_block() =
315       testAppStreaming_opChanged_softDenial_block(Api.API_1)
316 
317   @Test
318   fun testAppStreamingNdk_opChanged_softDenial_block() =
319       testAppStreaming_opChanged_softDenial_block(Api.NDK)
320 
321   private fun testAppStreaming_opChanged_softDenial_block(api: Api) {
322     setSensorPrivacy(enabled = false)
323     openCameraResultFuture =
324         startOpenCameraActivityForOpenCamera(api, shouldStream = true, shouldRepeat = true)
325 
326     assertStreamOpened()
327 
328     setOpMode(OPEN_CAMERA_APP, AppOpsManager.MODE_IGNORED)
329 
330     checkAppOpenedCamera(
331         cameraOpenedKey(api), expectStreamOpened = true, expectError = cameraOpChangedError(api))
332   }
333 
334   @Test
335   fun testAppStreaming2_opChanged_softDenial_cameraMute() =
336       testAppStreaming_opChanged_softDenial_cameraMute(Api.API_2)
337 
338   @Test
339   fun testAppStreaming1_opChanged_softDenial_cameraMute() =
340       testAppStreaming_opChanged_softDenial_cameraMute(Api.API_1)
341 
342   @Test
343   fun testAppStreamingNdk_opChanged_softDenial_cameraMute() =
344       testAppStreaming_opChanged_softDenial_cameraMute(Api.NDK)
345 
346   private fun testAppStreaming_opChanged_softDenial_cameraMute(api: Api) {
347     assumeTrue(supportsCameraMute())
348     openCameraResultFuture =
349         startOpenCameraActivityForOpenCamera(api, shouldStream = true, shouldRepeat = true)
350 
351     assertStreamOpened()
352 
353     val am = context.getSystemService(ActivityManager::class.java)
354     val importance = am.getPackageImportance(OPEN_CAMERA_APP.packageName)
355     Log.v(TAG, "OpenCameraApp importance: ${importance}")
356 
357     setSensorPrivacy(enabled = true)
358 
359     // Wait for any potential block()
360     Thread.sleep(TIMEOUT_MILLIS)
361 
362     sendStopRepeating(OPEN_CAMERA_APP)
363     checkAppOpenedCamera(
364         cameraOpenedKey(api), expectStreamOpened = true, expectStoppedRepeating = true)
365   }
366 
367   @Test fun testProxyConnectDevice() = testProxyOpenCamera(Api.API_2)
368 
369   @Test fun testProxyConnect() = testProxyOpenCamera(Api.API_1)
370 
371   @Test fun testProxyNdkConnect() = testProxyOpenCamera(Api.NDK)
372 
373   private fun testProxyOpenCamera(api: Api) {
374     openCameraByProxy(openCameraByProxyKey(api))
375     checkAppOpenedCameraByProxy(cameraOpenedKey(api))
376   }
377 
378   @Test
379   fun testProxyConnectDevice_noOpenCameraPermission() =
380       testProxyOpenCamera_noPermission(Api.API_2, OPEN_CAMERA_APP)
381 
382   @Test
383   @RequiresFlagsEnabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
384   fun testProxyConnectDevice_noCameraProxyPermission() =
385       testProxyOpenCamera_noPermission(Api.API_2, CAMERA_PROXY_APP)
386 
387   private fun testProxyOpenCamera_noPermission(api: Api, deniedApp: TestApp) {
388     denyAppPermission(deniedApp)
389     openCameraByProxy(openCameraByProxyKey(api))
390     checkAppFailedToOpenCameraByProxy(cameraOpenedKey(api))
391   }
392 
393   @Test
394   @RequiresFlagsDisabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
395   fun testProxyConnectDevice_noCameraProxyPermission_dataDeliveryPermissionChecks_off() {
396     denyAppPermission(CAMERA_PROXY_APP)
397     openCameraByProxy(OPEN_CAMERA_APP.keys.openCamera2ByProxy)
398     checkAppOpenedCameraByProxy(OPEN_CAMERA_APP.keys.cameraOpened2)
399   }
400 
401   @Test fun testProxyStreaming2() = testProxyStreaming(Api.API_2)
402 
403   @Test fun testProxyStreaming1() = testProxyStreaming(Api.API_1)
404 
405   @Test fun testProxyStreamingNdk() = testProxyStreaming(Api.NDK)
406 
407   private fun testProxyStreaming(api: Api) {
408     openCameraByProxy(openCameraByProxyKey(api), shouldStream = true)
409     assertStreamOpened()
410     checkAppOpenedCameraByProxy(cameraOpenedKey(api), expectStreamOpened = true)
411   }
412 
413   @Test
414   fun testProxyStreaming2_opChanged_softDenial_cameraMute() =
415       testProxyStreaming_opChanged_softDenial_cameraMute(Api.API_2)
416 
417   @Test
418   fun testProxyStreaming1_opChanged_softDenial_cameraMute() =
419       testProxyStreaming_opChanged_softDenial_cameraMute(Api.API_1)
420 
421   @Test
422   fun testProxyStreamingNdk_opChanged_softDenial_cameraMute() =
423       testProxyStreaming_opChanged_softDenial_cameraMute(Api.NDK)
424 
425   private fun testProxyStreaming_opChanged_softDenial_cameraMute(api: Api) {
426     assumeTrue(supportsCameraMute())
427     openCameraByProxy(openCameraByProxyKey(api), shouldStream = true, shouldRepeat = true)
428 
429     val pid =
430         onResumeFuture
431             .get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
432             .getIntExtra(OPEN_CAMERA_APP.keys.pid, -1)
433 
434     assertStreamOpened()
435 
436     val am = context.getSystemService(ActivityManager::class.java)
437 
438     // Wait until OpenCameraApp is no longer visible according to ActivityManager
439     for (i in 0..7) { // 7s
440       val importance = am.getPackageImportance(OPEN_CAMERA_APP.packageName)
441       Log.v(TAG, "OpenCameraApp importance: ${importance}")
442       if (importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
443         break
444       }
445       Thread.sleep(1000)
446     }
447 
448     setSensorPrivacy(enabled = true)
449 
450     // Wait for any potential block()
451     Thread.sleep(TIMEOUT_MILLIS)
452 
453     sendStopRepeating(CAMERA_PROXY_APP)
454     checkAppOpenedCameraByProxy(
455         cameraOpenedKey(api),
456         expectStreamOpened = true,
457         expectStoppedRepeating = true,
458         expectError = 0)
459   }
460 
461   @Test
462   @RequiresFlagsEnabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
463   fun testProxyStreaming2_noOpenCameraPermission_opChanged_softDenial_block() {
464     testProxyStreaming2_opChanged_softDenial_block(OPEN_CAMERA_APP)
465   }
466 
467   @Test
468   @RequiresFlagsEnabled(Flags.FLAG_DATA_DELIVERY_PERMISSION_CHECKS)
469   fun testProxyStreaming2_noCameraProxyPermission_opChanged_softDenial_block() {
470     testProxyStreaming2_opChanged_softDenial_block(CAMERA_PROXY_APP)
471   }
472 
473   @Test
474   fun testSpoofedAttributionSourceConnectDevice() {
475     val clientAttribution = startActivityForSpoofing()
476     testConnectDeviceWithAttribution(clientAttribution, ICameraService.ERROR_PERMISSION_DENIED)
477   }
478 
479   @Test
480   fun testSpoofedAttributionSourceConnect() {
481     val clientAttribution = startActivityForSpoofing()
482     testConnectWithAttribution(clientAttribution, ICameraService.ERROR_PERMISSION_DENIED)
483   }
484 
485   private fun openCameraByProxy(
486       openCameraKey: String,
487       shouldStream: Boolean = false,
488       shouldRepeat: Boolean = false
489   ): Int {
490     openCameraResultFuture = startOpenCameraActivity()
491     val pid =
492         onResumeFuture
493             .get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
494             .getIntExtra(OPEN_CAMERA_APP.keys.pid, -1)
495     context.sendBroadcast(
496         Intent(openCameraKey).apply {
497           putExtra(OPEN_CAMERA_APP.keys.shouldStream, shouldStream)
498           putExtra(OPEN_CAMERA_APP.keys.shouldRepeat, shouldRepeat)
499           setPackage(OPEN_CAMERA_APP.packageName)
500         })
501     return pid
502   }
503 
504   private fun requireActivityResultData(timeout: Long = TIMEOUT_MILLIS) =
505       openCameraResultFuture!!
506           .get(timeout, TimeUnit.MILLISECONDS)
507           .apply { assertEquals(Activity.RESULT_OK, resultCode) }
508           .resultData!!
509 
510   private fun checkAppOpenedCamera(
511       openCameraKey: String,
512       expectStreamOpened: Boolean = false,
513       expectStoppedRepeating: Boolean = false,
514       expectError: Int = 0,
515   ) {
516     requireActivityResultData().let {
517       maybePrintAttributionSource(it)
518       Log.v(TAG, "checkAppOpenedCamera Intent:")
519       Log.v(TAG, "${it.getExtras().toString()}")
520       OPEN_CAMERA_APP.keys.apply {
521         assumeFalse(it.getBooleanExtra(noCamera, false))
522         assertEquals(true, it.getBooleanExtra(openCameraKey, false))
523         assertEquals(null, it.getStringExtra(exception))
524         assertEquals(expectStreamOpened, it.getBooleanExtra(streamOpened, false))
525         assertEquals(expectError, it.getIntExtra(error, 0))
526         assertEquals(expectStoppedRepeating, it.getBooleanExtra(stoppedRepeating, false))
527       }
528     }
529   }
530 
531   private fun checkAppOpenedCameraByProxy(
532       openCameraKey: String,
533       expectStreamOpened: Boolean = false,
534       expectStoppedRepeating: Boolean = false,
535       expectError: Int = 0
536   ) {
537     requireActivityResultData().let {
538       maybePrintAttributionSource(it)
539       Log.v(TAG, "checkAppOpenedCameraByProxy Intent:")
540       Log.v(TAG, "${it.getExtras().toString()}")
541 
542       assertEquals(null, it.getStringExtra(CAMERA_PROXY_APP.keys.exception))
543 
544       OPEN_CAMERA_APP.keys.apply {
545         assumeFalse(it.getBooleanExtra(noCamera, false))
546         assertEquals(null, it.getStringExtra(exception))
547         assertEquals(expectStreamOpened, it.getBooleanExtra(streamOpened, false))
548         assertEquals(expectError, it.getIntExtra(error, 0))
549         assertEquals(expectStoppedRepeating, it.getBooleanExtra(stoppedRepeating, false))
550       }
551 
552       assertEquals(true, it.getBooleanExtra(openCameraKey, false))
553     }
554   }
555 
556   private fun checkAppFailedToOpenCamera(openCameraKey: String) {
557     requireActivityResultData().let {
558       maybePrintAttributionSource(it)
559       assumeFalse(it.getBooleanExtra(OPEN_CAMERA_APP.keys.noCamera, false))
560       assertNotNull(it.getStringExtra(OPEN_CAMERA_APP.keys.exception))
561       assertEquals(false, it.getBooleanExtra(openCameraKey, false))
562     }
563   }
564 
565   private fun checkAppFailedToOpenCameraNdk() {
566     requireActivityResultData().let {
567       maybePrintAttributionSource(it)
568       assumeFalse(it.getBooleanExtra(OPEN_CAMERA_APP.keys.noCamera, false))
569 
570       // Check for ACAMERA_ERROR_PERMISSION_DENIED
571       assertEquals(-10013, it.getIntExtra(OPEN_CAMERA_APP.keys.error, 0))
572       assertEquals(false, it.getBooleanExtra(OPEN_CAMERA_APP.keys.cameraOpenedNdk, false))
573     }
574   }
575 
576   private fun checkAppFailedToOpenCameraByProxy(openCameraKey: String) {
577     requireActivityResultData().let {
578       maybePrintAttributionSource(it)
579       Log.v(TAG, "testProxyConnectDevice_noOpenCameraPermission Intent:")
580       Log.v(TAG, "${it.getExtras().toString()}")
581       assumeFalse(it.getBooleanExtra(OPEN_CAMERA_APP.keys.noCamera, false))
582       assertNotNull(it.getStringExtra(OPEN_CAMERA_APP.keys.exception))
583       assertEquals(null, it.getStringExtra(CAMERA_PROXY_APP.keys.exception))
584       assertEquals(false, it.getBooleanExtra(openCameraKey, false))
585     }
586   }
587 
588   private fun testConnectDevice(expectDenial: Boolean) {
589     val clientAttribution = context.getAttributionSource().asState()
590     val expectedError =
591         if (expectDenial) {
592           ICameraService.ERROR_PERMISSION_DENIED
593         } else {
594           0
595         }
596     testConnectDeviceWithAttribution(clientAttribution, expectedError)
597   }
598 
599   private fun testConnect(expectDenial: Boolean) {
600     val clientAttribution = context.getAttributionSource().asState()
601     val expectedError =
602         if (expectDenial) {
603           ICameraService.ERROR_PERMISSION_DENIED
604         } else {
605           0
606         }
607     testConnectWithAttribution(clientAttribution, expectedError)
608   }
609 
610   private fun testConnectDeviceWithAttribution(
611       clientAttribution: AttributionSourceState,
612       expectedError: Int,
613   ) {
614     var errorCode = 0
615     try {
616       TestApis.permissions().withPermission(Manifest.permission.CAMERA).use {
617         connectDevice(clientAttribution)
618       }
619     } catch (e: ServiceSpecificException) {
620       Log.i(TAG, "Received error ${e.errorCode}")
621       errorCode = e.errorCode
622     }
623 
624     assertEquals(expectedError, errorCode)
625   }
626 
627   private fun connectDevice(clientAttribution: AttributionSourceState) {
628     getCameraService()
629         .connectDevice(
630             DummyCameraDeviceCallbacks(),
631             cameraManager.getCameraIdList()[0],
632             0 /*oomScoreOffset*/,
633             context.applicationInfo.targetSdkVersion,
634             ICameraService.ROTATION_OVERRIDE_NONE,
635             clientAttribution,
636             DEVICE_POLICY_DEFAULT,
637             false)
638         .disconnect()
639   }
640 
641   private fun testConnectWithAttribution(
642       clientAttribution: AttributionSourceState,
643       expectedError: Int,
644   ) {
645     var errorCode = 0
646     try {
647       TestApis.permissions().withPermission(Manifest.permission.CAMERA).use {
648         connect(clientAttribution)
649       }
650     } catch (e: ServiceSpecificException) {
651       Log.i(TAG, "Received error ${e.errorCode}")
652       errorCode = e.errorCode
653     }
654 
655     assertEquals(expectedError, errorCode)
656   }
657 
658   private fun connect(clientAttribution: AttributionSourceState) {
659     getCameraService()
660         .connect(
661             DummyCameraClient(),
662             /* cameraId= */ 0,
663             context.applicationInfo.targetSdkVersion,
664             ICameraService.ROTATION_OVERRIDE_NONE,
665             /* forceSlowJpegMode= */ false,
666             clientAttribution,
667             DEVICE_POLICY_DEFAULT)
668         .disconnect()
669   }
670 
671   private fun startActivityForSpoofing(): AttributionSourceState {
672     openCameraResultFuture = startOpenCameraActivity()
673 
674     val pid =
675         onResumeFuture
676             .get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
677             .getIntExtra(OPEN_CAMERA_APP.keys.pid, Process.INVALID_PID)
678     val uid = context.packageManager.getApplicationInfo(OPEN_CAMERA_APP.packageName, 0).uid
679 
680     val context = context
681     val contextAttribution = context.getAttributionSource().asState()
682     val clientAttribution = AttributionSourceState()
683     clientAttribution.uid = uid
684     clientAttribution.pid = pid
685     clientAttribution.deviceId = contextAttribution.deviceId
686     clientAttribution.packageName = OPEN_CAMERA_APP.packageName
687     clientAttribution.next = arrayOf<AttributionSourceState>()
688 
689     Log.i(
690         TAG,
691         "Spoofing client uid = $uid, pid = $pid : myUid = ${Process.myUid()}, myPid = ${Process.myPid()}")
692 
693     return clientAttribution
694   }
695 
696   private fun getCameraService(): ICameraService {
697     val cameraServiceBinder = ServiceManager.getService("media.camera")
698     assertNotNull("Camera service IBinder should not be null", cameraServiceBinder)
699 
700     val cameraService = ICameraService.Stub.asInterface(cameraServiceBinder)
701     assertNotNull("Camera service should not be null", cameraService)
702 
703     return cameraService
704   }
705 
706   private fun finishActivity() {
707     openCameraResultFuture?.let {
708       val finishIntent = Intent(OPEN_CAMERA_APP.keys.finish)
709       finishIntent.setPackage(OPEN_CAMERA_APP.packageName)
710       context.sendBroadcast(finishIntent)
711     }
712   }
713 
714   private fun startOpenCameraActivity(
715       openCamera1: Boolean = false,
716       openCamera2: Boolean = false,
717       openCameraNdk: Boolean = false,
718       shouldStream: Boolean = false,
719       shouldRepeat: Boolean = false,
720   ): CompletableFuture<Instrumentation.ActivityResult> =
721       CompletableFuture<Instrumentation.ActivityResult>().also {
722         ActivityScenario.launch(StartForFutureActivity::class.java).onActivity {
723             startForFutureActivity ->
724           startForFutureActivity.startActivityForFuture(
725               Intent().apply {
726                 component = ComponentName(OPEN_CAMERA_APP.packageName, OPEN_CAMERA_ACTIVITY)
727                 putExtra(OPEN_CAMERA_APP.keys.shouldOpenCamera1, openCamera1)
728                 putExtra(OPEN_CAMERA_APP.keys.shouldOpenCamera2, openCamera2)
729                 putExtra(OPEN_CAMERA_APP.keys.shouldOpenCameraNdk, openCameraNdk)
730                 putExtra(OPEN_CAMERA_APP.keys.shouldStream, shouldStream)
731                 putExtra(OPEN_CAMERA_APP.keys.shouldRepeat, shouldRepeat)
732               },
733               it)
734         }
735       }
736 
737   private fun startOpenCameraActivityForOpenCamera(
738       api: Api,
739       shouldStream: Boolean = false,
740       shouldRepeat: Boolean = false
741   ) =
742       startOpenCameraActivity(
743           openCamera1 = (api == Api.API_1),
744           openCamera2 = (api == Api.API_2),
745           openCameraNdk = (api == Api.NDK),
746           shouldStream = shouldStream,
747           shouldRepeat = shouldRepeat)
748 
749   private fun maybePrintAttributionSource(intent: Intent) {
750     intent.getStringExtra(OPEN_CAMERA_APP.keys.attributionSource)?.let { Log.i(TAG, it) }
751   }
752 
753   private fun denyAppPermission(app: TestApp) {
754     TestApis.packages().find(app.packageName).denyPermission(Manifest.permission.CAMERA)
755   }
756 
757   private fun setSensorPrivacy(enabled: Boolean) {
758     val spm = context.getSystemService(SensorPrivacyManager::class.java)!!
759     val supportsToggle = supportsSoftwarePrivacyToggle(spm)
760     if (enabled) {
761       assumeTrue(supportsToggle)
762     } else if (!supportsToggle) {
763       return // No need to do anything if enabled = false and the software toggle is not supported
764     }
765 
766     TestApis.permissions()
767         .withPermission(
768             Manifest.permission.OBSERVE_SENSOR_PRIVACY, Manifest.permission.MANAGE_SENSOR_PRIVACY)
769         .use {
770           val newState =
771               if (enabled) SensorPrivacyManager.StateTypes.ENABLED
772               else SensorPrivacyManager.StateTypes.DISABLED
773           val stateStr = if (enabled) "Enabled" else "Disabled"
774           val oldState =
775               spm.getSensorPrivacyState(
776                   SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, TOGGLE_SENSOR_CAMERA)
777 
778           spm.setSensorPrivacyState(TOGGLE_SENSOR_CAMERA, newState)
779           Log.v(TAG, "${stateStr} sensor privacy")
780           restoreSensorPrivacy = {
781             TestApis.permissions()
782                 .withPermission(
783                     Manifest.permission.OBSERVE_SENSOR_PRIVACY,
784                     Manifest.permission.MANAGE_SENSOR_PRIVACY)
785                 .use { spm.setSensorPrivacyState(TOGGLE_SENSOR_CAMERA, oldState) }
786             restoreSensorPrivacy = null
787           }
788         }
789   }
790 
791   private fun supportsSoftwarePrivacyToggle(spm: SensorPrivacyManager): Boolean =
792       spm.supportsSensorToggle(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, TOGGLE_SENSOR_CAMERA)
793 
794   private fun supportsCameraMute(): Boolean {
795     val cameraIdList = cameraManager.cameraIdList
796     assumeFalse(cameraIdList.isEmpty())
797 
798     val cameraId = cameraManager.cameraIdList[0]
799     val availableTestPatternModes =
800         cameraManager
801             .getCameraCharacteristics(cameraId)
802             .get(CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES) ?: return false
803     for (mode in availableTestPatternModes) {
804       if ((mode == CameraMetadata.SENSOR_TEST_PATTERN_MODE_SOLID_COLOR) ||
805           (mode == CameraMetadata.SENSOR_TEST_PATTERN_MODE_BLACK)) {
806         return true
807       }
808     }
809     return false
810   }
811 
812   private fun setOpMode(app: TestApp, @AppOpsManager.Mode mode: Int) {
813     runWithShellPermissionIdentity {
814       val uid = context.packageManager.getApplicationInfo(app.packageName, 0).uid
815 
816       val oldMode =
817           appOpsManager.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_CAMERA, uid, app.packageName)
818 
819       appOpsManager.setUidMode(AppOpsManager.OP_CAMERA, uid, mode)
820 
821       onTearDown.add({
822         runWithShellPermissionIdentity {
823           appOpsManager.setUidMode(AppOpsManager.OP_CAMERA, uid, oldMode)
824         }
825       })
826 
827       val currentMode =
828           appOpsManager.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_CAMERA, uid, app.packageName)
829       assertEquals(mode, currentMode)
830     }
831   }
832 
833   private fun assertStreamOpened() {
834     val streamOpened =
835         try {
836           streamOpenedFuture
837               .get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
838               .getBooleanExtra(OPEN_CAMERA_APP.keys.streamOpened, false)
839         } catch (e: TimeoutException) {
840           Log.e(TAG, "assertStreamOpened: TimeoutException")
841           false
842         }
843 
844     if (!streamOpened) {
845       // Assert the error / exception first, to make the cause more clear
846       requireActivityResultData(timeout = 1000L).let {
847         assertEquals(0, it.getIntExtra(OPEN_CAMERA_APP.keys.error, 0))
848         assertEquals(null, it.getStringExtra(OPEN_CAMERA_APP.keys.exception))
849       }
850     }
851     assertTrue(streamOpened)
852   }
853 
854   private fun testProxyStreaming2_opChanged_softDenial_block(deniedApp: TestApp) {
855     setSensorPrivacy(enabled = false)
856     openCameraByProxy(
857         OPEN_CAMERA_APP.keys.openCamera2ByProxy, shouldStream = true, shouldRepeat = true)
858 
859     assertStreamOpened()
860 
861     setOpMode(deniedApp, AppOpsManager.MODE_IGNORED)
862 
863     checkAppOpenedCameraByProxy(
864         OPEN_CAMERA_APP.keys.cameraOpened2,
865         expectStreamOpened = true,
866         expectError = CameraDevice.StateCallback.ERROR_CAMERA_DISABLED)
867   }
868 
869   private fun sendStopRepeating(app: TestApp) {
870     val stopRepeatingIntent = Intent(app.keys.stopRepeating)
871     stopRepeatingIntent.setPackage(app.packageName)
872     context.sendBroadcast(stopRepeatingIntent)
873   }
874 
875   private companion object {
876     val TAG = CameraPermissionTest::class.java.simpleName
877     val OPEN_CAMERA_APP =
878         TestApp("OpenCameraApp", "android.security.cts.camera.open", 30, false, "OpenCameraApp.apk")
879     val CAMERA_PROXY_APP =
880         TestApp(
881             "CameraProxyApp", "android.security.cts.camera.proxy", 30, false, "CameraProxyApp.apk")
882     val OPEN_CAMERA_APP_KEYS = IntentKeys(OPEN_CAMERA_APP.packageName)
883     val CAMERA_PROXY_APP_KEYS = IntentKeys(CAMERA_PROXY_APP.packageName)
884     val APP_TO_KEYS =
885         mapOf(OPEN_CAMERA_APP to OPEN_CAMERA_APP_KEYS, CAMERA_PROXY_APP to CAMERA_PROXY_APP_KEYS)
886     val OPEN_CAMERA_ACTIVITY = "${OPEN_CAMERA_APP.packageName}.OpenCameraActivity"
887     val CAMERA_PROXY_ACTIVITY = "${CAMERA_PROXY_APP.packageName}.CameraProxyActivity"
888     const val TIMEOUT_MILLIS: Long = 10000
889     const val TOGGLE_SENSOR_CAMERA = SensorPrivacyManager.Sensors.CAMERA
890 
891     enum class Api {
892       API_1,
893       API_2,
894       NDK,
895     }
896 
897     val TestApp.keys: IntentKeys
898       get() = APP_TO_KEYS.getValue(this)
899 
900     val instrumentation = InstrumentationRegistry.getInstrumentation()
901 
902     fun cameraOpenedKey(api: Api) =
903         when (api) {
904           Api.API_1 -> OPEN_CAMERA_APP.keys.cameraOpened1
905           Api.API_2 -> OPEN_CAMERA_APP.keys.cameraOpened2
906           Api.NDK -> OPEN_CAMERA_APP.keys.cameraOpenedNdk
907         }
908 
909     fun cameraBlockedError(api: Api) =
910         when (api) {
911           Api.API_1 -> Camera.CAMERA_ERROR_UNKNOWN
912           Api.API_2 -> CameraDevice.StateCallback.ERROR_CAMERA_DISABLED
913           Api.NDK -> -10013 // ACAMERA_ERROR_PERMISSION_DENIED
914         }
915 
916     fun cameraOpChangedError(api: Api) =
917         when (api) {
918           Api.NDK -> CameraDevice.StateCallback.ERROR_CAMERA_DEVICE
919           else -> cameraBlockedError(api)
920         }
921 
922     fun openCameraByProxyKey(api: Api) =
923         when (api) {
924           Api.API_1 -> OPEN_CAMERA_APP.keys.openCamera1ByProxy
925           Api.API_2 -> OPEN_CAMERA_APP.keys.openCamera2ByProxy
926           Api.NDK -> OPEN_CAMERA_APP.keys.openCameraNdkByProxy
927         }
928 
929     @BeforeClass
930     @JvmStatic
931     fun beforeClass() {
932       TestApis.permissions().withPermission(Manifest.permission.DELETE_PACKAGES).use {
933         Uninstall.packages(OPEN_CAMERA_APP.packageName, CAMERA_PROXY_APP.packageName)
934       }
935 
936       TestApis.permissions().withPermission(Manifest.permission.INSTALL_PACKAGES).use {
937         Install.multi(OPEN_CAMERA_APP, CAMERA_PROXY_APP).commit()
938       }
939     }
940 
941     @AfterClass
942     @JvmStatic
943     fun afterClass() {
944       TestApis.permissions().withPermission(Manifest.permission.DELETE_PACKAGES).use {
945         Uninstall.packages(OPEN_CAMERA_APP.packageName, CAMERA_PROXY_APP.packageName)
946       }
947     }
948   }
949 }
950