summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/system-current.txt88
-rw-r--r--core/java/android/hardware/camera2/CameraExtensionCharacteristics.java21
-rw-r--r--core/java/android/hardware/camera2/extension/AdvancedExtender.java353
-rw-r--r--core/java/android/hardware/camera2/extension/CameraExtensionService.java170
-rw-r--r--core/java/android/hardware/camera2/extension/CameraOutputSurface.java75
-rw-r--r--core/java/android/hardware/camera2/extension/CharacteristicsMap.java58
-rw-r--r--core/java/android/hardware/camera2/extension/ExtensionConfiguration.java69
-rw-r--r--core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java83
-rw-r--r--core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl2
-rw-r--r--core/java/android/hardware/camera2/extension/RequestProcessor.java582
-rw-r--r--core/java/android/hardware/camera2/extension/SessionProcessor.java495
-rw-r--r--core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java3
-rw-r--r--core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java9
-rw-r--r--packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java2
15 files changed, 2006 insertions, 5 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 119a3de6835e..228a8f867844 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18976,6 +18976,7 @@ package android.hardware.camera2 {
field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
field public static final int EXTENSION_BOKEH = 2; // 0x2
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
field public static final int EXTENSION_HDR = 3; // 0x3
field public static final int EXTENSION_NIGHT = 4; // 0x4
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 30d57bb190be..4fe1c8b0a9e4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4480,6 +4480,94 @@ package android.hardware.camera2 {
}
+package android.hardware.camera2.extension {
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class AdvancedExtender {
+ ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected AdvancedExtender(@NonNull android.hardware.camera2.CameraManager);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getMetadataVendorId(@NonNull String);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedPreviewOutputResolutions(@NonNull String);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void init(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class CameraExtensionService extends android.app.Service {
+ ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected CameraExtensionService();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean onRegisterClient(@NonNull android.os.IBinder);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onUnregisterClient(@NonNull android.os.IBinder);
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface {
+ ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @Nullable android.util.Size);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.util.Size getSize();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.view.Surface getSurface();
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap {
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.Set<java.lang.String> getCameraIds();
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
+ ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
+ ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionOutputConfiguration(@NonNull java.util.List<android.hardware.camera2.extension.CameraOutputSurface>, int, @Nullable String, int);
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class RequestProcessor {
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void abortCaptures();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void stopRepeating();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback);
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request {
+ ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int);
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback {
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int, long);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long);
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class SessionProcessor {
+ ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected SessionProcessor();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void deInitSession(@NonNull android.os.IBinder);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionEnd();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startCapture(@Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startRepeating(@Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void stopRepeating();
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface SessionProcessor.CaptureCallback {
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(long, int, @NonNull android.hardware.camera2.CaptureResult);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProcessStarted(int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(int, long);
+ }
+
+}
+
package android.hardware.camera2.params {
public final class OutputConfiguration implements android.os.Parcelable {
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 0a61c32a9cf5..d4d1ab373157 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -15,6 +15,7 @@
*/
package android.hardware.camera2;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,11 +40,15 @@ import android.os.ConditionVariable;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.util.FeatureFlagUtils;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Range;
import android.util.Size;
+import com.android.internal.camera.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -129,6 +134,12 @@ public final class CameraExtensionCharacteristics {
public static final int EXTENSION_NIGHT = 4;
/**
+ * An extension that aims to lock and stabilize a given region or object of interest.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5;
+
+ /**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -136,7 +147,8 @@ public final class CameraExtensionCharacteristics {
EXTENSION_FACE_RETOUCH,
EXTENSION_BOKEH,
EXTENSION_HDR,
- EXTENSION_NIGHT})
+ EXTENSION_NIGHT,
+ EXTENSION_EYES_FREE_VIDEOGRAPHY})
public @interface Extension {
}
@@ -594,8 +606,13 @@ public final class CameraExtensionCharacteristics {
return Collections.unmodifiableList(ret);
}
+ IntArray extensionList = new IntArray(EXTENSION_LIST.length);
+ extensionList.addAll(EXTENSION_LIST);
+ if (Flags.concertMode()) {
+ extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
+ }
try {
- for (int extensionType : EXTENSION_LIST) {
+ for (int extensionType : extensionList.toArray()) {
if (isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) {
ret.add(extensionType);
}
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
new file mode 100644
index 000000000000..fb2df546f545
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -0,0 +1,353 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.impl.CaptureCallback;
+import android.util.Log;
+import android.util.Size;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Advanced contract for implementing Extensions. ImageCapture/Preview
+ * Extensions are both implemented on this interface.
+ *
+ * <p>This advanced contract empowers implementations to gain access to
+ * more Camera2 capability. This includes: (1) Add custom surfaces with
+ * specific formats like YUV, RAW, RAW_DEPTH. (2) Access to
+ * the capture request callbacks as well as all the images retrieved of
+ * various image formats. (3)
+ * Able to triggers single or repeating request with the capabilities to
+ * specify target surfaces, template id and parameters.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public abstract class AdvancedExtender {
+ private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
+ private final CameraManager mCameraManager;
+
+ private static final String TAG = "AdvancedExtender";
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ protected AdvancedExtender(@NonNull CameraManager cameraManager) {
+ mCameraManager = cameraManager;
+ try {
+ String [] cameraIds = mCameraManager.getCameraIdListNoLazy();
+ if (cameraIds != null) {
+ for (String cameraId : cameraIds) {
+ CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
+ Object thisClass = CameraCharacteristics.Key.class;
+ Class<CameraCharacteristics.Key<?>> keyClass =
+ (Class<CameraCharacteristics.Key<?>>)thisClass;
+ ArrayList<CameraCharacteristics.Key<?>> vendorKeys =
+ chars.getNativeMetadata().getAllVendorKeys(keyClass);
+ if ((vendorKeys != null) && !vendorKeys.isEmpty()) {
+ mMetadataVendorIdMap.put(cameraId, vendorKeys.get(0).getVendorId());
+ }
+ }
+ }
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to query camera characteristics!");
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public long getMetadataVendorId(@NonNull String cameraId) {
+ long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ?
+ mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE;
+ return vendorId;
+ }
+
+ /**
+ * Indicates whether the extension is supported on the device.
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @param charsMap A map consisting of the camera ids and
+ * the {@link android.hardware.camera2.CameraCharacteristics}s. For
+ * every camera, the map contains at least
+ * the CameraCharacteristics for the camera
+ * id.
+ * If the camera is logical camera, it will
+ * also contain associated
+ * physical camera ids and their
+ * CameraCharacteristics.
+ * @return true if the extension is supported, otherwise false
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract boolean isExtensionAvailable(@NonNull String cameraId,
+ @NonNull CharacteristicsMap charsMap);
+
+ /**
+ * Initializes the extender to be used with the specified camera.
+ *
+ * <p>This should be called before any other method on the extender.
+ * The exception is {@link #isExtensionAvailable}.
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @param map A map consisting of the camera ids and
+ * the {@link android.hardware.camera2.CameraCharacteristics}s. For
+ * every camera, the map contains at least
+ * the CameraCharacteristics for the camera
+ * id.
+ * If the camera is logical camera, it will
+ * also contain associated
+ * physical camera ids and their
+ * CameraCharacteristics.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract void init(@NonNull String cameraId, @NonNull CharacteristicsMap map);
+
+ /**
+ * Returns supported output format/size map for preview. The format
+ * could be PRIVATE or YUV_420_888. Implementations must support
+ * PRIVATE format at least.
+ *
+ * <p>The preview surface format in the CameraCaptureSession may not
+ * be identical to the supported preview output format returned here.
+ * @param cameraId The camera2 id string of the camera.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public abstract Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
+ @NonNull String cameraId);
+
+ /**
+ * Returns supported output format/size map for image capture. OEM is
+ * required to support both JPEG and YUV_420_888 format output.
+ *
+ * <p>The surface created with this supported
+ * format/size could be either added in CameraCaptureSession with HAL
+ * processing OR it configures intermediate surfaces(YUV/RAW..) and
+ * writes the output to the output surface.
+ * @param cameraId The camera2 id string of the camera.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public abstract Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
+ @NonNull String cameraId);
+
+ /**
+ * Returns a processor for activating extension sessions. It
+ * implements all the interactions required for starting an extension
+ * and cleanup.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public abstract SessionProcessor getSessionProcessor();
+
+ /**
+ * Returns a list of orthogonal capture request keys.
+ *
+ * <p>Any keys included in the list will be configurable by clients of
+ * the extension and will affect the extension functionality.</p>
+ *
+ * <p>Please note that the keys {@link CaptureRequest#JPEG_QUALITY}
+ * and {@link CaptureRequest#JPEG_ORIENTATION} are always supported
+ * regardless of being added to the list or not. To support common
+ * camera operations like zoom, tap-to-focus, flash and
+ * exposure compensation, we recommend supporting the following keys
+ * if possible.
+ * <pre>
+ * zoom: {@link CaptureRequest#CONTROL_ZOOM_RATIO}
+ * {@link CaptureRequest#SCALER_CROP_REGION}
+ * tap-to-focus:
+ * {@link CaptureRequest#CONTROL_AF_MODE}
+ * {@link CaptureRequest#CONTROL_AF_TRIGGER}
+ * {@link CaptureRequest#CONTROL_AF_REGIONS}
+ * {@link CaptureRequest#CONTROL_AE_REGIONS}
+ * {@link CaptureRequest#CONTROL_AWB_REGIONS}
+ * flash:
+ * {@link CaptureRequest#CONTROL_AE_MODE}
+ * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER}
+ * {@link CaptureRequest#FLASH_MODE}
+ * exposure compensation:
+ * {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION}
+ * </pre>
+ *
+ * @param cameraId The camera2 id string of the camera.
+ *
+ * @return The list of supported orthogonal capture keys, or empty
+ * list if no capture settings are not supported.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public abstract List<CaptureRequest.Key> getAvailableCaptureRequestKeys(
+ @NonNull String cameraId);
+
+ /**
+ * Returns a list of supported capture result keys.
+ *
+ * <p>Any keys included in this list must be available as part of the
+ * registered {@link CaptureCallback#onCaptureCompleted} callback.</p>
+ *
+ * <p>At the very minimum, it is expected that the result key list is
+ * a superset of the capture request keys.</p>
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @return The list of supported capture result keys, or
+ * empty list if capture results are not supported.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public abstract List<CaptureResult.Key> getAvailableCaptureResultKeys(
+ @NonNull String cameraId);
+
+
+ private final class AdvancedExtenderImpl extends IAdvancedExtenderImpl.Stub {
+ @Override
+ public boolean isExtensionAvailable(String cameraId,
+ Map<String, CameraMetadataNative> charsMapNative) {
+ return AdvancedExtender.this.isExtensionAvailable(cameraId,
+ new CharacteristicsMap(charsMapNative));
+ }
+
+ @Override
+ public void init(String cameraId, Map<String, CameraMetadataNative> charsMapNative) {
+ AdvancedExtender.this.init(cameraId, new CharacteristicsMap(charsMapNative));
+ }
+
+ @Override
+ public List<SizeList> getSupportedPostviewResolutions(
+ android.hardware.camera2.extension.Size captureSize) {
+ // Feature is currently unsupported
+ return null;
+ }
+
+ @Override
+ public List<SizeList> getSupportedPreviewOutputResolutions(String cameraId) {
+ return initializeParcelable(
+ AdvancedExtender.this.getSupportedPreviewOutputResolutions(cameraId));
+ }
+
+ @Override
+ public List<SizeList> getSupportedCaptureOutputResolutions(String cameraId) {
+ return initializeParcelable(
+ AdvancedExtender.this.getSupportedCaptureOutputResolutions(cameraId));
+ }
+
+ @Override
+ public LatencyRange getEstimatedCaptureLatencyRange(String cameraId,
+ android.hardware.camera2.extension.Size outputSize, int format) {
+ // Feature is currently unsupported
+ return null;
+ }
+
+ @Override
+ public ISessionProcessorImpl getSessionProcessor() {
+ return AdvancedExtender.this.getSessionProcessor().getSessionProcessorBinder();
+ }
+
+ @Override
+ public CameraMetadataNative getAvailableCaptureRequestKeys(String cameraId) {
+ List<CaptureRequest.Key> supportedCaptureKeys =
+ AdvancedExtender.this.getAvailableCaptureRequestKeys(cameraId);
+
+ if (!supportedCaptureKeys.isEmpty()) {
+ CameraMetadataNative ret = new CameraMetadataNative();
+ long vendorId = getMetadataVendorId(cameraId);
+ ret.setVendorId(vendorId);
+ int requestKeyTags[] = new int[supportedCaptureKeys.size()];
+ int i = 0;
+ for (CaptureRequest.Key key : supportedCaptureKeys) {
+ requestKeyTags[i++] = CameraMetadataNative.getTag(key.getName(), vendorId);
+ }
+ ret.set(CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS, requestKeyTags);
+
+ return ret;
+ }
+
+ return null;
+ }
+
+ @Override
+ public CameraMetadataNative getAvailableCaptureResultKeys(String cameraId) {
+ List<CaptureResult.Key> supportedResultKeys =
+ AdvancedExtender.this.getAvailableCaptureResultKeys(cameraId);
+
+ if (!supportedResultKeys.isEmpty()) {
+ CameraMetadataNative ret = new CameraMetadataNative();
+ long vendorId = getMetadataVendorId(cameraId);
+ ret.setVendorId(vendorId);
+ int resultKeyTags [] = new int[supportedResultKeys.size()];
+ int i = 0;
+ for (CaptureResult.Key key : supportedResultKeys) {
+ resultKeyTags[i++] = CameraMetadataNative.getTag(key.getName(), vendorId);
+ }
+ ret.set(CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS, resultKeyTags);
+
+ return ret;
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isCaptureProcessProgressAvailable() {
+ // Feature is currently unsupported
+ return false;
+ }
+
+ @Override
+ public boolean isPostviewAvailable() {
+ // Feature is currently unsupported
+ return false;
+ }
+ }
+
+ @NonNull IAdvancedExtenderImpl getAdvancedExtenderBinder() {
+ return new AdvancedExtenderImpl();
+ }
+
+ private static List<SizeList> initializeParcelable(
+ Map<Integer, List<android.util.Size>> sizes) {
+ if (sizes == null) {
+ return null;
+ }
+ ArrayList<SizeList> ret = new ArrayList<>(sizes.size());
+ for (Map.Entry<Integer, List<android.util.Size>> entry : sizes.entrySet()) {
+ SizeList sizeList = new SizeList();
+ sizeList.format = entry.getKey();
+ sizeList.sizes = new ArrayList<>();
+ for (android.util.Size size : entry.getValue()) {
+ android.hardware.camera2.extension.Size sz =
+ new android.hardware.camera2.extension.Size();
+ sz.width = size.getWidth();
+ sz.height = size.getHeight();
+ sizeList.sizes.add(sz);
+ }
+ ret.add(sizeList);
+ }
+
+ return ret;
+ }
+}
diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
new file mode 100644
index 000000000000..1426d7bceb7f
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
@@ -0,0 +1,170 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.camera.flags.Flags;
+
+/**
+ * Base service class that extension service implementations must extend.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public abstract class CameraExtensionService extends Service {
+ private static final String TAG = "CameraExtensionService";
+ private static Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private static IInitializeSessionCallback mInitializeCb = null;
+
+ private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mInitializeCb = null;
+ }
+ }
+ };
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ protected CameraExtensionService() {}
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Override
+ @NonNull
+ public IBinder onBind(@Nullable Intent intent) {
+ return new CameraExtensionServiceImpl();
+ }
+
+ private class CameraExtensionServiceImpl extends ICameraExtensionsProxyService.Stub {
+ @Override
+ public boolean registerClient(IBinder token) throws RemoteException {
+ return CameraExtensionService.this.onRegisterClient(token);
+ }
+
+ @Override
+ public void unregisterClient(IBinder token) throws RemoteException {
+ CameraExtensionService.this.onUnregisterClient(token);
+ }
+
+ @Override
+ public boolean advancedExtensionsSupported() throws RemoteException {
+ return true;
+ }
+
+ @Override
+ public void initializeSession(IInitializeSessionCallback cb) {
+ boolean ret = false;
+ synchronized (mLock) {
+ if (mInitializeCb == null) {
+ mInitializeCb = cb;
+ try {
+ mInitializeCb.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failure to register binder death notifier!");
+ }
+ ret = true;
+ }
+ }
+
+ try {
+ if (ret) {
+ cb.onSuccess();
+ } else {
+ cb.onFailure();
+ }
+ } catch (RemoteException e) {
+
+ Log.e(TAG, "Client doesn't respond!");
+ }
+ }
+
+ @Override
+ public void releaseSession() {
+ synchronized (mLock) {
+ if (mInitializeCb != null) {
+ mInitializeCb.asBinder().unlinkToDeath(mDeathRecipient, 0);
+ mInitializeCb = null;
+ }
+ }
+ }
+
+ @Override
+ public IPreviewExtenderImpl initializePreviewExtension(int extensionType)
+ throws RemoteException {
+ // Basic Extension API is not supported
+ return null;
+ }
+
+ @Override
+ public IImageCaptureExtenderImpl initializeImageExtension(int extensionType)
+ throws RemoteException {
+ // Basic Extension API is not supported
+ return null;
+ }
+
+ @Override
+ public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
+ throws RemoteException {
+ return CameraExtensionService.this.onInitializeAdvancedExtension(
+ extensionType).getAdvancedExtenderBinder();
+ }
+ }
+
+ /**
+ * Register an extension client. The client must call this method
+ * after successfully binding to the service.
+ *
+ * @param token Binder token that can be used for adding
+ * death notifier in case the client exits
+ * unexpectedly.
+ * @return true if the registration is successful, false otherwise
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract boolean onRegisterClient(@NonNull IBinder token);
+
+ /**
+ * Unregister an extension client.
+ *
+ * @param token Binder token
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract void onUnregisterClient(@NonNull IBinder token);
+
+ /**
+ * Initialize and return an advanced extension.
+ *
+ * @param extensionType {@link android.hardware.camera2.CameraExtensionCharacteristics}
+ * extension type
+ * @return Valid advanced extender of the requested type
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public abstract AdvancedExtender onInitializeAdvancedExtension(int extensionType);
+}
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
new file mode 100644
index 000000000000..f98ebee5d7c7
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -0,0 +1,75 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.camera2.utils.SurfaceUtils;
+import android.util.Size;
+import android.view.Surface;
+
+import com.android.internal.camera.flags.Flags;
+
+
+/**
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public final class CameraOutputSurface {
+ private final OutputSurface mOutputSurface;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ CameraOutputSurface(@NonNull OutputSurface surface) {
+ mOutputSurface = surface;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public CameraOutputSurface(@NonNull Surface surface,
+ @Nullable Size size ) {
+ mOutputSurface = new OutputSurface();
+ mOutputSurface.surface = surface;
+ mOutputSurface.imageFormat = SurfaceUtils.getSurfaceFormat(surface);
+ if (size != null) {
+ mOutputSurface.size = new android.hardware.camera2.extension.Size();
+ mOutputSurface.size.width = size.getWidth();
+ mOutputSurface.size.height = size.getHeight();
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Nullable
+ public Surface getSurface() {
+ return mOutputSurface.surface;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Nullable
+ public android.util.Size getSize() {
+ if (mOutputSurface.size != null) {
+ return new Size(mOutputSurface.size.width, mOutputSurface.size.height);
+ }
+ return null;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public int getImageFormat() {
+ return mOutputSurface.imageFormat;
+ }
+}
diff --git a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
new file mode 100644
index 000000000000..af83595babef
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
@@ -0,0 +1,58 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public class CharacteristicsMap {
+ private final HashMap<String, CameraCharacteristics> mCharMap;
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ CharacteristicsMap(@NonNull Map<String, CameraMetadataNative> charsMap) {
+ mCharMap = new HashMap<>();
+ for (Map.Entry<String, CameraMetadataNative> entry : charsMap.entrySet()) {
+ mCharMap.put(entry.getKey(), new CameraCharacteristics(entry.getValue()));
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public Set<String> getCameraIds() {
+ return mCharMap.keySet();
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @Nullable
+ public CameraCharacteristics get(@NonNull String cameraId) {
+ return mCharMap.get(cameraId);
+ }
+}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
new file mode 100644
index 000000000000..2d9ab765d1e0
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -0,0 +1,69 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.camera2.CaptureRequest;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public class ExtensionConfiguration {
+ private final int mSessionType;
+ private final int mSessionTemplateId;
+ private final List<ExtensionOutputConfiguration> mOutputs;
+ private final CaptureRequest mSessionParameters;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public ExtensionConfiguration(int sessionType, int sessionTemplateId, @NonNull
+ List<ExtensionOutputConfiguration> outputs, @Nullable CaptureRequest sessionParams) {
+ mSessionType = sessionType;
+ mSessionTemplateId = sessionTemplateId;
+ mOutputs = outputs;
+ mSessionParameters = sessionParams;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ CameraSessionConfig getCameraSessionConfig() {
+ if (mOutputs.isEmpty()) {
+ return null;
+ }
+
+ CameraSessionConfig ret = new CameraSessionConfig();
+ ret.sessionTemplateId = mSessionTemplateId;
+ ret.sessionType = mSessionType;
+ ret.outputConfigs = new ArrayList<>(mOutputs.size());
+ for (ExtensionOutputConfiguration outputConfig : mOutputs) {
+ ret.outputConfigs.add(outputConfig.getOutputConfig());
+ }
+ if (mSessionParameters != null) {
+ ret.sessionParameter = mSessionParameters.getNativeCopy();
+ }
+
+ return ret;
+ }
+}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
new file mode 100644
index 000000000000..85d180d379df
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public class ExtensionOutputConfiguration {
+ private final List<CameraOutputSurface> mSurfaces;
+ private final String mPhysicalCameraId;
+ private final int mOutputConfigId;
+ private final int mSurfaceGroupId;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public ExtensionOutputConfiguration(@NonNull List<CameraOutputSurface> outputs,
+ int outputConfigId, @Nullable String physicalCameraId, int surfaceGroupId) {
+ mSurfaces = outputs;
+ mPhysicalCameraId = physicalCameraId;
+ mOutputConfigId = outputConfigId;
+ mSurfaceGroupId = surfaceGroupId;
+ }
+
+ private void initializeOutputConfig(@NonNull CameraOutputConfig config,
+ @NonNull CameraOutputSurface surface) {
+ config.surface = surface.getSurface();
+ if (surface.getSize() != null) {
+ config.size = new Size();
+ config.size.width = surface.getSize().getWidth();
+ config.size.height = surface.getSize().getHeight();
+ }
+ config.imageFormat = surface.getImageFormat();
+ config.type = CameraOutputConfig.TYPE_SURFACE;
+ config.physicalCameraId = mPhysicalCameraId;
+ config.outputId = new OutputConfigId();
+ config.outputId.id = mOutputConfigId;
+ config.surfaceGroupId = mSurfaceGroupId;
+ }
+
+ @Nullable CameraOutputConfig getOutputConfig() {
+ if (mSurfaces.isEmpty()) {
+ return null;
+ }
+
+ CameraOutputConfig ret = new CameraOutputConfig();
+ initializeOutputConfig(ret, mSurfaces.get(0));
+ if (mSurfaces.size() > 1) {
+ ret.sharedSurfaceConfigs = new ArrayList<>(mSurfaces.size() - 1);
+ for (int i = 1; i < mSurfaces.size(); i++) {
+ CameraOutputConfig sharedConfig = new CameraOutputConfig();
+ initializeOutputConfig(sharedConfig, mSurfaces.get(i));
+ ret.sharedSurfaceConfigs.add(sharedConfig);
+ }
+ }
+
+ return ret;
+ }
+}
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index 0581ec08a131..a4a17701604c 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -34,7 +34,7 @@ interface ISessionProcessorImpl
in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface,
in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface);
void deInitSession(in IBinder token);
- void onCaptureSessionStart(IRequestProcessorImpl requestProcessor);
+ void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, in String statsKey);
void onCaptureSessionEnd();
int startRepeating(in ICaptureCallback callback);
void stopRepeating();
diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java
new file mode 100644
index 000000000000..7c099d67e6e9
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -0,0 +1,582 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * An Interface to execute Camera2 capture requests.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public final class RequestProcessor {
+ private final static String TAG = "RequestProcessor";
+ private final IRequestProcessorImpl mRequestProcessor;
+ private final long mVendorId;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ RequestProcessor (@NonNull IRequestProcessorImpl requestProcessor, long vendorId) {
+ mRequestProcessor = requestProcessor;
+ mVendorId = vendorId;
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public interface RequestCallback {
+ /**
+ * This method is called when the camera device has started
+ * capturing the output image for the request, at the beginning of
+ * image exposure, or when the camera device has started
+ * processing an input image for a reprocess request.
+ *
+ * @param request The request that was given to the
+ * RequestProcessor
+ * @param timestamp the timestamp at start of capture for a
+ * regular request, or the timestamp at the input
+ * image's start of capture for a
+ * reprocess request, in nanoseconds.
+ * @param frameNumber the frame number for this capture
+ *
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureStarted(@NonNull Request request, long frameNumber, long timestamp);
+
+ /**
+ * This method is called when an image capture makes partial forward
+ * progress; some (but not all) results from an image capture are
+ * available.
+ *
+ * <p>The result provided here will contain some subset of the fields
+ * of a full result. Multiple {@link #onCaptureProgressed} calls may
+ * happen per capture; a given result field will only be present in
+ * one partial capture at most. The final {@link #onCaptureCompleted}
+ * call will always contain all the fields (in particular, the union
+ * of all the fields of all the partial results composing the total
+ * result).</p>
+ *
+ * <p>For each request, some result data might be available earlier
+ * than others. The typical delay between each partial result (per
+ * request) is a single frame interval.
+ * For performance-oriented use-cases, applications should query the
+ * metadata they need to make forward progress from the partial
+ * results and avoid waiting for the completed result.</p>
+ *
+ * <p>For a particular request, {@link #onCaptureProgressed} may happen
+ * before or after {@link #onCaptureStarted}.</p>
+ *
+ * <p>Each request will generate at least {@code 1} partial results,
+ * and at most {@link
+ * CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial
+ * results.</p>
+ *
+ * <p>Depending on the request settings, the number of partial
+ * results per request will vary, although typically the partial
+ * count could be the same as long as the
+ * camera device subsystems enabled stay the same.</p>
+ *
+ * @param request The request that was given to the RequestProcessor
+ * @param partialResult The partial output metadata from the capture,
+ * which includes a subset of the {@link
+ * TotalCaptureResult} fields.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureProgressed(@NonNull Request request, @NonNull CaptureResult partialResult);
+
+ /**
+ * This method is called when an image capture has fully completed and
+ * all the result metadata is available.
+ *
+ * <p>This callback will always fire after the last {@link
+ * #onCaptureProgressed}; in other words, no more partial results will
+ * be delivered once the completed result is available.</p>
+ *
+ * <p>For performance-intensive use-cases where latency is a factor,
+ * consider using {@link #onCaptureProgressed} instead.</p>
+ *
+ *
+ * @param request The request that was given to the RequestProcessor
+ * @param totalCaptureResult The total output metadata from the
+ * capture, including the final capture
+ * parameters and the state of the camera
+ * system during capture.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureCompleted(@NonNull Request request,
+ @Nullable TotalCaptureResult totalCaptureResult);
+
+ /**
+ * This method is called instead of {@link #onCaptureCompleted} when the
+ * camera device failed to produce a {@link CaptureResult} for the
+ * request.
+ *
+ * <p>Other requests are unaffected, and some or all image buffers
+ * from the capture may have been pushed to their respective output
+ * streams.</p>
+ *
+ * <p>If a logical multi-camera fails to generate capture result for
+ * one of its physical cameras, this method will be called with a
+ * {@link CaptureFailure} for that physical camera. In such cases, as
+ * long as the logical camera capture result is valid, {@link
+ * #onCaptureCompleted} will still be called.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param request The request that was given to the RequestProcessor
+ * @param failure The output failure from the capture, including the
+ * failure reason and the frame number.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureFailed(@NonNull Request request, @NonNull CaptureFailure failure);
+
+ /**
+ * <p>This method is called if a single buffer for a capture could not
+ * be sent to its destination surface.</p>
+ *
+ * <p>If the whole capture failed, then {@link #onCaptureFailed} will be
+ * called instead. If some but not all buffers were captured but the
+ * result metadata will not be available, then captureFailed will be
+ * invoked with {@link CaptureFailure#wasImageCaptured}
+ * returning true, along with one or more calls to {@link
+ * #onCaptureBufferLost} for the failed outputs.</p>
+ *
+ * @param request The request that was given to the RequestProcessor
+ * @param frameNumber The frame number for the request
+ * @param outputStreamId The output stream id that the buffer will not
+ * be produced for
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureBufferLost(@NonNull Request request, long frameNumber, int outputStreamId);
+
+ /**
+ * This method is called independently of the others in
+ * CaptureCallback, when a capture sequence finishes and all {@link
+ * CaptureResult} or {@link CaptureFailure} for it have been returned
+ * via this listener.
+ *
+ * <p>In total, there will be at least one result/failure returned by
+ * this listener before this callback is invoked. If the capture
+ * sequence is aborted before any requests have been processed,
+ * {@link #onCaptureSequenceAborted} is invoked instead.</p>
+ *
+ * @param sequenceId A sequence ID returned by the RequestProcessor
+ * capture family of methods
+ * @param frameNumber The last frame number (returned by {@link
+ * CaptureResult#getFrameNumber}
+ * or {@link CaptureFailure#getFrameNumber}) in
+ * the capture sequence.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureSequenceCompleted(int sequenceId, long frameNumber);
+
+ /**
+ * This method is called independently of the others in
+ * CaptureCallback, when a capture sequence aborts before any {@link
+ * CaptureResult} or {@link CaptureFailure} for it have been returned
+ * via this listener.
+ *
+ * <p>Due to the asynchronous nature of the camera device, not all
+ * submitted captures are immediately processed. It is possible to
+ * clear out the pending requests by a variety of operations such as
+ * {@link RequestProcessor#stopRepeating} or
+ * {@link RequestProcessor#abortCaptures}. When such an event
+ * happens, {@link #onCaptureSequenceCompleted} will not be called.</p>
+ * @param sequenceId A sequence ID returned by the RequestProcessor
+ * capture family of methods
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureSequenceAborted(int sequenceId);
+ }
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public final static class Request {
+ private final List<Integer> mOutputIds;
+ private final List<Pair<CaptureRequest.Key, Object>> mParameters;
+ private final int mTemplateId;
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public Request(@NonNull List<Integer> outputConfigIds,
+ @NonNull List<Pair<CaptureRequest.Key, Object>> parameters, int templateId) {
+ mOutputIds = outputConfigIds;
+ mParameters = parameters;
+ mTemplateId = templateId;
+ }
+
+ /**
+ * Gets the target ids of {@link ExtensionOutputConfiguration} which identifies
+ * corresponding Surface to be the targeted for the request.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ List<Integer> getOutputConfigIds() {
+ return mOutputIds;
+ }
+
+ /**
+ * Gets all the parameters.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ List<Pair<CaptureRequest.Key, Object>> getParameters() {
+ return mParameters;
+ }
+
+ /**
+ * Gets the template id.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ Integer getTemplateId() {
+ return mTemplateId;
+ }
+
+ @NonNull List<OutputConfigId> getTargetIds() {
+ ArrayList<OutputConfigId> ret = new ArrayList<>(mOutputIds.size());
+ int idx = 0;
+ for (Integer outputId : mOutputIds) {
+ OutputConfigId configId = new OutputConfigId();
+ configId.id = outputId;
+ ret.add(idx++, configId);
+ }
+
+ return ret;
+ }
+
+ @NonNull
+ static CameraMetadataNative getParametersMetadata(long vendorId,
+ @NonNull List<Pair<CaptureRequest.Key, Object>> parameters) {
+ CameraMetadataNative ret = new CameraMetadataNative();
+ ret.setVendorId(vendorId);
+ for (Pair<CaptureRequest.Key, Object> pair : parameters) {
+ ret.set(pair.first, pair.second);
+ }
+
+ return ret;
+ }
+
+ @NonNull
+ static List<android.hardware.camera2.extension.Request> initializeParcelable(
+ long vendorId, @NonNull List<Request> requests) {
+ ArrayList<android.hardware.camera2.extension.Request> ret =
+ new ArrayList<>(requests.size());
+ int requestId = 0;
+ for (Request req : requests) {
+ android.hardware.camera2.extension.Request request =
+ new android.hardware.camera2.extension.Request();
+ request.requestId = requestId++;
+ request.templateId = req.getTemplateId();
+ request.targetOutputConfigIds = req.getTargetIds();
+ request.parameters = getParametersMetadata(vendorId, req.getParameters());
+ ret.add(request.requestId, request);
+ }
+
+ return ret;
+ }
+ }
+
+ /**
+ * Submit a capture request.
+ * @param request Capture request to queued in the Camera2 session
+ * @param executor the executor which will be used for
+ * invoking the callbacks or null to use the
+ * current thread's looper
+ * @param callback Request callback implementation
+ * @return the id of the capture sequence or -1 in case the processor
+ * encounters a fatal error or receives an invalid argument.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public int submit(@NonNull Request request, @Nullable Executor executor,
+ @NonNull RequestCallback callback) {
+ ArrayList<Request> requests = new ArrayList<>(1);
+ requests.add(0, request);
+ List<android.hardware.camera2.extension.Request> parcelableRequests =
+ Request.initializeParcelable(mVendorId, requests);
+
+ try {
+ return mRequestProcessor.submit(parcelableRequests.get(0),
+ new RequestCallbackImpl(requests, callback, executor));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Submits a list of requests.
+ * @param requests List of capture requests to be queued in the
+ * Camera2 session
+ * @param executor the executor which will be used for
+ * invoking the callbacks or null to use the
+ * current thread's looper
+ * @param callback Request callback implementation
+ * @return the id of the capture sequence or -1 in case the processor
+ * encounters a fatal error or receives an invalid argument.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public int submitBurst(@NonNull List<Request> requests, @Nullable Executor executor,
+ @NonNull RequestCallback callback) {
+ List<android.hardware.camera2.extension.Request> parcelableRequests =
+ Request.initializeParcelable(mVendorId, requests);
+
+ try {
+ return mRequestProcessor.submitBurst(parcelableRequests,
+ new RequestCallbackImpl(requests, callback, executor));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Set a repeating request.
+ * @param request Repeating capture request to be se in the
+ * Camera2 session
+ * @param executor the executor which will be used for
+ * invoking the callbacks or null to use the
+ * current thread's looper
+ * @param callback Request callback implementation
+ * @return the id of the capture sequence or -1 in case the processor
+ * encounters a fatal error or receives an invalid argument.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public int setRepeating(@NonNull Request request, @Nullable Executor executor,
+ @NonNull RequestCallback callback) {
+ ArrayList<Request> requests = new ArrayList<>(1);
+ requests.add(0, request);
+ List<android.hardware.camera2.extension.Request> parcelableRequests =
+ Request.initializeParcelable(mVendorId, requests);
+
+ try {
+ return mRequestProcessor.setRepeating(parcelableRequests.get(0),
+ new RequestCallbackImpl(requests, callback, executor));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Abort all ongoing capture requests.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public void abortCaptures() {
+ try {
+ mRequestProcessor.abortCaptures();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Stop the current repeating request.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public void stopRepeating() {
+ try {
+ mRequestProcessor.stopRepeating();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class RequestCallbackImpl extends IRequestCallback.Stub {
+ private final List<Request> mRequests;
+ private final RequestCallback mCallback;
+ private final Executor mExecutor;
+
+ public RequestCallbackImpl(@NonNull List<Request> requests,
+ @NonNull RequestCallback callback, @Nullable Executor executor) {
+ mCallback = callback;
+ mRequests = requests;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCaptureStarted(int requestId, long frameNumber, long timestamp) {
+ if (mRequests.get(requestId) != null) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> mCallback.onCaptureStarted(
+ mRequests.get(requestId), frameNumber, timestamp));
+ } else {
+ mCallback.onCaptureStarted(mRequests.get(requestId), frameNumber,
+ timestamp);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ Log.e(TAG,"Request id: " + requestId + " not found!");
+ }
+ }
+
+ @Override
+ public void onCaptureProgressed(int requestId, ParcelCaptureResult partialResult) {
+ if (mRequests.get(requestId) != null) {
+ CaptureResult result = new CaptureResult(partialResult.cameraId,
+ partialResult.results, partialResult.parent, partialResult.sequenceId,
+ partialResult.frameNumber);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> mCallback.onCaptureProgressed(mRequests.get(requestId),
+ result));
+ } else {
+ mCallback.onCaptureProgressed(mRequests.get(requestId), result);
+ }
+
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ Log.e(TAG,"Request id: " + requestId + " not found!");
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(int requestId, ParcelTotalCaptureResult totalCaptureResult) {
+ if (mRequests.get(requestId) != null) {
+ PhysicalCaptureResultInfo[] physicalResults = new PhysicalCaptureResultInfo[0];
+ if ((totalCaptureResult.physicalResult != null) &&
+ (!totalCaptureResult.physicalResult.isEmpty())) {
+ int count = totalCaptureResult.physicalResult.size();
+ physicalResults = new PhysicalCaptureResultInfo[count];
+ physicalResults = totalCaptureResult.physicalResult.toArray(
+ physicalResults);
+ }
+ ArrayList<CaptureResult> partials = new ArrayList<>(
+ totalCaptureResult.partials.size());
+ for (ParcelCaptureResult parcelResult : totalCaptureResult.partials) {
+ partials.add(new CaptureResult(parcelResult.cameraId, parcelResult.results,
+ parcelResult.parent, parcelResult.sequenceId,
+ parcelResult.frameNumber));
+ }
+ TotalCaptureResult result = new TotalCaptureResult(
+ totalCaptureResult.logicalCameraId, totalCaptureResult.results,
+ totalCaptureResult.parent, totalCaptureResult.sequenceId,
+ totalCaptureResult.frameNumber, partials, totalCaptureResult.sessionId,
+ physicalResults);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> mCallback.onCaptureCompleted(mRequests.get(requestId),
+ result));
+ } else {
+ mCallback.onCaptureCompleted(mRequests.get(requestId), result);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ Log.e(TAG,"Request id: " + requestId + " not found!");
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(int requestId,
+ android.hardware.camera2.extension.CaptureFailure captureFailure) {
+ if (mRequests.get(requestId) != null) {
+ android.hardware.camera2.CaptureFailure failure =
+ new android.hardware.camera2.CaptureFailure(captureFailure.request,
+ captureFailure.reason, captureFailure.dropped,
+ captureFailure.sequenceId, captureFailure.frameNumber,
+ captureFailure.errorPhysicalCameraId);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> mCallback.onCaptureFailed(mRequests.get(requestId),
+ failure));
+ } else {
+ mCallback.onCaptureFailed(mRequests.get(requestId), failure);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ Log.e(TAG,"Request id: " + requestId + " not found!");
+ }
+ }
+
+ @Override
+ public void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId) {
+ if (mRequests.get(requestId) != null) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> mCallback.onCaptureBufferLost(mRequests.get(requestId),
+ frameNumber, outputStreamId));
+ } else {
+ mCallback.onCaptureBufferLost(mRequests.get(requestId), frameNumber,
+ outputStreamId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ Log.e(TAG,"Request id: " + requestId + " not found!");
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> mCallback.onCaptureSequenceCompleted(sequenceId,
+ frameNumber));
+ } else {
+ mCallback.onCaptureSequenceCompleted(sequenceId, frameNumber);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(int sequenceId) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> mCallback.onCaptureSequenceAborted(sequenceId));
+ } else {
+ mCallback.onCaptureSequenceAborted(sequenceId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+}
diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java
new file mode 100644
index 000000000000..6ed0c1404212
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -0,0 +1,495 @@
+/*
+ * 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.hardware.camera2.extension;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Interface for creating Camera2 CameraCaptureSessions with extension
+ * enabled based on the advanced extension interface.
+ *
+ * <p><pre>
+ * The flow of a extension session is shown below:
+ * (1) {@link #initSession}: Camera framework prepares streams
+ * configuration for creating CameraCaptureSession. Output surfaces for
+ * Preview and ImageCapture are passed in and implementation is
+ * responsible for outputting the results to these surfaces.
+ *
+ * (2) {@link #onCaptureSessionStart}: It is called after
+ * CameraCaptureSession is configured. A {@link RequestProcessor} is
+ * passed for the implementation to send repeating requests and single
+ * requests.
+ *
+ * (3) {@link #startRepeating}: Camera framework will call this method to
+ * start the repeating request after CameraCaptureSession is called.
+ * Implementations should start the repeating request by {@link
+ * RequestProcessor}. Implementations can also update the repeating
+ * request if needed later.
+ *
+ * (4) {@link #setParameters}: The passed parameters will be attached
+ * to the repeating request and single requests but the implementation can
+ * choose to apply some of them only.
+ *
+ * (5) {@link #startCapture}: It is called when apps want
+ * to start a multi-frame image capture. {@link CaptureCallback} will be
+ * called to report the status and the output image will be written to the
+ * capture output surface specified in {@link #initSession}.
+ *
+ * (5) {@link #onCaptureSessionEnd}: It is called right BEFORE
+ * CameraCaptureSession.close() is called.
+ *
+ * (6) {@link #deInitSession}: called when CameraCaptureSession is closed.
+ * </pre>
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public abstract class SessionProcessor {
+ private static final String TAG = "SessionProcessor";
+
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ protected SessionProcessor() {}
+
+ /**
+ * Callback for notifying the status of {@link
+ * #startCapture} and {@link #startRepeating}.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public interface CaptureCallback {
+ /**
+ * This method is called when the camera device has started
+ * capturing the initial input
+ * image.
+ *
+ * For a multi-frame capture, the method is called when the
+ * CameraCaptureSession.CaptureCallback onCaptureStarted of first
+ * frame is called and its timestamp is directly forwarded to
+ * timestamp parameter of this method.
+ *
+ * @param captureSequenceId id of the current capture sequence
+ * @param timestamp the timestamp at start of capture for
+ * repeating request or the timestamp at
+ * start of capture of the
+ * first frame in a multi-frame capture,
+ * in nanoseconds.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureStarted(int captureSequenceId, long timestamp);
+
+ /**
+ * This method is called when an image (or images in case of
+ * multi-frame capture) is captured and device-specific extension
+ * processing is triggered.
+ *
+ * @param captureSequenceId id of the current capture sequence
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureProcessStarted(int captureSequenceId);
+
+ /**
+ * This method is called instead of
+ * {@link #onCaptureProcessStarted} when the camera device failed
+ * to produce the required input for the device-specific
+ * extension. The cause could be a failed camera capture request,
+ * a failed capture result or dropped camera frame.
+ *
+ * @param captureSequenceId id of the current capture sequence
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureFailed(int captureSequenceId);
+
+ /**
+ * This method is called independently of the others in the
+ * CaptureCallback, when a capture sequence finishes.
+ *
+ * <p>In total, there will be at least one
+ * {@link #onCaptureProcessStarted}/{@link #onCaptureFailed}
+ * invocation before this callback is triggered. If the capture
+ * sequence is aborted before any requests have begun processing,
+ * {@link #onCaptureSequenceAborted} is invoked instead.</p>
+ *
+ * @param captureSequenceId id of the current capture sequence
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureSequenceCompleted(int captureSequenceId);
+
+ /**
+ * This method is called when a capture sequence aborts.
+ *
+ * @param captureSequenceId id of the current capture sequence
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureSequenceAborted(int captureSequenceId);
+
+ /**
+ * Capture result callback that needs to be called when the
+ * process capture results are ready as part of frame
+ * post-processing.
+ *
+ * This callback will fire after {@link #onCaptureStarted}, {@link
+ * #onCaptureProcessStarted} and before {@link
+ * #onCaptureSequenceCompleted}. The callback is not expected to
+ * fire in case of capture failure {@link #onCaptureFailed} or
+ * capture abort {@link #onCaptureSequenceAborted}.
+ *
+ * @param shutterTimestamp The timestamp at the start
+ * of capture. The same timestamp value
+ * passed to {@link #onCaptureStarted}.
+ * @param requestId the capture request id that generated the
+ * capture results. This is the return value of
+ * either {@link #startRepeating} or {@link
+ * #startCapture}.
+ * @param results The supported capture results. Do note
+ * that if results 'android.jpeg.quality' and
+ * android.jpeg.orientation' are present in the
+ * process capture input results, then the values
+ * must also be passed as part of this callback.
+ * The camera framework guarantees that those two
+ * settings and results are always supported and
+ * applied by the corresponding framework.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ void onCaptureCompleted(long shutterTimestamp, int requestId,
+ @NonNull CaptureResult results);
+ }
+
+ /**
+ * Initializes the session for the extension. This is where the
+ * extension implementations allocate resources for
+ * preparing a CameraCaptureSession. After initSession() is called,
+ * the camera ID, cameraCharacteristics and context will not change
+ * until deInitSession() has been called.
+ *
+ * <p>The framework specifies the output surface configurations for
+ * preview using the 'previewSurface' argument and for still capture
+ * using the 'imageCaptureSurface' argument and implementations must
+ * return a {@link ExtensionConfiguration} which consists of a list of
+ * {@link CameraOutputSurface} and session parameters. The {@link
+ * ExtensionConfiguration} will be used to configure the
+ * CameraCaptureSession.
+ *
+ * <p>Implementations are responsible for outputting correct camera
+ * images output to these output surfaces.</p>
+ *
+ * @param token Binder token that can be used to register a death
+ * notifier callback
+ * @param cameraId The camera2 id string of the camera.
+ * @param map Maps camera ids to camera characteristics
+ * @param previewSurface contains output surface for preview
+ * @param imageCaptureSurface contains the output surface for image
+ * capture
+ * @return a {@link ExtensionConfiguration} consisting of a list of
+ * {@link CameraOutputConfig} and session parameters which will decide
+ * the {@link android.hardware.camera2.params.SessionConfiguration}
+ * for configuring the CameraCaptureSession. Please note that the
+ * OutputConfiguration list may not be part of any
+ * supported or mandatory stream combination BUT implementations must
+ * ensure this list will always produce a valid camera capture
+ * session.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @NonNull
+ public abstract ExtensionConfiguration initSession(@NonNull IBinder token,
+ @NonNull String cameraId, @NonNull CharacteristicsMap map,
+ @NonNull CameraOutputSurface previewSurface,
+ @NonNull CameraOutputSurface imageCaptureSurface);
+
+ /**
+ * Notify to de-initialize the extension. This callback will be
+ * invoked after CameraCaptureSession is closed. After onDeInit() was
+ * called, it is expected that the camera ID, cameraCharacteristics
+ * will no longer hold and tear down any resources allocated
+ * for this extension. Aborts all pending captures.
+ * @param token Binder token that can be used to unlink any previously
+ * linked death notifier callbacks
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract void deInitSession(@NonNull IBinder token);
+
+ /**
+ * This will be invoked once after the {@link
+ * android.hardware.camera2.CameraCaptureSession}
+ * has been created. {@link RequestProcessor} is passed for
+ * implementations to submit single requests or set repeating
+ * requests. This extension RequestProcessor will be valid to use
+ * until onCaptureSessionEnd is called.
+ * @param requestProcessor The request processor to be used for
+ * managing capture requests
+ * @param statsKey Unique key for telemetry
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor,
+ @NonNull String statsKey);
+
+ /**
+ * This will be invoked before the {@link
+ * android.hardware.camera2.CameraCaptureSession} is
+ * closed. {@link RequestProcessor} passed in onCaptureSessionStart
+ * will no longer accept any requests after onCaptureSessionEnd()
+ * returns.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract void onCaptureSessionEnd();
+
+ /**
+ * Starts the repeating request after CameraCaptureSession is called.
+ * Implementations should start the repeating request by {@link
+ * RequestProcessor}. Implementations can also update the
+ * repeating request when needed later.
+ *
+ * @param executor the executor which will be used for
+ * invoking the callbacks or null to use the
+ * current thread's looper
+ * @param callback a callback to report the status.
+ * @return the id of the capture sequence.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract int startRepeating(@Nullable Executor executor,
+ @NonNull CaptureCallback callback);
+
+ /**
+ * Stop the repeating request. To prevent implementations from not
+ * calling stopRepeating, the framework will first stop the repeating
+ * request of current CameraCaptureSession and call this API to signal
+ * implementations that the repeating request was stopped and going
+ * forward calling {@link RequestProcessor#setRepeating} will simply
+ * do nothing.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract void stopRepeating();
+
+ /**
+ * Start a multi-frame capture.
+ *
+ * When the capture is completed, {@link
+ * CaptureCallback#onCaptureSequenceCompleted}
+ * is called and {@code OnImageAvailableListener#onImageAvailable}
+ * will also be called on the ImageReader that creates the image
+ * capture output surface.
+ *
+ * <p>Only one capture can perform at a time. Starting a capture when
+ * another capture is running will cause onCaptureFailed to be called
+ * immediately.
+ *
+ * @param executor the executor which will be used for
+ * invoking the callbacks or null to use the
+ * current thread's looper
+ * @param callback a callback to report the status.
+ * @return the id of the capture sequence.
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract int startCapture(@Nullable Executor executor,
+ @NonNull CaptureCallback callback);
+
+ /**
+ * The camera framework will call these APIs to pass parameters from
+ * the app to the extension implementation. It is expected that the
+ * implementation would (eventually) update the repeating request if
+ * the keys are supported. Setting a value to null explicitly un-sets
+ * the value.
+ *@param captureRequest Request that includes all client parameter
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract void setParameters(@NonNull CaptureRequest captureRequest);
+
+ /**
+ * The camera framework will call this interface in response to client
+ * requests involving the output preview surface. Typical examples
+ * include requests that include AF/AE triggers.
+ * Extensions can disregard any capture request keys that were not
+ * advertised in
+ * {@link AdvancedExtender#getAvailableCaptureRequestKeys}.
+ *
+ * @param captureRequest Capture request that includes the respective
+ * triggers.
+ * @param executor the executor which will be used for
+ * invoking the callbacks or null to use the
+ * current thread's looper
+ * @param callback a callback to report the status.
+ * @return the id of the capture sequence.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ public abstract int startTrigger(@NonNull CaptureRequest captureRequest,
+ @Nullable Executor executor, @NonNull CaptureCallback callback);
+
+ private final class SessionProcessorImpl extends ISessionProcessorImpl.Stub {
+ private long mVendorId = -1;
+ @Override
+ public CameraSessionConfig initSession(IBinder token, String cameraId,
+ Map<String, CameraMetadataNative> charsMap, OutputSurface previewSurface,
+ OutputSurface imageCaptureSurface, OutputSurface postviewSurface)
+ throws RemoteException {
+ ExtensionConfiguration config = SessionProcessor.this.initSession(token, cameraId,
+ new CharacteristicsMap(charsMap),
+ new CameraOutputSurface(previewSurface),
+ new CameraOutputSurface(imageCaptureSurface));
+ if (config == null) {
+ throw new IllegalArgumentException("Invalid extension configuration");
+ }
+
+ Object thisClass = CameraCharacteristics.Key.class;
+ Class<CameraCharacteristics.Key<?>> keyClass =
+ (Class<CameraCharacteristics.Key<?>>)thisClass;
+ ArrayList<CameraCharacteristics.Key<?>> vendorKeys =
+ charsMap.get(cameraId).getAllVendorKeys(keyClass);
+ if ((vendorKeys != null) && !vendorKeys.isEmpty()) {
+ mVendorId = vendorKeys.get(0).getVendorId();
+ }
+ return config.getCameraSessionConfig();
+ }
+
+ @Override
+ public void deInitSession(IBinder token) throws RemoteException {
+ SessionProcessor.this.deInitSession(token);
+ }
+
+ @Override
+ public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, String statsKey)
+ throws RemoteException {
+ SessionProcessor.this.onCaptureSessionStart(
+ new RequestProcessor(requestProcessor, mVendorId), statsKey);
+ }
+
+ @Override
+ public void onCaptureSessionEnd() throws RemoteException {
+ SessionProcessor.this.onCaptureSessionEnd();
+ }
+
+ @Override
+ public int startRepeating(ICaptureCallback callback) throws RemoteException {
+ return SessionProcessor.this.startRepeating(/*executor*/ null,
+ new CaptureCallbackImpl(callback));
+ }
+
+ @Override
+ public void stopRepeating() throws RemoteException {
+ SessionProcessor.this.stopRepeating();
+ }
+
+ @Override
+ public int startCapture(ICaptureCallback callback, boolean isPostviewRequested)
+ throws RemoteException {
+ return SessionProcessor.this.startCapture(/*executor*/ null,
+ new CaptureCallbackImpl(callback));
+ }
+
+ @Override
+ public void setParameters(CaptureRequest captureRequest) throws RemoteException {
+ SessionProcessor.this.setParameters(captureRequest);
+ }
+
+ @Override
+ public int startTrigger(CaptureRequest captureRequest, ICaptureCallback callback)
+ throws RemoteException {
+ return SessionProcessor.this.startTrigger(captureRequest, /*executor*/ null,
+ new CaptureCallbackImpl(callback));
+ }
+
+ @Override
+ public LatencyPair getRealtimeCaptureLatency() throws RemoteException {
+ // Feature is not supported
+ return null;
+ }
+ }
+
+ private static final class CaptureCallbackImpl implements CaptureCallback {
+ private final ICaptureCallback mCaptureCallback;
+
+ CaptureCallbackImpl(@NonNull ICaptureCallback cb) {
+ mCaptureCallback = cb;
+ }
+
+ @Override
+ public void onCaptureStarted(int captureSequenceId, long timestamp) {
+ try {
+ mCaptureCallback.onCaptureStarted(captureSequenceId, timestamp);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify capture start due to remote exception!");
+ }
+ }
+
+ @Override
+ public void onCaptureProcessStarted(int captureSequenceId) {
+ try {
+ mCaptureCallback.onCaptureProcessStarted(captureSequenceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify process start due to remote exception!");
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(int captureSequenceId) {
+ try {
+ mCaptureCallback.onCaptureFailed(captureSequenceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify capture failure start due to remote exception!");
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(int captureSequenceId) {
+ try {
+ mCaptureCallback.onCaptureSequenceCompleted(captureSequenceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify capture sequence done due to remote exception!");
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(int captureSequenceId) {
+ try {
+ mCaptureCallback.onCaptureSequenceAborted(captureSequenceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify capture sequence abort due to remote exception!");
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(long shutterTimestamp, int requestId,
+ @androidx.annotation.NonNull CaptureResult results) {
+ try {
+ mCaptureCallback.onCaptureCompleted(shutterTimestamp, requestId,
+ results.getNativeCopy());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify capture complete due to remote exception!");
+ }
+ }
+ }
+
+ @NonNull ISessionProcessorImpl getSessionProcessorBinder() {
+ return new SessionProcessorImpl();
+ }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 4ef45726e7a9..98bc31161591 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -674,7 +674,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
try {
if (mSessionProcessor != null) {
mInitialized = true;
- mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
+ mSessionProcessor.onCaptureSessionStart(mRequestProcessor,
+ mStatsAggregator.getStatsKey());
} else {
Log.v(TAG, "Failed to start capture session, session " +
" released before extension start!");
diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
index 8cd5e83241ad..3050a51d7955 100644
--- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
+++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
@@ -118,4 +118,13 @@ public class ExtensionSessionStatsAggregator {
+ " type: '" + stats.type + "'\n"
+ " isAdvanced: '" + stats.isAdvanced + "'\n";
}
+
+ /**
+ * Return the current statistics key
+ *
+ * @return the current statistics key
+ */
+ public String getStatsKey() {
+ return mStats.key;
+ }
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 502ee4db80c8..b315f4a0f0c5 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -1329,7 +1329,7 @@ public class CameraExtensionsProxyService extends Service {
}
@Override
- public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor) {
+ public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, String statsKey) {
mSessionProcessor.onCaptureSessionStart(
new RequestProcessorStub(requestProcessor, mCameraId));
}