Camera: Add logic to switch between vendor and fallback extension impl

- Enables extensions to switch to a fallback software implementation
if the vendor does not support an extension that is equipped with a
fallback
- Changes the way extensions connect to the proxy service by enabling
a connection per extension. This allows multiple proxy service
connections simultaneously.

Test: Camera CTS Test
Bug: 302582551
Change-Id: I25b4ee7089e4c1e83cde51854322f32d4176e8bb
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 7abe821..3b10e0d 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -19,7 +19,9 @@
 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;
@@ -40,6 +42,7 @@
 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;
@@ -53,6 +56,7 @@
 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;
@@ -243,16 +247,19 @@
         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 InitializerFuture mInitFuture = null;
-        private ServiceConnection mConnection = null;
-        private int mConnectionCount = 0;
-        private ICameraExtensionsProxyService mProxy = null;
-        private boolean mSupportsAdvancedExtensions = false;
+        private ExtensionConnectionManager mConnectionManager = new ExtensionConnectionManager();
 
         // Singleton, don't allow construction
         private CameraExtensionManagerGlobal() {}
@@ -261,17 +268,17 @@
             return GLOBAL_CAMERA_MANAGER;
         }
 
-        private void releaseProxyConnectionLocked(Context ctx) {
-            if (mConnection != null ) {
-                ctx.unbindService(mConnection);
-                mConnection = null;
-                mProxy = null;
-                mConnectionCount = 0;
+        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) {
-            if (mConnection == null) {
+        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(
@@ -287,34 +294,55 @@
                       + vendorProxyService);
                   intent.setClassName(vendorProxyPackage, vendorProxyService);
                 }
-                mInitFuture = new InitializerFuture();
-                mConnection = new ServiceConnection() {
+
+                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) {
-                        mConnection = null;
-                        mProxy = null;
+                        mConnectionManager.setConnection(extension, null);
+                        mConnectionManager.setProxy(extension, null);
                     }
 
                     @Override
                     public void onServiceConnected(ComponentName component, IBinder binder) {
-                        mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
-                        if (mProxy == null) {
+                        ICameraExtensionsProxyService proxy =
+                                ICameraExtensionsProxyService.Stub.asInterface(binder);
+                        mConnectionManager.setProxy(extension, proxy);
+                        if (mConnectionManager.getProxy(extension) == null) {
                             throw new IllegalStateException("Camera Proxy service is null");
                         }
                         try {
-                            mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
+                            mConnectionManager.setAdvancedExtensionsSupported(extension,
+                                    mConnectionManager.getProxy(extension)
+                                    .advancedExtensionsSupported());
                         } catch (RemoteException e) {
                             Log.e(TAG, "Remote IPC failed!");
                         }
-                        mInitFuture.setStatus(true);
+                        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, mConnection);
+                        android.os.AsyncTask.THREAD_POOL_EXECUTOR, connection);
+                mConnectionManager.setConnection(extension, connection);
 
                 try {
-                    mInitFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
+                    initFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
                 } catch (TimeoutException e) {
                     Log.e(TAG, "Timed out while initializing proxy service!");
                 }
@@ -366,64 +394,102 @@
             }
         }
 
