diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 5b259f71feda0..5cfba3d945cda 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -25,6 +25,7 @@ import android.graphics.ImageFormat; 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; @@ -357,6 +358,27 @@ public final class CameraExtensionCharacteristics { } } + public void initializeSession(IInitializeSessionCallback cb) throws RemoteException { + synchronized (mLock) { + if (mProxy != null) { + mProxy.initializeSession(cb); + } + } + } + + public void releaseSession() { + synchronized (mLock) { + if (mProxy != null) { + try { + mProxy.releaseSession(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to release session! Extension service does" + + " not respond!"); + } + } + } + } + public boolean areAdvancedExtensionsSupported() { return mSupportsAdvancedExtensions; } @@ -409,6 +431,20 @@ public final class CameraExtensionCharacteristics { CameraExtensionManagerGlobal.get().unregisterClient(clientId); } + /** + * @hide + */ + public static void initializeSession(IInitializeSessionCallback cb) throws RemoteException { + CameraExtensionManagerGlobal.get().initializeSession(cb); + } + + /** + * @hide + */ + public static void releaseSession() { + CameraExtensionManagerGlobal.get().releaseSession(); + } + /** * @hide */ diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java index e1b817768461a..5892f682dd494 100644 --- a/core/java/android/hardware/camera2/CameraExtensionSession.java +++ b/core/java/android/hardware/camera2/CameraExtensionSession.java @@ -195,8 +195,9 @@ public abstract class CameraExtensionSession implements AutoCloseable { * This method is called if the session cannot be configured as requested. * *

This can happen if the set of requested outputs contains unsupported sizes, - * too many outputs are requested at once or the camera device encounters an - * unrecoverable error during configuration.

+ * too many outputs are requested at once or when trying to initialize multiple + * concurrent extension sessions from two (or more) separate camera devices + * or the camera device encounters an unrecoverable error during configuration.

* *

The session is considered to be closed, and all methods called on it after this * callback is invoked will throw an IllegalStateException.

diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index a1c8d29566bee..5833b3dbef869 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -162,6 +162,9 @@ public final class CameraManager { *

The set of combinations may include camera devices that may be in use by other camera API * clients.

* + *

Concurrent camera extension sessions {@link CameraExtensionSession} are not currently + * supported.

+ * *

The set of combinations doesn't contain physical cameras that can only be used as * part of a logical multi-camera device.

* diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl index bc29e9a481aed..b52c650086f4a 100644 --- a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl +++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl @@ -18,6 +18,7 @@ package android.hardware.camera2.extension; import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; +import android.hardware.camera2.extension.IInitializeSessionCallback; /** @hide */ interface ICameraExtensionsProxyService @@ -25,6 +26,8 @@ interface ICameraExtensionsProxyService long registerClient(); void unregisterClient(long clientId); boolean advancedExtensionsSupported(); + void initializeSession(in IInitializeSessionCallback cb); + void releaseSession(); @nullable IPreviewExtenderImpl initializePreviewExtension(int extensionType); @nullable IImageCaptureExtenderImpl initializeImageExtension(int extensionType); @nullable IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType); diff --git a/core/java/android/hardware/camera2/extension/IInitializeSessionCallback.aidl b/core/java/android/hardware/camera2/extension/IInitializeSessionCallback.aidl new file mode 100644 index 0000000000000..1747760d7d8cc --- /dev/null +++ b/core/java/android/hardware/camera2/extension/IInitializeSessionCallback.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.camera2.extension; + +/** @hide */ +interface IInitializeSessionCallback +{ + void onSuccess(); + void onFailure(); +} diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 5cf50a25de78c..bfc1f2765c3b9 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -35,9 +35,11 @@ import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.extension.CameraOutputConfig; import android.hardware.camera2.extension.CameraSessionConfig; +import android.hardware.camera2.extension.CaptureStageImpl; import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.ICaptureCallback; import android.hardware.camera2.extension.IImageProcessorImpl; +import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.IRequestCallback; import android.hardware.camera2.extension.IRequestProcessorImpl; import android.hardware.camera2.extension.ISessionProcessorImpl; @@ -89,6 +91,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private Surface mClientCaptureSurface; private CameraCaptureSession mCaptureSession = null; private ISessionProcessorImpl mSessionProcessor = null; + private final InitializeSessionHandler mInitializeHandler; private boolean mInitialized; @@ -181,6 +184,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mInitialized = false; + mInitializeHandler = new InitializeSessionHandler(); } /** @@ -444,7 +448,6 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes public void release() { synchronized (mInterfaceLock) { - mInitialized = false; mHandlerThread.quitSafely(); if (mSessionProcessor != null) { @@ -459,7 +462,11 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes if (mExtensionClientId >= 0) { CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); + if (mInitialized) { + CameraExtensionCharacteristics.releaseSession(); + } } + mInitialized = false; for (ImageReader reader : mReaderMap.values()) { reader.close(); @@ -512,9 +519,24 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @Override public void onConfigured(@NonNull CameraCaptureSession session) { - boolean status = true; synchronized (mInterfaceLock) { mCaptureSession = session; + try { + CameraExtensionCharacteristics.initializeSession(mInitializeHandler); + } catch (RemoteException e) { + Log.e(TAG, "Failed to initialize session! Extension service does" + + " not respond!"); + notifyConfigurationFailure(); + } + } + } + } + + private class InitializeSessionHandler extends IInitializeSessionCallback.Stub { + @Override + public void onSuccess() { + boolean status = true; + synchronized (mInterfaceLock) { try { mSessionProcessor.onCaptureSessionStart(mRequestProcessor); mInitialized = true; @@ -522,7 +544,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes Log.e(TAG, "Failed to start capture session," + " extension service does not respond!"); status = false; - session.close(); + mCaptureSession.close(); } } @@ -538,6 +560,15 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes notifyConfigurationFailure(); } } + + @Override + public void onFailure() { + mCaptureSession.close(); + Log.e(TAG, "Failed to initialize proxy service session!" + + " This can happen when trying to configure multiple " + + "concurrent extension sessions!"); + notifyConfigurationFailure(); + } } private final class RequestCallbackHandler extends ICaptureCallback.Stub { diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 4bcc4942d8a05..537b894d9a6a6 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -34,6 +34,7 @@ import android.hardware.camera2.extension.CaptureBundle; import android.hardware.camera2.extension.CaptureStageImpl; import android.hardware.camera2.extension.ICaptureProcessorImpl; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; +import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; import android.hardware.camera2.extension.ParcelImage; @@ -48,6 +49,8 @@ import android.media.ImageWriter; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; +import android.os.IInterface; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.annotation.NonNull; @@ -80,6 +83,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private final HandlerThread mHandlerThread; private final StateCallback mCallbacks; private final List mSupportedPreviewSizes; + private final InitializeSessionHandler mInitializeHandler; private CameraCaptureSession mCaptureSession = null; private Surface mCameraRepeatingSurface, mClientRepeatingRequestSurface; @@ -216,6 +220,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mInitialized = false; + mInitializeHandler = new InitializeSessionHandler(); } private void initializeRepeatingRequestPipeline() throws RemoteException { @@ -621,7 +626,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { public void release() { synchronized (mInterfaceLock) { mInternalRepeatingRequestEnabled = false; - mInitialized = false; mHandlerThread.quitSafely(); try { @@ -634,7 +638,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { if (mExtensionClientId >= 0) { CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); + if (mInitialized) { + CameraExtensionCharacteristics.releaseSession(); + } } + mInitialized = false; if (mRepeatingRequestImageCallback != null) { mRepeatingRequestImageCallback.close(); @@ -739,36 +747,63 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @Override public void onConfigured(@NonNull CameraCaptureSession session) { - boolean status = true; synchronized (mInterfaceLock) { mCaptureSession = session; - - ArrayList initialRequestList = compileInitialRequestList(); - if (!initialRequestList.isEmpty()) { - try { - setInitialCaptureRequest(initialRequestList, - new InitialRequestHandler(mRepeatingRequestImageCallback)); - } catch (CameraAccessException e) { - Log.e(TAG, "Failed to initialize the initial capture request!"); - status = false; - } - } else { - try { - setRepeatingRequest(mPreviewExtender.getCaptureStage(), - new RepeatingRequestHandler(null, null, null, - mRepeatingRequestImageCallback)); - } catch (CameraAccessException | RemoteException e) { - Log.e(TAG, "Failed to initialize internal repeating request!"); - status = false; - } - + try { + CameraExtensionCharacteristics.initializeSession(mInitializeHandler); + } catch (RemoteException e) { + Log.e(TAG, "Failed to initialize session! Extension service does" + + " not respond!"); + notifyConfigurationFailure(); } } + } + } + + private class InitializeSessionHandler extends IInitializeSessionCallback.Stub { + @Override + public void onSuccess() { + boolean status = true; + ArrayList initialRequestList = + compileInitialRequestList(); + if (!initialRequestList.isEmpty()) { + try { + setInitialCaptureRequest(initialRequestList, + new InitialRequestHandler( + mRepeatingRequestImageCallback)); + } catch (CameraAccessException e) { + Log.e(TAG, + "Failed to initialize the initial capture " + + "request!"); + status = false; + } + } else { + try { + setRepeatingRequest(mPreviewExtender.getCaptureStage(), + new RepeatingRequestHandler(null, null, null, + mRepeatingRequestImageCallback)); + } catch (CameraAccessException | RemoteException e) { + Log.e(TAG, + "Failed to initialize internal repeating " + + "request!"); + status = false; + } + + } if (!status) { notifyConfigurationFailure(); } } + + @Override + public void onFailure() { + mCaptureSession.close(); + Log.e(TAG, "Failed to initialize proxy service session!" + + " This can happen when trying to configure multiple " + + "concurrent extension sessions!"); + notifyConfigurationFailure(); + } } private class BurstRequestHandler extends CameraCaptureSession.CaptureCallback { diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 69874f8a305a3..ab3643c2a911c 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -18,6 +18,7 @@ package com.android.cameraextensions; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.GraphicBuffer; import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; @@ -42,6 +43,7 @@ import android.hardware.camera2.extension.IRequestProcessorImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; import android.hardware.camera2.extension.IImageProcessorImpl; +import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.ISessionProcessorImpl; import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.OutputConfigId; @@ -57,6 +59,7 @@ import android.hardware.HardwareBuffer; import android.hardware.camera2.impl.PhysicalCaptureResultInfo; import android.media.Image; import android.media.ImageReader; +import android.os.Binder; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerExecutor; @@ -176,6 +179,7 @@ public class CameraExtensionsProxyService extends Service { private long mCurrentClientCount = 0; private ArraySet mActiveClients = new ArraySet<>(); + private IInitializeSessionCallback mInitializeCb = null; // Singleton instance private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER = @@ -328,6 +332,40 @@ public class CameraExtensionsProxyService extends Service { } } } + + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + mInitializeCb = null; + } + } + }; + + public boolean initializeSession(IInitializeSessionCallback cb) { + synchronized (mLock) { + if (mInitializeCb == null) { + mInitializeCb = cb; + try { + mInitializeCb.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else { + return false; + } + } + return true; + } + + public void releaseSession() { + synchronized (mLock) { + if (mInitializeCb != null) { + mInitializeCb.asBinder().unlinkToDeath(mDeathRecipient, 0); + mInitializeCb = null; + } + } + } } /** @@ -350,6 +388,26 @@ public class CameraExtensionsProxyService extends Service { CameraExtensionManagerGlobal.get().unregisterClient(clientId); } + /** + * @hide + */ + public static boolean initializeSession(IInitializeSessionCallback cb) { + if (!EXTENSIONS_PRESENT) { + return false; + } + return CameraExtensionManagerGlobal.get().initializeSession(cb); + } + + /** + * @hide + */ + public static void releaseSession() { + if (!EXTENSIONS_PRESENT) { + return; + } + CameraExtensionManagerGlobal.get().releaseSession(); + } + /** * @hide */ @@ -538,6 +596,39 @@ public class CameraExtensionsProxyService extends Service { CameraExtensionsProxyService.unregisterClient(clientId); } + private boolean checkCameraPermission() { + int allowed = CameraExtensionsProxyService.this.checkPermission( + android.Manifest.permission.CAMERA, Binder.getCallingPid(), + Binder.getCallingUid()); + return (PackageManager.PERMISSION_GRANTED == allowed); + } + + @Override + public void initializeSession(IInitializeSessionCallback cb) { + try { + if (!checkCameraPermission()) { + Log.i(TAG, "Camera permission required for initializing capture session"); + cb.onFailure(); + return; + } + + if (CameraExtensionsProxyService.initializeSession(cb)) { + cb.onSuccess(); + } else { + cb.onFailure(); + } + } catch (RemoteException e) { + Log.e(TAG, "Client doesn't respond!"); + } + } + + @Override + public void releaseSession() { + if (checkCameraPermission()) { + CameraExtensionsProxyService.releaseSession(); + } + } + @Override public boolean advancedExtensionsSupported() { return ADVANCED_API_SUPPORTED;