blob: 1d9d644c9306eaeb58a1a443a765b6ad1d8551dd [file] [log] [blame]
/*
* Copyright (C) 2019 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.impl;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraOfflineSession;
import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.Range;
import android.util.SparseArray;
import android.view.Surface;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Executor;
import static com.android.internal.util.Preconditions.*;
public class CameraOfflineSessionImpl extends CameraOfflineSession
implements IBinder.DeathRecipient {
private static final String TAG = "CameraOfflineSessionImpl";
private static final int REQUEST_ID_NONE = -1;
private static final long NANO_PER_SECOND = 1000000000; //ns
private final boolean DEBUG = false;
private ICameraOfflineSession mRemoteSession;
private final AtomicBoolean mClosing = new AtomicBoolean();
private SimpleEntry<Integer, InputConfiguration> mOfflineInput =
new SimpleEntry<>(REQUEST_ID_NONE, null);
private SparseArray<OutputConfiguration> mOfflineOutputs = new SparseArray<>();
private SparseArray<OutputConfiguration> mConfiguredOutputs = new SparseArray<>();
final Object mInterfaceLock = new Object(); // access from this class and Session only!
private final String mCameraId;
private final CameraCharacteristics mCharacteristics;
private final int mTotalPartialCount;
private final Executor mOfflineExecutor;
private final CameraOfflineSessionCallback mOfflineCallback;
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
/**
* A list tracking request and its expected last regular/reprocess/zslStill frame
* number.
*/
private List<RequestLastFrameNumbersHolder> mOfflineRequestLastFrameNumbersList =
new ArrayList<>();
/**
* An object tracking received frame numbers.
* Updated when receiving callbacks from ICameraDeviceCallbacks.
*/
private FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
/** map request IDs to callback/request data */
private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
new SparseArray<CaptureCallbackHolder>();
public CameraOfflineSessionImpl(String cameraId, CameraCharacteristics characteristics,
Executor offlineExecutor, CameraOfflineSessionCallback offlineCallback,
SparseArray<OutputConfiguration> offlineOutputs,
SimpleEntry<Integer, InputConfiguration> offlineInput,
SparseArray<OutputConfiguration> configuredOutputs,
FrameNumberTracker frameNumberTracker, SparseArray<CaptureCallbackHolder> callbackMap,
List<RequestLastFrameNumbersHolder> frameNumberList) {
if ((cameraId == null) || (characteristics == null)) {
throw new IllegalArgumentException("Null argument given");
}
mCameraId = cameraId;
mCharacteristics = characteristics;
Integer partialCount =
mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
if (partialCount == null) {
// 1 means partial result is not supported.
mTotalPartialCount = 1;
} else {
mTotalPartialCount = partialCount;
}
mOfflineRequestLastFrameNumbersList.addAll(frameNumberList);
mFrameNumberTracker = frameNumberTracker;
mCaptureCallbackMap = callbackMap;
mConfiguredOutputs = configuredOutputs;
mOfflineOutputs = offlineOutputs;
mOfflineInput = offlineInput;
mOfflineExecutor = checkNotNull(offlineExecutor, "offline executor must not be null");
mOfflineCallback = checkNotNull(offlineCallback, "offline callback must not be null");
}
public CameraDeviceCallbacks getCallbacks() {
return mCallbacks;
}
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
@Override
public IBinder asBinder() {
return this;
}
@Override
public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
synchronized(mInterfaceLock) {
switch (errorCode) {
case CameraDeviceCallbacks.ERROR_CAMERA_REQUEST:
case CameraDeviceCallbacks.ERROR_CAMERA_RESULT:
case CameraDeviceCallbacks.ERROR_CAMERA_BUFFER:
onCaptureErrorLocked(errorCode, resultExtras);
break;
default:
Runnable errorDispatch = new Runnable() {
@Override
public void run() {
if (!isClosed()) {
mOfflineCallback.onError(CameraOfflineSessionImpl.this,
CameraOfflineSessionCallback.STATUS_INTERNAL_ERROR);
}
}
};
final long ident = Binder.clearCallingIdentity();
try {
mOfflineExecutor.execute(errorDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
@Override
public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
Log.e(TAG, "Unexpected repeating request error received. Last frame number is " +
lastFrameNumber);
}
@Override
public void onDeviceIdle() {
synchronized(mInterfaceLock) {
if (mRemoteSession == null) {
Log.v(TAG, "Ignoring idle state notifications during offline switches");
return;
}
Runnable idleDispatch = new Runnable() {
@Override
public void run() {
if (!isClosed()) {
mOfflineCallback.onIdle(CameraOfflineSessionImpl.this);
}
}
};
final long ident = Binder.clearCallingIdentity();
try {
mOfflineExecutor.execute(idleDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
int requestId = resultExtras.getRequestId();
final long frameNumber = resultExtras.getFrameNumber();
final CaptureCallbackHolder holder;
synchronized(mInterfaceLock) {
// Get the callback for this frame ID, if there is one
holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
if (holder == null) {
return;
}
final Executor executor = holder.getCallback().getExecutor();
if (isClosed() || (executor == null)) return;
// Dispatch capture start notice
final long ident = Binder.clearCallingIdentity();
try {
executor.execute(
new Runnable() {
@Override
public void run() {
final CameraCaptureSession.CaptureCallback callback =
holder.getCallback().getSessionCallback();
if (!CameraOfflineSessionImpl.this.isClosed() &&
(callback != null)) {
final int subsequenceId = resultExtras.getSubsequenceId();
final CaptureRequest request = holder.getRequest(subsequenceId);
if (holder.hasBatchedOutputs()) {
// Send derived onCaptureStarted for requests within the
// batch
final Range<Integer> fpsRange =
request.get(
CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
for (int i = 0; i < holder.getRequestCount(); i++) {
final CaptureRequest cbRequest = holder.getRequest(i);
final long cbTimestamp =
timestamp - (subsequenceId - i) *
NANO_PER_SECOND/fpsRange.getUpper();
final long cbFrameNumber =
frameNumber - (subsequenceId - i);
callback.onCaptureStarted(CameraOfflineSessionImpl.this,
cbRequest, cbTimestamp, cbFrameNumber);
}
} else {
callback.onCaptureStarted(CameraOfflineSessionImpl.this,
holder.getRequest(
resultExtras.getSubsequenceId()),
timestamp, frameNumber);
}
}
}
});
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
public void onResultReceived(CameraMetadataNative result,
CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
throws RemoteException {
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
synchronized(mInterfaceLock) {
// TODO: Handle CameraCharacteristics access from CaptureResult correctly.
result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
mCharacteristics.get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
final CaptureCallbackHolder holder =
CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
boolean isPartialResult =
(resultExtras.getPartialResultCount() < mTotalPartialCount);
int requestType = request.getRequestType();
// Check if we have a callback for this
if (holder == null) {
mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
requestType);
return;
}
if (isClosed()) {
mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
requestType);
return;
}
Runnable resultDispatch = null;
CaptureResult finalResult;
// Make a copy of the native metadata before it gets moved to a CaptureResult
// object.
final CameraMetadataNative resultCopy;
if (holder.hasBatchedOutputs()) {
resultCopy = new CameraMetadataNative(result);
} else {
resultCopy = null;
}
final Executor executor = holder.getCallback().getExecutor();
// Either send a partial result or the final capture completed result
if (isPartialResult) {
final CaptureResult resultAsCapture =
new CaptureResult(result, request, resultExtras);
// Partial result
resultDispatch = new Runnable() {
@Override
public void run() {
final CameraCaptureSession.CaptureCallback callback =
holder.getCallback().getSessionCallback();
if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
if (holder.hasBatchedOutputs()) {
// Send derived onCaptureProgressed for requests within
// the batch.
for (int i = 0; i < holder.getRequestCount(); i++) {
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
final CaptureResult resultInBatch = new CaptureResult(
resultLocal, holder.getRequest(i), resultExtras);
final CaptureRequest cbRequest = holder.getRequest(i);
callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
cbRequest, resultInBatch);
}
} else {
callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
request, resultAsCapture);
}
}
}
};
finalResult = resultAsCapture;
} else {
List<CaptureResult> partialResults =
mFrameNumberTracker.popPartialResults(frameNumber);
final long sensorTimestamp =
result.get(CaptureResult.SENSOR_TIMESTAMP);
final Range<Integer> fpsRange =
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
final int subsequenceId = resultExtras.getSubsequenceId();
final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
request, resultExtras, partialResults, holder.getSessionId(),
physicalResults);
// Final capture result
resultDispatch = new Runnable() {
@Override
public void run() {
final CameraCaptureSession.CaptureCallback callback =
holder.getCallback().getSessionCallback();
if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
if (holder.hasBatchedOutputs()) {
// Send derived onCaptureCompleted for requests within
// the batch.
for (int i = 0; i < holder.getRequestCount(); i++) {
resultCopy.set(CaptureResult.SENSOR_TIMESTAMP,
sensorTimestamp - (subsequenceId - i) *
NANO_PER_SECOND/fpsRange.getUpper());
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
// No logical multi-camera support for batched output mode.
TotalCaptureResult resultInBatch = new TotalCaptureResult(
resultLocal, holder.getRequest(i), resultExtras,
partialResults, holder.getSessionId(),
new PhysicalCaptureResultInfo[0]);
final CaptureRequest cbRequest = holder.getRequest(i);
callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
cbRequest, resultInBatch);
}
} else {
callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
request, resultAsCapture);
}
}
}
};
finalResult = resultAsCapture;
}
if (executor != null) {
final long ident = Binder.clearCallingIdentity();
try {
executor.execute(resultDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// Collect the partials for a total result; or mark the frame as totally completed
mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
requestType);
// Fire onCaptureSequenceCompleted
if (!isPartialResult) {
checkAndFireSequenceComplete();
}
}
}
@Override
public void onPrepared(int streamId) {
Log.e(TAG, "Unexpected stream " + streamId + " is prepared");
}
@Override
public void onRequestQueueEmpty() {
// No-op during offline mode
Log.v(TAG, "onRequestQueueEmpty");
}
/**
* Called by onDeviceError for handling single-capture failures.
*/
private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
final int requestId = resultExtras.getRequestId();
final int subsequenceId = resultExtras.getSubsequenceId();
final long frameNumber = resultExtras.getFrameNumber();
final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
final CaptureCallbackHolder holder =
CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
if (holder == null) {
Log.e(TAG, String.format("Receive capture error on unknown request ID %d",
requestId));
return;
}
final CaptureRequest request = holder.getRequest(subsequenceId);
Runnable failureDispatch = null;
if (errorCode == ERROR_CAMERA_BUFFER) {
// Because 1 stream id could map to multiple surfaces, we need to specify both
// streamId and surfaceId.
OutputConfiguration config;
if ((mRemoteSession == null) && !isClosed()) {
config = mConfiguredOutputs.get(resultExtras.getErrorStreamId());
} else {
config = mOfflineOutputs.get(resultExtras.getErrorStreamId());
}
if (config == null) {
Log.v(TAG, String.format(
"Stream %d has been removed. Skipping buffer lost callback",
resultExtras.getErrorStreamId()));
return;
}
for (Surface surface : config.getSurfaces()) {
if (!request.containsTarget(surface)) {
continue;
}
final Executor executor = holder.getCallback().getExecutor();
failureDispatch = new Runnable() {
@Override
public void run() {
final CameraCaptureSession.CaptureCallback callback =
holder.getCallback().getSessionCallback();
if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
callback.onCaptureBufferLost( CameraOfflineSessionImpl.this,
request, surface, frameNumber);
}
}
};
if (executor != null) {
// Dispatch the failure callback
final long ident = Binder.clearCallingIdentity();
try {
executor.execute(failureDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
} else {
boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
int reason = CaptureFailure.REASON_ERROR;
final CaptureFailure failure = new CaptureFailure(
request,
reason,
/*dropped*/ mayHaveBuffers,
requestId,
frameNumber,
errorPhysicalCameraId);
final Executor executor = holder.getCallback().getExecutor();
failureDispatch = new Runnable() {
@Override
public void run() {
final CameraCaptureSession.CaptureCallback callback =
holder.getCallback().getSessionCallback();
if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
callback.onCaptureFailed(CameraOfflineSessionImpl.this, request,
failure);
}
}
};
// Fire onCaptureSequenceCompleted if appropriate
mFrameNumberTracker.updateTracker(frameNumber,
/*error*/true, request.getRequestType());
checkAndFireSequenceComplete();
if (executor != null) {
// Dispatch the failure callback
final long ident = Binder.clearCallingIdentity();
try {
executor.execute(failureDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
}
private void checkAndFireSequenceComplete() {
long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber();
Iterator<RequestLastFrameNumbersHolder> iter =
mOfflineRequestLastFrameNumbersList.iterator();
while (iter.hasNext()) {
final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
boolean sequenceCompleted = false;
final int requestId = requestLastFrameNumbers.getRequestId();
final CaptureCallbackHolder holder;
final Executor executor;
final CameraCaptureSession.CaptureCallback callback;
synchronized(mInterfaceLock) {
int index = mCaptureCallbackMap.indexOfKey(requestId);
holder = (index >= 0) ?
mCaptureCallbackMap.valueAt(index) : null;
if (holder != null) {
long lastRegularFrameNumber =
requestLastFrameNumbers.getLastRegularFrameNumber();
long lastReprocessFrameNumber =
requestLastFrameNumbers.getLastReprocessFrameNumber();
long lastZslStillFrameNumber =
requestLastFrameNumbers.getLastZslStillFrameNumber();
executor = holder.getCallback().getExecutor();
callback = holder.getCallback().getSessionCallback();
// check if it's okay to remove request from mCaptureCallbackMap
if (lastRegularFrameNumber <= completedFrameNumber
&& lastReprocessFrameNumber <= completedReprocessFrameNumber
&& lastZslStillFrameNumber <= completedZslStillFrameNumber) {
sequenceCompleted = true;
mCaptureCallbackMap.removeAt(index);
}
} else {
executor = null;
callback = null;
}
}
// If no callback is registered for this requestId or sequence completed, remove it
// from the frame number->request pair because it's not needed anymore.
if (holder == null || sequenceCompleted) {
iter.remove();
}
// Call onCaptureSequenceCompleted
if ((sequenceCompleted) && (callback != null) && (executor != null)) {
Runnable resultDispatch = new Runnable() {
@Override
public void run() {
if (!isClosed()) {
callback.onCaptureSequenceCompleted(CameraOfflineSessionImpl.this,
requestId, requestLastFrameNumbers.getLastFrameNumber());
}
}
};
final long ident = Binder.clearCallingIdentity();
try {
executor.execute(resultDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
if (mCaptureCallbackMap.size() == 0) {
getCallbacks().onDeviceIdle();
}
}
}
}
public void notifyFailedSwitch() {
synchronized(mInterfaceLock) {
Runnable switchFailDispatch = new Runnable() {
@Override
public void run() {
mOfflineCallback.onSwitchFailed(CameraOfflineSessionImpl.this);
}
};
final long ident = Binder.clearCallingIdentity();
try {
mOfflineExecutor.execute(switchFailDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
/**
* Set remote session.
*
*/
public void setRemoteSession(ICameraOfflineSession remoteSession) throws CameraAccessException {
synchronized(mInterfaceLock) {
if (remoteSession == null) {
notifyFailedSwitch();
return;
}
mRemoteSession = remoteSession;
IBinder remoteSessionBinder = remoteSession.asBinder();
if (remoteSessionBinder == null) {
throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
"The camera offline session has encountered a serious error");
}
try {
remoteSessionBinder.linkToDeath(this, /*flag*/ 0);
} catch (RemoteException e) {
throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
"The camera offline session has encountered a serious error");
}
Runnable readyDispatch = new Runnable() {
@Override
public void run() {
if (!isClosed()) {
mOfflineCallback.onReady(CameraOfflineSessionImpl.this);
}
}
};
final long ident = Binder.clearCallingIdentity();
try {
mOfflineExecutor.execute(readyDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
/** Whether the offline session has started to close (may not yet have finished) */
private boolean isClosed() {
return mClosing.get();
}
private void disconnect() {
synchronized (mInterfaceLock) {
if (mClosing.getAndSet(true)) {
return;
}
if (mRemoteSession != null) {
mRemoteSession.asBinder().unlinkToDeath(this, /*flags*/0);
try {
mRemoteSession.disconnect();
} catch (RemoteException e) {
Log.e(TAG, "Exception while disconnecting from offline session: ", e);
}
} else {
throw new IllegalStateException("Offline session is not yet ready");
}
mRemoteSession = null;
Runnable closeDispatch = new Runnable() {
@Override
public void run() {
mOfflineCallback.onClosed(CameraOfflineSessionImpl.this);
}
};
final long ident = Binder.clearCallingIdentity();
try {
mOfflineExecutor.execute(closeDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
protected void finalize() throws Throwable {
try {
disconnect();
}
finally {
super.finalize();
}
}
/**
* Listener for binder death.
*
* <p> Handle binder death for ICameraOfflineSession.</p>
*/
@Override
public void binderDied() {
Log.w(TAG, "CameraOfflineSession on device " + mCameraId + " died unexpectedly");
disconnect();
}
@Override
public CameraDevice getDevice() {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void prepare(Surface surface) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void prepare(int maxCount, Surface surface) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void tearDown(Surface surface) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void finalizeOutputConfigurations(
List<OutputConfiguration> outputConfigs) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int capture(CaptureRequest request, CaptureCallback callback,
Handler handler) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int captureSingleRequest(CaptureRequest request, Executor executor,
CaptureCallback callback) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
Handler handler) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int captureBurstRequests(List<CaptureRequest> requests, Executor executor,
CaptureCallback callback) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
Handler handler) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int setSingleRepeatingRequest(CaptureRequest request, Executor executor,
CaptureCallback callback) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int setRepeatingBurst(List<CaptureRequest> requests,
CaptureCallback callback, Handler handler) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public int setRepeatingBurstRequests(List<CaptureRequest> requests, Executor executor,
CaptureCallback callback) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void stopRepeating() throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void abortCaptures() throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void updateOutputConfiguration(OutputConfiguration config)
throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public boolean isReprocessable() {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public Surface getInputSurface() {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public CameraOfflineSession switchToOffline(Collection<Surface> offlineOutputs,
Executor executor, CameraOfflineSessionCallback listener) throws CameraAccessException {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public boolean supportsOfflineProcessing(Surface surface) {
throw new UnsupportedOperationException("Operation not supported in offline mode");
}
@Override
public void close() {
disconnect();
}
}