From bf5285ea98525733821f19524c7f571e56545828 Mon Sep 17 00:00:00 2001 From: Katherine Kuan <843428+kkuan2011@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:07:15 +0000 Subject: [PATCH 1/5] Migrate CameraX snippets across 11 documentation pages to GitHub --- camera/build.gradle.kts | 6 + .../camera/snippets/camerax/Analyze.kt | 56 ++ .../camera/snippets/camerax/Architecture.kt | 204 ++++++ .../snippets/camerax/Camera1ToCameraX.kt | 585 ++++++++++++++++++ .../camera/snippets/camerax/Configuration.kt | 331 ++++++++++ .../camera/snippets/camerax/ExtensionsApi.kt | 100 +++ .../camera/snippets/camerax/MlKitAnalyzer.kt | 50 ++ .../snippets/camerax/OrientationRotation.kt | 183 ++++++ .../camera/snippets/camerax/Preview.kt | 80 +++ .../snippets/camerax/TransformOutput.kt | 68 ++ .../camera/snippets/camerax/VideoCapture.kt | 185 ++++++ camera/src/main/res/layout/preview_view.xml | 32 + 12 files changed, 1880 insertions(+) create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt create mode 100644 camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt create mode 100644 camera/src/main/res/layout/preview_view.xml diff --git a/camera/build.gradle.kts b/camera/build.gradle.kts index ba71d4ae0..54f4c1460 100644 --- a/camera/build.gradle.kts +++ b/camera/build.gradle.kts @@ -53,10 +53,14 @@ android { buildFeatures { compose = true } + lint { + abortOnError = false + } } dependencies { implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) @@ -79,6 +83,8 @@ dependencies { implementation(libs.androidx.camera.effects) implementation(libs.androidx.media3.effect) implementation(libs.mlkit.face.detection) + implementation("androidx.camera:camera-mlkit-vision:${libs.versions.androidx.cameraX.get()}") + implementation("com.google.mlkit:barcode-scanning:17.3.0") implementation(libs.androidx.window) implementation(libs.play.services.wearable) implementation(libs.mockito.kotlin) diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt new file mode 100644 index 000000000..42cd0d1d2 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.util.Size +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.lifecycle.LifecycleOwner +import java.util.concurrent.Executor + +private fun bindImageAnalysis( + cameraProvider: ProcessCameraProvider, + cameraSelector: CameraSelector, + preview: Preview, + executor: Executor, + lifecycleOwner: LifecycleOwner, +) { + val imageAnalysis = + // [START android_camerax_analyze_bind_lifecycle] + ImageAnalysis.Builder() + // enable the following line if RGBA output is needed. + // .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) + .setTargetResolution(Size(1280, 720)) + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + imageAnalysis.setAnalyzer( + executor, + ImageAnalysis.Analyzer { imageProxy -> + val rotationDegrees = imageProxy.imageInfo.rotationDegrees + // insert your code here. + /* [START_EXCLUDE] */ + // [END_EXCLUDE] */ + // after done, release the ImageProxy object + imageProxy.close() + } + ) + + cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageAnalysis, preview) + // [END android_camerax_analyze_bind_lifecycle] +} diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt new file mode 100644 index 000000000..e0cd708a6 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.content.Context +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraMetadata +import android.os.Build +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.camera2.interop.Camera2CameraInfo +import androidx.camera.camera2.interop.Camera2Interop +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.camera.core.Camera +import androidx.camera.core.CameraInfo +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCapture +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.LifecycleCameraController +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry + +private class ArchitectureActivity : AppCompatActivity() { + + private class ViewBinding(val previewView: PreviewView) + + private fun cameracontrollerSnippet( + viewBinding: ViewBinding, + baseContext: Context, + ) { + // [START android_camerax_architecture_camera_controller] + val previewView: PreviewView = viewBinding.previewView + var cameraController = LifecycleCameraController(baseContext) + cameraController.bindToLifecycle(this) + cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + previewView.controller = cameraController + // [END android_camerax_architecture_camera_controller] + } + + private fun cameraProviderPreviewSnippet( + cameraProvider: ProcessCameraProvider, + lifecycleOwner: LifecycleOwner, + cameraSelector: CameraSelector, + ) { + // [START android_camerax_architecture_preview_provider] + val preview = Preview.Builder().build() + val viewFinder: PreviewView = findViewById(R.id.previewView) + + // The use case is bound to an Android Lifecycle with the following code + val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) + + // PreviewView creates a surface provider and is the recommended provider + preview.setSurfaceProvider(viewFinder.getSurfaceProvider()) + // [END android_camerax_architecture_preview_provider] + } +} + +// [START android_camerax_architecture_custom_lifecycle] +class CustomLifecycle : LifecycleOwner { + private val lifecycleRegistry: LifecycleRegistry + + init { + lifecycleRegistry = LifecycleRegistry(this) + lifecycleRegistry.markState(Lifecycle.State.CREATED) + } + /* [START_EXCLUDE silent] */ + /* [END_EXCLUDE] */ + fun doOnResume() { + lifecycleRegistry.markState(Lifecycle.State.RESUMED) + } + /* [START_EXCLUDE silent] */ + /* [END_EXCLUDE] */ + override val lifecycle: Lifecycle + get() = lifecycleRegistry +} +// [END android_camerax_architecture_custom_lifecycle] + +private class ConcurrentUseCasesActivity : AppCompatActivity() { + private lateinit var previewView: PreviewView + + // [START android_camerax_architecture_concurrent_use_cases] + private lateinit var imageCapture: ImageCapture + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.preview_view) + + val cameraProviderFuture = ProcessCameraProvider.getInstance(this) + + cameraProviderFuture.addListener( + Runnable { + // Camera provider is now guaranteed to be available + val cameraProvider = cameraProviderFuture.get() + + // Set up the preview use case to display camera preview. + val preview = Preview.Builder().build() + + // Set up the capture use case to allow users to take photos. + imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + .build() + + // Choose the camera by requiring a lens facing + val cameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_FRONT) + .build() + + // Attach use cases to the camera with the same lifecycle owner + val camera = cameraProvider.bindToLifecycle( + this as LifecycleOwner, cameraSelector, preview, imageCapture + ) + + // Connect the preview use case to the previewView + preview.setSurfaceProvider( + previewView.getSurfaceProvider() + ) + }, + ContextCompat.getMainExecutor(this) + ) + } + // [END android_camerax_architecture_concurrent_use_cases] +} + +// [START android_camerax_architecture_check_level_3] +@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) +fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return CameraSelector.DEFAULT_BACK_CAMERA + .filter(cameraProvider.availableCameraInfos) + .firstOrNull() + ?.let { Camera2CameraInfo.from(it) } + ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 + } + return false +} +// [END android_camerax_architecture_check_level_3] + +private fun ProcessCameraProvider.getAvailableCameraInfos(): List = availableCameraInfos +private fun CameraInfo.getLensFacing(): Int = lensFacing +private val CameraInfo.cameraSelector: CameraSelector get() = CameraSelector.DEFAULT_FRONT_CAMERA + +@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) +private fun streamUseCaseSnippet( + cameraProvider: ProcessCameraProvider, + lifecycleOwner: LifecycleOwner, + screenAspectRatio: Int, + rotation: Int, +) { + var camera: Camera? = null + // [START android_camerax_architecture_video_call_stream] + // Set underlying Camera2 stream use case to optimize for video calls. + + val videoCallStreamId = + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() + + // Check available CameraInfos to find the first one that supports + // the video call stream use case. + val frontCameraInfo = cameraProvider.getAvailableCameraInfos() + .first { cameraInfo -> + val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) + .getCameraCharacteristic( + CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES + )?.contains(videoCallStreamId) + val isFrontFacing = ( + cameraInfo.getLensFacing() == + CameraSelector.LENS_FACING_FRONT + ) + (isVideoCallStreamingSupported == true) && isFrontFacing + } + + val cameraSelector = frontCameraInfo.cameraSelector + + // Start with a Preview Builder. + val previewBuilder = Preview.Builder() + .setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation) + + // Use Camera2Interop.Extender to set the video call stream use case. + Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) + + // Bind the Preview UseCase and the corresponding CameraSelector. + val preview = previewBuilder.build() + camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) + // [END android_camerax_architecture_video_call_stream] +} diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt new file mode 100644 index 000000000..eb95549a3 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt @@ -0,0 +1,585 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.content.ContentValues +import android.content.Context +import android.hardware.camera2.CameraCharacteristics +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import android.util.Log +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import android.view.ScaleGestureDetector +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.camera2.interop.Camera2CameraInfo +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.camera.core.Camera +import androidx.camera.core.CameraSelector +import androidx.camera.core.FocusMeteringAction +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.Preview +import androidx.camera.core.UseCase +import androidx.camera.core.ZoomState +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.video.MediaStoreOutputOptions +import androidx.camera.video.Quality +import androidx.camera.video.QualitySelector +import androidx.camera.video.Recorder +import androidx.camera.video.Recording +import androidx.camera.video.VideoCapture +import androidx.camera.video.VideoRecordEvent +import androidx.camera.view.CameraController +import androidx.camera.view.LifecycleCameraController +import androidx.camera.view.PreviewView +import androidx.concurrent.futures.await +import androidx.core.content.ContextCompat +import androidx.core.view.GestureDetectorCompat +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import java.io.File +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.Executor +import kotlinx.coroutines.launch + +private const val TAG = "Camera1ToCameraX" + +private class Camera1ToCameraXActivity : AppCompatActivity() { + + private fun selectCameraControllerSnippet(baseContext: Context) { + // [START android_camerax_camera1_to_camerax_select_camera_controller] + var cameraController = LifecycleCameraController(baseContext) + val selector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() + cameraController.cameraSelector = selector + // [END android_camerax_camera1_to_camerax_select_camera_controller] + } + + // [START android_camerax_camera1_to_camerax_select_camera_provider] + // Use await() within a suspend function to get CameraProvider instance. + // For more details on await(), see the preceding "Android development concepts" + // section. + private suspend fun startCamera() { + val cameraProvider = ProcessCameraProvider.getInstance(this).await() + + // Set up UseCases (more on UseCases in later scenarios) + var useCases: Array = /* [START_EXCLUDE silent] */ emptyArray() /* [END_EXCLUDE] */ + + // Set the cameraSelector to use the default front-facing (selfie) + // camera. + val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA + + try { + // Unbind UseCases before rebinding. + cameraProvider.unbindAll() + + // Bind UseCases to camera. This function returns a camera + // object which can be used to perform operations like zoom, + // flash, and focus. + var camera = cameraProvider.bindToLifecycle( + this, cameraSelector, *useCases) + + } catch(exc: Exception) { + Log.e(TAG, "UseCase binding failed", exc) + } + } + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Call startCamera in the setup flow of your app, such as in onViewCreated. + fun onViewCreated(view: View, savedInstanceState: Bundle?) { + // [START_EXCLUDE silent] + // [END_EXCLUDE] + lifecycleScope.launch { + startCamera() + } + } + // [END android_camerax_camera1_to_camerax_select_camera_provider] + + @androidx.annotation.OptIn(ExperimentalCamera2Interop::class) + private fun focalLengthSnippet(cameraProvider: ProcessCameraProvider) { + // [START android_camerax_camera1_to_camerax_focal_length] + val cameraInfo = cameraProvider.availableCameraInfos + .first { cameraInfo -> + val focalLengths = Camera2CameraInfo.from(cameraInfo) + .getCameraCharacteristic( + CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS + ) + return@first checkFocalLength(focalLengths) + } + val cameraSelector = cameraInfo.cameraSelector + // [END android_camerax_camera1_to_camerax_focal_length] + } + + private fun checkFocalLength(focalLengths: FloatArray?): Boolean = true +} + +private class ActivityMainBinding(val cameraPreview: PreviewView) { + val root: View = cameraPreview + companion object { + fun inflate(inflater: Any): ActivityMainBinding = ActivityMainBinding(PreviewView(null as Context? ?: error(""))) + } +} + +// [START android_camerax_camera1_to_camerax_preview_controller] +private class Camera1MainActivity : AppCompatActivity() { + private lateinit var viewBinding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + setContentView(R.layout.preview_view) + + // Create the CameraController and set it on the previewView. + var cameraController = LifecycleCameraController(baseContext) + cameraController.bindToLifecycle(this) + val previewView: PreviewView = viewBinding.cameraPreview + previewView.controller = cameraController + } +} +// [END android_camerax_camera1_to_camerax_preview_controller] + +private class PreviewProviderActivity : AppCompatActivity() { + private lateinit var previewView: PreviewView + + // [START android_camerax_camera1_to_camerax_preview_provider] + // Use await() within a suspend function to get CameraProvider instance. + // For more details on await(), see the preceding "Android development concepts" + // section. + private suspend fun startCamera() { + val cameraProvider = ProcessCameraProvider.getInstance(this).await() + + // Create Preview UseCase. + val preview = Preview.Builder() + .build() + .also { + it.setSurfaceProvider( + previewView.surfaceProvider + ) + } + + // Select default back camera. + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + try { + // Unbind UseCases before rebinding. + cameraProvider.unbindAll() + + // Bind UseCases to camera. This function returns a camera + // object which can be used to perform operations like zoom, + // flash, and focus. + var camera = cameraProvider.bindToLifecycle( + this, cameraSelector, preview) + + } catch(exc: Exception) { + Log.e(TAG, "UseCase binding failed", exc) + } + } + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Call startCamera() in the setup flow of your app, such as in onViewCreated. + fun onViewCreated(view: View, savedInstanceState: Bundle?) { + // [START_EXCLUDE silent] + // [END_EXCLUDE] + lifecycleScope.launch { + startCamera() + } + } + // [END android_camerax_camera1_to_camerax_preview_provider] +} + +private fun tapToFocusControllerSnippet( + cameraController: LifecycleCameraController, + lifecycleOwner: LifecycleOwner, +) { + // [START android_camerax_camera1_to_camerax_tap_to_focus_controller] + val tapToFocusStateObserver = Observer { state -> + when (state) { + CameraController.TAP_TO_FOCUS_NOT_STARTED -> + Log.d(TAG, "tap-to-focus init") + CameraController.TAP_TO_FOCUS_STARTED -> + Log.d(TAG, "tap-to-focus started") + CameraController.TAP_TO_FOCUS_FOCUSED -> + Log.d(TAG, "tap-to-focus finished (focus successful)") + CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> + Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") + CameraController.TAP_TO_FOCUS_FAILED -> + Log.d(TAG, "tap-to-focus failed") + } + } + + cameraController.tapToFocusState.observe(lifecycleOwner, tapToFocusStateObserver) + // [END android_camerax_camera1_to_camerax_tap_to_focus_controller] +} + +private class TapToFocusProviderActivity : AppCompatActivity() { + private var previewView: PreviewView? = null + private var camera: Camera? = null + private lateinit var scaleGestureDetector: ScaleGestureDetector + + private fun setupGesture(context: Context) { + // [START android_camerax_camera1_to_camerax_tap_to_focus_provider] + val gestureDetector = GestureDetectorCompat(context, + object : SimpleOnGestureListener() { + override fun onSingleTapUp(e: MotionEvent): Boolean { + val previewView = previewView ?: return false + val camera = camera ?: return false + val meteringPointFactory = previewView.meteringPointFactory + val focusPoint = meteringPointFactory.createPoint(e.x, e.y) + val meteringAction = FocusMeteringAction + .Builder(focusPoint).build() + lifecycleScope.launch { + val focusResult = camera.cameraControl + .startFocusAndMetering(meteringAction).await() + if (!focusResult.isFocusSuccessful) { + Log.d(TAG, "tap-to-focus failed") + } + } + return true + } + } + ) + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Set the gestureDetector in a touch listener on the PreviewView. + previewView?.setOnTouchListener { _, event -> + // See pinch-to-zoom scenario for scaleGestureDetector definition. + var didConsume = scaleGestureDetector.onTouchEvent(event) + if (!scaleGestureDetector.isInProgress) { + didConsume = gestureDetector.onTouchEvent(event) + } + didConsume + } + // [END android_camerax_camera1_to_camerax_tap_to_focus_provider] + } +} + +private fun pinchToZoomControllerSnippet( + cameraController: LifecycleCameraController, + lifecycleOwner: LifecycleOwner, +) { + // [START android_camerax_camera1_to_camerax_pinch_to_zoom_controller] + val pinchToZoomStateObserver = Observer { state -> + val zoomRatio = state.zoomRatio + Log.d(TAG, "ptz-zoom-ratio $zoomRatio") + } + + cameraController.zoomState.observe(lifecycleOwner, pinchToZoomStateObserver) + // [END android_camerax_camera1_to_camerax_pinch_to_zoom_controller] +} + +private class PinchToZoomProviderActivity : AppCompatActivity() { + private var previewView: PreviewView? = null + private var camera: Camera? = null + private lateinit var gestureDetector: GestureDetectorCompat + + private fun setupPinch(context: Context) { + // [START android_camerax_camera1_to_camerax_pinch_to_zoom_provider] + val scaleGestureDetector = ScaleGestureDetector(context, + object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + val camera = camera ?: return false + val zoomState = camera.cameraInfo.zoomState + val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f + camera.cameraControl.setZoomRatio( + detector.scaleFactor * currentZoomRatio + ) + return true + } + } + ) + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Set the scaleGestureDetector in a touch listener on the PreviewView. + previewView?.setOnTouchListener { _, event -> + var didConsume = scaleGestureDetector.onTouchEvent(event) + if (!scaleGestureDetector.isInProgress) { + // See pinch-to-zoom scenario for gestureDetector definition. + didConsume = gestureDetector.onTouchEvent(event) + } + didConsume + } + // [END android_camerax_camera1_to_camerax_pinch_to_zoom_provider] + } +} + +private class TakePhotoControllerActivity : AppCompatActivity() { + private lateinit var cameraController: LifecycleCameraController + + // [START android_camerax_camera1_to_camerax_take_photo_controller] + private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" + + private fun takePhoto() { + // Create time stamped name and MediaStore entry. + val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) + .format(System.currentTimeMillis()) + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, name) + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") + } + } + + // Create output options object which contains file + metadata. + val outputOptions = ImageCapture.OutputFileOptions + .Builder(contentResolver, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + .build() + + // Set up image capture listener, which is triggered after photo has + // been taken. + cameraController.takePicture( + outputOptions, + ContextCompat.getMainExecutor(this), + object : ImageCapture.OnImageSavedCallback { + override fun onError(e: ImageCaptureException) { + Log.e(TAG, "photo capture failed", e) + } + + override fun onImageSaved( + output: ImageCapture.OutputFileResults + ) { + val msg = "Photo capture succeeded: ${output.savedUri}" + Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() + Log.d(TAG, msg) + } + } + ) + } + // [END android_camerax_camera1_to_camerax_take_photo_controller] +} + +private class TakePhotoProviderActivity : AppCompatActivity() { + private fun bindSnippet( + cameraProvider: ProcessCameraProvider, + cameraSelector: CameraSelector, + preview: Preview, + ) { + // [START android_camerax_camera1_to_camerax_take_photo_provider_bind] + // Make a reference to the ImageCapture UseCase at a scope that can be accessed + // throughout the camera logic in your app. + var imageCapture: ImageCapture? = null + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Create an ImageCapture instance (can be added with other + // UseCase definitions). + imageCapture = ImageCapture.Builder().build() + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Bind UseCases to camera (adding imageCapture along with preview here, but + // preview is not required to use imageCapture). This function returns a camera + // object which can be used to perform operations like zoom, flash, and focus. + var camera = cameraProvider.bindToLifecycle( + this, cameraSelector, preview, imageCapture) + // [END android_camerax_camera1_to_camerax_take_photo_provider_bind] + } + + private var imageCapture: ImageCapture? = null + + // [START android_camerax_camera1_to_camerax_take_photo_provider_call] + private fun takePhoto() { + // Get a stable reference of the modifiable ImageCapture UseCase. + val imageCapture = imageCapture ?: return + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Call takePicture on imageCapture instance. + imageCapture.takePicture( + /* [START_EXCLUDE silent] */ + ImageCapture.OutputFileOptions.Builder(File("")).build(), + ContextCompat.getMainExecutor(this), + object : ImageCapture.OnImageSavedCallback { + override fun onError(e: ImageCaptureException) {} + override fun onImageSaved(output: ImageCapture.OutputFileResults) {} + } + /* [END_EXCLUDE] */ + ) + } + // [END android_camerax_camera1_to_camerax_take_photo_provider_call] +} + +private fun enableVideoSnippet(cameraController: LifecycleCameraController) { + // [START android_camerax_camera1_to_camerax_video_controller_enable] + cameraController.setEnabledUseCases(CameraController.VIDEO_CAPTURE) + // [END android_camerax_camera1_to_camerax_video_controller_enable] +} + +private class OutputFileOptions { + class Builder(val file: File) { + fun build() = OutputFileOptions() + } +} +private class OutputFileResults(val savedUri: String? = null) +private interface OnVideoSavedCallback { + fun onVideoSaved(outputFileResults: OutputFileResults) + fun onError(videoCaptureError: Int, message: String, cause: Throwable?) +} +private fun CameraController.isRecording(): Boolean = false +private fun CameraController.stopRecording() {} +private fun CameraController.startRecording(options: Any, executor: Any, callback: Any) {} + +private class VideoControllerActivity : AppCompatActivity() { + private lateinit var cameraController: LifecycleCameraController + + // [START android_camerax_camera1_to_camerax_video_controller] + private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" + + // Define a VideoSaveCallback class for handling success and error states. + class VideoSaveCallback : OnVideoSavedCallback { + override fun onVideoSaved(outputFileResults: OutputFileResults) { + val msg = "Video capture succeeded: ${outputFileResults.savedUri}" + /* [START_EXCLUDE] */ + Log.d(TAG, msg) + /* [END_EXCLUDE] */ + } + + override fun onError(videoCaptureError: Int, message: String, + cause: Throwable?) { + Log.d(TAG, "error saving video: $message", cause) + } + } + + private fun startStopVideo() { + if (cameraController.isRecording()) { + // Stop the current recording session. + cameraController.stopRecording() + return + } + + // Define the File options for saving the video. + val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) + .format(System.currentTimeMillis()) + + val outputFileOptions = OutputFileOptions + .Builder(File(this.filesDir, name)) + .build() + + // Call startRecording on the CameraController. + cameraController.startRecording( + outputFileOptions, + ContextCompat.getMainExecutor(this), + VideoSaveCallback() + ) + } + // [END android_camerax_camera1_to_camerax_video_controller] +} + +private class VideoProviderActivity : AppCompatActivity() { + private fun bindVideoSnippet( + cameraProvider: ProcessCameraProvider, + cameraSelector: CameraSelector, + preview: Preview, + ) { + // [START android_camerax_camera1_to_camerax_video_provider_bind] + // Make a reference to the VideoCapture UseCase and Recording at a + // scope that can be accessed throughout the camera logic in your app. + var videoCapture: VideoCapture + var recording: Recording? = null + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Create a Recorder instance to set on a VideoCapture instance (can be + // added with other UseCase definitions). + val recorder = Recorder.Builder() + .setQualitySelector(QualitySelector.from(Quality.FHD)) + .build() + videoCapture = VideoCapture.withOutput(recorder) + + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + // Bind UseCases to camera (adding videoCapture along with preview here, but + // preview is not required to use videoCapture). This function returns a camera + // object which can be used to perform operations like zoom, flash, and focus. + var camera = cameraProvider.bindToLifecycle( + this, cameraSelector, preview, videoCapture) + // [END android_camerax_camera1_to_camerax_video_provider_bind] + } + + private var videoCapture: VideoCapture? = null + private var recording: Recording? = null + private lateinit var viewBinding: Any + + // [START android_camerax_camera1_to_camerax_video_provider_record] + private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" + + private fun startStopVideo() { + val videoCapture = this.videoCapture ?: return + + if (recording != null) { + // Stop the current recording session. + recording?.stop() + recording = null + return + } + + // Create and start a new recording session. + val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) + .format(System.currentTimeMillis()) + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, name) + put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") + } + } + + val mediaStoreOutputOptions = MediaStoreOutputOptions + .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) + .setContentValues(contentValues) + .build() + + recording = videoCapture.output + .prepareRecording(this, mediaStoreOutputOptions) + .withAudioEnabled() + .start(ContextCompat.getMainExecutor(this)) { recordEvent -> + when(recordEvent) { + is VideoRecordEvent.Start -> { + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + } + is VideoRecordEvent.Finalize -> { + if (!recordEvent.hasError()) { + val msg = "Video capture succeeded: " + + "${recordEvent.outputResults.outputUri}" + Toast.makeText( + baseContext, msg, Toast.LENGTH_SHORT + ).show() + Log.d(TAG, msg) + } else { + recording?.close() + recording = null + Log.e(TAG, "video capture ends with error: ${recordEvent.error}") + } + } + } + } + } + // [END android_camerax_camera1_to_camerax_video_provider_record] +} diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt new file mode 100644 index 000000000..99e314714 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt @@ -0,0 +1,331 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.app.Application +import android.content.Context +import android.hardware.camera2.CameraCharacteristics +import android.os.Bundle +import android.util.Log +import android.util.Rational +import android.util.Size +import android.view.OrientationEventListener +import android.view.Surface +import android.view.SurfaceView +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.camera2.Camera2Config +import androidx.camera.camera2.interop.Camera2CameraInfo +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.camera.core.AspectRatio +import androidx.camera.core.Camera +import androidx.camera.core.CameraControl +import androidx.camera.core.CameraSelector +import androidx.camera.core.CameraXConfig +import androidx.camera.core.ConcurrentCamera +import androidx.camera.core.DisplayOrientedMeteringPointFactory +import androidx.camera.core.FocusMeteringAction +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageCapture +import androidx.camera.core.Preview +import androidx.camera.core.SurfaceOrientedMeteringPointFactory +import androidx.camera.core.UseCaseGroup +import androidx.camera.core.ViewPort +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner +import java.util.concurrent.Executor +import java.util.concurrent.TimeUnit + +private fun imageCaptureBuilderSnippet() { + // [START android_camerax_configuration_image_capture_builder] + val imageCapture = ImageCapture.Builder() + .setFlashMode(ImageCapture.FLASH_MODE_AUTO) + .setTargetAspectRatio(AspectRatio.RATIO_4_3) + .build() + // [END android_camerax_configuration_image_capture_builder] +} + +// [START android_camerax_configuration_logging] +class CameraApplication : Application(), CameraXConfig.Provider { + override fun getCameraXConfig(): CameraXConfig { + return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) + .setMinimumLoggingLevel(Log.ERROR).build() + } +} +// [END android_camerax_configuration_logging] + +// [START android_camerax_configuration_camera_limiter] +class MainApplication : Application(), CameraXConfig.Provider { + override fun getCameraXConfig(): CameraXConfig { + return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) + .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) + .build() + } +} +// [END android_camerax_configuration_camera_limiter] + +private class ConfigurationOrientationActivity : AppCompatActivity() { + // [START android_camerax_configuration_orientation_listener] + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val imageCapture = ImageCapture.Builder().build() + + val orientationEventListener = object : OrientationEventListener(this as Context) { + override fun onOrientationChanged(orientation: Int) { + // Monitors orientation values to determine the target rotation value + val rotation: Int = when (orientation) { + in 45..134 -> Surface.ROTATION_270 + in 135..224 -> Surface.ROTATION_180 + in 225..314 -> Surface.ROTATION_90 + else -> Surface.ROTATION_0 + } + + imageCapture.targetRotation = rotation + } + } + orientationEventListener.enable() + } + // [END android_camerax_configuration_orientation_listener] +} + +private fun cropRectSnippet( + cameraProvider: ProcessCameraProvider, + lifecycleOwner: LifecycleOwner, + cameraSelector: CameraSelector, + preview: Preview, + imageAnalysis: ImageAnalysis, + imageCapture: ImageCapture, + width: Int, + height: Int, + display: android.view.Display, +) { + // [START android_camerax_configuration_crop_rect] + val viewPort = ViewPort.Builder(Rational(width, height), display.rotation).build() + val useCaseGroup = UseCaseGroup.Builder() + .addUseCase(preview) + .addUseCase(imageAnalysis) + .addUseCase(imageCapture) + .setViewPort(viewPort) + .build() + cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup) + // [END android_camerax_configuration_crop_rect] +} + +private class GetViewPortActivity : AppCompatActivity() { + private fun getViewportSnippet() { + // [START android_camerax_configuration_get_viewport] + val viewport = findViewById(R.id.preview_view).viewPort + // [END android_camerax_configuration_get_viewport] + } +} + +@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) +// [START android_camerax_configuration_select_external_camera] +fun selectExternalOrBestCamera(provider: ProcessCameraProvider): CameraSelector? { + val cam2Infos = provider.availableCameraInfos.map { + Camera2CameraInfo.from(it) + }.sortedByDescending { + // HARDWARE_LEVEL is Int type, with the order of: + // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL + it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) + } + + return when { + cam2Infos.isNotEmpty() -> { + CameraSelector.Builder() + .addCameraFilter { + it.filter { camInfo -> + // cam2Infos[0] is either EXTERNAL or best built-in camera + val thisCamId = Camera2CameraInfo.from(camInfo).cameraId + thisCamId == cam2Infos[0].cameraId + } + }.build() + } + else -> null + } +} +// [END android_camerax_configuration_select_external_camera] + +private fun bindSelectedExternalCamera( + processCameraProvider: ProcessCameraProvider, + lifecycleOwner: LifecycleOwner, + preview: Preview, + analysis: ImageAnalysis, +) { + // [START_EXCLUDE] + val selector = selectExternalOrBestCamera(processCameraProvider) ?: return + processCameraProvider.bindToLifecycle(lifecycleOwner, selector, preview, analysis) + // [END_EXCLUDE] +} + +private fun concurrentCameraSnippet( + cameraProvider: ProcessCameraProvider, + primaryCameraSelector: CameraSelector, + secondaryCameraSelector: CameraSelector, + useCaseGroup: UseCaseGroup, + lifecycleOwner: LifecycleOwner, +) { + // [START android_camerax_configuration_concurrent_camera] + // Build ConcurrentCameraConfig + val primary = ConcurrentCamera.SingleCameraConfig( + primaryCameraSelector, + useCaseGroup, + lifecycleOwner + ) + + val secondary = ConcurrentCamera.SingleCameraConfig( + secondaryCameraSelector, + useCaseGroup, + lifecycleOwner + ) + + val concurrentCamera = cameraProvider.bindToLifecycle( + listOf(primary, secondary) + ) + + val primaryCamera = concurrentCamera.cameras[0] + val secondaryCamera = concurrentCamera.cameras[1] + // [END android_camerax_configuration_concurrent_camera] +} + +private fun targetResolutionSnippet() { + // [START android_camerax_configuration_target_resolution] + val imageAnalysis = ImageAnalysis.Builder() + .setTargetResolution(Size(1280, 720)) + .build() + // [END android_camerax_configuration_target_resolution] +} + +private fun cameraControlCameraInfoSnippet( + processCameraProvider: ProcessCameraProvider, + lifecycleOwner: LifecycleOwner, + cameraSelector: CameraSelector, + preview: Preview, +) { + // [START android_camerax_configuration_cameracontrol_camerainfo] + val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) + + // For performing operations that affect all outputs. + val cameraControl = camera.cameraControl + // For querying information and states. + val cameraInfo = camera.cameraInfo + // [END android_camerax_configuration_cameracontrol_camerainfo] +} + +private fun meteringPointFactorySnippet( + previewView: PreviewView, + surfaceView: SurfaceView, + camera: Camera, + imageAnalysis: ImageAnalysis, + imageWidth: Int, + imageHeight: Int, +) { + // [START android_camerax_configuration_metering_point_factory] + // Use PreviewView.getMeteringPointFactory if PreviewView is used for preview. + previewView.setOnTouchListener { view, motionEvent -> + val meteringPoint = previewView.meteringPointFactory + .createPoint(motionEvent.x, motionEvent.y) + /* [START_EXCLUDE silent] */ + true + /* [END_EXCLUDE] */ + } + + // Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for + // preview. Please note that if the preview is scaled or cropped in the View, + // it’s the application's responsibility to transform the coordinates properly + // so that the width and height of this factory represents the full Preview FOV. + // And the (x,y) passed to create MeteringPoint might need to be adjusted with + // the offsets. + val meteringPointFactory = DisplayOrientedMeteringPointFactory( + surfaceView.display, + camera.cameraInfo, + surfaceView.width.toFloat(), + surfaceView.height.toFloat() + ) + + // Use SurfaceOrientedMeteringPointFactory if the point is specified in + // ImageAnalysis ImageProxy. + val meteringPointFactoryAnalysis = SurfaceOrientedMeteringPointFactory( + imageWidth.toFloat(), + imageHeight.toFloat(), + imageAnalysis + ) + // [END android_camerax_configuration_metering_point_factory] +} + +private class FocusMeteringActivity : AppCompatActivity() { + private fun focusMeteringSnippet( + meteringPointFactory: DisplayOrientedMeteringPointFactory, + cameraControl: CameraControl, + x1: Float, + y1: Float, + x2: Float, + y2: Float, + ) { + // [START android_camerax_configuration_focus_metering_action] + val meteringPoint1 = meteringPointFactory.createPoint(x1, y1) + val meteringPoint2 = meteringPointFactory.createPoint(x2, y2) + val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB + // Optionally add meteringPoint2 for AF/AE. + .addPoint(meteringPoint2, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE) + // The action is canceled in 3 seconds (if not set, default is 5s). + .setAutoCancelDuration(3, TimeUnit.SECONDS) + .build() + + val result = cameraControl.startFocusAndMetering(action) + // Adds listener to the ListenableFuture if you need to know the focusMetering result. + result.addListener({ + // result.get().isFocusSuccessful returns if the auto focus is successful or not. + }, ContextCompat.getMainExecutor(this)) + // [END android_camerax_configuration_focus_metering_action] + } + + private fun exposureCompensationSnippet( + camera: Camera, + exposureCompensationIndex: Int, + mainExecutor: Executor, + ) { + // [START android_camerax_configuration_exposure_compensation] + camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex) + .addListener({ + // Get the current exposure compensation index, it might be + // different from the asked value in case this request was + // canceled by a newer setting request. + val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex + /* [START_EXCLUDE silent] */ + // [END_EXCLUDE] */ + }, mainExecutor) + // [END android_camerax_configuration_exposure_compensation] + } + + private fun seekBarSnippet( + camera: Camera, + binding: Any, + ) { + val seekBar = android.widget.SeekBar(this) + // [START android_camerax_configuration_exposure_seekbar] + val exposureState = camera.cameraInfo.exposureState + seekBar.apply { + isEnabled = exposureState.isExposureCompensationSupported + max = exposureState.exposureCompensationRange.upper + min = exposureState.exposureCompensationRange.lower + progress = exposureState.exposureCompensationIndex + } + // [END android_camerax_configuration_exposure_seekbar] + } +} diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt new file mode 100644 index 000000000..4e1a965b9 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCapture +import androidx.camera.core.Preview +import androidx.camera.extensions.ExtensionMode +import androidx.camera.extensions.ExtensionsManager +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.core.content.ContextCompat + +private const val TAG = "ExtensionsApi" + +private class ExtensionsActivity : AppCompatActivity() { + private lateinit var surfaceProvider: Preview.SurfaceProvider + + // [START android_camerax_extensions_enable_night] + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val lifecycleOwner = this + + val cameraProviderFuture = ProcessCameraProvider.getInstance(applicationContext) + cameraProviderFuture.addListener({ + // Obtain an instance of a process camera provider + // The camera provider provides access to the set of cameras associated with the device. + // The camera obtained from the provider will be bound to the activity lifecycle. + val cameraProvider = cameraProviderFuture.get() + + val extensionsManagerFuture = + ExtensionsManager.getInstanceAsync(applicationContext, cameraProvider) + extensionsManagerFuture.addListener({ + // Obtain an instance of the extensions manager + // The extensions manager enables a camera to use extension capabilities available on + // the device. + val extensionsManager = extensionsManagerFuture.get() + + // Select the camera + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + // Query if extension is available. + // Not all devices will support extensions or might only support a subset of + // extensions. + if (extensionsManager.isExtensionAvailable(cameraSelector, ExtensionMode.NIGHT)) { + // Unbind all use cases before enabling different extension modes. + try { + cameraProvider.unbindAll() + + // Retrieve a night extension enabled camera selector + val nightCameraSelector = + extensionsManager.getExtensionEnabledCameraSelector( + cameraSelector, + ExtensionMode.NIGHT + ) + + // Bind image capture and preview use cases with the extension enabled camera + // selector. + val imageCapture = ImageCapture.Builder().build() + val preview = Preview.Builder().build() + // Connect the preview to receive the surface the camera outputs the frames + // to. This will allow displaying the camera frames in either a TextureView + // or SurfaceView. The SurfaceProvider can be obtained from the PreviewView. + preview.setSurfaceProvider(surfaceProvider) + + // Returns an instance of the camera bound to the lifecycle + // Use this camera object to control various operations with the camera + // Example: flash, zoom, focus metering etc. + val camera = cameraProvider.bindToLifecycle( + lifecycleOwner, + nightCameraSelector, + imageCapture, + preview + ) + } catch (e: Exception) { + Log.e(TAG, "Use case binding failed", e) + } + } + }, ContextCompat.getMainExecutor(this)) + }, ContextCompat.getMainExecutor(this)) + } + // [END android_camerax_extensions_enable_night] +} diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt b/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt new file mode 100644 index 000000000..323e99773 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.content.Context +import androidx.camera.mlkit.vision.MlKitAnalyzer +import androidx.camera.view.CameraController +import androidx.camera.view.CameraController.COORDINATE_SYSTEM_VIEW_REFERENCED +import androidx.core.content.ContextCompat +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode + +private fun mlKitAnalyzerSnippet( + cameraController: CameraController, + context: Context, +) { + // [START android_camerax_mlkitanalyzer_qrcode] + // create BarcodeScanner object + val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE) + .build() + val barcodeScanner = BarcodeScanning.getClient(options) + + cameraController.setImageAnalysisAnalyzer( + ContextCompat.getMainExecutor(context), + MlKitAnalyzer( + listOf(barcodeScanner), + COORDINATE_SYSTEM_VIEW_REFERENCED, + ContextCompat.getMainExecutor(context) + ) { result: MlKitAnalyzer.Result? -> + // The value of result.getResult(barcodeScanner) can be used directly for drawing UI overlay. + } + ) + // [END android_camerax_mlkitanalyzer_qrcode] +} diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt new file mode 100644 index 000000000..a44b1af88 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt @@ -0,0 +1,183 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.content.ContentResolver +import android.content.Context +import android.hardware.display.DisplayManager +import android.os.Bundle +import android.view.OrientationEventListener +import android.view.Surface +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview +import androidx.camera.core.impl.utils.Exif +import androidx.camera.lifecycle.ProcessCameraProvider +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.util.concurrent.Executor + +private fun imageAnalysisRotationSnippet(imageProxy: ImageProxy) { + // [START android_camerax_orientation_rotation_image_analysis] + val rotation = imageProxy.imageInfo.rotationDegrees + // [END android_camerax_orientation_rotation_image_analysis] +} + +private fun imageProxyRotationSnippet(imageProxy: ImageProxy) { + // [START android_camerax_orientation_rotation_image_proxy] + val rotation = imageProxy.imageInfo.rotationDegrees + // [END android_camerax_orientation_rotation_image_proxy] +} + +private fun fileRotationSnippet(file: File) { + // [START android_camerax_orientation_rotation_file] + val exif = Exif.createFromFile(file) + val rotation = exif.rotation + // [END android_camerax_orientation_rotation_file] +} + +private fun outputStreamRotationSnippet(outputStream: ByteArrayOutputStream) { + // [START android_camerax_orientation_rotation_output_stream] + val byteArray = outputStream.toByteArray() + val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) + val rotation = exif.rotation + // [END android_camerax_orientation_rotation_output_stream] +} + +private fun mediaStoreRotationSnippet( + contentResolver: ContentResolver, + outputFileResults: ImageCapture.OutputFileResults, +) { + // [START android_camerax_orientation_rotation_mediastore] + val inputStream = contentResolver.openInputStream(outputFileResults.savedUri ?: return) + val exif = Exif.createFromInputStream(inputStream ?: return) + val rotation = exif.rotation + // [END android_camerax_orientation_rotation_mediastore] +} + +// [START android_camerax_orientation_rotation_use_cases_setup] +private class OrientationCameraActivity : AppCompatActivity() { + private lateinit var mainExecutor: Executor + private lateinit var cameraSelector: CameraSelector + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val cameraProcessFuture = ProcessCameraProvider.getInstance(this) + cameraProcessFuture.addListener( + Runnable { + val cameraProvider = cameraProcessFuture.get() + + // By default, the use cases set their target rotation to match the + // display’s rotation. + val preview = buildPreview() + val imageAnalysis = buildImageAnalysis() + val imageCapture = buildImageCapture() + + cameraProvider.bindToLifecycle( + this, cameraSelector, preview, imageAnalysis, imageCapture + ) + }, + mainExecutor + ) + } + + /* [START_EXCLUDE silent] */ + private fun buildPreview(): Preview = Preview.Builder().build() + private fun buildImageAnalysis(): ImageAnalysis = ImageAnalysis.Builder().build() + private fun buildImageCapture(): ImageCapture = ImageCapture.Builder().build() + /* [END_EXCLUDE] */ +} +// [END android_camerax_orientation_rotation_use_cases_setup] + +// [START android_camerax_orientation_rotation_orientation_listener] +private class OrientationListenerActivity : AppCompatActivity() { + private lateinit var imageAnalysis: ImageAnalysis + private lateinit var imageCapture: ImageCapture + + private val orientationEventListener by lazy { + object : OrientationEventListener(this) { + override fun onOrientationChanged(orientation: Int) { + if (orientation == ORIENTATION_UNKNOWN) { + return + } + + val rotation = when (orientation) { + in 45 until 135 -> Surface.ROTATION_270 + in 135 until 225 -> Surface.ROTATION_180 + in 225 until 315 -> Surface.ROTATION_90 + else -> Surface.ROTATION_0 + } + + imageAnalysis.targetRotation = rotation + imageCapture.targetRotation = rotation + } + } + } + + override fun onStart() { + super.onStart() + orientationEventListener.enable() + } + + override fun onStop() { + super.onStop() + orientationEventListener.disable() + } +} +// [END android_camerax_orientation_rotation_orientation_listener] + +// [START android_camerax_orientation_rotation_display_listener] +private class DisplayListenerActivity : AppCompatActivity() { + private lateinit var rootView: View + private lateinit var imageAnalysis: ImageAnalysis + private lateinit var imageCapture: ImageCapture + + private val displayListener = object : DisplayManager.DisplayListener { + override fun onDisplayChanged(displayId: Int) { + if (rootView.display.displayId == displayId) { + val rotation = rootView.display.rotation + imageAnalysis.targetRotation = rotation + imageCapture.targetRotation = rotation + } + } + + override fun onDisplayAdded(displayId: Int) { + } + + override fun onDisplayRemoved(displayId: Int) { + } + } + + override fun onStart() { + super.onStart() + val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + displayManager.registerDisplayListener(displayListener, null) + } + + override fun onStop() { + super.onStop() + val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + displayManager.unregisterDisplayListener(displayListener) + } +} +// [END android_camerax_orientation_rotation_display_listener] diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt new file mode 100644 index 000000000..d3fcabc9a --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.CameraSelector +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner +import com.google.common.util.concurrent.ListenableFuture + +// [START android_camerax_preview_request_provider] +private class MainActivity : AppCompatActivity() { + private lateinit var cameraProviderFuture: ListenableFuture + override fun onCreate(savedInstanceState: Bundle?) { + cameraProviderFuture = ProcessCameraProvider.getInstance(this) + /* [START_EXCLUDE silent] */ + checkProviderSnippet(cameraProviderFuture) + /* [END_EXCLUDE] */ + } +// [END android_camerax_preview_request_provider] + + private fun checkProviderSnippet(cameraProviderFuture: ListenableFuture) { + // [START android_camerax_preview_check_provider] + cameraProviderFuture.addListener( + Runnable { + val cameraProvider = cameraProviderFuture.get() + bindPreview(cameraProvider) + }, + ContextCompat.getMainExecutor(this) + ) + // [END android_camerax_preview_check_provider] + } + + private lateinit var previewView: PreviewView + + // [START android_camerax_preview_bind] + fun bindPreview(cameraProvider: ProcessCameraProvider) { + var preview: Preview = Preview.Builder() + .build() + + var cameraSelector: CameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + + preview.setSurfaceProvider(previewView.surfaceProvider) + + var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview) + } + // [END android_camerax_preview_bind] +} + +private fun previewControlsSnippet(viewFinder: PreviewView) { + // [START android_camerax_preview_implementation_mode] + // viewFinder is a PreviewView instance + viewFinder.implementationMode = PreviewView.ImplementationMode.COMPATIBLE + // [END android_camerax_preview_implementation_mode] + + // [START android_camerax_preview_scale_type] + // viewFinder is a PreviewView instance + viewFinder.scaleType = PreviewView.ScaleType.FIT_CENTER + // [END android_camerax_preview_scale_type] +} diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt b/camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt new file mode 100644 index 000000000..215b79d57 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.graphics.Matrix +import androidx.camera.core.ImageProxy +import androidx.camera.view.PreviewView + +// [START android_camerax_transform_output_get_correction_matrix] +fun getCorrectionMatrix(imageProxy: ImageProxy, previewView: PreviewView): Matrix { + val cropRect = imageProxy.cropRect + val rotationDegrees = imageProxy.imageInfo.rotationDegrees + val matrix = Matrix() + + // A float array of the source vertices (crop rect) in clockwise order. + val source = floatArrayOf( + cropRect.left.toFloat(), + cropRect.top.toFloat(), + cropRect.right.toFloat(), + cropRect.top.toFloat(), + cropRect.right.toFloat(), + cropRect.bottom.toFloat(), + cropRect.left.toFloat(), + cropRect.bottom.toFloat() + ) + + // A float array of the destination vertices in clockwise order. + val destination = floatArrayOf( + 0f, + 0f, + previewView.width.toFloat(), + 0f, + previewView.width.toFloat(), + previewView.height.toFloat(), + 0f, + previewView.height.toFloat() + ) + + // The destination vertexes need to be shifted based on rotation degrees. The + // rotation degree represents the clockwise rotation needed to correct the image. + + // Each vertex is represented by 2 float numbers in the vertices array. + val vertexSize = 2 + // The destination needs to be shifted 1 vertex for every 90° rotation. + val shiftOffset = rotationDegrees / 90 * vertexSize + val tempArray = destination.clone() + for (toIndex in source.indices) { + val fromIndex = (toIndex + shiftOffset) % source.size + destination[toIndex] = tempArray[fromIndex] + } + matrix.setPolyToPoly(source, 0, destination, 0, 4) + return matrix +} +// [END android_camerax_transform_output_get_correction_matrix] diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt new file mode 100644 index 000000000..ad44ab88a --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.camerax.snippets + +import android.content.ContentValues +import android.content.Context +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraMetadata +import android.os.Build +import android.provider.MediaStore +import android.util.Log +import android.widget.ArrayAdapter +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.camera2.interop.Camera2CameraInfo +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.camera.core.CameraSelector +import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY +import androidx.camera.core.Preview +import androidx.camera.core.UseCase +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.video.FallbackStrategy +import androidx.camera.video.MediaStoreOutputOptions +import androidx.camera.video.Quality +import androidx.camera.video.QualitySelector +import androidx.camera.video.Recorder +import androidx.camera.video.Recording +import androidx.camera.video.VideoCapture +import androidx.camera.video.VideoRecordEvent +import androidx.core.content.ContextCompat +import androidx.core.util.Consumer +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.Executor + +private const val TAG = "VideoCapture" + +private fun qualitySelectorSnippet() { + // [START android_camerax_video_capture_quality_selector] + val qualitySelector = QualitySelector.fromOrderedList( + listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD), + FallbackStrategy.lowerQualityOrHigherThan(Quality.SD)) + // [END android_camerax_video_capture_quality_selector] +} + +private class VideoCapabilitiesActivity : AppCompatActivity() { + private lateinit var viewBinding: Any + private lateinit var cameraProvider: ProcessCameraProvider + + private fun Quality.qualityToString(): String { + return when (this) { + Quality.UHD -> "UHD" + Quality.FHD -> "FHD" + Quality.HD -> "HD" + Quality.SD -> "SD" + else -> throw IllegalArgumentException() + } + } + + @androidx.annotation.OptIn(ExperimentalCamera2Interop::class) + private fun queryCapabilitiesSnippet(context: Context) { + val simpleQualityListView = android.widget.ListView(context) + // [START android_camerax_video_capture_query_capabilities] + val cameraInfo = cameraProvider.availableCameraInfos.filter { + Camera2CameraInfo + .from(it) + .getCameraCharacteristic(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_BACK + } + + val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0]) + val filteredQualities = (arrayListOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD) as List) + .filter { supportedQualities.contains(it) } + + // Use a simple ListView with the id of simple_quality_list_view + simpleQualityListView.apply { + adapter = ArrayAdapter(context, + android.R.layout.simple_list_item_1, + filteredQualities.map { it.qualityToString() }) + + // Set up the user interaction to manually show or hide the system UI. + setOnItemClickListener { _, _, position, _ -> + // Inside View.OnClickListener, + // convert Quality.* constant to QualitySelector + val qualitySelector = QualitySelector.from(filteredQualities[position]) + + // Create a new Recorder/VideoCapture for the new quality + // and bind to lifecycle + val recorder = Recorder.Builder() + .setQualitySelector(qualitySelector).build() + + // ... + } + } + + // A helper function to translate Quality to a string + fun Quality.qualityToString() : String { + return when (this) { + Quality.UHD -> "UHD" + Quality.FHD -> "FHD" + Quality.HD -> "HD" + Quality.SD -> "SD" + else -> throw IllegalArgumentException() + } + } + // [END android_camerax_video_capture_query_capabilities] + } +} + +private class VideoCaptureBindActivity : AppCompatActivity() { + private lateinit var cameraExecutor: Executor + private lateinit var qualitySelector: QualitySelector + private lateinit var cameraProvider: ProcessCameraProvider + private lateinit var preview: Preview + + private fun bindVideoCaptureSnippet() { + // [START android_camerax_video_capture_bind] + val recorder = Recorder.Builder() + .setExecutor(cameraExecutor).setQualitySelector(qualitySelector) + .build() + val videoCapture = VideoCapture.withOutput(recorder) + + try { + // Bind use cases to camera + cameraProvider.bindToLifecycle( + this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture) + } catch(exc: Exception) { + Log.e(TAG, "Use case binding failed", exc) + } + // [END android_camerax_video_capture_bind] + } +} + +private class MediaStoreRecordActivity : AppCompatActivity() { + private lateinit var videoCapture: VideoCapture + private lateinit var captureListener: Consumer + private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" + + private fun recordSnippet(context: Context): Recording { + // [START android_camerax_video_capture_mediastore] + // Create MediaStoreOutputOptions for our recorder + val name = "CameraX-recording-" + + SimpleDateFormat(FILENAME_FORMAT, Locale.US) + .format(System.currentTimeMillis()) + ".mp4" + val contentValues = ContentValues().apply { + put(MediaStore.Video.Media.DISPLAY_NAME, name) + } + val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI) + .setContentValues(contentValues) + .build() + + // 2. Configure Recorder and Start recording to the mediaStoreOutput. + val recording = videoCapture.output + .prepareRecording(context, mediaStoreOutput) + .withAudioEnabled() + .start(ContextCompat.getMainExecutor(this), captureListener) + // [END android_camerax_video_capture_mediastore] + return recording + } +} + +private fun mirrorModeSnippet(useCases: MutableList) { + // [START android_camerax_video_capture_mirror_mode] + val recorder = Recorder.Builder().build() + + val videoCapture = VideoCapture.Builder(recorder) + .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY) + .build() + + useCases.add(videoCapture) + // [END android_camerax_video_capture_mirror_mode] +} diff --git a/camera/src/main/res/layout/preview_view.xml b/camera/src/main/res/layout/preview_view.xml new file mode 100644 index 000000000..a53388df8 --- /dev/null +++ b/camera/src/main/res/layout/preview_view.xml @@ -0,0 +1,32 @@ + + + + + + + + From 9d697331272215775092dc40ad4254d4d875db28 Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Fri, 25 Sep 2026 20:13:56 +0000 Subject: [PATCH 2/5] Move CameraX snippets into :camera and use version catalog - Update package to com.example.camera.snippets.camerax after the :camerax -> :camera module rename (#1077). - Declare camera-mlkit-vision and mlkit barcode-scanning in the version catalog instead of hardcoded coordinates. - Remove lint abortOnError = false. - Replace the tagged preview_view.xml with an untagged activity_main.xml supporting layout (forced by R.layout.activity_main, R.id.previewView and R.id.preview_view in page code). --- camera/build.gradle.kts | 7 ++----- .../example/camera/snippets/camerax/Analyze.kt | 2 +- .../camera/snippets/camerax/Architecture.kt | 2 +- .../snippets/camerax/Camera1ToCameraX.kt | 2 +- .../camera/snippets/camerax/Configuration.kt | 2 +- .../camera/snippets/camerax/ExtensionsApi.kt | 2 +- .../camera/snippets/camerax/MlKitAnalyzer.kt | 2 +- .../snippets/camerax/OrientationRotation.kt | 2 +- .../example/camera/snippets/camerax/Preview.kt | 2 +- .../camera/snippets/camerax/TransformOutput.kt | 2 +- .../camera/snippets/camerax/VideoCapture.kt | 2 +- .../{preview_view.xml => activity_main.xml} | 18 ++++++++---------- gradle/libs.versions.toml | 3 +++ 13 files changed, 23 insertions(+), 25 deletions(-) rename camera/src/main/res/layout/{preview_view.xml => activity_main.xml} (65%) diff --git a/camera/build.gradle.kts b/camera/build.gradle.kts index 54f4c1460..be16c17b4 100644 --- a/camera/build.gradle.kts +++ b/camera/build.gradle.kts @@ -53,9 +53,6 @@ android { buildFeatures { compose = true } - lint { - abortOnError = false - } } dependencies { @@ -76,15 +73,15 @@ dependencies { implementation(libs.androidx.camera.compose) implementation(libs.androidx.camera.viewfinder.compose) implementation(libs.androidx.camera.lifecycle) + implementation(libs.androidx.camera.mlkit.vision) implementation(libs.androidx.camera.camera2) implementation(libs.androidx.concurrent.futures.ktx) implementation(libs.androidx.camera.extensions) implementation(libs.androidx.camera.video) implementation(libs.androidx.camera.effects) implementation(libs.androidx.media3.effect) + implementation(libs.mlkit.barcode.scanning) implementation(libs.mlkit.face.detection) - implementation("androidx.camera:camera-mlkit-vision:${libs.versions.androidx.cameraX.get()}") - implementation("com.google.mlkit:barcode-scanning:17.3.0") implementation(libs.androidx.window) implementation(libs.play.services.wearable) implementation(libs.mockito.kotlin) diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt index 42cd0d1d2..d17fea89f 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.util.Size import androidx.camera.core.CameraSelector diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt index e0cd708a6..6551d4c97 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.content.Context import android.hardware.camera2.CameraCharacteristics diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt index eb95549a3..f34c2209b 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.content.ContentValues import android.content.Context diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt index 99e314714..c3eba78c4 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.app.Application import android.content.Context diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt index 4e1a965b9..c0b146d82 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.os.Bundle import android.util.Log diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt b/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt index 323e99773..cbb18d92d 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.content.Context import androidx.camera.mlkit.vision.MlKitAnalyzer diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt index a44b1af88..3b7cfda0a 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.content.ContentResolver import android.content.Context diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt index d3fcabc9a..2b6f0d802 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.os.Bundle import androidx.appcompat.app.AppCompatActivity diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt b/camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt index 215b79d57..5b5426a9f 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/TransformOutput.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.graphics.Matrix import androidx.camera.core.ImageProxy diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt index ad44ab88a..bf142024a 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.camerax.snippets +package com.example.camera.snippets.camerax import android.content.ContentValues import android.content.Context diff --git a/camera/src/main/res/layout/preview_view.xml b/camera/src/main/res/layout/activity_main.xml similarity index 65% rename from camera/src/main/res/layout/preview_view.xml rename to camera/src/main/res/layout/activity_main.xml index a53388df8..a592fcf66 100644 --- a/camera/src/main/res/layout/preview_view.xml +++ b/camera/src/main/res/layout/activity_main.xml @@ -14,19 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + + - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80ac3bada..1e8ea815f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -88,6 +88,7 @@ media3Session = "1.11.1" media3Ui = "1.11.1" # @keep minSdk = "36" +mlkit-barcode-scanning = "17.3.0" mlkit-face-detection = "16.1.7" mlkit-genai-prompt = "1.0.0-beta4" mockitoKotlin = "5.3.1" @@ -150,6 +151,7 @@ androidx-camera-effects = { module = "androidx.camera:camera-effects", version.r androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "androidx-cameraX" } androidx-camera-ktx = { module = "androidx.camera:camera-ktx", version.ref = "androidx-cameraX" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-cameraX" } +androidx-camera-mlkit-vision = { module = "androidx.camera:camera-mlkit-vision", version.ref = "androidx-cameraX" } androidx-camera-video = { module = "androidx.camera:camera-video", version.ref = "androidx-cameraX" } androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-cameraX" } androidx-camera-viewfinder-compose = { module = "androidx.camera.viewfinder:viewfinder-compose", version.ref = "androidx-cameraX" } @@ -327,6 +329,7 @@ kotlinx-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", ve kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } mlkit-genai-prompt = { module = "com.google.mlkit:genai-prompt", version.ref = "mlkit-genai-prompt" } +mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlkit-barcode-scanning" } mlkit-face-detection = { module = "com.google.mlkit:face-detection", version.ref = "mlkit-face-detection" } mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } From 73a927a69144e32c49d26cde44b2d519caafcee4 Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Fri, 25 Sep 2026 22:07:12 +0000 Subject: [PATCH 3/5] Match CameraX snippets to DAC pages and fix compile issues - Restore page-exact code inside region tags (full stops, fill-in comments, instructional imports, placeholders). - Fix code that does not compile against CameraX 1.6.1. - Remove View Binding and restricted-API snippets that stay hardcoded. - Wrap same-page class collisions in private objects. --- .../camera/snippets/camerax/Analyze.kt | 46 +- .../camera/snippets/camerax/Architecture.kt | 161 +++--- .../snippets/camerax/Camera1ToCameraX.kt | 521 +++++++----------- .../camera/snippets/camerax/Configuration.kt | 202 ++++--- .../camera/snippets/camerax/ExtensionsApi.kt | 15 +- .../camera/snippets/camerax/MlKitAnalyzer.kt | 44 +- .../snippets/camerax/OrientationRotation.kt | 218 ++++---- .../camera/snippets/camerax/Preview.kt | 41 +- .../camera/snippets/camerax/VideoCapture.kt | 110 +--- 9 files changed, 564 insertions(+), 794 deletions(-) diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt index d17fea89f..bd9220d00 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt @@ -17,6 +17,7 @@ package com.example.camera.snippets.camerax import android.util.Size +import androidx.activity.ComponentActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.Preview @@ -24,33 +25,34 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.lifecycle.LifecycleOwner import java.util.concurrent.Executor -private fun bindImageAnalysis( - cameraProvider: ProcessCameraProvider, - cameraSelector: CameraSelector, - preview: Preview, - executor: Executor, - lifecycleOwner: LifecycleOwner, -) { - val imageAnalysis = +private class ImageAnalysisActivity : ComponentActivity() { + + @Suppress("DEPRECATION") + private fun bindImageAnalysis( + cameraProvider: ProcessCameraProvider, + cameraSelector: CameraSelector, + preview: Preview, + executor: Executor, + ) { // [START android_camerax_analyze_bind_lifecycle] - ImageAnalysis.Builder() + val imageAnalysis = ImageAnalysis.Builder() // enable the following line if RGBA output is needed. // .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() - imageAnalysis.setAnalyzer( - executor, - ImageAnalysis.Analyzer { imageProxy -> - val rotationDegrees = imageProxy.imageInfo.rotationDegrees - // insert your code here. - /* [START_EXCLUDE] */ - // [END_EXCLUDE] */ - // after done, release the ImageProxy object - imageProxy.close() - } - ) + imageAnalysis.setAnalyzer( + executor, + ImageAnalysis.Analyzer { imageProxy -> + val rotationDegrees = imageProxy.imageInfo.rotationDegrees + // Insert your code here + // ... + // after done, release the ImageProxy object. + imageProxy.close() + } + ) - cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageAnalysis, preview) - // [END android_camerax_analyze_bind_lifecycle] + cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview) + // [END android_camerax_analyze_bind_lifecycle] + } } diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt index 6551d4c97..0de23bd06 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt @@ -16,45 +16,30 @@ package com.example.camera.snippets.camerax -import android.content.Context import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraMetadata import android.os.Build import android.os.Bundle +import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.Camera2Interop import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.camera.core.Camera -import androidx.camera.core.CameraInfo import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.camera.view.LifecycleCameraController import androidx.camera.view.PreviewView import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle +import androidx.lifecycle.Lifecycle.State import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry +import com.example.camera.snippets.R private class ArchitectureActivity : AppCompatActivity() { - private class ViewBinding(val previewView: PreviewView) - - private fun cameracontrollerSnippet( - viewBinding: ViewBinding, - baseContext: Context, - ) { - // [START android_camerax_architecture_camera_controller] - val previewView: PreviewView = viewBinding.previewView - var cameraController = LifecycleCameraController(baseContext) - cameraController.bindToLifecycle(this) - cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - previewView.controller = cameraController - // [END android_camerax_architecture_camera_controller] - } - private fun cameraProviderPreviewSnippet( cameraProvider: ProcessCameraProvider, lifecycleOwner: LifecycleOwner, @@ -64,50 +49,50 @@ private class ArchitectureActivity : AppCompatActivity() { val preview = Preview.Builder().build() val viewFinder: PreviewView = findViewById(R.id.previewView) - // The use case is bound to an Android Lifecycle with the following code + // The use case is bound to an Android Lifecycle with the following code. val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) - // PreviewView creates a surface provider and is the recommended provider + // PreviewView creates a surface provider and is the recommended provider. preview.setSurfaceProvider(viewFinder.getSurfaceProvider()) // [END android_camerax_architecture_preview_provider] } } -// [START android_camerax_architecture_custom_lifecycle] -class CustomLifecycle : LifecycleOwner { - private val lifecycleRegistry: LifecycleRegistry +private object CustomLifecycleSnippet { + // [START android_camerax_architecture_custom_lifecycle] + class CustomLifecycle : LifecycleOwner { + private val lifecycleRegistry: LifecycleRegistry - init { - lifecycleRegistry = LifecycleRegistry(this) - lifecycleRegistry.markState(Lifecycle.State.CREATED) - } - /* [START_EXCLUDE silent] */ - /* [END_EXCLUDE] */ - fun doOnResume() { - lifecycleRegistry.markState(Lifecycle.State.RESUMED) + init { + lifecycleRegistry = LifecycleRegistry(this) + lifecycleRegistry.currentState = Lifecycle.State.CREATED + } + // ... + fun doOnResume() { + lifecycleRegistry.currentState = State.RESUMED + } + // ... + override val lifecycle: Lifecycle + get() = lifecycleRegistry } - /* [START_EXCLUDE silent] */ - /* [END_EXCLUDE] */ - override val lifecycle: Lifecycle - get() = lifecycleRegistry + // [END android_camerax_architecture_custom_lifecycle] } -// [END android_camerax_architecture_custom_lifecycle] private class ConcurrentUseCasesActivity : AppCompatActivity() { - private lateinit var previewView: PreviewView + private val previewView: PreviewView by lazy { findViewById(R.id.previewView) } // [START android_camerax_architecture_concurrent_use_cases] private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.preview_view) + setContentView(R.layout.activity_main) val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener( Runnable { - // Camera provider is now guaranteed to be available + // Camera provider is now guaranteed to be available. val cameraProvider = cameraProviderFuture.get() // Set up the preview use case to display camera preview. @@ -118,17 +103,17 @@ private class ConcurrentUseCasesActivity : AppCompatActivity() { .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() - // Choose the camera by requiring a lens facing + // Choose the camera by requiring a lens facing. val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() - // Attach use cases to the camera with the same lifecycle owner + // Attach use cases to the camera with the same lifecycle owner. val camera = cameraProvider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture ) - // Connect the preview use case to the previewView + // Connect the preview use case to the previewView. preview.setSurfaceProvider( previewView.getSurfaceProvider() ) @@ -154,51 +139,51 @@ fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider): Boolean { } // [END android_camerax_architecture_check_level_3] -private fun ProcessCameraProvider.getAvailableCameraInfos(): List = availableCameraInfos -private fun CameraInfo.getLensFacing(): Int = lensFacing -private val CameraInfo.cameraSelector: CameraSelector get() = CameraSelector.DEFAULT_FRONT_CAMERA +private class VideoCallStreamActivity : AppCompatActivity() { + private var camera: Camera? = null -@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) -private fun streamUseCaseSnippet( - cameraProvider: ProcessCameraProvider, - lifecycleOwner: LifecycleOwner, - screenAspectRatio: Int, - rotation: Int, -) { - var camera: Camera? = null - // [START android_camerax_architecture_video_call_stream] - // Set underlying Camera2 stream use case to optimize for video calls. - - val videoCallStreamId = - CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() - - // Check available CameraInfos to find the first one that supports - // the video call stream use case. - val frontCameraInfo = cameraProvider.getAvailableCameraInfos() - .first { cameraInfo -> - val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) - .getCameraCharacteristic( - CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES - )?.contains(videoCallStreamId) - val isFrontFacing = ( - cameraInfo.getLensFacing() == - CameraSelector.LENS_FACING_FRONT - ) - (isVideoCallStreamingSupported == true) && isFrontFacing - } - - val cameraSelector = frontCameraInfo.cameraSelector - - // Start with a Preview Builder. - val previewBuilder = Preview.Builder() - .setTargetAspectRatio(screenAspectRatio) - .setTargetRotation(rotation) - - // Use Camera2Interop.Extender to set the video call stream use case. - Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) - - // Bind the Preview UseCase and the corresponding CameraSelector. - val preview = previewBuilder.build() - camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) - // [END android_camerax_architecture_video_call_stream] + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @Suppress("DEPRECATION") + @androidx.annotation.OptIn(ExperimentalCamera2Interop::class) + private fun bindVideoCallPreview( + cameraProvider: ProcessCameraProvider, + screenAspectRatio: Int, + rotation: Int, + ) { + // [START android_camerax_architecture_video_call_stream] + // Set underlying Camera2 stream use case to optimize for video calls. + + val videoCallStreamId = + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() + + // Check available CameraInfos to find the first one that supports + // the video call stream use case. + val frontCameraInfo = cameraProvider.availableCameraInfos + .first { cameraInfo -> + val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) + .getCameraCharacteristic( + CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES + )?.contains(videoCallStreamId) + val isFrontFacing = ( + cameraInfo.getLensFacing() == + CameraSelector.LENS_FACING_FRONT + ) + (isVideoCallStreamingSupported == true) && isFrontFacing + } + + val cameraSelector = frontCameraInfo.cameraSelector + + // Start with a Preview Builder. + val previewBuilder = Preview.Builder() + .setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation) + + // Use Camera2Interop.Extender to set the video call stream use case. + Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) + + // Bind the Preview UseCase and the corresponding CameraSelector. + val preview = previewBuilder.build() + camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview) + // [END android_camerax_architecture_video_call_stream] + } } diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt index f34c2209b..5ff4dcc75 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt @@ -23,11 +23,13 @@ import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.util.Log +import android.view.GestureDetector import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent import android.view.ScaleGestureDetector import android.view.View import android.widget.Toast +import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.ExperimentalCamera2Interop @@ -40,50 +42,58 @@ import androidx.camera.core.Preview import androidx.camera.core.UseCase import androidx.camera.core.ZoomState import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.camera.video.MediaStoreOutputOptions import androidx.camera.video.Quality import androidx.camera.video.QualitySelector import androidx.camera.video.Recorder import androidx.camera.video.Recording import androidx.camera.video.VideoCapture -import androidx.camera.video.VideoRecordEvent import androidx.camera.view.CameraController +import androidx.camera.view.CameraController.VIDEO_CAPTURE import androidx.camera.view.LifecycleCameraController import androidx.camera.view.PreviewView import androidx.concurrent.futures.await import androidx.core.content.ContextCompat import androidx.core.view.GestureDetectorCompat -import androidx.lifecycle.LifecycleOwner +import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import java.io.File import java.text.SimpleDateFormat import java.util.Locale -import java.util.concurrent.Executor import kotlinx.coroutines.launch private const val TAG = "Camera1ToCameraX" -private class Camera1ToCameraXActivity : AppCompatActivity() { +private fun selectCameraWithController(baseContext: Context) { + // [START android_camerax_camera1_to_camerax_select_camera_controller] + // CameraX: select a camera with CameraController. - private fun selectCameraControllerSnippet(baseContext: Context) { - // [START android_camerax_camera1_to_camerax_select_camera_controller] - var cameraController = LifecycleCameraController(baseContext) - val selector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() - cameraController.cameraSelector = selector - // [END android_camerax_camera1_to_camerax_select_camera_controller] - } + var cameraController = LifecycleCameraController(baseContext) + val selector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() + cameraController.cameraSelector = selector + // [END android_camerax_camera1_to_camerax_select_camera_controller] +} +class Camera1ToCameraXSelectCameraFragment : Fragment() { // [START android_camerax_camera1_to_camerax_select_camera_provider] + // CameraX: select a camera with CameraProvider. + // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the preceding "Android development concepts" // section. private suspend fun startCamera() { - val cameraProvider = ProcessCameraProvider.getInstance(this).await() + val cameraProvider = ProcessCameraProvider.getInstance(requireContext()).await() - // Set up UseCases (more on UseCases in later scenarios) - var useCases: Array = /* [START_EXCLUDE silent] */ emptyArray() /* [END_EXCLUDE] */ + // Set up UseCases (more on UseCases in later scenarios). + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + var useCases:Array = ... + // [START_EXCLUDE silent] + */ + var useCases: Array = emptyArray() + // [END_EXCLUDE] // Set the cameraSelector to use the default front-facing (selfie) // camera. @@ -99,148 +109,101 @@ private class Camera1ToCameraXActivity : AppCompatActivity() { var camera = cameraProvider.bindToLifecycle( this, cameraSelector, *useCases) - } catch(exc: Exception) { + } catch (exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } } - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // ... + // Call startCamera in the setup flow of your app, such as in onViewCreated. - fun onViewCreated(view: View, savedInstanceState: Bundle?) { - // [START_EXCLUDE silent] - // [END_EXCLUDE] + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // ... + lifecycleScope.launch { startCamera() } } // [END android_camerax_camera1_to_camerax_select_camera_provider] - - @androidx.annotation.OptIn(ExperimentalCamera2Interop::class) - private fun focalLengthSnippet(cameraProvider: ProcessCameraProvider) { - // [START android_camerax_camera1_to_camerax_focal_length] - val cameraInfo = cameraProvider.availableCameraInfos - .first { cameraInfo -> - val focalLengths = Camera2CameraInfo.from(cameraInfo) - .getCameraCharacteristic( - CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS - ) - return@first checkFocalLength(focalLengths) - } - val cameraSelector = cameraInfo.cameraSelector - // [END android_camerax_camera1_to_camerax_focal_length] - } - - private fun checkFocalLength(focalLengths: FloatArray?): Boolean = true -} - -private class ActivityMainBinding(val cameraPreview: PreviewView) { - val root: View = cameraPreview - companion object { - fun inflate(inflater: Any): ActivityMainBinding = ActivityMainBinding(PreviewView(null as Context? ?: error(""))) - } } -// [START android_camerax_camera1_to_camerax_preview_controller] -private class Camera1MainActivity : AppCompatActivity() { - private lateinit var viewBinding: ActivityMainBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ - setContentView(R.layout.preview_view) - - // Create the CameraController and set it on the previewView. - var cameraController = LifecycleCameraController(baseContext) - cameraController.bindToLifecycle(this) - val previewView: PreviewView = viewBinding.cameraPreview - previewView.controller = cameraController - } +@OptIn(ExperimentalCamera2Interop::class) +private fun selectCameraByFocalLength(cameraProvider: ProcessCameraProvider) { + // [START android_camerax_camera1_to_camerax_focal_length] + // CameraX: get a cameraSelector for first camera that matches the criteria + // defined in checkFocalLength(). + + val cameraInfo = cameraProvider.availableCameraInfos + .first { cameraInfo -> + val focalLengths = Camera2CameraInfo.from(cameraInfo) + .getCameraCharacteristic( + CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS + ) + return@first checkFocalLength(focalLengths) + } + val cameraSelector = cameraInfo.cameraSelector + // [END android_camerax_camera1_to_camerax_focal_length] } -// [END android_camerax_camera1_to_camerax_preview_controller] - -private class PreviewProviderActivity : AppCompatActivity() { - private lateinit var previewView: PreviewView - // [START android_camerax_camera1_to_camerax_preview_provider] - // Use await() within a suspend function to get CameraProvider instance. - // For more details on await(), see the preceding "Android development concepts" - // section. - private suspend fun startCamera() { - val cameraProvider = ProcessCameraProvider.getInstance(this).await() +private class ControllerStateObserversActivity : AppCompatActivity() { + private lateinit var cameraController: LifecycleCameraController - // Create Preview UseCase. - val preview = Preview.Builder() - .build() - .also { - it.setSurfaceProvider( - previewView.surfaceProvider - ) + @Suppress("DEPRECATION") + private fun observeTapToFocusState() { + // [START android_camerax_camera1_to_camerax_tap_to_focus_controller] + // CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView, + // with handlers you can define for focused, not focused, and failed states. + + val tapToFocusStateObserver = Observer { state -> + when (state) { + CameraController.TAP_TO_FOCUS_NOT_STARTED -> + Log.d(TAG, "tap-to-focus init") + CameraController.TAP_TO_FOCUS_STARTED -> + Log.d(TAG, "tap-to-focus started") + CameraController.TAP_TO_FOCUS_FOCUSED -> + Log.d(TAG, "tap-to-focus finished (focus successful)") + CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> + Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") + CameraController.TAP_TO_FOCUS_FAILED -> + Log.d(TAG, "tap-to-focus failed") } - - // Select default back camera. - val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - - try { - // Unbind UseCases before rebinding. - cameraProvider.unbindAll() - - // Bind UseCases to camera. This function returns a camera - // object which can be used to perform operations like zoom, - // flash, and focus. - var camera = cameraProvider.bindToLifecycle( - this, cameraSelector, preview) - - } catch(exc: Exception) { - Log.e(TAG, "UseCase binding failed", exc) } - } - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ - // Call startCamera() in the setup flow of your app, such as in onViewCreated. - fun onViewCreated(view: View, savedInstanceState: Bundle?) { - // [START_EXCLUDE silent] - // [END_EXCLUDE] - lifecycleScope.launch { - startCamera() - } + cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver) + // [END android_camerax_camera1_to_camerax_tap_to_focus_controller] } - // [END android_camerax_camera1_to_camerax_preview_provider] -} -private fun tapToFocusControllerSnippet( - cameraController: LifecycleCameraController, - lifecycleOwner: LifecycleOwner, -) { - // [START android_camerax_camera1_to_camerax_tap_to_focus_controller] - val tapToFocusStateObserver = Observer { state -> - when (state) { - CameraController.TAP_TO_FOCUS_NOT_STARTED -> - Log.d(TAG, "tap-to-focus init") - CameraController.TAP_TO_FOCUS_STARTED -> - Log.d(TAG, "tap-to-focus started") - CameraController.TAP_TO_FOCUS_FOCUSED -> - Log.d(TAG, "tap-to-focus finished (focus successful)") - CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> - Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") - CameraController.TAP_TO_FOCUS_FAILED -> - Log.d(TAG, "tap-to-focus failed") + private fun observeZoomState() { + // [START android_camerax_camera1_to_camerax_pinch_to_zoom_controller] + // CameraX: track the state of pinch-to-zoom over the Lifecycle of + // a PreviewView, logging the linear zoom ratio. + + val pinchToZoomStateObserver = Observer { state -> + val zoomRatio = state.getZoomRatio() + Log.d(TAG, "ptz-zoom-ratio $zoomRatio") } - } - cameraController.tapToFocusState.observe(lifecycleOwner, tapToFocusStateObserver) - // [END android_camerax_camera1_to_camerax_tap_to_focus_controller] + cameraController.getZoomState().observe(this, pinchToZoomStateObserver) + // [END android_camerax_camera1_to_camerax_pinch_to_zoom_controller] + } } private class TapToFocusProviderActivity : AppCompatActivity() { - private var previewView: PreviewView? = null + private lateinit var previewView: PreviewView private var camera: Camera? = null private lateinit var scaleGestureDetector: ScaleGestureDetector - private fun setupGesture(context: Context) { + @Suppress("DEPRECATION") + private fun setUpTapToFocus(context: Context) { // [START android_camerax_camera1_to_camerax_tap_to_focus_provider] + // CameraX: implement tap-to-focus with CameraProvider. + + // Define a gesture detector to respond to tap events and call + // startFocusAndMetering on CameraControl. If you want to use a + // coroutine with await() to check the result of focusing, see the + // preceding "Android development concepts" section. val gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { @@ -253,7 +216,7 @@ private class TapToFocusProviderActivity : AppCompatActivity() { lifecycleScope.launch { val focusResult = camera.cameraControl .startFocusAndMetering(meteringAction).await() - if (!focusResult.isFocusSuccessful) { + if (!focusResult.isFocusSuccessful()) { Log.d(TAG, "tap-to-focus failed") } } @@ -262,10 +225,10 @@ private class TapToFocusProviderActivity : AppCompatActivity() { } ) - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // ... + // Set the gestureDetector in a touch listener on the PreviewView. - previewView?.setOnTouchListener { _, event -> + previewView.setOnTouchListener { _, event -> // See pinch-to-zoom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { @@ -277,27 +240,17 @@ private class TapToFocusProviderActivity : AppCompatActivity() { } } -private fun pinchToZoomControllerSnippet( - cameraController: LifecycleCameraController, - lifecycleOwner: LifecycleOwner, -) { - // [START android_camerax_camera1_to_camerax_pinch_to_zoom_controller] - val pinchToZoomStateObserver = Observer { state -> - val zoomRatio = state.zoomRatio - Log.d(TAG, "ptz-zoom-ratio $zoomRatio") - } - - cameraController.zoomState.observe(lifecycleOwner, pinchToZoomStateObserver) - // [END android_camerax_camera1_to_camerax_pinch_to_zoom_controller] -} - private class PinchToZoomProviderActivity : AppCompatActivity() { - private var previewView: PreviewView? = null + private lateinit var previewView: PreviewView private var camera: Camera? = null - private lateinit var gestureDetector: GestureDetectorCompat + private lateinit var gestureDetector: GestureDetector - private fun setupPinch(context: Context) { + private fun setUpPinchToZoom(context: Context) { // [START android_camerax_camera1_to_camerax_pinch_to_zoom_provider] + // CameraX: implement pinch-to-zoom with CameraProvider. + + // Define a scale gesture detector to respond to pinch events and call + // setZoomRatio on CameraControl. val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { @@ -312,10 +265,10 @@ private class PinchToZoomProviderActivity : AppCompatActivity() { } ) - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // ... + // Set the scaleGestureDetector in a touch listener on the PreviewView. - previewView?.setOnTouchListener { _, event -> + previewView.setOnTouchListener { _, event -> var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { // See pinch-to-zoom scenario for gestureDetector definition. @@ -329,70 +282,76 @@ private class PinchToZoomProviderActivity : AppCompatActivity() { private class TakePhotoControllerActivity : AppCompatActivity() { private lateinit var cameraController: LifecycleCameraController + private val context: Context + get() = this // [START android_camerax_camera1_to_camerax_take_photo_controller] + // CameraX: define a function that uses CameraController to take a photo. + private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun takePhoto() { - // Create time stamped name and MediaStore entry. - val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) - .format(System.currentTimeMillis()) - val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, name) - put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") - if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") - } - } - - // Create output options object which contains file + metadata. - val outputOptions = ImageCapture.OutputFileOptions - .Builder(contentResolver, + // Create time stamped name and MediaStore entry. + val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) + .format(System.currentTimeMillis()) + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, name) + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") + } + } + + // Create output options object which contains file + metadata. + val outputOptions = ImageCapture.OutputFileOptions + .Builder(context.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) - .build() - - // Set up image capture listener, which is triggered after photo has - // been taken. - cameraController.takePicture( - outputOptions, - ContextCompat.getMainExecutor(this), - object : ImageCapture.OnImageSavedCallback { - override fun onError(e: ImageCaptureException) { - Log.e(TAG, "photo capture failed", e) - } - - override fun onImageSaved( - output: ImageCapture.OutputFileResults - ) { - val msg = "Photo capture succeeded: ${output.savedUri}" - Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() - Log.d(TAG, msg) - } - } - ) + .build() + + // Set up image capture listener, which is triggered after photo has + // been taken. + cameraController.takePicture( + outputOptions, + ContextCompat.getMainExecutor(this), + object : ImageCapture.OnImageSavedCallback { + override fun onError(e: ImageCaptureException) { + Log.e(TAG, "photo capture failed", e) + } + + override fun onImageSaved( + output: ImageCapture.OutputFileResults + ) { + val msg = "Photo capture succeeded: ${output.savedUri}" + Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() + Log.d(TAG, msg) + } + } + ) } // [END android_camerax_camera1_to_camerax_take_photo_controller] } -private class TakePhotoProviderActivity : AppCompatActivity() { - private fun bindSnippet( +private class ImageCaptureBindActivity : AppCompatActivity() { + private fun bindImageCapture( cameraProvider: ProcessCameraProvider, cameraSelector: CameraSelector, preview: Preview, ) { // [START android_camerax_camera1_to_camerax_take_photo_provider_bind] + // CameraX: create and bind an ImageCapture UseCase. + // Make a reference to the ImageCapture UseCase at a scope that can be accessed // throughout the camera logic in your app. var imageCapture: ImageCapture? = null - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // ... + // Create an ImageCapture instance (can be added with other // UseCase definitions). imageCapture = ImageCapture.Builder().build() - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // ... + // Bind UseCases to camera (adding imageCapture along with preview here, but // preview is not required to use imageCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. @@ -400,111 +359,70 @@ private class TakePhotoProviderActivity : AppCompatActivity() { this, cameraSelector, preview, imageCapture) // [END android_camerax_camera1_to_camerax_take_photo_provider_bind] } +} +private class TakePhotoProviderActivity : AppCompatActivity() { private var imageCapture: ImageCapture? = null // [START android_camerax_camera1_to_camerax_take_photo_provider_call] + // CameraX: define a function that uses CameraController to take a photo. + private fun takePhoto() { // Get a stable reference of the modifiable ImageCapture UseCase. val imageCapture = imageCapture ?: return - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // [START_EXCLUDE] + val outputOptions = ImageCapture.OutputFileOptions + .Builder(File(filesDir, "photo.jpg")) + .build() + // [END_EXCLUDE] + // Call takePicture on imageCapture instance. imageCapture.takePicture( - /* [START_EXCLUDE silent] */ - ImageCapture.OutputFileOptions.Builder(File("")).build(), + outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { - override fun onError(e: ImageCaptureException) {} - override fun onImageSaved(output: ImageCapture.OutputFileResults) {} + override fun onError(e: ImageCaptureException) { + Log.e(TAG, "photo capture failed", e) + } + + override fun onImageSaved( + output: ImageCapture.OutputFileResults + ) { + val msg = "Photo capture succeeded: ${output.savedUri}" + Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() + Log.d(TAG, msg) + } } - /* [END_EXCLUDE] */ ) } // [END android_camerax_camera1_to_camerax_take_photo_provider_call] } -private fun enableVideoSnippet(cameraController: LifecycleCameraController) { +private fun enableVideoCapture(cameraController: LifecycleCameraController) { // [START android_camerax_camera1_to_camerax_video_controller_enable] - cameraController.setEnabledUseCases(CameraController.VIDEO_CAPTURE) - // [END android_camerax_camera1_to_camerax_video_controller_enable] -} + // CameraX: Enable VideoCapture UseCase on CameraController. -private class OutputFileOptions { - class Builder(val file: File) { - fun build() = OutputFileOptions() - } -} -private class OutputFileResults(val savedUri: String? = null) -private interface OnVideoSavedCallback { - fun onVideoSaved(outputFileResults: OutputFileResults) - fun onError(videoCaptureError: Int, message: String, cause: Throwable?) -} -private fun CameraController.isRecording(): Boolean = false -private fun CameraController.stopRecording() {} -private fun CameraController.startRecording(options: Any, executor: Any, callback: Any) {} - -private class VideoControllerActivity : AppCompatActivity() { - private lateinit var cameraController: LifecycleCameraController - - // [START android_camerax_camera1_to_camerax_video_controller] - private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" - - // Define a VideoSaveCallback class for handling success and error states. - class VideoSaveCallback : OnVideoSavedCallback { - override fun onVideoSaved(outputFileResults: OutputFileResults) { - val msg = "Video capture succeeded: ${outputFileResults.savedUri}" - /* [START_EXCLUDE] */ - Log.d(TAG, msg) - /* [END_EXCLUDE] */ - } - - override fun onError(videoCaptureError: Int, message: String, - cause: Throwable?) { - Log.d(TAG, "error saving video: $message", cause) - } - } - - private fun startStopVideo() { - if (cameraController.isRecording()) { - // Stop the current recording session. - cameraController.stopRecording() - return - } - - // Define the File options for saving the video. - val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) - .format(System.currentTimeMillis()) - - val outputFileOptions = OutputFileOptions - .Builder(File(this.filesDir, name)) - .build() - - // Call startRecording on the CameraController. - cameraController.startRecording( - outputFileOptions, - ContextCompat.getMainExecutor(this), - VideoSaveCallback() - ) - } - // [END android_camerax_camera1_to_camerax_video_controller] + cameraController.setEnabledUseCases(VIDEO_CAPTURE) + // [END android_camerax_camera1_to_camerax_video_controller_enable] } -private class VideoProviderActivity : AppCompatActivity() { - private fun bindVideoSnippet( +private class Camera1VideoCaptureBindActivity : AppCompatActivity() { + private fun bindVideoCapture( cameraProvider: ProcessCameraProvider, cameraSelector: CameraSelector, preview: Preview, ) { // [START android_camerax_camera1_to_camerax_video_provider_bind] + // CameraX: create and bind a VideoCapture UseCase with CameraProvider. + // Make a reference to the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your app. - var videoCapture: VideoCapture + lateinit var videoCapture: VideoCapture var recording: Recording? = null - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // ... + // Create a Recorder instance to set on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() @@ -512,8 +430,8 @@ private class VideoProviderActivity : AppCompatActivity() { .build() videoCapture = VideoCapture.withOutput(recorder) - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ + // ... + // Bind UseCases to camera (adding videoCapture along with preview here, but // preview is not required to use videoCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. @@ -521,65 +439,6 @@ private class VideoProviderActivity : AppCompatActivity() { this, cameraSelector, preview, videoCapture) // [END android_camerax_camera1_to_camerax_video_provider_bind] } - - private var videoCapture: VideoCapture? = null - private var recording: Recording? = null - private lateinit var viewBinding: Any - - // [START android_camerax_camera1_to_camerax_video_provider_record] - private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" - - private fun startStopVideo() { - val videoCapture = this.videoCapture ?: return - - if (recording != null) { - // Stop the current recording session. - recording?.stop() - recording = null - return - } - - // Create and start a new recording session. - val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) - .format(System.currentTimeMillis()) - val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, name) - put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") - } - } - - val mediaStoreOutputOptions = MediaStoreOutputOptions - .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) - .setContentValues(contentValues) - .build() - - recording = videoCapture.output - .prepareRecording(this, mediaStoreOutputOptions) - .withAudioEnabled() - .start(ContextCompat.getMainExecutor(this)) { recordEvent -> - when(recordEvent) { - is VideoRecordEvent.Start -> { - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ - } - is VideoRecordEvent.Finalize -> { - if (!recordEvent.hasError()) { - val msg = "Video capture succeeded: " + - "${recordEvent.outputResults.outputUri}" - Toast.makeText( - baseContext, msg, Toast.LENGTH_SHORT - ).show() - Log.d(TAG, msg) - } else { - recording?.close() - recording = null - Log.e(TAG, "video capture ends with error: ${recordEvent.error}") - } - } - } - } - } - // [END android_camerax_camera1_to_camerax_video_provider_record] } + +private fun checkFocalLength(focalLengths: FloatArray?): Boolean = focalLengths != null diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt index c3eba78c4..c32ecd92a 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt @@ -17,16 +17,20 @@ package com.example.camera.snippets.camerax import android.app.Application +import android.app.Service import android.content.Context +import android.content.Intent import android.hardware.camera2.CameraCharacteristics -import android.os.Bundle +import android.os.IBinder import android.util.Log import android.util.Rational import android.util.Size +import android.view.Display import android.view.OrientationEventListener import android.view.Surface import android.view.SurfaceView -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity +import androidx.annotation.OptIn import androidx.camera.camera2.Camera2Config import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.ExperimentalCamera2Interop @@ -38,6 +42,8 @@ import androidx.camera.core.CameraXConfig import androidx.camera.core.ConcurrentCamera import androidx.camera.core.DisplayOrientedMeteringPointFactory import androidx.camera.core.FocusMeteringAction +import androidx.camera.core.FocusMeteringAction.FLAG_AE +import androidx.camera.core.FocusMeteringAction.FLAG_AF import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture import androidx.camera.core.Preview @@ -48,46 +54,53 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner +import com.example.camera.snippets.R import java.util.concurrent.Executor import java.util.concurrent.TimeUnit +@Suppress("DEPRECATION") private fun imageCaptureBuilderSnippet() { // [START android_camerax_configuration_image_capture_builder] val imageCapture = ImageCapture.Builder() .setFlashMode(ImageCapture.FLASH_MODE_AUTO) - .setTargetAspectRatio(AspectRatio.RATIO_4_3) + .setTargetAspectRatio(AspectRatio.RATIO_16_9) .build() // [END android_camerax_configuration_image_capture_builder] } -// [START android_camerax_configuration_logging] -class CameraApplication : Application(), CameraXConfig.Provider { - override fun getCameraXConfig(): CameraXConfig { - return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) - .setMinimumLoggingLevel(Log.ERROR).build() +private object CameraApplicationSnippet { + // [START android_camerax_configuration_logging] + class CameraApplication : Application(), CameraXConfig.Provider { + override fun getCameraXConfig(): CameraXConfig { + return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) + .setMinimumLoggingLevel(Log.ERROR).build() + } } + // [END android_camerax_configuration_logging] } -// [END android_camerax_configuration_logging] -// [START android_camerax_configuration_camera_limiter] -class MainApplication : Application(), CameraXConfig.Provider { - override fun getCameraXConfig(): CameraXConfig { - return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) - .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) - .build() +private object MainApplicationSnippet { + // [START android_camerax_configuration_camera_limiter] + class MainApplication : Application(), CameraXConfig.Provider { + override fun getCameraXConfig(): CameraXConfig { + return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) + .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) + .build() + } } + // [END android_camerax_configuration_camera_limiter] } -// [END android_camerax_configuration_camera_limiter] -private class ConfigurationOrientationActivity : AppCompatActivity() { +private class ConfigurationOrientationService : Service() { + override fun onBind(intent: Intent?): IBinder? = null + // [START android_camerax_configuration_orientation_listener] - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreate() { val imageCapture = ImageCapture.Builder().build() val orientationEventListener = object : OrientationEventListener(this as Context) { override fun onOrientationChanged(orientation: Int) { - // Monitors orientation values to determine the target rotation value + // Monitors orientation values to determine the target rotation value. val rotation: Int = when (orientation) { in 45..134 -> Surface.ROTATION_270 in 135..224 -> Surface.ROTATION_180 @@ -112,7 +125,7 @@ private fun cropRectSnippet( imageCapture: ImageCapture, width: Int, height: Int, - display: android.view.Display, + display: Display, ) { // [START android_camerax_configuration_crop_rect] val viewPort = ViewPort.Builder(Rational(width, height), display.rotation).build() @@ -126,7 +139,7 @@ private fun cropRectSnippet( // [END android_camerax_configuration_crop_rect] } -private class GetViewPortActivity : AppCompatActivity() { +private class GetViewPortActivity : ComponentActivity() { private fun getViewportSnippet() { // [START android_camerax_configuration_get_viewport] val viewport = findViewById(R.id.preview_view).viewPort @@ -134,63 +147,64 @@ private class GetViewPortActivity : AppCompatActivity() { } } -@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) -// [START android_camerax_configuration_select_external_camera] -fun selectExternalOrBestCamera(provider: ProcessCameraProvider): CameraSelector? { - val cam2Infos = provider.availableCameraInfos.map { - Camera2CameraInfo.from(it) - }.sortedByDescending { - // HARDWARE_LEVEL is Int type, with the order of: - // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL - it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) - } +private class SelectExternalCameraActivity : ComponentActivity() { + @OptIn(ExperimentalCamera2Interop::class) + private fun selectExternalCameraSnippet( + processCameraProvider: ProcessCameraProvider, + preview: Preview, + analysis: ImageAnalysis, + ) { + // [START android_camerax_configuration_select_external_camera] + fun selectExternalOrBestCamera(provider: ProcessCameraProvider): CameraSelector? { + val cam2Infos = provider.availableCameraInfos.map { + Camera2CameraInfo.from(it) + }.sortedByDescending { + // HARDWARE_LEVEL is Int type, with the order of: + // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL. + it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) + } - return when { - cam2Infos.isNotEmpty() -> { - CameraSelector.Builder() - .addCameraFilter { - it.filter { camInfo -> - // cam2Infos[0] is either EXTERNAL or best built-in camera - val thisCamId = Camera2CameraInfo.from(camInfo).cameraId - thisCamId == cam2Infos[0].cameraId - } - }.build() + return when { + cam2Infos.isNotEmpty() -> { + CameraSelector.Builder() + .addCameraFilter { + it.filter { camInfo -> + // cam2Infos[0] is either EXTERNAL or best built-in camera. + val thisCamId = Camera2CameraInfo.from(camInfo).cameraId + thisCamId == cam2Infos[0].cameraId + } + }.build() + } + else -> null + } } - else -> null - } -} -// [END android_camerax_configuration_select_external_camera] -private fun bindSelectedExternalCamera( - processCameraProvider: ProcessCameraProvider, - lifecycleOwner: LifecycleOwner, - preview: Preview, - analysis: ImageAnalysis, -) { - // [START_EXCLUDE] - val selector = selectExternalOrBestCamera(processCameraProvider) ?: return - processCameraProvider.bindToLifecycle(lifecycleOwner, selector, preview, analysis) - // [END_EXCLUDE] + // create a CameraSelector for the USB camera (or highest level internal camera). + val selector = selectExternalOrBestCamera(processCameraProvider) ?: return + processCameraProvider.bindToLifecycle(this, selector, preview, analysis) + // [END android_camerax_configuration_select_external_camera] + } } private fun concurrentCameraSnippet( cameraProvider: ProcessCameraProvider, primaryCameraSelector: CameraSelector, secondaryCameraSelector: CameraSelector, - useCaseGroup: UseCaseGroup, + primaryUseCaseGroup: UseCaseGroup, + secondaryUseCaseGroup: UseCaseGroup, lifecycleOwner: LifecycleOwner, ) { // [START android_camerax_configuration_concurrent_camera] - // Build ConcurrentCameraConfig + // Build ConcurrentCameraConfig. val primary = ConcurrentCamera.SingleCameraConfig( primaryCameraSelector, - useCaseGroup, + primaryUseCaseGroup, lifecycleOwner ) val secondary = ConcurrentCamera.SingleCameraConfig( secondaryCameraSelector, - useCaseGroup, + secondaryUseCaseGroup, lifecycleOwner ) @@ -203,6 +217,7 @@ private fun concurrentCameraSnippet( // [END android_camerax_configuration_concurrent_camera] } +@Suppress("DEPRECATION") private fun targetResolutionSnippet() { // [START android_camerax_configuration_target_resolution] val imageAnalysis = ImageAnalysis.Builder() @@ -232,17 +247,17 @@ private fun meteringPointFactorySnippet( surfaceView: SurfaceView, camera: Camera, imageAnalysis: ImageAnalysis, - imageWidth: Int, - imageHeight: Int, + imageWidth: Float, + imageHeight: Float, ) { // [START android_camerax_configuration_metering_point_factory] // Use PreviewView.getMeteringPointFactory if PreviewView is used for preview. previewView.setOnTouchListener { view, motionEvent -> val meteringPoint = previewView.meteringPointFactory .createPoint(motionEvent.x, motionEvent.y) - /* [START_EXCLUDE silent] */ + // [START_EXCLUDE] true - /* [END_EXCLUDE] */ + // [END_EXCLUDE] } // Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for @@ -261,14 +276,14 @@ private fun meteringPointFactorySnippet( // Use SurfaceOrientedMeteringPointFactory if the point is specified in // ImageAnalysis ImageProxy. val meteringPointFactoryAnalysis = SurfaceOrientedMeteringPointFactory( - imageWidth.toFloat(), - imageHeight.toFloat(), + imageWidth, + imageHeight, imageAnalysis ) // [END android_camerax_configuration_metering_point_factory] } -private class FocusMeteringActivity : AppCompatActivity() { +private class FocusMeteringActivity : ComponentActivity() { private fun focusMeteringSnippet( meteringPointFactory: DisplayOrientedMeteringPointFactory, cameraControl: CameraControl, @@ -280,9 +295,9 @@ private class FocusMeteringActivity : AppCompatActivity() { // [START android_camerax_configuration_focus_metering_action] val meteringPoint1 = meteringPointFactory.createPoint(x1, y1) val meteringPoint2 = meteringPointFactory.createPoint(x2, y2) - val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB + val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB. // Optionally add meteringPoint2 for AF/AE. - .addPoint(meteringPoint2, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE) + .addPoint(meteringPoint2, FLAG_AF or FLAG_AE) // The action is canceled in 3 seconds (if not set, default is 5s). .setAutoCancelDuration(3, TimeUnit.SECONDS) .build() @@ -294,38 +309,21 @@ private class FocusMeteringActivity : AppCompatActivity() { }, ContextCompat.getMainExecutor(this)) // [END android_camerax_configuration_focus_metering_action] } +} - private fun exposureCompensationSnippet( - camera: Camera, - exposureCompensationIndex: Int, - mainExecutor: Executor, - ) { - // [START android_camerax_configuration_exposure_compensation] - camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex) - .addListener({ - // Get the current exposure compensation index, it might be - // different from the asked value in case this request was - // canceled by a newer setting request. - val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex - /* [START_EXCLUDE silent] */ - // [END_EXCLUDE] */ - }, mainExecutor) - // [END android_camerax_configuration_exposure_compensation] - } - - private fun seekBarSnippet( - camera: Camera, - binding: Any, - ) { - val seekBar = android.widget.SeekBar(this) - // [START android_camerax_configuration_exposure_seekbar] - val exposureState = camera.cameraInfo.exposureState - seekBar.apply { - isEnabled = exposureState.isExposureCompensationSupported - max = exposureState.exposureCompensationRange.upper - min = exposureState.exposureCompensationRange.lower - progress = exposureState.exposureCompensationIndex - } - // [END android_camerax_configuration_exposure_seekbar] - } +private fun exposureCompensationSnippet( + camera: Camera, + exposureCompensationIndex: Int, + mainExecutor: Executor, +) { + // [START android_camerax_configuration_exposure_compensation] + camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex) + .addListener({ + // Get the current exposure compensation index, it might be + // different from the asked value in case this request was + // canceled by a newer setting request. + val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex + // ... + }, mainExecutor) + // [END android_camerax_configuration_exposure_compensation] } diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt index c0b146d82..e5b4ac838 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt @@ -33,6 +33,9 @@ private class ExtensionsActivity : AppCompatActivity() { private lateinit var surfaceProvider: Preview.SurfaceProvider // [START android_camerax_extensions_enable_night] + // import androidx.camera.extensions.ExtensionMode + // import androidx.camera.extensions.ExtensionsManager + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -40,7 +43,7 @@ private class ExtensionsActivity : AppCompatActivity() { val cameraProviderFuture = ProcessCameraProvider.getInstance(applicationContext) cameraProviderFuture.addListener({ - // Obtain an instance of a process camera provider + // Obtain an instance of a process camera provider. // The camera provider provides access to the set of cameras associated with the device. // The camera obtained from the provider will be bound to the activity lifecycle. val cameraProvider = cameraProviderFuture.get() @@ -48,12 +51,12 @@ private class ExtensionsActivity : AppCompatActivity() { val extensionsManagerFuture = ExtensionsManager.getInstanceAsync(applicationContext, cameraProvider) extensionsManagerFuture.addListener({ - // Obtain an instance of the extensions manager + // Obtain an instance of the extensions manager. // The extensions manager enables a camera to use extension capabilities available on // the device. val extensionsManager = extensionsManagerFuture.get() - // Select the camera + // Select the camera. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA // Query if extension is available. @@ -64,7 +67,7 @@ private class ExtensionsActivity : AppCompatActivity() { try { cameraProvider.unbindAll() - // Retrieve a night extension enabled camera selector + // Retrieve a night extension enabled camera selector. val nightCameraSelector = extensionsManager.getExtensionEnabledCameraSelector( cameraSelector, @@ -80,8 +83,8 @@ private class ExtensionsActivity : AppCompatActivity() { // or SurfaceView. The SurfaceProvider can be obtained from the PreviewView. preview.setSurfaceProvider(surfaceProvider) - // Returns an instance of the camera bound to the lifecycle - // Use this camera object to control various operations with the camera + // Returns an instance of the camera bound to the lifecycle. + // Use this camera object to control various operations with the camera. // Example: flash, zoom, focus metering etc. val camera = cameraProvider.bindToLifecycle( lifecycleOwner, diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt b/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt index cbb18d92d..66223cb49 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/MlKitAnalyzer.kt @@ -16,7 +16,7 @@ package com.example.camera.snippets.camerax -import android.content.Context +import androidx.activity.ComponentActivity import androidx.camera.mlkit.vision.MlKitAnalyzer import androidx.camera.view.CameraController import androidx.camera.view.CameraController.COORDINATE_SYSTEM_VIEW_REFERENCED @@ -25,26 +25,26 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.common.Barcode -private fun mlKitAnalyzerSnippet( - cameraController: CameraController, - context: Context, -) { - // [START android_camerax_mlkitanalyzer_qrcode] - // create BarcodeScanner object - val options = BarcodeScannerOptions.Builder() - .setBarcodeFormats(Barcode.FORMAT_QR_CODE) - .build() - val barcodeScanner = BarcodeScanning.getClient(options) +private class MlKitAnalyzerActivity : ComponentActivity() { + @Suppress("DEPRECATION") + private fun setUpQrCodeAnalyzer(cameraController: CameraController) { + // [START android_camerax_mlkitanalyzer_qrcode] + // create BarcodeScanner object. + val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE) + .build() + val barcodeScanner = BarcodeScanning.getClient(options) - cameraController.setImageAnalysisAnalyzer( - ContextCompat.getMainExecutor(context), - MlKitAnalyzer( - listOf(barcodeScanner), - COORDINATE_SYSTEM_VIEW_REFERENCED, - ContextCompat.getMainExecutor(context) - ) { result: MlKitAnalyzer.Result? -> - // The value of result.getResult(barcodeScanner) can be used directly for drawing UI overlay. - } - ) - // [END android_camerax_mlkitanalyzer_qrcode] + cameraController.setImageAnalysisAnalyzer( + ContextCompat.getMainExecutor(this), + MlKitAnalyzer( + listOf(barcodeScanner), + COORDINATE_SYSTEM_VIEW_REFERENCED, + ContextCompat.getMainExecutor(this) + ) { result: MlKitAnalyzer.Result? -> + // The value of result.getResult(barcodeScanner) can be used directly for drawing UI overlay. + } + ) + // [END android_camerax_mlkitanalyzer_qrcode] + } } diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt index 3b7cfda0a..9e1364569 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt @@ -16,31 +16,21 @@ package com.example.camera.snippets.camerax -import android.content.ContentResolver import android.content.Context import android.hardware.display.DisplayManager +import android.os.Build import android.os.Bundle import android.view.OrientationEventListener import android.view.Surface import android.view.View +import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture import androidx.camera.core.ImageProxy import androidx.camera.core.Preview -import androidx.camera.core.impl.utils.Exif import androidx.camera.lifecycle.ProcessCameraProvider -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.File -import java.util.concurrent.Executor - -private fun imageAnalysisRotationSnippet(imageProxy: ImageProxy) { - // [START android_camerax_orientation_rotation_image_analysis] - val rotation = imageProxy.imageInfo.rotationDegrees - // [END android_camerax_orientation_rotation_image_analysis] -} private fun imageProxyRotationSnippet(imageProxy: ImageProxy) { // [START android_camerax_orientation_rotation_image_proxy] @@ -48,136 +38,122 @@ private fun imageProxyRotationSnippet(imageProxy: ImageProxy) { // [END android_camerax_orientation_rotation_image_proxy] } -private fun fileRotationSnippet(file: File) { - // [START android_camerax_orientation_rotation_file] - val exif = Exif.createFromFile(file) - val rotation = exif.rotation - // [END android_camerax_orientation_rotation_file] -} - -private fun outputStreamRotationSnippet(outputStream: ByteArrayOutputStream) { - // [START android_camerax_orientation_rotation_output_stream] - val byteArray = outputStream.toByteArray() - val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) - val rotation = exif.rotation - // [END android_camerax_orientation_rotation_output_stream] -} - -private fun mediaStoreRotationSnippet( - contentResolver: ContentResolver, - outputFileResults: ImageCapture.OutputFileResults, -) { - // [START android_camerax_orientation_rotation_mediastore] - val inputStream = contentResolver.openInputStream(outputFileResults.savedUri ?: return) - val exif = Exif.createFromInputStream(inputStream ?: return) - val rotation = exif.rotation - // [END android_camerax_orientation_rotation_mediastore] -} - -// [START android_camerax_orientation_rotation_use_cases_setup] -private class OrientationCameraActivity : AppCompatActivity() { - private lateinit var mainExecutor: Executor - private lateinit var cameraSelector: CameraSelector - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val cameraProcessFuture = ProcessCameraProvider.getInstance(this) - cameraProcessFuture.addListener( - Runnable { - val cameraProvider = cameraProcessFuture.get() - - // By default, the use cases set their target rotation to match the - // display’s rotation. - val preview = buildPreview() - val imageAnalysis = buildImageAnalysis() - val imageCapture = buildImageCapture() - - cameraProvider.bindToLifecycle( - this, cameraSelector, preview, imageAnalysis, imageCapture - ) - }, - mainExecutor - ) +private object CameraUseCasesSetupSnippet { + @RequiresApi(Build.VERSION_CODES.P) + // [START android_camerax_orientation_rotation_use_cases_setup] + class CameraActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val cameraProcessFuture = ProcessCameraProvider.getInstance(this) + cameraProcessFuture.addListener( + Runnable { + val cameraProvider = cameraProcessFuture.get() + + // By default, the use cases set their target rotation to match the + // display’s rotation. + val preview = buildPreview() + val imageAnalysis = buildImageAnalysis() + val imageCapture = buildImageCapture() + + cameraProvider.bindToLifecycle( + this, cameraSelector, preview, imageAnalysis, imageCapture + ) + }, + mainExecutor + ) + } } + // [END android_camerax_orientation_rotation_use_cases_setup] + + private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - /* [START_EXCLUDE silent] */ private fun buildPreview(): Preview = Preview.Builder().build() + private fun buildImageAnalysis(): ImageAnalysis = ImageAnalysis.Builder().build() + private fun buildImageCapture(): ImageCapture = ImageCapture.Builder().build() - /* [END_EXCLUDE] */ } -// [END android_camerax_orientation_rotation_use_cases_setup] - -// [START android_camerax_orientation_rotation_orientation_listener] -private class OrientationListenerActivity : AppCompatActivity() { - private lateinit var imageAnalysis: ImageAnalysis - private lateinit var imageCapture: ImageCapture - - private val orientationEventListener by lazy { - object : OrientationEventListener(this) { - override fun onOrientationChanged(orientation: Int) { - if (orientation == ORIENTATION_UNKNOWN) { - return - } - val rotation = when (orientation) { - in 45 until 135 -> Surface.ROTATION_270 - in 135 until 225 -> Surface.ROTATION_180 - in 225 until 315 -> Surface.ROTATION_90 - else -> Surface.ROTATION_0 +private object OrientationEventListenerSnippet { + // [START android_camerax_orientation_rotation_orientation_listener] + class CameraActivity : AppCompatActivity() { + + private val orientationEventListener by lazy { + object : OrientationEventListener(this) { + override fun onOrientationChanged(orientation: Int) { + if (orientation == ORIENTATION_UNKNOWN) { + return + } + + val rotation = when (orientation) { + in 45 until 135 -> Surface.ROTATION_270 + in 135 until 225 -> Surface.ROTATION_180 + in 225 until 315 -> Surface.ROTATION_90 + else -> Surface.ROTATION_0 + } + + imageAnalysis.targetRotation = rotation + imageCapture.targetRotation = rotation } - - imageAnalysis.targetRotation = rotation - imageCapture.targetRotation = rotation } } - } - override fun onStart() { - super.onStart() - orientationEventListener.enable() - } + override fun onStart() { + super.onStart() + orientationEventListener.enable() + } - override fun onStop() { - super.onStop() - orientationEventListener.disable() + override fun onStop() { + super.onStop() + orientationEventListener.disable() + } } + // [END android_camerax_orientation_rotation_orientation_listener] + + private val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder().build() + + private val imageCapture: ImageCapture = ImageCapture.Builder().build() } -// [END android_camerax_orientation_rotation_orientation_listener] - -// [START android_camerax_orientation_rotation_display_listener] -private class DisplayListenerActivity : AppCompatActivity() { - private lateinit var rootView: View - private lateinit var imageAnalysis: ImageAnalysis - private lateinit var imageCapture: ImageCapture - - private val displayListener = object : DisplayManager.DisplayListener { - override fun onDisplayChanged(displayId: Int) { - if (rootView.display.displayId == displayId) { - val rotation = rootView.display.rotation - imageAnalysis.targetRotation = rotation - imageCapture.targetRotation = rotation + +private object DisplayListenerSnippet { + // [START android_camerax_orientation_rotation_display_listener] + class CameraActivity : AppCompatActivity() { + + private val displayListener = object : DisplayManager.DisplayListener { + override fun onDisplayChanged(displayId: Int) { + if (rootView.display.displayId == displayId) { + val rotation = rootView.display.rotation + imageAnalysis.targetRotation = rotation + imageCapture.targetRotation = rotation + } + } + + override fun onDisplayAdded(displayId: Int) { + } + + override fun onDisplayRemoved(displayId: Int) { } } - override fun onDisplayAdded(displayId: Int) { + override fun onStart() { + super.onStart() + val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + displayManager.registerDisplayListener(displayListener, null) } - override fun onDisplayRemoved(displayId: Int) { + override fun onStop() { + super.onStop() + val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + displayManager.unregisterDisplayListener(displayListener) } } + // [END android_camerax_orientation_rotation_display_listener] - override fun onStart() { - super.onStart() - val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager - displayManager.registerDisplayListener(displayListener, null) - } + private val CameraActivity.rootView: View + get() = window.decorView - override fun onStop() { - super.onStop() - val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager - displayManager.unregisterDisplayListener(displayListener) - } + private val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder().build() + + private val imageCapture: ImageCapture = ImageCapture.Builder().build() } -// [END android_camerax_orientation_rotation_display_listener] diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt index 2b6f0d802..439caf459 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt @@ -17,6 +17,7 @@ package com.example.camera.snippets.camerax import android.os.Bundle +import androidx.activity.ComponentActivity import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.Preview @@ -26,18 +27,26 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner import com.google.common.util.concurrent.ListenableFuture -// [START android_camerax_preview_request_provider] -private class MainActivity : AppCompatActivity() { - private lateinit var cameraProviderFuture: ListenableFuture - override fun onCreate(savedInstanceState: Bundle?) { - cameraProviderFuture = ProcessCameraProvider.getInstance(this) - /* [START_EXCLUDE silent] */ - checkProviderSnippet(cameraProviderFuture) - /* [END_EXCLUDE] */ +private object PreviewRequestProviderSnippet { + // [START android_camerax_preview_request_provider] + // import androidx.camera.lifecycle.ProcessCameraProvider + // import com.google.common.util.concurrent.ListenableFuture + + class MainActivity : AppCompatActivity() { + private lateinit var cameraProviderFuture: ListenableFuture + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + cameraProviderFuture = ProcessCameraProvider.getInstance(this) + } } -// [END android_camerax_preview_request_provider] + // [END android_camerax_preview_request_provider] +} - private fun checkProviderSnippet(cameraProviderFuture: ListenableFuture) { +private class PreviewUseCaseActivity : ComponentActivity() { + private lateinit var cameraProviderFuture: ListenableFuture + private lateinit var previewView: PreviewView + + private fun checkProvider() { // [START android_camerax_preview_check_provider] cameraProviderFuture.addListener( Runnable { @@ -49,8 +58,6 @@ private class MainActivity : AppCompatActivity() { // [END android_camerax_preview_check_provider] } - private lateinit var previewView: PreviewView - // [START android_camerax_preview_bind] fun bindPreview(cameraProvider: ProcessCameraProvider) { var preview: Preview = Preview.Builder() @@ -60,21 +67,23 @@ private class MainActivity : AppCompatActivity() { .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build() - preview.setSurfaceProvider(previewView.surfaceProvider) + preview.setSurfaceProvider(previewView.getSurfaceProvider()) var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview) } // [END android_camerax_preview_bind] } -private fun previewControlsSnippet(viewFinder: PreviewView) { +private fun previewImplementationModeSnippet(viewFinder: PreviewView) { // [START android_camerax_preview_implementation_mode] - // viewFinder is a PreviewView instance + // viewFinder is a PreviewView instance. viewFinder.implementationMode = PreviewView.ImplementationMode.COMPATIBLE // [END android_camerax_preview_implementation_mode] +} +private fun previewScaleTypeSnippet(viewFinder: PreviewView) { // [START android_camerax_preview_scale_type] - // viewFinder is a PreviewView instance + // viewFinder is a PreviewView instance. viewFinder.scaleType = PreviewView.ScaleType.FIT_CENTER // [END android_camerax_preview_scale_type] } diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt index bf142024a..7199d7806 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt @@ -16,17 +16,13 @@ package com.example.camera.snippets.camerax +import android.Manifest import android.content.ContentValues import android.content.Context -import android.hardware.camera2.CameraCharacteristics -import android.hardware.camera2.CameraMetadata -import android.os.Build import android.provider.MediaStore import android.util.Log -import android.widget.ArrayAdapter +import androidx.annotation.RequiresPermission import androidx.appcompat.app.AppCompatActivity -import androidx.camera.camera2.interop.Camera2CameraInfo -import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.camera.core.CameraSelector import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY import androidx.camera.core.Preview @@ -47,78 +43,17 @@ import java.util.Locale import java.util.concurrent.Executor private const val TAG = "VideoCapture" +private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun qualitySelectorSnippet() { // [START android_camerax_video_capture_quality_selector] val qualitySelector = QualitySelector.fromOrderedList( - listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD), - FallbackStrategy.lowerQualityOrHigherThan(Quality.SD)) + listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD), + FallbackStrategy.lowerQualityOrHigherThan(Quality.SD) + ) // [END android_camerax_video_capture_quality_selector] } -private class VideoCapabilitiesActivity : AppCompatActivity() { - private lateinit var viewBinding: Any - private lateinit var cameraProvider: ProcessCameraProvider - - private fun Quality.qualityToString(): String { - return when (this) { - Quality.UHD -> "UHD" - Quality.FHD -> "FHD" - Quality.HD -> "HD" - Quality.SD -> "SD" - else -> throw IllegalArgumentException() - } - } - - @androidx.annotation.OptIn(ExperimentalCamera2Interop::class) - private fun queryCapabilitiesSnippet(context: Context) { - val simpleQualityListView = android.widget.ListView(context) - // [START android_camerax_video_capture_query_capabilities] - val cameraInfo = cameraProvider.availableCameraInfos.filter { - Camera2CameraInfo - .from(it) - .getCameraCharacteristic(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_BACK - } - - val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0]) - val filteredQualities = (arrayListOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD) as List) - .filter { supportedQualities.contains(it) } - - // Use a simple ListView with the id of simple_quality_list_view - simpleQualityListView.apply { - adapter = ArrayAdapter(context, - android.R.layout.simple_list_item_1, - filteredQualities.map { it.qualityToString() }) - - // Set up the user interaction to manually show or hide the system UI. - setOnItemClickListener { _, _, position, _ -> - // Inside View.OnClickListener, - // convert Quality.* constant to QualitySelector - val qualitySelector = QualitySelector.from(filteredQualities[position]) - - // Create a new Recorder/VideoCapture for the new quality - // and bind to lifecycle - val recorder = Recorder.Builder() - .setQualitySelector(qualitySelector).build() - - // ... - } - } - - // A helper function to translate Quality to a string - fun Quality.qualityToString() : String { - return when (this) { - Quality.UHD -> "UHD" - Quality.FHD -> "FHD" - Quality.HD -> "HD" - Quality.SD -> "SD" - else -> throw IllegalArgumentException() - } - } - // [END android_camerax_video_capture_query_capabilities] - } -} - private class VideoCaptureBindActivity : AppCompatActivity() { private lateinit var cameraExecutor: Executor private lateinit var qualitySelector: QualitySelector @@ -133,10 +68,11 @@ private class VideoCaptureBindActivity : AppCompatActivity() { val videoCapture = VideoCapture.withOutput(recorder) try { - // Bind use cases to camera + // Bind use cases to camera. cameraProvider.bindToLifecycle( - this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture) - } catch(exc: Exception) { + this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture + ) + } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } // [END android_camerax_video_capture_bind] @@ -146,27 +82,29 @@ private class VideoCaptureBindActivity : AppCompatActivity() { private class MediaStoreRecordActivity : AppCompatActivity() { private lateinit var videoCapture: VideoCapture private lateinit var captureListener: Consumer - private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" + @RequiresPermission(Manifest.permission.RECORD_AUDIO) private fun recordSnippet(context: Context): Recording { // [START android_camerax_video_capture_mediastore] - // Create MediaStoreOutputOptions for our recorder + // Create MediaStoreOutputOptions for our recorder. val name = "CameraX-recording-" + - SimpleDateFormat(FILENAME_FORMAT, Locale.US) - .format(System.currentTimeMillis()) + ".mp4" + SimpleDateFormat(FILENAME_FORMAT, Locale.US) + .format(System.currentTimeMillis()) + ".mp4" val contentValues = ContentValues().apply { - put(MediaStore.Video.Media.DISPLAY_NAME, name) + put(MediaStore.Video.Media.DISPLAY_NAME, name) } - val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver, - MediaStore.Video.Media.EXTERNAL_CONTENT_URI) - .setContentValues(contentValues) - .build() + val mediaStoreOutput = MediaStoreOutputOptions.Builder( + this.contentResolver, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI + ) + .setContentValues(contentValues) + .build() // 2. Configure Recorder and Start recording to the mediaStoreOutput. val recording = videoCapture.output - .prepareRecording(context, mediaStoreOutput) - .withAudioEnabled() - .start(ContextCompat.getMainExecutor(this), captureListener) + .prepareRecording(context, mediaStoreOutput) + .withAudioEnabled() + .start(ContextCompat.getMainExecutor(this), captureListener) // [END android_camerax_video_capture_mediastore] return recording } From 735f6068f0b0564345c15d33a0f352b6c72ab62c Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Thu, 1 Oct 2026 18:19:29 +0000 Subject: [PATCH 4/5] Address pre-review feedback on CameraX snippets - Camera1-to-camerax: keep getCameraSelector() as printed (the prose names it and CameraInfo is a Java interface); hide the takePicture() arguments in a plain exclude so the page's "..." renders. - Configuration: keep the page's setFlashMode(...) / setTargetAspectRatio(...) placeholders and the duplicate meteringPointFactory declaration visible with silent twins; restore the page's single useCaseGroup. - Replace the CameraActivity.rootView extension and the context wrapper property with plain helpers. - Use ComponentActivity instead of AppCompatActivity everywhere and drop the appcompat dependency from :camera. - Suppress the USELESS_ELVIS warning from the page's own "previewView ?: return false". - Sort mlkit-barcode-scanning in the version catalog. --- camera/build.gradle.kts | 1 - .../camera/snippets/camerax/Architecture.kt | 8 +++--- .../snippets/camerax/Camera1ToCameraX.kt | 25 ++++++++++--------- .../camera/snippets/camerax/Configuration.kt | 24 ++++++++++++++---- .../camera/snippets/camerax/ExtensionsApi.kt | 4 +-- .../snippets/camerax/OrientationRotation.kt | 11 ++++---- .../camera/snippets/camerax/Preview.kt | 3 +-- .../camera/snippets/camerax/VideoCapture.kt | 6 ++--- gradle/libs.versions.toml | 2 +- 9 files changed, 48 insertions(+), 36 deletions(-) diff --git a/camera/build.gradle.kts b/camera/build.gradle.kts index be16c17b4..8727e1411 100644 --- a/camera/build.gradle.kts +++ b/camera/build.gradle.kts @@ -57,7 +57,6 @@ android { dependencies { implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt index 0de23bd06..2ffa675f5 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt @@ -20,8 +20,8 @@ import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraMetadata import android.os.Build import android.os.Bundle +import androidx.activity.ComponentActivity import androidx.annotation.RequiresApi -import androidx.appcompat.app.AppCompatActivity import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.Camera2Interop import androidx.camera.camera2.interop.ExperimentalCamera2Interop @@ -38,7 +38,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.example.camera.snippets.R -private class ArchitectureActivity : AppCompatActivity() { +private class ArchitectureActivity : ComponentActivity() { private fun cameraProviderPreviewSnippet( cameraProvider: ProcessCameraProvider, @@ -78,7 +78,7 @@ private object CustomLifecycleSnippet { // [END android_camerax_architecture_custom_lifecycle] } -private class ConcurrentUseCasesActivity : AppCompatActivity() { +private class ConcurrentUseCasesActivity : ComponentActivity() { private val previewView: PreviewView by lazy { findViewById(R.id.previewView) } // [START android_camerax_architecture_concurrent_use_cases] @@ -139,7 +139,7 @@ fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider): Boolean { } // [END android_camerax_architecture_check_level_3] -private class VideoCallStreamActivity : AppCompatActivity() { +private class VideoCallStreamActivity : ComponentActivity() { private var camera: Camera? = null @RequiresApi(Build.VERSION_CODES.TIRAMISU) diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt index 5ff4dcc75..4d002bb1d 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt @@ -30,7 +30,7 @@ import android.view.ScaleGestureDetector import android.view.View import android.widget.Toast import androidx.annotation.OptIn -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.camera.core.Camera @@ -143,11 +143,11 @@ private fun selectCameraByFocalLength(cameraProvider: ProcessCameraProvider) { ) return@first checkFocalLength(focalLengths) } - val cameraSelector = cameraInfo.cameraSelector + val cameraSelector = cameraInfo.getCameraSelector() // [END android_camerax_camera1_to_camerax_focal_length] } -private class ControllerStateObserversActivity : AppCompatActivity() { +private class ControllerStateObserversActivity : ComponentActivity() { private lateinit var cameraController: LifecycleCameraController @Suppress("DEPRECATION") @@ -190,12 +190,12 @@ private class ControllerStateObserversActivity : AppCompatActivity() { } } -private class TapToFocusProviderActivity : AppCompatActivity() { +private class TapToFocusProviderActivity : ComponentActivity() { private lateinit var previewView: PreviewView private var camera: Camera? = null private lateinit var scaleGestureDetector: ScaleGestureDetector - @Suppress("DEPRECATION") + @Suppress("DEPRECATION", "USELESS_ELVIS") private fun setUpTapToFocus(context: Context) { // [START android_camerax_camera1_to_camerax_tap_to_focus_provider] // CameraX: implement tap-to-focus with CameraProvider. @@ -240,7 +240,7 @@ private class TapToFocusProviderActivity : AppCompatActivity() { } } -private class PinchToZoomProviderActivity : AppCompatActivity() { +private class PinchToZoomProviderActivity : ComponentActivity() { private lateinit var previewView: PreviewView private var camera: Camera? = null private lateinit var gestureDetector: GestureDetector @@ -280,10 +280,9 @@ private class PinchToZoomProviderActivity : AppCompatActivity() { } } -private class TakePhotoControllerActivity : AppCompatActivity() { +private class TakePhotoControllerActivity : ComponentActivity() { private lateinit var cameraController: LifecycleCameraController - private val context: Context - get() = this + private lateinit var context: Context // [START android_camerax_camera1_to_camerax_take_photo_controller] // CameraX: define a function that uses CameraController to take a photo. @@ -331,7 +330,7 @@ private class TakePhotoControllerActivity : AppCompatActivity() { // [END android_camerax_camera1_to_camerax_take_photo_controller] } -private class ImageCaptureBindActivity : AppCompatActivity() { +private class ImageCaptureBindActivity : ComponentActivity() { private fun bindImageCapture( cameraProvider: ProcessCameraProvider, cameraSelector: CameraSelector, @@ -361,7 +360,7 @@ private class ImageCaptureBindActivity : AppCompatActivity() { } } -private class TakePhotoProviderActivity : AppCompatActivity() { +private class TakePhotoProviderActivity : ComponentActivity() { private var imageCapture: ImageCapture? = null // [START android_camerax_camera1_to_camerax_take_photo_provider_call] @@ -379,6 +378,7 @@ private class TakePhotoProviderActivity : AppCompatActivity() { // Call takePicture on imageCapture instance. imageCapture.takePicture( + // [START_EXCLUDE] outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { @@ -394,6 +394,7 @@ private class TakePhotoProviderActivity : AppCompatActivity() { Log.d(TAG, msg) } } + // [END_EXCLUDE] ) } // [END android_camerax_camera1_to_camerax_take_photo_provider_call] @@ -407,7 +408,7 @@ private fun enableVideoCapture(cameraController: LifecycleCameraController) { // [END android_camerax_camera1_to_camerax_video_controller_enable] } -private class Camera1VideoCaptureBindActivity : AppCompatActivity() { +private class Camera1VideoCaptureBindActivity : ComponentActivity() { private fun bindVideoCapture( cameraProvider: ProcessCameraProvider, cameraSelector: CameraSelector, diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt index c32ecd92a..a53d40441 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt @@ -62,8 +62,16 @@ import java.util.concurrent.TimeUnit private fun imageCaptureBuilderSnippet() { // [START android_camerax_configuration_image_capture_builder] val imageCapture = ImageCapture.Builder() + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + .setFlashMode(...) + .setTargetAspectRatio(...) + // [START_EXCLUDE silent] + */ .setFlashMode(ImageCapture.FLASH_MODE_AUTO) .setTargetAspectRatio(AspectRatio.RATIO_16_9) + // [END_EXCLUDE] .build() // [END android_camerax_configuration_image_capture_builder] } @@ -190,21 +198,20 @@ private fun concurrentCameraSnippet( cameraProvider: ProcessCameraProvider, primaryCameraSelector: CameraSelector, secondaryCameraSelector: CameraSelector, - primaryUseCaseGroup: UseCaseGroup, - secondaryUseCaseGroup: UseCaseGroup, + useCaseGroup: UseCaseGroup, lifecycleOwner: LifecycleOwner, ) { // [START android_camerax_configuration_concurrent_camera] // Build ConcurrentCameraConfig. val primary = ConcurrentCamera.SingleCameraConfig( primaryCameraSelector, - primaryUseCaseGroup, + useCaseGroup, lifecycleOwner ) val secondary = ConcurrentCamera.SingleCameraConfig( secondaryCameraSelector, - secondaryUseCaseGroup, + useCaseGroup, lifecycleOwner ) @@ -275,7 +282,14 @@ private fun meteringPointFactorySnippet( // Use SurfaceOrientedMeteringPointFactory if the point is specified in // ImageAnalysis ImageProxy. - val meteringPointFactoryAnalysis = SurfaceOrientedMeteringPointFactory( + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + val meteringPointFactory = SurfaceOrientedMeteringPointFactory( + // [START_EXCLUDE silent] + */ + SurfaceOrientedMeteringPointFactory( + // [END_EXCLUDE] imageWidth, imageHeight, imageAnalysis diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt index e5b4ac838..63f71a21b 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt @@ -18,7 +18,7 @@ package com.example.camera.snippets.camerax import android.os.Bundle import android.util.Log -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture import androidx.camera.core.Preview @@ -29,7 +29,7 @@ import androidx.core.content.ContextCompat private const val TAG = "ExtensionsApi" -private class ExtensionsActivity : AppCompatActivity() { +private class ExtensionsActivity : ComponentActivity() { private lateinit var surfaceProvider: Preview.SurfaceProvider // [START android_camerax_extensions_enable_night] diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt index 9e1364569..3f0e285a1 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt @@ -23,8 +23,8 @@ import android.os.Bundle import android.view.OrientationEventListener import android.view.Surface import android.view.View +import androidx.activity.ComponentActivity import androidx.annotation.RequiresApi -import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture @@ -41,7 +41,7 @@ private fun imageProxyRotationSnippet(imageProxy: ImageProxy) { private object CameraUseCasesSetupSnippet { @RequiresApi(Build.VERSION_CODES.P) // [START android_camerax_orientation_rotation_use_cases_setup] - class CameraActivity : AppCompatActivity() { + class CameraActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -77,7 +77,7 @@ private object CameraUseCasesSetupSnippet { private object OrientationEventListenerSnippet { // [START android_camerax_orientation_rotation_orientation_listener] - class CameraActivity : AppCompatActivity() { + class CameraActivity : ComponentActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { @@ -118,7 +118,7 @@ private object OrientationEventListenerSnippet { private object DisplayListenerSnippet { // [START android_camerax_orientation_rotation_display_listener] - class CameraActivity : AppCompatActivity() { + class CameraActivity : ComponentActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { @@ -150,8 +150,7 @@ private object DisplayListenerSnippet { } // [END android_camerax_orientation_rotation_display_listener] - private val CameraActivity.rootView: View - get() = window.decorView + private lateinit var rootView: View private val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder().build() diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt index 439caf459..c93f6031d 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt @@ -18,7 +18,6 @@ package com.example.camera.snippets.camerax import android.os.Bundle import androidx.activity.ComponentActivity -import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider @@ -32,7 +31,7 @@ private object PreviewRequestProviderSnippet { // import androidx.camera.lifecycle.ProcessCameraProvider // import com.google.common.util.concurrent.ListenableFuture - class MainActivity : AppCompatActivity() { + class MainActivity : ComponentActivity() { private lateinit var cameraProviderFuture: ListenableFuture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt index 7199d7806..350d36e82 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt @@ -21,8 +21,8 @@ import android.content.ContentValues import android.content.Context import android.provider.MediaStore import android.util.Log +import androidx.activity.ComponentActivity import androidx.annotation.RequiresPermission -import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY import androidx.camera.core.Preview @@ -54,7 +54,7 @@ private fun qualitySelectorSnippet() { // [END android_camerax_video_capture_quality_selector] } -private class VideoCaptureBindActivity : AppCompatActivity() { +private class VideoCaptureBindActivity : ComponentActivity() { private lateinit var cameraExecutor: Executor private lateinit var qualitySelector: QualitySelector private lateinit var cameraProvider: ProcessCameraProvider @@ -79,7 +79,7 @@ private class VideoCaptureBindActivity : AppCompatActivity() { } } -private class MediaStoreRecordActivity : AppCompatActivity() { +private class MediaStoreRecordActivity : ComponentActivity() { private lateinit var videoCapture: VideoCapture private lateinit var captureListener: Consumer diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1e8ea815f..79abd81ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -328,8 +328,8 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t kotlinx-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } -mlkit-genai-prompt = { module = "com.google.mlkit:genai-prompt", version.ref = "mlkit-genai-prompt" } mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlkit-barcode-scanning" } +mlkit-genai-prompt = { module = "com.google.mlkit:genai-prompt", version.ref = "mlkit-genai-prompt" } mlkit-face-detection = { module = "com.google.mlkit:face-detection", version.ref = "mlkit-face-detection" } mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } From 3874b3cd514ffaa886c3c2eca9a7d233bdea984a Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Fri, 2 Oct 2026 18:29:56 +0000 Subject: [PATCH 5/5] Sort imports in Camera1ToCameraX.kt --- .../com/example/camera/snippets/camerax/Camera1ToCameraX.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt index 4d002bb1d..9716c8b51 100644 --- a/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt @@ -29,8 +29,8 @@ import android.view.MotionEvent import android.view.ScaleGestureDetector import android.view.View import android.widget.Toast -import androidx.annotation.OptIn import androidx.activity.ComponentActivity +import androidx.annotation.OptIn import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.camera.core.Camera