/* * Copyright (C) 2020 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; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.ImageFormat; import android.hardware.camera2.CameraCharacteristics.Key; import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.ICameraExtensionsProxyService; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.SizeList; import android.hardware.camera2.impl.CameraExtensionUtils; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Binder; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; import android.provider.Settings; 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; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** *

Allows clients to query availability and supported resolutions of camera extensions.

* *

Camera extensions give camera clients access to device-specific algorithms and sequences that * can improve the overall image quality of snapshots in various cases such as low light, selfies, * portraits, and scenes that can benefit from enhanced dynamic range. Often such sophisticated * processing sequences will rely on multiple camera frames as input and will produce a single * output.

* *

Camera extensions are not guaranteed to be present on all devices so camera clients must * query for their availability via {@link CameraExtensionCharacteristics#getSupportedExtensions()}. *

* *

In order to use any available camera extension, camera clients must create a corresponding * {@link CameraExtensionSession} via * {@link CameraDevice#createExtensionSession(ExtensionSessionConfiguration)}

* *

Camera clients must be aware that device-specific camera extensions may support only a * subset of the available camera resolutions and must first query * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)} for supported * single high-quality request output sizes and * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, Class)} for supported * repeating request output sizes.

* *

The extension characteristics for a given device are expected to remain static under * normal operating conditions.

* * @see CameraManager#getCameraExtensionCharacteristics(String) */ public final class CameraExtensionCharacteristics { private static final String TAG = "CameraExtensionCharacteristics"; /** * Device-specific extension implementation for automatic selection of particular extension * such as HDR or NIGHT depending on the current lighting and environment conditions. */ public static final int EXTENSION_AUTOMATIC = 0; /** * Device-specific extension implementation which tends to smooth the skin and apply other * cosmetic effects to people's faces. */ public static final int EXTENSION_FACE_RETOUCH = 1; /** * Device-specific extension implementation which tends to smooth the skin and apply other * cosmetic effects to people's faces. * * @deprecated Use {@link #EXTENSION_FACE_RETOUCH} instead. */ public @Deprecated static final int EXTENSION_BEAUTY = EXTENSION_FACE_RETOUCH; /** * Device-specific extension implementation which can blur certain regions of the final image * thereby "enhancing" focus for all remaining non-blurred parts. */ public static final int EXTENSION_BOKEH = 2; /** * Device-specific extension implementation for enhancing the dynamic range of the * final image. */ public static final int EXTENSION_HDR = 3; /** * Device-specific extension implementation that aims to suppress noise and improve the * overall image quality under low light conditions. */ 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_API) public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = {EXTENSION_AUTOMATIC, EXTENSION_FACE_RETOUCH, EXTENSION_BOKEH, EXTENSION_HDR, EXTENSION_NIGHT, EXTENSION_EYES_FREE_VIDEOGRAPHY}) public @interface Extension { } /** * Default camera output in case additional processing from CameraX extensions is not needed * * @hide */ public static final int NON_PROCESSING_INPUT_FORMAT = ImageFormat.PRIVATE; /** * CameraX extensions require YUV_420_888 as default input for processing at the moment * * @hide */ public static final int PROCESSING_INPUT_FORMAT = ImageFormat.YUV_420_888; private static final @Extension int[] EXTENSION_LIST = new int[]{ EXTENSION_AUTOMATIC, EXTENSION_FACE_RETOUCH, EXTENSION_BOKEH, EXTENSION_HDR, EXTENSION_NIGHT}; /** * List of synthetic CameraCharacteristics keys that are supported in the extensions. */ private static final List SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS = Arrays.asList( CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES, CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES ); private final Context mContext; private final String mCameraId; private final Map mCharacteristicsMap; private final Map mCharacteristicsMapNative; /** * @hide */ public CameraExtensionCharacteristics(Context context, String cameraId, Map characteristicsMap) { mContext = context; mCameraId = cameraId; mCharacteristicsMap = characteristicsMap; mCharacteristicsMapNative = CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap); } private static ArrayList getSupportedSizes(List sizesList, Integer format) { ArrayList ret = new ArrayList<>(); if ((sizesList != null) && (!sizesList.isEmpty())) { for (SizeList entry : sizesList) { if ((entry.format == format) && !entry.sizes.isEmpty()) { for (android.hardware.camera2.extension.Size sz : entry.sizes) { ret.add(new Size(sz.width, sz.height)); } return ret; } } } return ret; } private static List generateSupportedSizes(List sizesList, Integer format, StreamConfigurationMap streamMap) { ArrayList ret = getSupportedSizes(sizesList, format); if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888 || format == ImageFormat.PRIVATE) { // Per API contract it is assumed that the extension is able to support all // camera advertised sizes for JPEG, YUV_420_888 and PRIVATE in case it doesn't return // a valid non-empty size list. Size[] supportedSizes = streamMap.getOutputSizes(format); if ((ret.isEmpty()) && (supportedSizes != null)) { ret.addAll(Arrays.asList(supportedSizes)); } } return ret; } private static List generateJpegSupportedSizes(List sizesList, StreamConfigurationMap streamMap) { ArrayList extensionSizes = getSupportedSizes(sizesList, ImageFormat.YUV_420_888); HashSet supportedSizes = extensionSizes.isEmpty() ? new HashSet<>(Arrays.asList( streamMap.getOutputSizes(ImageFormat.YUV_420_888))) : new HashSet<>(extensionSizes); HashSet supportedJpegSizes = new HashSet<>(Arrays.asList(streamMap.getOutputSizes( ImageFormat.JPEG))); supportedSizes.retainAll(supportedJpegSizes); return new ArrayList<>(supportedSizes); } /** * A per-process global camera extension manager instance, to track and * initialize/release extensions depending on client activity. */ private static final class CameraExtensionManagerGlobal { private static final String TAG = "CameraExtensionManagerGlobal"; private static final String PROXY_PACKAGE_NAME = "com.android.cameraextensions"; private static final String PROXY_SERVICE_NAME = "com.android.cameraextensions.CameraExtensionsProxyService"; @FlaggedApi(Flags.FLAG_CONCERT_MODE) private static final int FALLBACK_PACKAGE_NAME = com.android.internal.R.string.config_extensionFallbackPackageName; @FlaggedApi(Flags.FLAG_CONCERT_MODE) private static final int FALLBACK_SERVICE_NAME = com.android.internal.R.string.config_extensionFallbackServiceName; // Singleton instance private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER = new CameraExtensionManagerGlobal(); private final Object mLock = new Object(); private final int PROXY_SERVICE_DELAY_MS = 2000; private ExtensionConnectionManager mConnectionManager = new ExtensionConnectionManager(); // Singleton, don't allow construction private CameraExtensionManagerGlobal() {} public static CameraExtensionManagerGlobal get() { return GLOBAL_CAMERA_MANAGER; } private void releaseProxyConnectionLocked(Context ctx, int extension) { if (mConnectionManager.getConnection(extension) != null) { ctx.unbindService(mConnectionManager.getConnection(extension)); mConnectionManager.setConnection(extension, null); mConnectionManager.setProxy(extension, null); mConnectionManager.resetConnectionCount(extension); } } private void connectToProxyLocked(Context ctx, int extension, boolean useFallback) { if (mConnectionManager.getConnection(extension) == null) { Intent intent = new Intent(); intent.setClassName(PROXY_PACKAGE_NAME, PROXY_SERVICE_NAME); String vendorProxyPackage = SystemProperties.get( "ro.vendor.camera.extensions.package"); String vendorProxyService = SystemProperties.get( "ro.vendor.camera.extensions.service"); if (!vendorProxyPackage.isEmpty() && !vendorProxyService.isEmpty()) { Log.v(TAG, "Choosing the vendor camera extensions proxy package: " + vendorProxyPackage); Log.v(TAG, "Choosing the vendor camera extensions proxy service: " + vendorProxyService); intent.setClassName(vendorProxyPackage, vendorProxyService); } if (Flags.concertMode() && useFallback) { String packageName = ctx.getResources().getString(FALLBACK_PACKAGE_NAME); String serviceName = ctx.getResources().getString(FALLBACK_SERVICE_NAME); if (!packageName.isEmpty() && !serviceName.isEmpty()) { Log.v(TAG, "Choosing the fallback software implementation package: " + packageName); Log.v(TAG, "Choosing the fallback software implementation service: " + serviceName); intent.setClassName(packageName, serviceName); } } InitializerFuture initFuture = new InitializerFuture(); ServiceConnection connection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName component) { mConnectionManager.setConnection(extension, null); mConnectionManager.setProxy(extension, null); } @Override public void onServiceConnected(ComponentName component, IBinder binder) { ICameraExtensionsProxyService proxy = ICameraExtensionsProxyService.Stub.asInterface(binder); mConnectionManager.setProxy(extension, proxy); if (mConnectionManager.getProxy(extension) == null) { throw new IllegalStateException("Camera Proxy service is null"); } try { mConnectionManager.setAdvancedExtensionsSupported(extension, mConnectionManager.getProxy(extension) .advancedExtensionsSupported()); } catch (RemoteException e) { Log.e(TAG, "Remote IPC failed!"); } initFuture.setStatus(true); } }; ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | Context.BIND_ABOVE_CLIENT | Context.BIND_NOT_VISIBLE, android.os.AsyncTask.THREAD_POOL_EXECUTOR, connection); mConnectionManager.setConnection(extension, connection); try { initFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { Log.e(TAG, "Timed out while initializing proxy service!"); } } } private static class InitializerFuture implements Future { private volatile Boolean mStatus; ConditionVariable mCondVar = new ConditionVariable(/*opened*/false); public void setStatus(boolean status) { mStatus = status; mCondVar.open(); } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; // don't allow canceling this task } @Override public boolean isCancelled() { return false; // can never cancel this task } @Override public boolean isDone() { return mStatus != null; } @Override public Boolean get() { mCondVar.block(); return mStatus; } @Override public Boolean get(long timeout, TimeUnit unit) throws TimeoutException { long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS); if (!mCondVar.block(timeoutMs)) { throw new TimeoutException( "Failed to receive status after " + timeout + " " + unit); } if (mStatus == null) { throw new AssertionError(); } return mStatus; } } public boolean registerClientHelper(Context ctx, IBinder token, int extension, boolean useFallback) { synchronized (mLock) { boolean ret = false; connectToProxyLocked(ctx, extension, useFallback); if (mConnectionManager.getProxy(extension) == null) { return false; } mConnectionManager.incrementConnectionCount(extension); try { ret = mConnectionManager.getProxy(extension).registerClient(token); } catch (RemoteException e) { Log.e(TAG, "Failed to initialize extension! Extension service does " + " not respond!"); } if (!ret) { mConnectionManager.decrementConnectionCount(extension); } if (mConnectionManager.getConnectionCount(extension) <= 0) { releaseProxyConnectionLocked(ctx, extension); } return ret; } } @SuppressLint("NonUserGetterCalled") public boolean registerClient(Context ctx, IBinder token, int extension, String cameraId, Map characteristicsMapNative) { boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/); if (Flags.concertMode()) { // Check if user enabled fallback impl ContentResolver resolver = ctx.getContentResolver(); int userEnabled = Settings.Secure.getInt(resolver, Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1); boolean vendorImpl = true; if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) { // At this point, we are connected to either CameraExtensionsProxyService or // the vendor extension proxy service. If the vendor does not support the // extension, unregisterClient and re-register client with the proxy service // containing the fallback impl vendorImpl = isExtensionSupported(cameraId, extension, characteristicsMapNative); } if (!vendorImpl) { unregisterClient(ctx, token, extension); ret = registerClientHelper(ctx, token, extension, true /*useFallback*/); } } return ret; } public void unregisterClient(Context ctx, IBinder token, int extension) { synchronized (mLock) { if (mConnectionManager.getProxy(extension) != null) { try { mConnectionManager.getProxy(extension).unregisterClient(token); } catch (RemoteException e) { Log.e(TAG, "Failed to de-initialize extension! Extension service does" + " not respond!"); } finally { mConnectionManager.decrementConnectionCount(extension); if (mConnectionManager.getConnectionCount(extension) <= 0) { releaseProxyConnectionLocked(ctx, extension); } } } } } public void initializeSession(IInitializeSessionCallback cb, int extension) throws RemoteException { synchronized (mLock) { if (mConnectionManager.getProxy(extension) != null && !mConnectionManager.isSessionInitialized()) { mConnectionManager.getProxy(extension).initializeSession(cb); mConnectionManager.setSessionInitialized(true); } else { cb.onFailure(); } } } public void releaseSession(int extension) { synchronized (mLock) { if (mConnectionManager.getProxy(extension) != null) { try { mConnectionManager.getProxy(extension).releaseSession(); mConnectionManager.setSessionInitialized(false); } catch (RemoteException e) { Log.e(TAG, "Failed to release session! Extension service does" + " not respond!"); } } } } public boolean areAdvancedExtensionsSupported(int extension) { return mConnectionManager.areAdvancedExtensionsSupported(extension); } public IPreviewExtenderImpl initializePreviewExtension(int extension) throws RemoteException { synchronized (mLock) { if (mConnectionManager.getProxy(extension) != null) { return mConnectionManager.getProxy(extension) .initializePreviewExtension(extension); } else { return null; } } } public IImageCaptureExtenderImpl initializeImageExtension(int extension) throws RemoteException { synchronized (mLock) { if (mConnectionManager.getProxy(extension) != null) { return mConnectionManager.getProxy(extension) .initializeImageExtension(extension); } else { return null; } } } public IAdvancedExtenderImpl initializeAdvancedExtension(int extension) throws RemoteException { synchronized (mLock) { if (mConnectionManager.getProxy(extension) != null) { return mConnectionManager.getProxy(extension) .initializeAdvancedExtension(extension); } else { return null; } } } private class ExtensionConnectionManager { // Maps extension to ExtensionConnection private Map mConnections = new HashMap<>(); private boolean mSessionInitialized = false; public ExtensionConnectionManager() { IntArray extensionList = new IntArray(EXTENSION_LIST.length); extensionList.addAll(EXTENSION_LIST); if (Flags.concertModeApi()) { extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY); } for (int extensionType : extensionList.toArray()) { mConnections.put(extensionType, new ExtensionConnection()); } } public ICameraExtensionsProxyService getProxy(@Extension int extension) { return mConnections.get(extension).mProxy; } public ServiceConnection getConnection(@Extension int extension) { return mConnections.get(extension).mConnection; } public int getConnectionCount(@Extension int extension) { return mConnections.get(extension).mConnectionCount; } public boolean areAdvancedExtensionsSupported(@Extension int extension) { return mConnections.get(extension).mSupportsAdvancedExtensions; } public boolean isSessionInitialized() { return mSessionInitialized; } public void setProxy(@Extension int extension, ICameraExtensionsProxyService proxy) { mConnections.get(extension).mProxy = proxy; } public void setConnection(@Extension int extension, ServiceConnection connection) { mConnections.get(extension).mConnection = connection; } public void incrementConnectionCount(@Extension int extension) { mConnections.get(extension).mConnectionCount++; } public void decrementConnectionCount(@Extension int extension) { mConnections.get(extension).mConnectionCount--; } public void resetConnectionCount(@Extension int extension) { mConnections.get(extension).mConnectionCount = 0; } public void setAdvancedExtensionsSupported(@Extension int extension, boolean advancedExtSupported) { mConnections.get(extension).mSupportsAdvancedExtensions = advancedExtSupported; } public void setSessionInitialized(boolean initialized) { mSessionInitialized = initialized; } private class ExtensionConnection { public ICameraExtensionsProxyService mProxy = null; public ServiceConnection mConnection = null; public int mConnectionCount = 0; public boolean mSupportsAdvancedExtensions = false; } } } /** * @hide */ public static boolean registerClient(Context ctx, IBinder token, int extension, String cameraId, Map characteristicsMapNative) { return CameraExtensionManagerGlobal.get().registerClient(ctx, token, extension, cameraId, characteristicsMapNative); } /** * @hide */ public static void unregisterClient(Context ctx, IBinder token, int extension) { CameraExtensionManagerGlobal.get().unregisterClient(ctx, token, extension); } /** * @hide */ public static void initializeSession(IInitializeSessionCallback cb, int extension) throws RemoteException { CameraExtensionManagerGlobal.get().initializeSession(cb, extension); } /** * @hide */ public static void releaseSession(int extension) { CameraExtensionManagerGlobal.get().releaseSession(extension); } /** * @hide */ public static boolean areAdvancedExtensionsSupported(int extension) { return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported(extension); } /** * @hide */ public static boolean isExtensionSupported(String cameraId, int extensionType, Map characteristicsMap) { if (areAdvancedExtensionsSupported(extensionType)) { try { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType); return extender.isExtensionAvailable(cameraId, characteristicsMap); } catch (RemoteException e) { Log.e(TAG, "Failed to query extension availability! Extension service does not" + " respond!"); return false; } } else { Pair extenders; try { extenders = initializeExtension(extensionType); } catch (IllegalArgumentException e) { return false; } try { return extenders.first.isExtensionAvailable(cameraId, characteristicsMap.get(cameraId)) && extenders.second.isExtensionAvailable(cameraId, characteristicsMap.get(cameraId)); } catch (RemoteException e) { Log.e(TAG, "Failed to query extension availability! Extension service does not" + " respond!"); return false; } } } /** * @hide */ public static IAdvancedExtenderImpl initializeAdvancedExtension(@Extension int extensionType) { IAdvancedExtenderImpl extender; try { extender = CameraExtensionManagerGlobal.get().initializeAdvancedExtension( extensionType); } catch (RemoteException e) { throw new IllegalStateException("Failed to initialize extension: " + extensionType); } if (extender == null) { throw new IllegalArgumentException("Unknown extension: " + extensionType); } return extender; } /** * @hide */ public static Pair initializeExtension( @Extension int extensionType) { IPreviewExtenderImpl previewExtender; IImageCaptureExtenderImpl imageExtender; try { previewExtender = CameraExtensionManagerGlobal.get().initializePreviewExtension(extensionType); imageExtender = CameraExtensionManagerGlobal.get().initializeImageExtension(extensionType); } catch (RemoteException e) { throw new IllegalStateException("Failed to initialize extension: " + extensionType); } if ((imageExtender == null) || (previewExtender == null)) { throw new IllegalArgumentException("Unknown extension: " + extensionType); } return new Pair<>(previewExtender, imageExtender); } private static boolean isOutputSupportedFor(Class klass) { Objects.requireNonNull(klass, "klass must not be null"); if ((klass == android.graphics.SurfaceTexture.class) || (klass == android.view.SurfaceView.class)) { return true; } return false; } /** * Return a list of supported device-specific extensions for a given camera device. * * @return non-modifiable list of available extensions */ public @NonNull List getSupportedExtensions() { ArrayList ret = new ArrayList<>(); final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId); IntArray extensionList = new IntArray(EXTENSION_LIST.length); extensionList.addAll(EXTENSION_LIST); if (Flags.concertModeApi()) { extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY); } for (int extensionType : extensionList.toArray()) { try { boolean success = registerClient(mContext, token, extensionType, mCameraId, mCharacteristicsMapNative); if (success && isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) { ret.add(extensionType); } } finally { unregisterClient(mContext, token, extensionType); } } return Collections.unmodifiableList(ret); } /** * Gets an extension specific camera characteristics field value. * *

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 @Nullable T get(@Extension int extension, @NonNull CameraCharacteristics.Key key) { final IBinder token = new Binder(TAG + "#get:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } if (areAdvancedExtensionsSupported(extension) && getKeys(extension).contains(key)) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); CameraMetadataNative metadata = extender.getAvailableCharacteristicsKeyValues(mCameraId); if (metadata == null) { return null; } CameraCharacteristics characteristics = new CameraCharacteristics(metadata); return characteristics.get(key); } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension for the specified key! Extension " + "service does not respond!"); } finally { unregisterClient(mContext, token, extension); } return null; } /** * Returns the {@link CameraCharacteristics} keys that have extension-specific values. * *

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 getKeys(@Extension int extension) { final IBinder token = new Binder(TAG + "#getKeys:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } HashSet ret = new HashSet<>(); try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } if (areAdvancedExtensionsSupported(extension)) { 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> keyTyped = (Class>) key; ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys, /*includeSynthetic*/ false)); // Add synthetic keys to the available key list if they are part of the supported // synthetic camera characteristic key list for (CameraCharacteristics.Key charKey : SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) { if (chars.get(charKey) != null) { ret.add(charKey); } } } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension for all available keys! Extension " + "service does not respond!"); } finally { unregisterClient(mContext, token, extension); } return Collections.unmodifiableSet(ret); } /** * Checks for postview support of still capture. * *

