diff --git a/camera/build.gradle.kts b/camera/build.gradle.kts index ba71d4ae0..8727e1411 100644 --- a/camera/build.gradle.kts +++ b/camera/build.gradle.kts @@ -72,12 +72,14 @@ 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(libs.androidx.window) implementation(libs.play.services.wearable) 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..bd9220d00 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Analyze.kt @@ -0,0 +1,58 @@ +/* + * 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.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 +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.lifecycle.LifecycleOwner +import java.util.concurrent.Executor + +private class ImageAnalysisActivity : ComponentActivity() { + + @Suppress("DEPRECATION") + private fun bindImageAnalysis( + cameraProvider: ProcessCameraProvider, + cameraSelector: CameraSelector, + preview: Preview, + executor: Executor, + ) { + // [START android_camerax_analyze_bind_lifecycle] + 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 + // ... + // after done, release the ImageProxy object. + imageProxy.close() + } + ) + + 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 new file mode 100644 index 000000000..2ffa675f5 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Architecture.kt @@ -0,0 +1,189 @@ +/* + * 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.camera.snippets.camerax + +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.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.CameraSelector +import androidx.camera.core.ImageCapture +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +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 : ComponentActivity() { + + 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] + } +} + +private object CustomLifecycleSnippet { + // [START android_camerax_architecture_custom_lifecycle] + class CustomLifecycle : LifecycleOwner { + private val lifecycleRegistry: LifecycleRegistry + + init { + lifecycleRegistry = LifecycleRegistry(this) + lifecycleRegistry.currentState = Lifecycle.State.CREATED + } + // ... + fun doOnResume() { + lifecycleRegistry.currentState = State.RESUMED + } + // ... + override val lifecycle: Lifecycle + get() = lifecycleRegistry + } + // [END android_camerax_architecture_custom_lifecycle] +} + +private class ConcurrentUseCasesActivity : ComponentActivity() { + 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.activity_main) + + 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 class VideoCallStreamActivity : ComponentActivity() { + private var camera: Camera? = null + + @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 new file mode 100644 index 000000000..46aab5719 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Camera1ToCameraX.kt @@ -0,0 +1,438 @@ +/* + * 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.camera.snippets.camerax + +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 +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import android.view.ScaleGestureDetector +import android.view.View +import android.widget.Toast +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 +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.Quality +import androidx.camera.video.QualitySelector +import androidx.camera.video.Recorder +import androidx.camera.video.Recording +import androidx.camera.video.VideoCapture +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.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import java.io.File +import java.text.SimpleDateFormat +import java.util.Locale +import kotlinx.coroutines.launch + +private const val TAG = "Camera1ToCameraX" + +private fun selectCameraWithController(baseContext: Context) { + // [START android_camerax_camera1_to_camerax_select_camera_controller] + // CameraX: select a camera with CameraController. + + 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(requireContext()).await() + + // Set up UseCases (more on UseCases in later scenarios). + var useCases: Array = emptyArray() + + // 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) + } + } + + // ... + + // Call startCamera in the setup flow of your app, such as in onViewCreated. + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // ... + + lifecycleScope.launch { + startCamera() + } + } + // [END android_camerax_camera1_to_camerax_select_camera_provider] +} + +@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.getCameraSelector() + // [END android_camerax_camera1_to_camerax_focal_length] +} + +private class ControllerStateObserversActivity : ComponentActivity() { + private lateinit var cameraController: LifecycleCameraController + + @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") + } + } + + cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver) + // [END android_camerax_camera1_to_camerax_tap_to_focus_controller] + } + + 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.getZoomState().observe(this, pinchToZoomStateObserver) + // [END android_camerax_camera1_to_camerax_pinch_to_zoom_controller] + } +} + +private class TapToFocusProviderActivity : ComponentActivity() { + private lateinit var previewView: PreviewView + private var camera: Camera? = null + private lateinit var scaleGestureDetector: ScaleGestureDetector + + @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. + + // 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 { + 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 + } + } + ) + + // ... + + // 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 class PinchToZoomProviderActivity : ComponentActivity() { + private lateinit var previewView: PreviewView + private var camera: Camera? = null + private lateinit var gestureDetector: GestureDetector + + 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 { + 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 + } + } + ) + + // ... + + // 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 : ComponentActivity() { + private lateinit var cameraController: LifecycleCameraController + 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. + + 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(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) + } + } + ) + } + // [END android_camerax_camera1_to_camerax_take_photo_controller] +} + +private class ImageCaptureBindActivity : ComponentActivity() { + 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 + + // ... + + // Create an ImageCapture instance (can be added with other + // UseCase definitions). + imageCapture = ImageCapture.Builder().build() + + // ... + + // 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 class TakePhotoProviderActivity : ComponentActivity() { + 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] + val outputOptions = ImageCapture.OutputFileOptions + .Builder(File(filesDir, "photo.jpg")) + .build() + // [END_EXCLUDE] + + // Call takePicture on imageCapture instance. + imageCapture.takePicture( + // [START_EXCLUDE] + 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_EXCLUDE] + ) + } + // [END android_camerax_camera1_to_camerax_take_photo_provider_call] +} + +private fun enableVideoCapture(cameraController: LifecycleCameraController) { + // [START android_camerax_camera1_to_camerax_video_controller_enable] + // CameraX: Enable VideoCapture UseCase on CameraController. + + cameraController.setEnabledUseCases(VIDEO_CAPTURE) + // [END android_camerax_camera1_to_camerax_video_controller_enable] +} + +private class Camera1VideoCaptureBindActivity : ComponentActivity() { + 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. + lateinit var videoCapture: VideoCapture + var recording: Recording? = null + + // ... + + // 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) + + // ... + + // 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 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 new file mode 100644 index 000000000..a9be36be4 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Configuration.kt @@ -0,0 +1,285 @@ +/* + * 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.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.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 androidx.activity.ComponentActivity +import androidx.annotation.OptIn +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.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 +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 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_16_9) + .build() + // [END android_camerax_configuration_image_capture_builder] +} + +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] +} + +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] +} + +private class ConfigurationOrientationService : Service() { + override fun onBind(intent: Intent?): IBinder? = null + + // [START android_camerax_configuration_orientation_listener] + 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. + 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: 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 : ComponentActivity() { + private fun getViewportSnippet() { + // [START android_camerax_configuration_get_viewport] + val viewport = findViewById(R.id.preview_view).viewPort + // [END android_camerax_configuration_get_viewport] + } +} + +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() + } + else -> null + } + } + + // 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, + 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] +} + +@Suppress("DEPRECATION") +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 class FocusMeteringActivity : ComponentActivity() { + 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, FLAG_AF or 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 + // ... + }, 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 new file mode 100644 index 000000000..63f71a21b --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/ExtensionsApi.kt @@ -0,0 +1,103 @@ +/* + * 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.camera.snippets.camerax + +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +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 : ComponentActivity() { + 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) + + 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..66223cb49 --- /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.camera.snippets.camerax + +import androidx.activity.ComponentActivity +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 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(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 new file mode 100644 index 000000000..3f0e285a1 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/OrientationRotation.kt @@ -0,0 +1,158 @@ +/* + * 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.camera.snippets.camerax + +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.activity.ComponentActivity +import androidx.annotation.RequiresApi +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.lifecycle.ProcessCameraProvider + +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 object CameraUseCasesSetupSnippet { + @RequiresApi(Build.VERSION_CODES.P) + // [START android_camerax_orientation_rotation_use_cases_setup] + class CameraActivity : ComponentActivity() { + 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 + + private fun buildPreview(): Preview = Preview.Builder().build() + + private fun buildImageAnalysis(): ImageAnalysis = ImageAnalysis.Builder().build() + + private fun buildImageCapture(): ImageCapture = ImageCapture.Builder().build() +} + +private object OrientationEventListenerSnippet { + // [START android_camerax_orientation_rotation_orientation_listener] + class CameraActivity : ComponentActivity() { + + 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] + + private val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder().build() + + private val imageCapture: ImageCapture = ImageCapture.Builder().build() +} + +private object DisplayListenerSnippet { + // [START android_camerax_orientation_rotation_display_listener] + class CameraActivity : ComponentActivity() { + + 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] + + private lateinit var rootView: View + + private val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder().build() + + private val imageCapture: ImageCapture = ImageCapture.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 new file mode 100644 index 000000000..c93f6031d --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/Preview.kt @@ -0,0 +1,88 @@ +/* + * 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.camera.snippets.camerax + +import android.os.Bundle +import androidx.activity.ComponentActivity +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 + +private object PreviewRequestProviderSnippet { + // [START android_camerax_preview_request_provider] + // import androidx.camera.lifecycle.ProcessCameraProvider + // import com.google.common.util.concurrent.ListenableFuture + + class MainActivity : ComponentActivity() { + private lateinit var cameraProviderFuture: ListenableFuture + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + cameraProviderFuture = ProcessCameraProvider.getInstance(this) + } + } + // [END android_camerax_preview_request_provider] +} + +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 { + val cameraProvider = cameraProviderFuture.get() + bindPreview(cameraProvider) + }, + ContextCompat.getMainExecutor(this) + ) + // [END android_camerax_preview_check_provider] + } + + // [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.getSurfaceProvider()) + + var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview) + } + // [END android_camerax_preview_bind] +} + +private fun previewImplementationModeSnippet(viewFinder: PreviewView) { + // [START android_camerax_preview_implementation_mode] + // 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.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..5b5426a9f --- /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.camera.snippets.camerax + +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..350d36e82 --- /dev/null +++ b/camera/src/main/java/com/example/camera/snippets/camerax/VideoCapture.kt @@ -0,0 +1,123 @@ +/* + * 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.camera.snippets.camerax + +import android.Manifest +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.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 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) + ) + // [END android_camerax_video_capture_quality_selector] +} + +private class VideoCaptureBindActivity : ComponentActivity() { + 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 : ComponentActivity() { + private lateinit var videoCapture: VideoCapture + private lateinit var captureListener: Consumer + + @RequiresPermission(Manifest.permission.RECORD_AUDIO) + 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/activity_main.xml b/camera/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..a592fcf66 --- /dev/null +++ b/camera/src/main/res/layout/activity_main.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80ac3bada..79abd81ba 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" } @@ -326,6 +328,7 @@ 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-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" }