-        public boolean registerClient(Context ctx, IBinder token) {
+        public boolean registerClientHelper(Context ctx, IBinder token, int extension,
+                boolean useFallback) {
             synchronized (mLock) {
                 boolean ret = false;
-                connectToProxyLocked(ctx);
-                if (mProxy == null) {
+                connectToProxyLocked(ctx, extension, useFallback);
+                if (mConnectionManager.getProxy(extension) == null) {
                     return false;
                 }
-                mConnectionCount++;
+                mConnectionManager.incrementConnectionCount(extension);
 
                 try {
-                    ret = mProxy.registerClient(token);
+                    ret = mConnectionManager.getProxy(extension).registerClient(token);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to initialize extension! Extension service does "
                             + " not respond!");
                 }
                 if (!ret) {
-                    mConnectionCount--;
+                    mConnectionManager.decrementConnectionCount(extension);
                 }
 
-                if (mConnectionCount <= 0) {
-                    releaseProxyConnectionLocked(ctx);
+                if (mConnectionManager.getConnectionCount(extension) <= 0) {
+                    releaseProxyConnectionLocked(ctx, extension);
                 }
 
                 return ret;
             }
         }
 
-        public void unregisterClient(Context ctx, IBinder token) {
+        @SuppressLint("NonUserGetterCalled")
+        public boolean registerClient(Context ctx, IBinder token, int extension,
+                String cameraId, Map<String, CameraMetadataNative> 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 (mProxy != null) {
+                if (mConnectionManager.getProxy(extension) != null) {
                     try {
-                        mProxy.unregisterClient(token);
+                        mConnectionManager.getProxy(extension).unregisterClient(token);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to de-initialize extension! Extension service does"
                                 + " not respond!");
                     } finally {
-                        mConnectionCount--;
-                        if (mConnectionCount <= 0) {
-                            releaseProxyConnectionLocked(ctx);
+                        mConnectionManager.decrementConnectionCount(extension);
+                        if (mConnectionManager.getConnectionCount(extension) <= 0) {
+                            releaseProxyConnectionLocked(ctx, extension);
                         }
                     }
                 }
             }
         }
 
-        public void initializeSession(IInitializeSessionCallback cb) throws RemoteException {
+        public void initializeSession(IInitializeSessionCallback cb, int extension)
+                throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    mProxy.initializeSession(cb);
+                if (mConnectionManager.getProxy(extension) != null
+                        && !mConnectionManager.isSessionInitialized()) {
+                    mConnectionManager.getProxy(extension).initializeSession(cb);
+                    mConnectionManager.setSessionInitialized(true);
+                } else {
+                    cb.onFailure();
                 }
             }
         }
 
-        public void releaseSession() {
+        public void releaseSession(int extension) {
             synchronized (mLock) {
-                if (mProxy != null) {
+                if (mConnectionManager.getProxy(extension) != null) {
                     try {
-                        mProxy.releaseSession();
+                        mConnectionManager.getProxy(extension).releaseSession();
+                        mConnectionManager.setSessionInitialized(false);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to release session! Extension service does"
                                 + " not respond!");
@@ -432,77 +498,157 @@
             }
         }
 
-        public boolean areAdvancedExtensionsSupported() {
-            return mSupportsAdvancedExtensions;
+        public boolean areAdvancedExtensionsSupported(int extension) {
+            return mConnectionManager.areAdvancedExtensionsSupported(extension);
         }
 
-        public IPreviewExtenderImpl initializePreviewExtension(int extensionType)
+        public IPreviewExtenderImpl initializePreviewExtension(int extension)
                 throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    return mProxy.initializePreviewExtension(extensionType);
+                if (mConnectionManager.getProxy(extension) != null) {
+                    return mConnectionManager.getProxy(extension)
+                            .initializePreviewExtension(extension);
                 } else {
                     return null;
                 }
             }
         }
 
-        public IImageCaptureExtenderImpl initializeImageExtension(int extensionType)
+        public IImageCaptureExtenderImpl initializeImageExtension(int extension)
                 throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    return mProxy.initializeImageExtension(extensionType);
+                if (mConnectionManager.getProxy(extension) != null) {
+                    return mConnectionManager.getProxy(extension)
+                            .initializeImageExtension(extension);
                 } else {
                     return null;
                 }
             }
         }
 
-        public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
+        public IAdvancedExtenderImpl initializeAdvancedExtension(int extension)
                 throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    return mProxy.initializeAdvancedExtension(extensionType);
+                if (mConnectionManager.getProxy(extension) != null) {
+                    return mConnectionManager.getProxy(extension)
+                            .initializeAdvancedExtension(extension);
                 } else {
                     return null;
                 }
             }
         }
