xref: /aosp_15_r20/cts/tests/tests/security/CameraPermissionTestApp/src/CameraOpener.kt (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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.camera.open
18 
19 import android.content.AttributionSource
20 import android.content.Context
21 import android.content.ContextParams
22 import android.content.Intent
23 import android.graphics.ImageFormat
24 import android.graphics.SurfaceTexture
25 import android.hardware.Camera
26 import android.hardware.camera2.CameraCaptureSession
27 import android.hardware.camera2.CameraCharacteristics
28 import android.hardware.camera2.CameraDevice
29 import android.hardware.camera2.CameraManager
30 import android.hardware.camera2.CaptureFailure
31 import android.hardware.camera2.CaptureRequest
32 import android.hardware.camera2.TotalCaptureResult
33 import android.hardware.camera2.params.OutputConfiguration
34 import android.hardware.camera2.params.SessionConfiguration
35 import android.media.ImageReader
36 import android.os.Handler
37 import android.os.HandlerThread
38 import android.security.cts.camera.open.lib.ICameraOpener
39 import android.security.cts.camera.open.lib.IntentKeys
40 import android.util.Log
41 import android.view.Surface
42 import android.view.TextureView
43 import java.util.concurrent.Executors
44 import kotlin.coroutines.resume
45 import kotlinx.coroutines.CancellableContinuation
46 import kotlinx.coroutines.runBlocking
47 import kotlinx.coroutines.suspendCancellableCoroutine
48 
49 class CameraOpener(
50     private val context: Context,
51     private val keys: IntentKeys,
52     private val textureView: TextureView?,
53     private var surfaceTexture: SurfaceTexture?
54 ) {
55   private lateinit var cameraManager: CameraManager
56   private var handlerThread: HandlerThread? = null
57   private val cameraExecutor = Executors.newSingleThreadExecutor()
58 
59   var onStopRepeating: () -> Unit = {}
60 
61   val aidlInterface =
62       object : ICameraOpener.Stub() {
63         override fun openCamera1(
64             attributionSource: AttributionSource,
65             shouldStream: Boolean,
66             shouldRepeat: Boolean
67         ): Intent = runBlocking {
68           Log.v(TAG, "AIDL openCamera1")
69           openCamera1Async(shouldStream, shouldRepeat, attributionSource)
70         }
71 
72         override fun openCamera2(
73             attributionSource: AttributionSource,
74             shouldStream: Boolean,
75             shouldRepeat: Boolean
76         ): Intent = runBlocking {
77           Log.v(TAG, "AIDL openCamera2")
78           openCamera2Async(shouldStream, shouldRepeat, attributionSource)
79         }
80 
81         override fun openCameraNdk(
82             attributionSource: AttributionSource,
83             shouldStream: Boolean,
84             shouldRepeat: Boolean
85         ): Intent = runBlocking {
86           Log.v(TAG, "AIDL openCameraNdk")
87           openCameraNdkAsync(shouldStream, shouldRepeat, attributionSource)
88         }
89 
90         override fun stopRepeating() = onStopRepeating()
91       }
92 
93   init {
94     System.loadLibrary("opencameraapp_jni")
95   }
96 
97   suspend fun openCamera1Async(
98       shouldStream: Boolean,
99       shouldRepeat: Boolean,
100       attributionSource: AttributionSource? = null,
101   ): Intent {
102     val result = Intent().apply { putExtra(keys.attributionSource, attributionSource.toString()) }
103     try {
104       if (Camera.getNumberOfCameras() > 0) {
105         val camera = Camera.open(0)
106         result.putExtra(keys.cameraOpened1, true)
107         if (shouldStream) {
108           return openCamera1Stream(camera, shouldRepeat, result)
109         } else {
110           camera.release()
111           return result
112         }
113       } else {
114         return result.apply { putExtra(keys.noCamera, true) }
115       }
116     } catch (e: Exception) {
117       Log.e(TAG, "Received exception: ${e.message}")
118       return result.apply { putException(keys, e) }
119     }
120   }
121 
122   suspend fun openCamera2Async(
123       shouldStream: Boolean,
124       shouldRepeat: Boolean,
125       attributionSource: AttributionSource? = null,
126   ): Intent = suspendCancellableCoroutine { continuation ->
127     val result = Intent().apply { putExtra(keys.attributionSource, attributionSource.toString()) }
128     continuation.tryOrResume(keys, result, "openCamera2Async") {
129       var customContext = context
130       attributionSource?.let {
131         val contextParams = ContextParams.Builder().setNextAttributionSource(it).build()
132         customContext = context.createContext(contextParams)
133       }
134       cameraManager = customContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
135       if (cameraManager.getCameraIdList().isEmpty()) {
136         continuation.resume(result.apply { putExtra(keys.noCamera, true) })
137       }
138 
139       val cameraId = cameraManager.getCameraIdList()[0]
140       cameraManager.openCamera(
141           cameraId,
142           cameraExecutor,
143           object : CameraDevice.StateCallback() {
144             override fun onOpened(cameraDevice: CameraDevice) {
145               Log.v(TAG, "onOpened")
146 
147               result.putExtra(keys.cameraOpened2, true)
148               if (shouldStream) {
149                 continuation.tryOrResume(keys, result, "openCamera2Async/onOpened") {
150                   openCamera2Stream(cameraDevice, result, continuation, shouldRepeat)
151                 }
152               } else {
153                 cameraDevice.close()
154                 continuation.resume(result)
155               }
156             }
157 
158             override fun onDisconnected(cameraDevice: CameraDevice) {
159               Log.v(TAG, "onDisconnected")
160               continuation.tryOrResume(keys, result, "openCamera2Async/onDisconnected") {
161                 cameraDevice.close()
162               }
163             }
164 
165             override fun onError(cameraDevice: CameraDevice, error: Int) {
166               Log.v(TAG, "onError: " + error)
167 
168               try {
169                 cameraDevice.close()
170               } catch (e: Exception) {
171                 Log.e(
172                     TAG,
173                     "openCamera2Async/onDisconnected: Received exception: ${e.exceptionString}")
174                 result.putException(keys, e)
175               }
176 
177               if (continuation.isActive) { // continuation may already have been resumed
178                 continuation.resume(result.apply { putExtra(keys.error, error) })
179               }
180             }
181           })
182     }
183   }
184 
185   suspend fun openCameraNdkAsync(
186       shouldStream: Boolean,
187       shouldRepeat: Boolean,
188       attributionSource: AttributionSource? = null
189   ): Intent = suspendCancellableCoroutine { continuation ->
190     Log.v(TAG, "openCameraNdkAsync: shouldStream ${shouldStream} shouldRepeat ${shouldRepeat}")
191     var result = Intent().apply { putExtra(keys.attributionSource, attributionSource.toString()) }
192 
193     // Avoid blocking the main thread
194     cameraExecutor.execute {
195       nativeInit()
196 
197       if (!nativeHasCamera()) {
198         continuation.resume(result.apply { putExtra(keys.noCamera, true) })
199         return@execute
200       }
201 
202       val openCameraResult = nativeOpenCamera()
203       if (openCameraResult != 0) {
204         Log.e(TAG, "Failed to open camera: ${openCameraResult}")
205         continuation.resume(result.apply { putExtra(keys.error, openCameraResult) })
206         return@execute
207       }
208 
209       result.putExtra(keys.cameraOpenedNdk, true)
210       if (shouldStream) {
211         openCameraNdkStream(result, continuation, shouldRepeat)
212       } else {
213         continuation.resume(result)
214       }
215     }
216 
217     // Run cleanup sequentially after the above
218     cameraExecutor.execute {
219       nativeCleanup()
220       if (continuation.isActive) {
221         continuation.resume(result)
222       }
223     }
224   }
225 
226   fun release() {
227     handlerThread?.quitSafely()
228   }
229 
230   private suspend fun openCamera1Stream(
231       camera: Camera,
232       shouldRepeat: Boolean,
233       result: Intent
234   ): Intent = suspendCancellableCoroutine { continuation ->
235     val params = camera.getParameters()
236     val previewSize = params.getSupportedPreviewSizes()[0]
237     params.setPreviewSize(previewSize.width, previewSize.height)
238     camera.setParameters(params)
239 
240     if (textureView?.surfaceTexture != null) {
241       surfaceTexture = textureView.surfaceTexture
242     }
243 
244     if (surfaceTexture != null) {
245       captureCamera1(camera, result, continuation, shouldRepeat)
246     } else if (textureView != null) {
247       textureView.setSurfaceTextureListener(
248           object : TextureView.SurfaceTextureListener {
249             override fun onSurfaceTextureAvailable(
250                 surfaceTexture: SurfaceTexture,
251                 width: Int,
252                 height: Int
253             ) {
254               surfaceTexture.let {
255                 this@CameraOpener.surfaceTexture = it
256                 captureCamera1(camera, result, continuation, shouldRepeat)
257               }
258             }
259 
260             override fun onSurfaceTextureSizeChanged(
261                 surface: SurfaceTexture,
262                 width: Int,
263                 height: Int
264             ) {}
265 
266             override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
267               Log.v(TAG, "openCamera1Stream/onSurfaceTextureDestroyed")
268               return true
269             }
270 
271             override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
272           })
273     }
274   }
275 
276   private fun captureCamera1(
277       camera: Camera,
278       result: Intent,
279       continuation: CancellableContinuation<Intent>,
280       shouldRepeat: Boolean,
281   ) {
282     camera.setPreviewTexture(surfaceTexture)
283 
284     val cleanup: () -> Unit = {
285       Log.v(TAG, "captureCamera1/cleanup")
286       continuation.tryOrResume(keys, result, "captureCamera1/cleanup") {
287         camera.stopPreview()
288         camera.setPreviewTexture(null)
289         camera.release()
290       }
291     }
292 
293     camera.setErrorCallback(
294         object : Camera.ErrorCallback {
295           override fun onError(error: Int, camera: Camera) {
296             Log.v(TAG, "captureCamera1/onError: " + error)
297 
298             try {
299               camera.release()
300             } catch (e: Exception) {
301               Log.e(TAG, "captureCamera1/onError: Received exception: ${e.exceptionString}")
302               result.putException(keys, e)
303             }
304 
305             if (continuation.isActive) { // continuation may already have been resumed
306               continuation.resume(result.apply { putExtra(keys.error, error) })
307             }
308           }
309         })
310 
311     camera.setPreviewCallback(
312         object : Camera.PreviewCallback {
313           private var firstCaptureCompleted = false
314 
315           override fun onPreviewFrame(data: ByteArray, camera: Camera) {
316             if (!firstCaptureCompleted) {
317               Log.v(TAG, "Camera.PreviewCallback.onPreviewFrame() (first)")
318               onStopRepeating = {
319                 Log.v(TAG, "onStopRepeating")
320                 cleanup()
321 
322                 if (continuation.isActive) {
323                   continuation.resume(result.apply { putExtra(keys.stoppedRepeating, true) })
324                 }
325               }
326 
327               signalStreamOpened(true, result)
328               firstCaptureCompleted = true
329 
330               if (!shouldRepeat) {
331                 cleanup()
332                 continuation.resume(result)
333               }
334             } else {
335               Log.v(TAG, "Camera.PreviewCallback.onPreviewFrame()")
336             }
337           }
338         })
339 
340     camera.startPreview()
341   }
342 
343   private fun openCamera2Stream(
344       cameraDevice: CameraDevice,
345       result: Intent,
346       continuation: CancellableContinuation<Intent>,
347       shouldRepeat: Boolean
348   ) {
349     handlerThread = HandlerThread("${context.packageName}.CAPTURE").apply { start() }
350     val captureHandler = Handler(handlerThread!!.getLooper())
351 
352     val characteristics = cameraManager.getCameraCharacteristics(cameraDevice.id)
353     val config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
354     val jpegSize = config.getOutputSizes(ImageFormat.JPEG)[0]
355     val imageReader =
356         ImageReader.newInstance(jpegSize.width, jpegSize.height, ImageFormat.JPEG, 1).apply {
357           setOnImageAvailableListener(
358               object : ImageReader.OnImageAvailableListener {
359                 override fun onImageAvailable(imageReader: ImageReader) {
360                   imageReader.acquireNextImage().close()
361                 }
362               },
363               captureHandler)
364         }
365     val outputConfiguration = OutputConfiguration(imageReader.surface)
366     val sessionConfiguration =
367         SessionConfiguration(
368             SessionConfiguration.SESSION_REGULAR,
369             listOf(outputConfiguration),
370             cameraExecutor,
371             object : CameraCaptureSession.StateCallback() {
372               override fun onConfigured(session: CameraCaptureSession) {
373                 Log.v(TAG, "CaptureCaptureSession.StateCallback.onConfigured()")
374                 continuation.tryOrResume(keys, result, "openCamera2Stream/onConfigured") {
375                   captureCamera2(
376                       cameraDevice,
377                       session,
378                       result,
379                       continuation,
380                       imageReader.surface,
381                       shouldRepeat,
382                       captureHandler)
383                 }
384               }
385 
386               override fun onConfigureFailed(session: CameraCaptureSession) {
387                 Log.v(TAG, "CaptureCaptureSession.StateCallback.onConfigureFailed()")
388                 session.close()
389                 cameraDevice.close()
390                 continuation.resume(signalStreamOpened(false, result))
391               }
392 
393               override fun onReady(session: CameraCaptureSession) {
394                 Log.v(TAG, "CaptureCaptureSession.StateCallback.onReady()")
395               }
396             })
397     cameraDevice.createCaptureSession(sessionConfiguration)
398   }
399 
400   private fun captureCamera2(
401       cameraDevice: CameraDevice,
402       session: CameraCaptureSession,
403       result: Intent,
404       continuation: CancellableContinuation<Intent>,
405       target: Surface,
406       shouldRepeat: Boolean,
407       captureHandler: Handler
408   ) {
409     val cleanup: () -> Unit = {
410       Log.v(TAG, "captureCamera2/cleanup")
411       continuation.tryOrResume(keys, result, "captureCamera2/cleanup") {
412         session.stopRepeating()
413         session.close()
414         cameraDevice.close()
415       }
416     }
417 
418     val captureRequest =
419         cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).let {
420           it.addTarget(target)
421           it.build()
422         }
423 
424     val captureCallback =
425         object : CameraCaptureSession.CaptureCallback() {
426           private var firstCaptureCompleted = false
427 
428           override fun onCaptureCompleted(
429               session: CameraCaptureSession,
430               request: CaptureRequest,
431               captureResult: TotalCaptureResult,
432           ) {
433             if (!firstCaptureCompleted) {
434               Log.v(TAG, "CameraCaptureSession.CaptureCallback.onCaptureCompleted() (first)")
435               onStopRepeating = {
436                 Log.v(TAG, "onStopRepeating")
437                 cleanup()
438 
439                 if (continuation.isActive) {
440                   continuation.resume(result.apply { putExtra(keys.stoppedRepeating, true) })
441                 }
442               }
443 
444               signalStreamOpened(true, result)
445               firstCaptureCompleted = true
446             } else {
447               Log.v(TAG, "CameraCaptureSession.CaptureCallback.onCaptureCompleted()")
448             }
449 
450             if (!shouldRepeat) {
451               cleanup()
452               continuation.resume(result)
453             }
454           }
455 
456           override fun onCaptureFailed(
457               session: CameraCaptureSession,
458               request: CaptureRequest,
459               failure: CaptureFailure
460           ) {
461             Log.v(TAG, "CameraCaptureSession.CaptureCallback.onCaptureFailed()")
462           }
463         }
464 
465     if (shouldRepeat) {
466       Log.v(TAG, "captureCamera2: setRepeatingRequest")
467       session.setRepeatingRequest(captureRequest, captureCallback, captureHandler)
468     } else {
469       Log.v(TAG, "captureCamera2: capture")
470       session.capture(captureRequest, captureCallback, captureHandler)
471     }
472   }
473 
474   private fun openCameraNdkStream(
475       result: Intent,
476       continuation: CancellableContinuation<Intent>,
477       shouldRepeat: Boolean
478   ) {
479     Log.v(TAG, "openCameraNdkStream: shouldRepeat ${shouldRepeat}")
480     val openCameraStreamResult = nativeOpenCameraStream(shouldRepeat)
481     if (openCameraStreamResult != 0) {
482       signalStreamOpened(false, result)
483       Log.e(TAG, "Failed to open camera stream: ${openCameraStreamResult}")
484       continuation.resume(result.apply { putExtra(keys.error, openCameraStreamResult) })
485       return
486     }
487 
488     signalStreamOpened(true, result)
489     if (shouldRepeat) {
490       onStopRepeating = {
491         Log.v(TAG, "onStopRepeating")
492         result.putExtra(keys.stoppedRepeating, true)
493         nativeStopRepeating()
494       }
495 
496       val stopRepeatingResult = nativeWaitStopRepeating()
497       result.putExtra(keys.error, stopRepeatingResult)
498     }
499     continuation.resume(result)
500   }
501 
502   private fun signalStreamOpened(streamOpened: Boolean, result: Intent): Intent {
503     val streamOpenedIntent =
504         Intent(keys.streamOpened).apply { putExtra(keys.streamOpened, streamOpened) }
505     streamOpenedIntent.setPackage("android.security.cts")
506     context.sendBroadcast(streamOpenedIntent)
507     return result.apply { putExtra(keys.streamOpened, streamOpened) }
508   }
509 
510   protected external fun nativeInit()
511 
512   protected external fun nativeCleanup()
513 
514   protected external fun nativeHasCamera(): Boolean
515 
516   protected external fun nativeOpenCamera(): Int
517 
518   protected external fun nativeOpenCameraStream(shouldRepeat: Boolean): Int
519 
520   protected external fun nativeWaitStopRepeating(): Int
521 
522   protected external fun nativeStopRepeating(): Int
523 
524   private companion object {
525     val TAG = CameraOpener::class.java.simpleName
526 
527     const val CAMERA_PROXY_APP_PACKAGE_NAME = "android.security.cts.camera.proxy"
528     const val CAMERA_PROXY_ACTIVITY = "$CAMERA_PROXY_APP_PACKAGE_NAME.CameraProxyActivity"
529   }
530 }
531