summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2024-03-07 06:41:12 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-03-07 06:41:12 +0000
commitd921d883426a8073300d91cd01dc436f39ceffde (patch)
tree1dcda999e51d96fe691d4d44bb50b26260e7bb6a
parent3e9a9b882113fcec5ee5c08625d57089cf2bc687 (diff)
parent6ad9c28642aecad64e9816f9bb265fa10b181f59 (diff)
downloadex-d921d883426a8073300d91cd01dc436f39ceffde.tar.gz
Merge "Merge Android 14 QPR2 to AOSP main" into main
-rw-r--r--camera2/extensions/eyesFreeVidSample/Android.bp26
-rw-r--r--camera2/extensions/eyesFreeVidSample/AndroidManifest.xml16
-rw-r--r--camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidService.java164
-rw-r--r--camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidSessionProcessor.java484
-rw-r--r--camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/EyesFreeVideographyAdvancedExtenderImpl.java108
5 files changed, 798 insertions, 0 deletions
diff --git a/camera2/extensions/eyesFreeVidSample/Android.bp b/camera2/extensions/eyesFreeVidSample/Android.bp
new file mode 100644
index 00000000..64709bdc
--- /dev/null
+++ b/camera2/extensions/eyesFreeVidSample/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 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
+//
+// http://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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "EyesFreeVidService",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.annotation_annotation"
+ ],
+ platform_apis: true
+}
diff --git a/camera2/extensions/eyesFreeVidSample/AndroidManifest.xml b/camera2/extensions/eyesFreeVidSample/AndroidManifest.xml
new file mode 100644
index 00000000..1e53b094
--- /dev/null
+++ b/camera2/extensions/eyesFreeVidSample/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.camera.extensions.impl.service">
+
+ <application
+ android:defaultToDeviceProtectedStorage="true"
+ android:forceQueryable="true"
+ android:directBootAware="true">
+
+ <service android:name=".EyesFreeVidService"
+ android:visibleToInstantApps="true"
+ android:exported="true">
+ </service>
+ </application>
+
+</manifest>
diff --git a/camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidService.java b/camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidService.java
new file mode 100644
index 00000000..cf669c7a
--- /dev/null
+++ b/camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidService.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 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
+ *
+ * http://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 android.camera.extensions.impl.service;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.annotation.FlaggedApi;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.extension.AdvancedExtender;
+import android.hardware.camera2.extension.CameraExtensionService;
+import android.hardware.camera2.extension.CharacteristicsMap;
+import android.hardware.camera2.extension.SessionProcessor;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.IBinder;
+import android.util.Size;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import com.android.internal.camera.flags.Flags;
+
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public class EyesFreeVidService extends CameraExtensionService {
+
+ private static final String TAG = "EyesFreeVidService";
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Set<IBinder> mAttachedClients = new HashSet<>();
+ CameraManager mCameraManager;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public boolean onRegisterClient(IBinder token) {
+ synchronized (mLock) {
+ if (mAttachedClients.contains(token)) {
+ return false;
+ }
+ mAttachedClients.add(token);
+ return true;
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public void onUnregisterClient(IBinder token) {
+ synchronized (mLock) {
+ mAttachedClients.remove(token);
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public AdvancedExtender onInitializeAdvancedExtension(int extensionType) {
+ mCameraManager = getSystemService(CameraManager.class);
+ return new AdvancedExtenderEyesFreeImpl(mCameraManager);
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public static class AdvancedExtenderEyesFreeImpl extends AdvancedExtender {
+ private CameraCharacteristics mCameraCharacteristics;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public AdvancedExtenderEyesFreeImpl(@NonNull CameraManager cameraManager) {
+ super(cameraManager);
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public boolean isExtensionAvailable(String cameraId,
+ CharacteristicsMap charsMap) {
+ return true;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public void init(String cameraId, CharacteristicsMap map) {
+ mCameraCharacteristics = map.get(cameraId);
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
+ String cameraId) {
+ return filterOutputResolutions(Arrays.asList(ImageFormat.YUV_420_888,
+ ImageFormat.PRIVATE));
+ }
+
+ protected Map<Integer, List<Size>> filterOutputResolutions(List<Integer> formats) {
+ Map<Integer, List<Size>> formatResolutions = new HashMap<>();
+
+ StreamConfigurationMap map = mCameraCharacteristics.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+ if (map != null) {
+ for (Integer format : formats) {
+ if (map.getOutputSizes(format) != null) {
+ formatResolutions.put(format, Arrays.asList(map.getOutputSizes(format)));
+ }
+ }
+ }
+
+ return formatResolutions;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
+ String cameraId) {
+ return filterOutputResolutions(Arrays.asList(ImageFormat.YUV_420_888,
+ ImageFormat.JPEG));
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public SessionProcessor getSessionProcessor() {
+ return new EyesFreeVidSessionProcessor(this);
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public List<CaptureRequest.Key> getAvailableCaptureRequestKeys(
+ String cameraId) {
+ final CaptureRequest.Key [] CAPTURE_REQUEST_SET = {CaptureRequest.CONTROL_ZOOM_RATIO,
+ CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_REGIONS,
+ CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.JPEG_QUALITY,
+ CaptureRequest.JPEG_ORIENTATION};
+ return Arrays.asList(CAPTURE_REQUEST_SET);
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public List<CaptureResult.Key> getAvailableCaptureResultKeys(
+ String cameraId) {
+ final CaptureResult.Key [] CAPTURE_RESULT_SET = {CaptureResult.CONTROL_ZOOM_RATIO,
+ CaptureResult.CONTROL_AF_MODE, CaptureResult.CONTROL_AF_REGIONS,
+ CaptureResult.CONTROL_AF_TRIGGER, CaptureResult.CONTROL_AF_STATE,
+ CaptureResult.JPEG_QUALITY, CaptureResult.JPEG_ORIENTATION};
+ return Arrays.asList(CAPTURE_RESULT_SET);
+ }
+ }
+}
diff --git a/camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidSessionProcessor.java b/camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidSessionProcessor.java
new file mode 100644
index 00000000..f23f3a68
--- /dev/null
+++ b/camera2/extensions/eyesFreeVidSample/src/android/camera/extensions/impl/service/EyesFreeVidSessionProcessor.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2023 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
+ *
+ * http://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 android.camera.extensions.impl.service;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.camera.extensions.impl.service.EyesFreeVidService.AdvancedExtenderEyesFreeImpl;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.extension.CameraOutputSurface;
+import android.hardware.camera2.extension.CharacteristicsMap;
+import android.hardware.camera2.extension.ExtensionConfiguration;
+import android.hardware.camera2.extension.ExtensionOutputConfiguration;
+import android.hardware.camera2.extension.RequestProcessor;
+import android.hardware.camera2.extension.SessionProcessor;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.Pair;
+import androidx.annotation.GuardedBy;
+
+import com.android.internal.camera.flags.Flags;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public class EyesFreeVidSessionProcessor extends SessionProcessor {
+
+ private static final String TAG = "EyesFreeVidSessionProcessor";
+
+ protected static final int MAX_NUM_IMAGES = 10;
+ protected static final int CAPTURE_OUTPUT_ID = 0;
+ protected static final int PREVIEW_OUTPUT_ID = 1;
+
+ protected HandlerThread mHandlerThread;
+ protected Handler mHandler;
+
+ protected CameraOutputSurface mPreviewOutputSurfaceConfig;
+ protected CameraOutputSurface mCaptureOutputSurfaceConfig;
+
+ protected final Object mParametersLock = new Object();
+ @GuardedBy("mParametersLock")
+ protected List<Pair<CaptureRequest.Key, Object>> mParameters = new ArrayList<>();
+
+ protected AtomicInteger mNextCaptureSequenceId = new AtomicInteger(1);
+
+ protected final Object mLockPreviewSurfaceImageWriter = new Object();
+ @GuardedBy("mLockPreviewSurfaceImageWriter")
+ protected ImageWriter mPreviewSurfaceImageWriter;
+
+ protected RequestProcessor mRequestProcessor;
+ protected AdvancedExtenderEyesFreeImpl mAdvancedExtender;
+ protected ImageReader mPreviewImageReader;
+ protected String mCameraId;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ protected EyesFreeVidSessionProcessor(AdvancedExtenderEyesFreeImpl advancedExtender) {
+ mAdvancedExtender = advancedExtender;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public ExtensionConfiguration initSession(@NonNull IBinder token,
+ @NonNull String cameraId, @NonNull CharacteristicsMap map,
+ @NonNull CameraOutputSurface previewSurface,
+ @NonNull CameraOutputSurface imageCaptureSurface) {
+
+ Log.d(TAG, "initSession cameraId=" + cameraId);
+
+ mPreviewOutputSurfaceConfig = previewSurface;
+ mCaptureOutputSurfaceConfig = imageCaptureSurface;
+
+ List<ExtensionOutputConfiguration> outputs = new ArrayList<>();
+
+ if (imageCaptureSurface.getSurface() != null) {
+ List<CameraOutputSurface> captureList = new ArrayList<>(List.of(imageCaptureSurface));
+
+ ExtensionOutputConfiguration captureConfig = new ExtensionOutputConfiguration(
+ captureList, CAPTURE_OUTPUT_ID, null, 0);
+ outputs.add(captureConfig);
+ }
+
+ // Register the image reader surface in the output configuration to process frames
+ // before enqueueing them to the clients preview surface
+ if (previewSurface.getSurface() != null) {
+ mPreviewImageReader = ImageReader.newInstance(previewSurface.getSize().getWidth(),
+ previewSurface.getSize().getHeight(), previewSurface.getImageFormat(),
+ MAX_NUM_IMAGES);
+
+ List<CameraOutputSurface> previewList = new ArrayList<>(List.of(
+ new CameraOutputSurface(mPreviewImageReader.getSurface(),
+ previewSurface.getSize())));
+
+ ExtensionOutputConfiguration previewConfig = new ExtensionOutputConfiguration(
+ previewList, PREVIEW_OUTPUT_ID, null, 0);
+ outputs.add(previewConfig);
+ }
+
+ ExtensionConfiguration res = new ExtensionConfiguration(0 /*session type*/,
+ CameraDevice.TEMPLATE_PREVIEW, outputs, null);
+
+ return res;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public void deInitSession(@NonNull IBinder token) {
+ if (mPreviewImageReader != null) {
+ mPreviewImageReader.close();
+ mPreviewImageReader = null;
+ }
+
+ if (mHandlerThread != null){
+ mHandlerThread.quitSafely();
+ }
+
+ mHandler = null;
+
+ synchronized (mLockPreviewSurfaceImageWriter) {
+ if (mPreviewSurfaceImageWriter != null) {
+ mPreviewSurfaceImageWriter.close();
+ mPreviewSurfaceImageWriter = null;
+ }
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor,
+ @NonNull String statsKey) {
+ mRequestProcessor = requestProcessor;
+
+ if (mPreviewOutputSurfaceConfig.getSurface() == null) {
+ return;
+ }
+
+ synchronized (mLockPreviewSurfaceImageWriter) {
+ mPreviewSurfaceImageWriter = new ImageWriter
+ .Builder(mPreviewOutputSurfaceConfig.getSurface())
+ .setImageFormat(mPreviewOutputSurfaceConfig.getImageFormat())
+ .setMaxImages(MAX_NUM_IMAGES)
+ .build();
+ }
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ mPreviewImageReader.setOnImageAvailableListener(new ImageListener(), mHandler);
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public void onCaptureSessionEnd() {
+ mRequestProcessor = null;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public int startRepeating(@Nullable Executor executor,
+ @NonNull CaptureCallback captureCallback) {
+ List<Integer> outputConfigIds = new ArrayList<>(List.of(PREVIEW_OUTPUT_ID));
+
+ RequestProcessor.Request requestRes;
+ synchronized (mParametersLock) {
+ requestRes = new RequestProcessor.Request(outputConfigIds, mParameters,
+ CameraDevice.TEMPLATE_PREVIEW);
+ }
+
+ final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+ RequestProcessor.RequestCallback resCallback = new RequestProcessor.RequestCallback() {
+ @Override
+ public void onCaptureStarted(RequestProcessor.Request request, long frameNumber,
+ long timestamp) {
+ captureCallback.onCaptureStarted(seqId, timestamp);
+ }
+
+ @Override
+ public void onCaptureProgressed(RequestProcessor.Request request,
+ CaptureResult partialResult) {
+ }
+
+ @Override
+ public void onCaptureCompleted(RequestProcessor.Request request,
+ TotalCaptureResult totalCaptureResult) {
+ addCaptureResultKeys(seqId, totalCaptureResult, captureCallback);
+ captureCallback.onCaptureProcessStarted(seqId);
+ }
+
+ @Override
+ public void onCaptureFailed(RequestProcessor.Request request,
+ CaptureFailure captureFailure) {
+ captureCallback.onCaptureFailed(seqId);
+ }
+
+ @Override
+ public void onCaptureBufferLost(RequestProcessor.Request request,
+ long frameNumber, int outputStreamId) {
+ captureCallback.onCaptureFailed(seqId);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+ captureCallback.onCaptureSequenceCompleted(seqId);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(int sequenceId) {
+ captureCallback.onCaptureSequenceAborted(seqId);
+ }
+ };
+
+ mRequestProcessor.setRepeating(requestRes, executor, resCallback);
+
+ return seqId;
+ }
+
+ protected void addCaptureResultKeys(
+ @NonNull int seqId,
+ @NonNull TotalCaptureResult result,
+ @NonNull CaptureCallback captureCallback) {
+
+ CameraMetadataNative cameraMetadataNative = new CameraMetadataNative();
+ long vendorId = mAdvancedExtender.getMetadataVendorId(mCameraId);
+ cameraMetadataNative.setVendorId(vendorId);
+
+ Long shutterTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ if (shutterTimestamp != null) {
+
+ List<CaptureResult.Key> captureResultKeys =
+ mAdvancedExtender.getAvailableCaptureResultKeys(mCameraId);
+ for (CaptureResult.Key key : captureResultKeys) {
+ if (result.get(key) != null) {
+ cameraMetadataNative.set(key, result.get(key));
+ }
+ }
+ CaptureResult captureResult = new CaptureResult(cameraMetadataNative, seqId);
+ captureCallback.onCaptureCompleted(shutterTimestamp, seqId,
+ captureResult);
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public void stopRepeating() {
+ mRequestProcessor.stopRepeating();
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public int startCapture(@Nullable Executor executor,
+ @NonNull CaptureCallback captureCallback) {
+ List<Integer> outputConfigIds = new ArrayList<>(List.of(CAPTURE_OUTPUT_ID));
+
+ RequestProcessor.Request requestRes;
+ synchronized (mParametersLock) {
+ requestRes = new RequestProcessor.Request(outputConfigIds, mParameters,
+ CameraDevice.TEMPLATE_PREVIEW);
+ }
+
+ final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+ RequestProcessor.RequestCallback resCallback = new RequestProcessor.RequestCallback() {
+
+ @Override
+ public void onCaptureStarted(RequestProcessor.Request request,
+ long frameNumber, long timestamp) {
+ captureCallback.onCaptureStarted(seqId, timestamp);
+ }
+
+ @Override
+ public void onCaptureProgressed(RequestProcessor.Request request,
+ CaptureResult partialResult) {
+
+ }
+
+ @Override
+ public void onCaptureCompleted(RequestProcessor.Request request,
+ TotalCaptureResult totalCaptureResult) {
+ addCaptureResultKeys(seqId, totalCaptureResult, captureCallback);
+ }
+
+ @Override
+ public void onCaptureFailed(RequestProcessor.Request request,
+ CaptureFailure captureFailure) {
+ captureCallback.onCaptureFailed(seqId);
+ }
+
+ @Override
+ public void onCaptureBufferLost(RequestProcessor.Request request,
+ long frameNumber, int outputStreamId) {
+ captureCallback.onCaptureFailed(seqId);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+ captureCallback.onCaptureSequenceCompleted(seqId);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(int sequenceId) {
+ captureCallback.onCaptureSequenceAborted(seqId);
+ }
+ };
+
+ mRequestProcessor.submit(requestRes, executor, resCallback);
+
+ captureCallback.onCaptureProcessStarted(seqId);
+ return seqId;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public int startTrigger(@NonNull CaptureRequest captureRequest,
+ @Nullable Executor executor, @NonNull CaptureCallback captureCallback) {
+ List<Integer> outputConfigIds = new ArrayList<>(List.of(PREVIEW_OUTPUT_ID));
+
+ RequestProcessor.Request requestRes = new RequestProcessor.Request(outputConfigIds,
+ getTriggersRequestKeys(captureRequest), CameraDevice.TEMPLATE_PREVIEW);
+
+ final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+ RequestProcessor.RequestCallback resCallback = new RequestProcessor.RequestCallback() {
+
+ @Override
+ public void onCaptureStarted(RequestProcessor.Request request,
+ long frameNumber, long timestamp) {
+ captureCallback.onCaptureStarted(seqId, timestamp);
+ }
+
+ @Override
+ public void onCaptureProgressed(RequestProcessor.Request request,
+ CaptureResult partialResult) {
+
+ }
+
+ @Override
+ public void onCaptureCompleted(RequestProcessor.Request request,
+ TotalCaptureResult totalCaptureResult) {
+ addCaptureResultKeys(seqId, totalCaptureResult, captureCallback);
+ }
+
+ @Override
+ public void onCaptureFailed(RequestProcessor.Request request,
+ CaptureFailure captureFailure) {
+ captureCallback.onCaptureFailed(seqId);
+ }
+
+ @Override
+ public void onCaptureBufferLost(RequestProcessor.Request request,
+ long frameNumber, int outputStreamId) {
+ captureCallback.onCaptureFailed(seqId);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+ captureCallback.onCaptureSequenceCompleted(seqId);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(int sequenceId) {
+ captureCallback.onCaptureSequenceAborted(seqId);
+ }
+ };
+
+ mRequestProcessor.submit(requestRes, executor, resCallback);
+
+ captureCallback.onCaptureProcessStarted(seqId);
+ return seqId;
+ }
+
+ protected List<Pair<CaptureRequest.Key, Object>>
+ getTriggersRequestKeys(@NonNull CaptureRequest captureRequest) {
+ List<Pair<CaptureRequest.Key, Object>> parameters = new ArrayList<>();
+ List<CaptureRequest.Key> availableRequestKeys =
+ mAdvancedExtender.getAvailableCaptureRequestKeys(mCameraId);
+
+ for (CaptureRequest.Key<?> key : captureRequest.getKeys()) {
+ if (availableRequestKeys.contains(key)) {
+ Object value = captureRequest.get(key);
+ parameters.add(new Pair<>(key, value));
+ }
+ }
+
+ return parameters;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ public void setParameters(@NonNull CaptureRequest captureRequest) {
+ synchronized (mParametersLock) {
+ for (CaptureRequest.Key<?> key : captureRequest.getKeys()) {
+ Object value = captureRequest.get(key);
+ mParameters.add(new Pair<>(key, value));
+ }
+ }
+ }
+
+ private class ImageListener implements ImageReader.OnImageAvailableListener {
+
+ private final Object mLock = new Object();
+ private AtomicBoolean mProcessing = new AtomicBoolean(false);
+
+ // Handler for background processing
+ private Handler backgroundHandler;
+
+ public ImageListener() {
+ HandlerThread handlerThread = new HandlerThread("ImageProcessingThread");
+ handlerThread.start();
+ backgroundHandler = new Handler(handlerThread.getLooper());
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+
+ Image image = reader.acquireNextImage();
+ if (image == null) {
+ return;
+ }
+
+ // If processing is ongoing, drop the image and return
+ if (mProcessing.get()) {
+ Log.w(TAG, "Dropping image due to ongoing processing");
+ image.close();
+ return;
+ }
+ mProcessing.set(true);
+
+ processImage(image);
+ }
+
+ private void processImage(Image image) {
+ backgroundHandler.post(() -> processAndQueueImage(image));
+ }
+
+ private void processAndQueueImage(Image image) {
+ try {
+ if (image != null) {
+ // Process the image here if needed
+ synchronized (mLockPreviewSurfaceImageWriter) {
+ mPreviewSurfaceImageWriter.queueInputImage(image);
+ }
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Output surface likely abandoned, dropping buffer!");
+ } finally {
+ if (image != null) {
+ image.close();
+ }
+ mProcessing.set(false);
+ }
+ }
+ }
+}
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/EyesFreeVideographyAdvancedExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/EyesFreeVideographyAdvancedExtenderImpl.java
new file mode 100644
index 00000000..01b65be0
--- /dev/null
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/EyesFreeVideographyAdvancedExtenderImpl.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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 androidx.camera.extensions.impl.advanced;
+
+import android.annotation.SuppressLint;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Range;
+import android.util.Size;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Stub advanced extender implementation for eyes free videography.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.5
+ */
+@SuppressLint("UnknownNullness")
+public class EyesFreeVideographyAdvancedExtenderImpl implements AdvancedExtenderImpl {
+ public EyesFreeVideographyAdvancedExtenderImpl() {
+ }
+
+ @Override
+ public boolean isExtensionAvailable(String cameraId,
+ Map<String, CameraCharacteristics> characteristicsMap) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public void init(String cameraId,
+ Map<String, CameraCharacteristics> characteristicsMap) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(
+ String cameraId, Size size, int imageFormat) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
+ String cameraId) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+
+ @Override
+ public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
+ String cameraId) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+ Size captureSize) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public List<Size> getSupportedYuvAnalysisResolutions(
+ String cameraId) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public SessionProcessorImpl createSessionProcessor() {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public boolean isCaptureProcessProgressAvailable() {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+
+ @Override
+ public boolean isPostviewAvailable() {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
+}