Merge "Camera Extensions: Add Get API to Framework" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index f4a9bd1..d7a99d3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19154,11 +19154,13 @@
   }
 
   public final class CameraExtensionCharacteristics {
+    method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") public <T> T get(int, @NonNull android.hardware.camera2.CameraCharacteristics.Key<T>);
     method @NonNull public java.util.Set<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(int);
     method @NonNull public java.util.Set<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(int);
     method @Nullable public android.util.Range<java.lang.Long> getEstimatedCaptureLatencyRangeMillis(int, @NonNull android.util.Size, int);
     method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>);
     method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
+    method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") @NonNull public java.util.Set<android.hardware.camera2.CameraCharacteristics.Key> getKeys(int);
     method @NonNull public java.util.List<android.util.Size> getPostviewSupportedSizes(int, @NonNull android.util.Size, int);
     method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
     method public boolean isCaptureProcessProgressAvailable(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f026b57..ca45be8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4581,6 +4581,7 @@
     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.camera_extensions_characteristics_get") @NonNull public abstract java.util.List<android.util.Pair<android.hardware.camera2.CameraCharacteristics.Key,java.lang.Object>> getAvailableCharacteristicsKeyValues();
     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);
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 1867a17..7abe821 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -624,6 +624,120 @@
     }
 
     /**
+     * Gets an extension specific camera characteristics field value.
+     *
+     * <p>An extension can have a reduced set of camera capabilities (such as limited zoom ratio
+     * range, available video stabilization modes, etc). This API enables applications to query for
+     * an extension’s specific camera characteristics. Applications are recommended to prioritize
+     * obtaining camera characteristics using this API when using an extension. A {@code null}
+     * result indicates that the extension specific characteristic is not defined or available.
+     *
+     * @param extension The extension type.
+     * @param key The characteristics field to read.
+     * @return The value of that key, or {@code null} if the field is not set.
+     *
+     * @throws IllegalArgumentException if the key is not valid or extension type is not a supported
+     * device-specific extension.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+    public <T> @Nullable T get(@Extension int extension,
+            @NonNull CameraCharacteristics.Key<T> key) {
+        final IBinder token = new Binder(TAG + "#get:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
+            throw new IllegalArgumentException("Unsupported extensions");
+        }
+
+        try {
+            if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
+                throw new IllegalArgumentException("Unsupported extension");
+            }
+
+            if (areAdvancedExtensionsSupported() && getKeys(extension).contains(key)) {
+                IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+                extender.init(mCameraId, mCharacteristicsMapNative);
+                CameraMetadataNative metadata =
+                        extender.getAvailableCharacteristicsKeyValues(mCameraId);
+                CameraCharacteristics fallbackCharacteristics = mCharacteristicsMap.get(mCameraId);
+                if (metadata == null) {
+                    return fallbackCharacteristics.get(key);
+                }
+                CameraCharacteristics characteristics = new CameraCharacteristics(metadata);
+                T value = characteristics.get(key);
+                return value == null ? fallbackCharacteristics.get(key) : value;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query the extension for the specified key! Extension "
+                    + "service does not respond!");
+        } finally {
+            unregisterClient(mContext, token);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the {@link CameraCharacteristics} keys that have extension-specific values.
+     *
+     * <p>An application can query the value from the key using
+     * {@link #get(int, CameraCharacteristics.Key)} API.
+     *
+     * @param extension The extension type.
+     * @return An unmodifiable set of keys that are extension specific.
+     *
+     * @throws IllegalArgumentException in case the extension type is not a
+     * supported device-specific extension
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+    public @NonNull Set<CameraCharacteristics.Key> getKeys(@Extension int extension) {
+        final IBinder token =
+                new Binder(TAG + "#getKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
+            throw new IllegalArgumentException("Unsupported extensions");
+        }
+
+        HashSet<CameraCharacteristics.Key> ret = new HashSet<>();
+
+        try {
+            if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
+                throw new IllegalArgumentException("Unsupported extension");
+            }
+
+            if (areAdvancedExtensionsSupported()) {
+                IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+                extender.init(mCameraId, mCharacteristicsMapNative);
+                CameraMetadataNative metadata =
+                        extender.getAvailableCharacteristicsKeyValues(mCameraId);
+                if (metadata == null) {
+                    return Collections.emptySet();
+                }
+
+                int[] keys = metadata.get(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+                if (keys == null) {
+                    throw new AssertionError(
+                            "android.request.availableCharacteristicsKeys must be non-null"
+                                    + " in the characteristics");
+                }
+                CameraCharacteristics chars = new CameraCharacteristics(metadata);
+
+                Object key = CameraCharacteristics.Key.class;
+                Class<CameraCharacteristics.Key<?>> keyTyped =
+                        (Class<CameraCharacteristics.Key<?>>) key;
+
+                ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
+                        /*includeSynthetic*/ true));
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query the extension for all available keys! Extension "
+                    + "service does not respond!");
+        } finally {
+            unregisterClient(mContext, token);
+        }
+        return Collections.unmodifiableSet(ret);
+    }
+
+    /**
      * Checks for postview support of still capture.
      *
      * <p>A postview is a preview version of the still capture that is available before the final
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index fb2df54..a06f017 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -27,6 +27,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureCallback;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Size;
 
 import com.android.internal.camera.flags.Flags;
@@ -222,6 +223,23 @@
     public abstract List<CaptureResult.Key> getAvailableCaptureResultKeys(
             @NonNull String cameraId);
 
+    /**
+     * Returns a list of {@link CameraCharacteristics} key/value pairs for apps to use when
+     * querying the Extensions specific {@link CameraCharacteristics}.
+     *
+     * <p>To ensure the correct {@link CameraCharacteristics} are used when an extension is
+     * enabled, an application should prioritize the value returned from the list if the
+     * {@link CameraCharacteristics} key is present. If the key doesn't exist in the returned list,
+     * then the application should query the value using
+     * {@link CameraCharacteristics#get(CameraCharacteristics.Key)}.
+     *
+     * <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return
+     * a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+    @NonNull
+    public abstract List<Pair<CameraCharacteristics.Key, Object>>
+            getAvailableCharacteristicsKeyValues();
 
     private final class AdvancedExtenderImpl extends IAdvancedExtenderImpl.Stub {
         @Override
@@ -322,6 +340,33 @@
             // Feature is currently unsupported
             return false;
         }
+
+        @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+        @Override
+        public CameraMetadataNative getAvailableCharacteristicsKeyValues(String cameraId) {
+            List<Pair<CameraCharacteristics.Key, Object>> entries =
+                    AdvancedExtender.this.getAvailableCharacteristicsKeyValues();
+
+            if ((entries != null) && !entries.isEmpty()) {
+                CameraMetadataNative ret = new CameraMetadataNative();
+                long vendorId = mMetadataVendorIdMap.containsKey(cameraId)
+                        ? mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE;
+                ret.setVendorId(vendorId);
+                int[] characteristicsKeyTags = new int[entries.size()];
+                int i = 0;
+                for (Pair<CameraCharacteristics.Key, Object> entry : entries) {
+                    int tag = CameraMetadataNative.getTag(entry.first.getName(), vendorId);
+                    characteristicsKeyTags[i++] = tag;
+                    ret.set(entry.first, entry.second);
+                }
+                ret.set(CameraCharacteristics.REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+                        characteristicsKeyTags);
+
+                return ret;
+            }
+
+            return null;
+        }
     }
 
     @NonNull IAdvancedExtenderImpl getAdvancedExtenderBinder() {
diff --git a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
index 101442f..3071f0d 100644
--- a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
@@ -38,4 +38,5 @@
     CameraMetadataNative getAvailableCaptureResultKeys(in String cameraId);
     boolean isCaptureProcessProgressAvailable();
     boolean isPostviewAvailable();
+    CameraMetadataNative getAvailableCharacteristicsKeyValues(in String cameraId);
 }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 155c523..c134a4c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -120,6 +120,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -142,11 +143,13 @@
     // Support for various latency improvements
     private static final String LATENCY_VERSION_PREFIX = "1.4";
     private static final String EFV_VERSION_PREFIX = "1.5";
+    private static final String GET_VERSION_PREFIX = "1.5";
     private static final String[] ADVANCED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
-            LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
+            LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX,
+            GET_VERSION_PREFIX};
     private static final String[] SUPPORTED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
             LATENCY_VERSION_PREFIX, RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1",
-            NON_INIT_VERSION_PREFIX};
+            NON_INIT_VERSION_PREFIX, GET_VERSION_PREFIX};
     private static final boolean EXTENSIONS_PRESENT = checkForExtensions();
     private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ?
             (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null;
@@ -156,6 +159,8 @@
                     (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)));
     private static final boolean EFV_SUPPORTED = EXTENSIONS_PRESENT &&
             (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
+    private static final boolean GET_API_SUPPORTED = EXTENSIONS_PRESENT
+            && (EXTENSIONS_VERSION.startsWith(GET_VERSION_PREFIX));
     private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI();
     private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT &&
             (!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX));
@@ -777,6 +782,12 @@
                         public boolean isPostviewAvailable() {
                             return false;
                         }
+
+                        @Override
+                        public List<Pair<CameraCharacteristics.Key, Object>>
+                                getAvailableCharacteristicsKeyValues() {
+                            return Collections.emptyList();
+                        }
                     };
                 }
             }
@@ -1186,6 +1197,35 @@
 
             return false;
         }
+
+        @Override
+        public CameraMetadataNative getAvailableCharacteristicsKeyValues(String cameraId) {
+            if (GET_API_SUPPORTED) {
+                List<Pair<CameraCharacteristics.Key, Object>> entries =
+                        mAdvancedExtender.getAvailableCharacteristicsKeyValues();
+
+                if ((entries != null) && !entries.isEmpty()) {
+                    CameraMetadataNative ret = new CameraMetadataNative();
+                    long vendorId = mMetadataVendorIdMap.containsKey(cameraId)
+                            ? mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE;
+                    ret.setVendorId(vendorId);
+                    int[] characteristicsKeyTags = new int[entries.size()];
+                    int i = 0;
+                    for (Pair<CameraCharacteristics.Key, Object> entry : entries) {
+                        int tag = CameraMetadataNative.getTag(entry.first.getName(), vendorId);
+                        characteristicsKeyTags[i++] = tag;
+                        ret.set(entry.first, entry.second);
+                    }
+                    ret.set(CameraCharacteristics.REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+                            characteristicsKeyTags);
+
+                    return ret;
+                }
+            }
+
+            return null;
+        }
+
     }
 
     private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {