1 /* <lambda>null2 * Copyright 2020 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 androidx.camera.integration.uiwidgets.rotations 18 19 import android.Manifest 20 import android.content.ContentValues 21 import android.content.Intent 22 import android.content.pm.PackageManager 23 import android.os.Build 24 import android.os.Bundle 25 import android.provider.MediaStore 26 import android.text.TextUtils 27 import android.util.Log 28 import android.util.Size 29 import androidx.annotation.OptIn 30 import androidx.annotation.VisibleForTesting 31 import androidx.appcompat.app.AppCompatActivity 32 import androidx.camera.camera2.Camera2Config 33 import androidx.camera.camera2.pipe.integration.CameraPipeConfig 34 import androidx.camera.core.Camera 35 import androidx.camera.core.CameraSelector 36 import androidx.camera.core.ImageAnalysis 37 import androidx.camera.core.ImageCapture 38 import androidx.camera.core.ImageCaptureException 39 import androidx.camera.core.ImageProxy 40 import androidx.camera.core.Preview 41 import androidx.camera.core.impl.utils.executor.CameraXExecutors 42 import androidx.camera.integration.uiwidgets.databinding.ActivityRotationsMainBinding 43 import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration 44 import androidx.camera.lifecycle.ProcessCameraProvider 45 import androidx.core.app.ActivityCompat 46 import androidx.core.content.ContextCompat 47 import androidx.lifecycle.Lifecycle 48 import java.io.File 49 import java.io.FileOutputStream 50 import java.util.concurrent.ExecutorService 51 import java.util.concurrent.Executors 52 import java.util.concurrent.Semaphore 53 54 open class CameraActivity : AppCompatActivity() { 55 56 private lateinit var mBinding: ActivityRotationsMainBinding 57 private lateinit var mCamera: Camera 58 protected lateinit var mImageAnalysis: ImageAnalysis 59 protected lateinit var mImageCapture: ImageCapture 60 private lateinit var mAnalysisExecutor: ExecutorService 61 62 override fun onCreate(savedInstanceState: Bundle?) { 63 super.onCreate(savedInstanceState) 64 mBinding = ActivityRotationsMainBinding.inflate(layoutInflater) 65 setContentView(mBinding.root) 66 mAnalysisExecutor = Executors.newSingleThreadExecutor() 67 if (shouldRequestPermissionsAtRuntime() && !hasPermissions()) { 68 ActivityCompat.requestPermissions(this, PERMISSIONS, REQUEST_CODE_PERMISSIONS) 69 } else { 70 setUpCamera() 71 } 72 } 73 74 override fun onDestroy() { 75 super.onDestroy() 76 mAnalysisExecutor.shutdown() 77 } 78 79 override fun onRequestPermissionsResult( 80 requestCode: Int, 81 permissions: Array<out String>, 82 grantResults: IntArray 83 ) { 84 super.onRequestPermissionsResult(requestCode, permissions, grantResults) 85 if (requestCode == REQUEST_CODE_PERMISSIONS) { 86 if (hasPermissions()) { 87 setUpCamera() 88 } else { 89 Log.d(TAG, "Camera permission is required") 90 finish() 91 } 92 } 93 } 94 95 private fun shouldRequestPermissionsAtRuntime(): Boolean { 96 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M 97 } 98 99 private fun hasPermissions(): Boolean { 100 return PERMISSIONS.all { 101 ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED 102 } 103 } 104 105 @OptIn(ExperimentalCameraProviderConfiguration::class) 106 private fun setUpCamera() { 107 val newCameraImpl = intent.getStringExtra(KEY_CAMERA_IMPLEMENTATION) 108 Log.d(TAG, "Set up cameraImpl: $newCameraImpl") 109 if (!TextUtils.isEmpty(newCameraImpl) && newCameraImpl != cameraImpl) { 110 try { 111 Log.d(TAG, "ProcessCameraProvider initialize using $newCameraImpl") 112 ProcessCameraProvider.configureInstance( 113 when (newCameraImpl) { 114 CAMERA2_IMPLEMENTATION_OPTION -> Camera2Config.defaultConfig() 115 CAMERA_PIPE_IMPLEMENTATION_OPTION -> CameraPipeConfig.defaultConfig() 116 else -> Camera2Config.defaultConfig() 117 } 118 ) 119 cameraImpl = newCameraImpl 120 } catch (e: IllegalStateException) { 121 throw IllegalStateException( 122 "WARNING: CameraX is currently configured to a different implementation " + 123 "this would have resulted in unexpected behavior.", 124 e 125 ) 126 } 127 } 128 129 if (intent.getBooleanExtra(KEY_CAMERA_IMPLEMENTATION_NO_HISTORY, false)) { 130 intent = 131 Intent(intent).apply { 132 removeExtra(KEY_CAMERA_IMPLEMENTATION) 133 removeExtra(KEY_CAMERA_IMPLEMENTATION_NO_HISTORY) 134 } 135 cameraImpl = null 136 } 137 138 val cameraProcessFuture = ProcessCameraProvider.getInstance(this) 139 cameraProcessFuture.addListener( 140 Runnable { 141 val cameraProvider = cameraProcessFuture.get() 142 if (lifecycle.currentState != Lifecycle.State.DESTROYED) { 143 setUpCamera(cameraProvider) 144 } else { 145 Log.d(TAG, "Skip camera setup since activity is closed") 146 } 147 }, 148 ContextCompat.getMainExecutor(this) 149 ) 150 } 151 152 private fun setUpCamera(cameraProvider: ProcessCameraProvider) { 153 val preview = 154 Preview.Builder().build().apply { 155 setSurfaceProvider(mBinding.previewView.getSurfaceProvider()) 156 } 157 mImageAnalysis = 158 ImageAnalysis.Builder().build().apply { 159 setAnalyzer(mAnalysisExecutor, createAnalyzer()) 160 } 161 mImageCapture = ImageCapture.Builder().build().also { it.setCallback() } 162 mCamera = 163 cameraProvider.bindToLifecycle( 164 this, 165 getCameraSelector(), 166 preview, 167 mImageAnalysis, 168 mImageCapture 169 ) 170 } 171 172 private fun getCameraSelector(): CameraSelector { 173 val lensFacing = intent.getIntExtra(KEY_LENS_FACING, CameraSelector.LENS_FACING_BACK) 174 return CameraSelector.Builder().requireLensFacing(lensFacing).build() 175 } 176 177 private fun createAnalyzer(): ImageAnalysis.Analyzer { 178 return ImageAnalysis.Analyzer { imageProxy -> 179 mAnalysisImageRotation = imageProxy.imageInfo.rotationDegrees 180 mAnalysisRunning.release() 181 Log.d(TAG, "Analyzed image rotation = $mAnalysisImageRotation, $mAnalysisRunning") 182 imageProxy.close() 183 } 184 } 185 186 private fun ImageCapture.setCallback() { 187 mBinding.previewView.setOnClickListener { 188 val imageCaptureMode = 189 intent.getIntExtra(KEY_IMAGE_CAPTURE_MODE, IMAGE_CAPTURE_MODE_IN_MEMORY) 190 when (imageCaptureMode) { 191 IMAGE_CAPTURE_MODE_IN_MEMORY -> setInMemoryCallback() 192 IMAGE_CAPTURE_MODE_FILE -> setFileCallback() 193 IMAGE_CAPTURE_MODE_OUTPUT_STREAM -> setOutputStreamCallback() 194 IMAGE_CAPTURE_MODE_MEDIA_STORE -> setMediaStoreCallback() 195 } 196 } 197 } 198 199 private fun ImageCapture.setInMemoryCallback() { 200 takePicture( 201 CameraXExecutors.mainThreadExecutor(), 202 object : ImageCapture.OnImageCapturedCallback() { 203 override fun onCaptureSuccess(image: ImageProxy) { 204 mCaptureResult = ImageCaptureResult.InMemory(image) 205 mCaptureDone.release() 206 image.close() 207 Log.d(TAG, "InMemory image capture successful") 208 } 209 210 override fun onError(exception: ImageCaptureException) { 211 mCaptureDone.release() 212 Log.e(TAG, "InMemory image capture failed", exception) 213 } 214 } 215 ) 216 } 217 218 private fun ImageCapture.setFileCallback() { 219 val imageFile = File("${cacheDir.absolutePath}/${System.currentTimeMillis()}.jpg") 220 val outputFileOptions = ImageCapture.OutputFileOptions.Builder(imageFile).build() 221 takePicture( 222 outputFileOptions, 223 CameraXExecutors.mainThreadExecutor(), 224 object : ImageCapture.OnImageSavedCallback { 225 override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { 226 mCaptureResult = ImageCaptureResult.FileOrOutputStream(imageFile) 227 mCaptureDone.release() 228 Log.d(TAG, "File image capture successful") 229 } 230 231 override fun onError(exception: ImageCaptureException) { 232 mCaptureDone.release() 233 Log.e(TAG, "File image capture failed", exception) 234 } 235 } 236 ) 237 } 238 239 private fun ImageCapture.setOutputStreamCallback() { 240 val imageFile = File("${cacheDir.absolutePath}/${System.currentTimeMillis()}.jpg") 241 val outputStream = FileOutputStream(imageFile) 242 val outputFileOptions = ImageCapture.OutputFileOptions.Builder(outputStream).build() 243 takePicture( 244 outputFileOptions, 245 CameraXExecutors.mainThreadExecutor(), 246 object : ImageCapture.OnImageSavedCallback { 247 override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { 248 mCaptureResult = ImageCaptureResult.FileOrOutputStream(imageFile) 249 mCaptureDone.release() 250 Log.d(TAG, "OutputStream image capture successful") 251 } 252 253 override fun onError(exception: ImageCaptureException) { 254 mCaptureDone.release() 255 Log.e(TAG, "OutputStream image capture failed", exception) 256 } 257 } 258 ) 259 } 260 261 private fun ImageCapture.setMediaStoreCallback() { 262 val contentValues = 263 ContentValues().apply { 264 put(MediaStore.MediaColumns.DISPLAY_NAME, "${System.currentTimeMillis()}") 265 put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") 266 } 267 val outputFileOptions = 268 ImageCapture.OutputFileOptions.Builder( 269 contentResolver, 270 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 271 contentValues 272 ) 273 .build() 274 takePicture( 275 outputFileOptions, 276 CameraXExecutors.mainThreadExecutor(), 277 object : ImageCapture.OnImageSavedCallback { 278 override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { 279 mCaptureResult = 280 ImageCaptureResult.MediaStore(contentResolver, outputFileResults.savedUri!!) 281 mCaptureDone.release() 282 Log.d(TAG, "MediaStore image capture successful") 283 } 284 285 override fun onError(exception: ImageCaptureException) { 286 mCaptureDone.release() 287 Log.e(TAG, "MediaStore image capture failed", exception) 288 } 289 } 290 ) 291 } 292 293 protected fun isImageAnalysisInitialized(): Boolean { 294 return ::mImageAnalysis.isInitialized 295 } 296 297 protected fun isImageCaptureInitialized(): Boolean { 298 return ::mImageCapture.isInitialized 299 } 300 301 // region For testing 302 @VisibleForTesting val mAnalysisRunning = Semaphore(0) 303 304 @VisibleForTesting var mAnalysisImageRotation = -1 305 306 @VisibleForTesting val mCaptureDone = Semaphore(0) 307 308 @VisibleForTesting var mCaptureResult: ImageCaptureResult? = null 309 310 @VisibleForTesting 311 fun getSensorRotationRelativeToAnalysisTargetRotation(): Int { 312 val targetRotation = mImageAnalysis.targetRotation 313 return mCamera.cameraInfo.getSensorRotationDegrees(targetRotation) 314 } 315 316 @VisibleForTesting 317 fun getSensorRotationRelativeToCaptureTargetRotation(): Int { 318 val targetRotation = mImageCapture.targetRotation 319 return mCamera.cameraInfo.getSensorRotationDegrees(targetRotation) 320 } 321 322 @VisibleForTesting 323 fun getCaptureResolution(): Size { 324 val resolution = 325 mImageCapture.attachedSurfaceResolution 326 ?: throw IllegalStateException("ImageCapture surface resolution is null") 327 328 val rotation = getSensorRotationRelativeToCaptureTargetRotation() 329 return if (rotation == 90 || rotation == 270) { 330 Size(resolution.height, resolution.width) 331 } else { 332 resolution 333 } 334 } 335 336 // endregion 337 338 companion object { 339 const val KEY_LENS_FACING = "lens-facing" 340 const val KEY_IMAGE_CAPTURE_MODE = "image-capture-mode" 341 const val KEY_CAMERA_IMPLEMENTATION = "camera_implementation" 342 const val KEY_CAMERA_IMPLEMENTATION_NO_HISTORY = "camera_implementation_no_history" 343 const val CAMERA2_IMPLEMENTATION_OPTION = "camera2" 344 const val CAMERA_PIPE_IMPLEMENTATION_OPTION = "camera_pipe" 345 const val IMAGE_CAPTURE_MODE_IN_MEMORY = 0 346 const val IMAGE_CAPTURE_MODE_FILE = 1 347 const val IMAGE_CAPTURE_MODE_OUTPUT_STREAM = 2 348 const val IMAGE_CAPTURE_MODE_MEDIA_STORE = 3 349 350 private const val TAG = "CameraActivity" 351 private const val REQUEST_CODE_PERMISSIONS = 20 352 val PERMISSIONS = 353 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 354 // in android 10 or later, we don't actually need WRITE_EXTERNAL_STORAGE to write to 355 // the external storage. 356 arrayOf(Manifest.permission.CAMERA) 357 else arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) 358 private var cameraImpl: String? = null 359 } 360 } 361