A postview is a preview version of the still capture that is available before the final * image. For example, it can be used as a temporary placeholder for the requested capture * while the final image is being processed. The supported sizes for a still capture's postview * can be retrieved using * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.

* *

Starting with Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, * the formats of the still capture and postview are not required to be equivalent upon capture * request.

* * @param extension the extension type * @return {@code true} in case postview is supported, {@code false} otherwise * * @throws IllegalArgumentException in case the extension type is not a * supported device-specific extension */ public boolean isPostviewAvailable(@Extension int extension) { final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } if (areAdvancedExtensionsSupported(extension)) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); return extender.isPostviewAvailable(); } else { Pair extenders = initializeExtension(extension); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); return extenders.second.isPostviewAvailable(); } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension for postview availability! Extension " + "service does not respond!"); } finally { unregisterClient(mContext, token, extension); } return false; } /** * Get a list of the postview sizes supported for a still capture, using its * capture size {@code captureSize}, to use as an output for the postview request. * *

Available postview sizes will always be either equal to or less than the still * capture size. When choosing the most applicable postview size for a usecase, it should * be noted that lower resolution postviews will generally be available more quickly * than larger resolution postviews. For example, when choosing a size for an optimized * postview that will be displayed as a placeholder while the final image is processed, * the resolution closest to the preview size may be most suitable.