+
+        private class ExtensionConnectionManager {
+            // Maps extension to ExtensionConnection
+            private Map<Integer, ExtensionConnection> mConnections = new HashMap<>();
+            private boolean mSessionInitialized = false;
+
+            public ExtensionConnectionManager() {
+                IntArray extensionList = new IntArray(EXTENSION_LIST.length);
+                extensionList.addAll(EXTENSION_LIST);
+                if (Flags.concertMode()) {
+                    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) {
-        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
+    public static boolean registerClient(Context ctx, IBinder token, int extension,
+            String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token, extension, cameraId,
+                characteristicsMapNative);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(Context ctx, IBinder token) {
-        CameraExtensionManagerGlobal.get().unregisterClient(ctx, token);
+    public static void unregisterClient(Context ctx, IBinder token, int extension) {
+        CameraExtensionManagerGlobal.get().unregisterClient(ctx, token, extension);
     }
 
     /**
      * @hide
      */
-    public static void initializeSession(IInitializeSessionCallback cb) throws RemoteException {
-        CameraExtensionManagerGlobal.get().initializeSession(cb);
+    public static void initializeSession(IInitializeSessionCallback cb, int extension)
+            throws RemoteException {
+        CameraExtensionManagerGlobal.get().initializeSession(cb, extension);
     }
 
     /**
      * @hide
      */
-    public static void releaseSession() {
-        CameraExtensionManagerGlobal.get().releaseSession();
+    public static void releaseSession(int extension) {
+        CameraExtensionManagerGlobal.get().releaseSession(extension);
     }
 
     /**
      * @hide
      */
-    public static boolean areAdvancedExtensionsSupported() {
-        return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported();
+    public static boolean areAdvancedExtensionsSupported(int extension) {
+        return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported(extension);
     }
 
     /**
@@ -510,7 +656,7 @@
      */
     public static boolean isExtensionSupported(String cameraId, int extensionType,
             Map<String, CameraMetadataNative> characteristicsMap) {
-        if (areAdvancedExtensionsSupported()) {
+        if (areAdvancedExtensionsSupported(extensionType)) {
             try {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType);
                 return extender.isExtensionAvailable(cameraId, characteristicsMap);
@@ -600,24 +746,24 @@
     public @NonNull List<Integer> getSupportedExtensions() {
         ArrayList<Integer> ret = new ArrayList<>();
         final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
-        boolean success = registerClient(mContext, token);
-        if (!success) {
-            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 : extensionList.toArray()) {
-                if (isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) {
+
+        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);
             }
-        } finally {
-            unregisterClient(mContext, token);
         }
 
         return Collections.unmodifiableList(ret);
@@ -643,7 +789,8 @@
     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);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -653,7 +800,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported() && getKeys(extension).contains(key)) {
+            if (areAdvancedExtensionsSupported(extension) && getKeys(extension).contains(key)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 CameraMetadataNative metadata =
@@ -670,7 +817,7 @@
             Log.e(TAG, "Failed to query the extension for the specified key! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
         return null;
     }
@@ -691,7 +838,8 @@
     public @NonNull Set<CameraCharacteristics.Key> getKeys(@Extension int extension) {
         final IBinder token =
                 new Binder(TAG + "#getKeys:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -703,7 +851,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 CameraMetadataNative metadata =
@@ -732,7 +880,7 @@
             Log.e(TAG, "Failed to query the extension for all available keys! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
         return Collections.unmodifiableSet(ret);
     }
@@ -755,7 +903,8 @@
      */
     public boolean isPostviewAvailable(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -765,7 +914,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 return extender.isPostviewAvailable();
@@ -779,7 +928,7 @@
             Log.e(TAG, "Failed to query the extension for postview availability! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return false;
@@ -813,7 +962,8 @@
     public List<Size> getPostviewSupportedSizes(@Extension int extension,
             @NonNull Size captureSize, int format) {
         final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -831,7 +981,7 @@
             StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 switch(format) {
                     case ImageFormat.YUV_420_888:
                     case ImageFormat.JPEG:
@@ -879,7 +1029,7 @@
                     + "service does not respond!");
             return Collections.emptyList();
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
     }
 
@@ -917,7 +1067,8 @@
         //       ambiguity is resolved in b/169799538.
 
         final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -929,7 +1080,7 @@
 
             StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 return generateSupportedSizes(
@@ -948,7 +1099,7 @@
                     + " not respond!");
             return new ArrayList<>();
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
     }
 
@@ -978,7 +1129,8 @@
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
         try {
             final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
-            boolean success = registerClient(mContext, token);
+            boolean success = registerClient(mContext, token, extension, mCameraId,
+                    mCharacteristicsMapNative);
             if (!success) {
                 throw new IllegalArgumentException("Unsupported extensions");
             }
@@ -990,7 +1142,7 @@
 
                 StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-                if (areAdvancedExtensionsSupported()) {
+                if (areAdvancedExtensionsSupported(extension)) {
                     switch(format) {
                         case ImageFormat.YUV_420_888:
                         case ImageFormat.JPEG:
@@ -1035,7 +1187,7 @@
                     }
                 }
             } finally {
-                unregisterClient(mContext, token);
+                unregisterClient(mContext, token, extension);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -1073,7 +1225,8 @@
         }
 
         final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1087,7 +1240,7 @@
                     new android.hardware.camera2.extension.Size();
             sz.width = captureOutputSize.getWidth();
             sz.height = captureOutputSize.getHeight();
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId,
@@ -1126,7 +1279,7 @@
             Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return null;
@@ -1143,7 +1296,8 @@
      */
     public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1153,7 +1307,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 return extender.isCaptureProcessProgressAvailable();
@@ -1167,7 +1321,7 @@
             Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return false;
@@ -1195,7 +1349,8 @@
     @NonNull
     public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1208,7 +1363,7 @@
             }
 
             CameraMetadataNative captureRequestMeta = null;
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 captureRequestMeta = extender.getAvailableCaptureRequestKeys(mCameraId);
@@ -1250,7 +1405,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture request keys!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return Collections.unmodifiableSet(ret);
@@ -1282,7 +1437,8 @@
     @NonNull
     public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1294,7 +1450,7 @@
             }
 
             CameraMetadataNative captureResultMeta = null;
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 captureResultMeta = extender.getAvailableCaptureResultKeys(mCameraId);
@@ -1336,7 +1492,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture result keys!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index f6c8f36..b2032fa 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -102,6 +102,8 @@
 
     private boolean mInitialized;
     private boolean mSessionClosed;
+    private int mExtensionType;
+
 
     private final Context mContext;
 
@@ -205,7 +207,7 @@
         CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
                 extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
                 burstCaptureSurface, postviewSurface, config.getStateCallback(),
-                config.getExecutor(), sessionId, token);
+                config.getExecutor(), sessionId, token, config.getExtension());
 
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
         ret.mStatsAggregator.setExtensionType(config.getExtension());
@@ -223,7 +225,8 @@
             @Nullable Surface postviewSurface,
             @NonNull StateCallback callback, @NonNull Executor executor,
             int sessionId,
-            @NonNull IBinder token) {
+            @NonNull IBinder token,
+            int extension) {
         mContext = ctx;
         mAdvancedExtender = extender;
         mCameraDevice = cameraDevice;
@@ -242,6 +245,7 @@
         mSessionId = sessionId;
         mToken = token;
         mInterfaceLock = cameraDevice.mInterfaceLock;
+        mExtensionType = extension;
 
         mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
                 /*isAdvanced=*/true);
@@ -583,9 +587,9 @@
             if (mToken != null) {
                 if (mInitialized || (mCaptureSession != null)) {
                     notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
+                    CameraExtensionCharacteristics.releaseSession(mExtensionType);
                 }
-                CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
+                CameraExtensionCharacteristics.unregisterClient(mContext, mToken, mExtensionType);
             }
             mInitialized = false;
             mToken = null;
@@ -654,7 +658,8 @@
             }
 
             try {
-                CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
+                CameraExtensionCharacteristics.initializeSession(
+                        mInitializeHandler, mExtensionType);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to initialize session! Extension service does"
                         + " not respond!");
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ccb24e7..f03876b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -2559,13 +2559,16 @@
         boolean initializationFailed = true;
         IBinder token = new Binder(TAG + " : " + mNextSessionId++);
         try {
-            boolean ret = CameraExtensionCharacteristics.registerClient(mContext, token);
+            boolean ret = CameraExtensionCharacteristics.registerClient(mContext, token,
+                    extensionConfiguration.getExtension(), mCameraId,
+                    CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap));
             if (!ret) {
                 token = null;
                 throw new UnsupportedOperationException("Unsupported extension!");
             }
 
-            if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported()) {
+            if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported(
+                        extensionConfiguration.getExtension())) {
                 mCurrentAdvancedExtensionSession =
                         CameraAdvancedExtensionSessionImpl.createCameraAdvancedExtensionSession(
                                 this, characteristicsMap, mContext, extensionConfiguration,
@@ -2580,7 +2583,8 @@
             throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
         } finally {
             if (initializationFailed && (token != null)) {
-                CameraExtensionCharacteristics.unregisterClient(mContext, token);
+                CameraExtensionCharacteristics.unregisterClient(mContext, token,
+                        extensionConfiguration.getExtension());
             }
         }
     }
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index db7055b..725b413 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -118,6 +118,7 @@
     // In case the client doesn't explicitly enable repeating requests, the framework
     // will do so internally.
     private boolean mInternalRepeatingRequestEnabled = true;
+    private int mExtensionType;
 
     private final Context mContext;
 
@@ -244,7 +245,8 @@
                 sessionId,
                 token,
                 extensionChars.getAvailableCaptureRequestKeys(config.getExtension()),
-                extensionChars.getAvailableCaptureResultKeys(config.getExtension()));
+                extensionChars.getAvailableCaptureResultKeys(config.getExtension()),
+                config.getExtension());
 
         session.mStatsAggregator.setClientName(ctx.getOpPackageName());
         session.mStatsAggregator.setExtensionType(config.getExtension());
@@ -266,7 +268,8 @@
             int sessionId,
             @NonNull IBinder token,
             @NonNull Set<CaptureRequest.Key> requestKeys,
-            @Nullable Set<CaptureResult.Key> resultKeys) {
+            @Nullable Set<CaptureResult.Key> resultKeys,
+            int extension) {
         mContext = ctx;
         mImageExtender = imageExtender;
         mPreviewExtender = previewExtender;
@@ -289,6 +292,7 @@
         mSupportedResultKeys = resultKeys;
         mCaptureResultsSupported = !resultKeys.isEmpty();
         mInterfaceLock = cameraDevice.mInterfaceLock;
+        mExtensionType = extension;
 
         mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
                 /*isAdvanced=*/false);
@@ -881,9 +885,9 @@
             if (mToken != null) {
                 if (mInitialized || (mCaptureSession != null)) {
                     notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
+                    CameraExtensionCharacteristics.releaseSession(mExtensionType);
                 }
-                CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
+                CameraExtensionCharacteristics.unregisterClient(mContext, mToken, mExtensionType);
             }
             mInitialized = false;
             mToken = null;
@@ -1000,7 +1004,8 @@
                 mStatsAggregator.commit(/*isFinal*/false);
                 try {
                     finishPipelineInitialization();
-                    CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
+                    CameraExtensionCharacteristics.initializeSession(
+                            mInitializeHandler, mExtensionType);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to initialize session! Extension service does"
                             + " not respond!");
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 95edb61..3f4a7228 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11833,6 +11833,7 @@
          * Whether to enable camera extensions software fallback.
          * @hide
          */
+        @Readable
         public static final String CAMERA_EXTENSIONS_FALLBACK = "camera_extensions_fallback";
 
         /**
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d4e727e..c6bc589 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2141,6 +2141,12 @@
         <item>com.android.location.fused</item>
     </string-array>
 
+   <!-- Package name of the extension software fallback. -->
+    <string name="config_extensionFallbackPackageName" translatable="false"></string>
+
+   <!-- Service name of the extension software fallback. -->
+    <string name="config_extensionFallbackServiceName" translatable="false"></string>
+
     <!-- Package name(s) of Advanced Driver Assistance applications. These packages have additional
     management of access to location, specific to driving assistance use-cases. They must be system
     packages. This configuration is only applicable to devices that declare
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 58427b1..e232346 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2153,6 +2153,8 @@
   <java-symbol type="string" name="config_systemImageEditor" />
   <java-symbol type="string" name="config_datause_iface" />
   <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
+  <java-symbol type="string" name="config_extensionFallbackPackageName" />
+  <java-symbol type="string" name="config_extensionFallbackServiceName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
   <java-symbol type="string" name="config_gnssLocationProviderPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />