Camera: Disallow concurrent extension sessions

Support for concurrent extension processing sessions
is not yet mandated. To avoid inconsistent behavior
across devices, explicitly document and disallow
concurrent extension sessions at the framework level.

Bug: 187341271
Test: Manual using test application,
Camera CTS

Change-Id: Ibff194c58bbb37e884c27151a2a27fb21e3db928
This commit is contained in:
Emilian Peev
2021-04-28 15:40:19 -07:00
parent c46c9e644f
commit 876e6afd6c
8 changed files with 250 additions and 27 deletions

View File

@@ -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
*/

View File

@@ -195,8 +195,9 @@ public abstract class CameraExtensionSession implements AutoCloseable {
* This method is called if the session cannot be configured as requested.
*
* <p>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.</p>
* 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.</p>
*
* <p>The session is considered to be closed, and all methods called on it after this
* callback is invoked will throw an IllegalStateException.</p>

View File

@@ -162,6 +162,9 @@ public final class CameraManager {
* <p>The set of combinations may include camera devices that may be in use by other camera API
* clients.</p>
*
* <p>Concurrent camera extension sessions {@link CameraExtensionSession} are not currently
* supported.</p>
*
* <p>The set of combinations doesn't contain physical cameras that can only be used as
* part of a logical multi-camera device.</p>
*

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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<Size> 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<CaptureStageImpl> 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<CaptureStageImpl> 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 {

View File

@@ -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<Long> 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;