* *

Note that device-specific extensions are allowed to support only a subset * of the camera resolutions advertised by * {@link StreamConfigurationMap#getOutputSizes}.

* * @param extension the extension type * @param captureSize size of the still capture for which the postview is requested * @param format device-specific extension output format of the postview * @return non-modifiable list of available sizes or an empty list if the format and * size is not supported. * @throws IllegalArgumentException in case of unsupported extension or if postview * feature is not supported by extension. */ @NonNull public List getPostviewSupportedSizes(@Extension int extension, @NonNull Size captureSize, int format) { final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } android.hardware.camera2.extension.Size sz = new android.hardware.camera2.extension.Size(); sz.width = captureSize.getWidth(); sz.height = captureSize.getHeight(); StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (areAdvancedExtensionsSupported(extension)) { switch(format) { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: case ImageFormat.YCBCR_P010: break; default: throw new IllegalArgumentException("Unsupported format: " + format); } IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); return getSupportedSizes(extender.getSupportedPostviewResolutions(sz), format); } else { Pair extenders = initializeExtension(extension); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if ((extenders.second.getCaptureProcessor() == null) || !isPostviewAvailable(extension)) { // Extensions that don't implement any capture processor // and have processing occur in the HAL don't currently support the // postview feature throw new IllegalArgumentException("Extension does not support " + "postview feature"); } if (format == ImageFormat.YUV_420_888) { return getSupportedSizes( extenders.second.getSupportedPostviewResolutions(sz), format); } else if (format == ImageFormat.JPEG) { // The framework will perform the additional encoding pass on the // processed YUV_420 buffers. return getSupportedSizes( extenders.second.getSupportedPostviewResolutions(sz), format); } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic // extension case return new ArrayList<>(); } else { throw new IllegalArgumentException("Unsupported format: " + format); } } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension postview supported sizes! Extension " + "service does not respond!"); return Collections.emptyList(); } finally { unregisterClient(mContext, token, extension); } } /** * Get a list of sizes compatible with {@code klass} to use as an output for the * repeating request * {@link CameraExtensionSession#setRepeatingRequest}. * *

Note that device-specific extensions are allowed to support only a subset * of the camera output surfaces and resolutions. * The {@link android.graphics.SurfaceTexture} class is guaranteed at least one size for * backward compatible cameras whereas other output classes are not guaranteed to be supported. *

* *

Starting with Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} * {@link android.view.SurfaceView} classes are also guaranteed to be supported and include * the same resolutions as {@link android.graphics.SurfaceTexture}. * Clients must set the desired SurfaceView resolution by calling * {@link android.view.SurfaceHolder#setFixedSize}.

* * @param extension the extension type * @param klass a non-{@code null} {@link Class} object reference * @return non-modifiable list of available sizes or an empty list if the Surface output is not * supported * @throws NullPointerException if {@code klass} was {@code null} * @throws IllegalArgumentException in case of unsupported extension. */ @NonNull public List getExtensionSupportedSizes(@Extension int extension, @NonNull Class klass) { if (!isOutputSupportedFor(klass)) { return new ArrayList<>(); } // TODO: Revisit this code once the Extension preview processor output format // ambiguity is resolved in b/169799538. final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (areAdvancedExtensionsSupported(extension)) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); return generateSupportedSizes( extender.getSupportedPreviewOutputResolutions(mCameraId), ImageFormat.PRIVATE, streamMap); } else { Pair extenders = initializeExtension(extension); extenders.first.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); return generateSupportedSizes(extenders.first.getSupportedResolutions(), ImageFormat.PRIVATE, streamMap); } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension supported sizes! Extension service does" + " not respond!"); return new ArrayList<>(); } finally { unregisterClient(mContext, token, extension); } } /** * Check whether a given extension is available and return the * supported output surface resolutions that can be used for high-quality capture * requests via {@link CameraExtensionSession#capture}. * *

Note that device-specific extensions are allowed to support only a subset * of the camera resolutions advertised by * {@link StreamConfigurationMap#getOutputSizes}.

* *

Device-specific extensions currently support at most three * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010 * may or may not be supported.

* * @param extension the extension type * @param format device-specific extension output format * @return non-modifiable list of available sizes or an empty list if the format is not * supported. * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG, * ImageFormat.YUV_420_888, ImageFormat.JPEG_R, * ImageFormat.YCBCR_P010; or unsupported extension. */ public @NonNull List getExtensionSupportedSizes(@Extension int extension, int format) { try { final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (areAdvancedExtensionsSupported(extension)) { switch(format) { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: case ImageFormat.YCBCR_P010: break; default: throw new IllegalArgumentException("Unsupported format: " + format); } IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); return generateSupportedSizes(extender.getSupportedCaptureOutputResolutions( mCameraId), format, streamMap); } else { if (format == ImageFormat.YUV_420_888) { Pair extenders = initializeExtension(extension); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if (extenders.second.getCaptureProcessor() == null) { // Extensions that don't implement any capture processor are limited to // JPEG only! return new ArrayList<>(); } return generateSupportedSizes(extenders.second.getSupportedResolutions(), format, streamMap); } else if (format == ImageFormat.JPEG) { Pair extenders = initializeExtension(extension); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if (extenders.second.getCaptureProcessor() != null) { // The framework will perform the additional encoding pass on the // processed YUV_420 buffers. return generateJpegSupportedSizes( extenders.second.getSupportedResolutions(), streamMap); } else { return generateSupportedSizes(null, format, streamMap); } } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the // basic extension case return new ArrayList<>(); } else { throw new IllegalArgumentException("Unsupported format: " + format); } } } finally { unregisterClient(mContext, token, extension); } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension supported sizes! Extension service does" + " not respond!"); return new ArrayList<>(); } } /** * Returns the estimated capture latency range in milliseconds for the * target capture resolution during the calls to {@link CameraExtensionSession#capture}. This * includes the time spent processing the multi-frame capture request along with any additional * time for encoding of the processed buffer if necessary. * * @param extension the extension type * @param captureOutputSize size of the capture output surface. If it is not in the supported * output sizes, maximum capture output size is used for the estimation * @param format device-specific extension output format * @return the range of estimated minimal and maximal capture latency in milliseconds * or null if no capture latency info can be provided * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG}, * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R} * {@link ImageFormat#YCBCR_P010}; * or unsupported extension. */ public @Nullable Range getEstimatedCaptureLatencyRangeMillis(@Extension int extension, @NonNull Size captureOutputSize, @ImageFormat.Format int format) { switch (format) { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: case ImageFormat.YCBCR_P010: //No op break; default: throw new IllegalArgumentException("Unsupported format: " + format); } final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } android.hardware.camera2.extension.Size sz = new android.hardware.camera2.extension.Size(); sz.width = captureOutputSize.getWidth(); sz.height = captureOutputSize.getHeight(); if (areAdvancedExtensionsSupported(extension)) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId, sz, format); if (latencyRange != null) { return new Range(latencyRange.min, latencyRange.max); } } else { Pair extenders = initializeExtension(extension); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if ((format == ImageFormat.YUV_420_888) && (extenders.second.getCaptureProcessor() == null) ){ // Extensions that don't implement any capture processor are limited to // JPEG only! return null; } if ((format == ImageFormat.JPEG) && (extenders.second.getCaptureProcessor() != null)) { // The framework will perform the additional encoding pass on the // processed YUV_420 buffers. Latency in this case is very device // specific and cannot be estimated accurately enough. return null; } if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions return null; } LatencyRange latencyRange = extenders.second.getEstimatedCaptureLatencyRange(sz); if (latencyRange != null) { return new Range(latencyRange.min, latencyRange.max); } } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension capture latency! Extension service does" + " not respond!"); } finally { unregisterClient(mContext, token, extension); } return null; } /** * Retrieve support for capture progress callbacks via * {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureProcessProgressed}. * * @param extension the extension type * @return {@code true} in case progress callbacks are supported, {@code false} otherwise * * @throws IllegalArgumentException in case of an unsupported extension. */ public boolean isCaptureProcessProgressAvailable(@Extension int extension) { final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } if (areAdvancedExtensionsSupported(extension)) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); return extender.isCaptureProcessProgressAvailable(); } else { Pair extenders = initializeExtension(extension); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); return extenders.second.isCaptureProcessProgressAvailable(); } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does" + " not respond!"); } finally { unregisterClient(mContext, token, extension); } return false; } /** * Returns the set of keys supported by a {@link CaptureRequest} submitted in a * {@link CameraExtensionSession} with a given extension type. * *

The set returned is not modifiable, so any attempts to modify it will throw * a {@code UnsupportedOperationException}.

* *

Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} * or newer versions are required to support {@link CaptureRequest#CONTROL_AF_MODE}, * {@link CaptureRequest#CONTROL_AF_REGIONS}, {@link CaptureRequest#CONTROL_AF_TRIGGER}, * {@link CaptureRequest#CONTROL_ZOOM_RATIO} for * {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.

* * @param extension the extension type * * @return non-modifiable set of capture keys supported by camera extension session initialized * with the given extension type. * @throws IllegalArgumentException in case of unsupported extension. */ @NonNull public Set getAvailableCaptureRequestKeys(@Extension int extension) { final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } HashSet ret = new HashSet<>(); try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } CameraMetadataNative captureRequestMeta = null; if (areAdvancedExtensionsSupported(extension)) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); captureRequestMeta = extender.getAvailableCaptureRequestKeys(mCameraId); } else { Pair extenders = initializeExtension(extension); extenders.second.onInit(token, mCameraId, mCharacteristicsMapNative.get(mCameraId)); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys(); extenders.second.onDeInit(token); } if (captureRequestMeta != null) { int[] requestKeys = captureRequestMeta.get( CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS); if (requestKeys == null) { throw new AssertionError( "android.request.availableRequestKeys must be non-null" + " in the characteristics"); } CameraCharacteristics requestChars = new CameraCharacteristics( captureRequestMeta); Object crKey = CaptureRequest.Key.class; Class> crKeyTyped = (Class>) crKey; ret.addAll(requestChars.getAvailableKeyList(CaptureRequest.class, crKeyTyped, requestKeys, /*includeSynthetic*/ true)); } // Jpeg quality and orientation must always be supported if (!ret.contains(CaptureRequest.JPEG_QUALITY)) { ret.add(CaptureRequest.JPEG_QUALITY); } if (!ret.contains(CaptureRequest.JPEG_ORIENTATION)) { ret.add(CaptureRequest.JPEG_ORIENTATION); } } catch (RemoteException e) { throw new IllegalStateException("Failed to query the available capture request keys!"); } finally { unregisterClient(mContext, token, extension); } return Collections.unmodifiableSet(ret); } /** * Returns the set of keys supported by a {@link CaptureResult} passed as an argument to * {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}. * *

The set returned is not modifiable, so any attempts to modify it will throw * a {@code UnsupportedOperationException}.

* *

In case the set is empty, then the extension is not able to support any capture results * and the {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable} * callback will not be fired.

* *

Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} * or newer versions are required to support {@link CaptureResult#CONTROL_AF_MODE}, * {@link CaptureResult#CONTROL_AF_REGIONS}, {@link CaptureResult#CONTROL_AF_TRIGGER}, * {@link CaptureResult#CONTROL_AF_STATE}, {@link CaptureResult#CONTROL_ZOOM_RATIO} for * {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.

* * @param extension the extension type * * @return non-modifiable set of capture result keys supported by camera extension session * initialized with the given extension type. * @throws IllegalArgumentException in case of unsupported extension. */ @NonNull public Set getAvailableCaptureResultKeys(@Extension int extension) { final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId); boolean success = registerClient(mContext, token, extension, mCameraId, mCharacteristicsMapNative); if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } HashSet ret = new HashSet<>(); try { if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } CameraMetadataNative captureResultMeta = null; if (areAdvancedExtensionsSupported(extension)) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); extender.init(mCameraId, mCharacteristicsMapNative); captureResultMeta = extender.getAvailableCaptureResultKeys(mCameraId); } else { Pair extenders = initializeExtension(extension); extenders.second.onInit(token, mCameraId, mCharacteristicsMapNative.get(mCameraId)); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureResultMeta = extenders.second.getAvailableCaptureResultKeys(); extenders.second.onDeInit(token); } if (captureResultMeta != null) { int[] resultKeys = captureResultMeta.get( CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS); if (resultKeys == null) { throw new AssertionError("android.request.availableResultKeys must be non-null " + "in the characteristics"); } CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta); Object crKey = CaptureResult.Key.class; Class> crKeyTyped = (Class>) crKey; ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped, resultKeys, /*includeSynthetic*/ true)); // Jpeg quality, orientation and sensor timestamp must always be supported if (!ret.contains(CaptureResult.JPEG_QUALITY)) { ret.add(CaptureResult.JPEG_QUALITY); } if (!ret.contains(CaptureResult.JPEG_ORIENTATION)) { ret.add(CaptureResult.JPEG_ORIENTATION); } if (!ret.contains(CaptureResult.SENSOR_TIMESTAMP)) { ret.add(CaptureResult.SENSOR_TIMESTAMP); } } } catch (RemoteException e) { throw new IllegalStateException("Failed to query the available capture result keys!"); } finally { unregisterClient(mContext, token, extension); } return Collections.unmodifiableSet(ret); } /** *

Minimum and maximum padding zoom factors supported by this camera device for * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for * the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } * extension.

*

The minimum and maximum padding zoom factors supported by the device for * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } * extension feature. This extension specific camera characteristic can be queried using * {@link android.hardware.camera2.CameraExtensionCharacteristics#get}.

*

Units: A pair of padding zoom factors in floating-points: * (minPaddingZoomFactor, maxPaddingZoomFactor)

*

Range of valid values:

*

1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor

*

Optional - The value for this key may be {@code null} on some devices.

*/ @PublicKey @NonNull @ExtensionKey @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key> EFV_PADDING_ZOOM_FACTOR_RANGE = CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE; }