RequestMonitor.java
/*
* Copyright 2023 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 androidx.camera.camera2.internal.compat.workaround;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.internal.Camera2CaptureCallbacks;
import androidx.camera.camera2.internal.compat.quirk.CaptureNoResponseQuirk;
import androidx.camera.camera2.internal.compat.quirk.CaptureSessionStuckQuirk;
import androidx.camera.camera2.internal.compat.quirk.IncorrectCaptureStateQuirk;
import androidx.camera.core.impl.annotation.ExecutedBy;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
/**
* Monitors in-flight capture sequences on devices with specific quirks.
*
* <p>Quirks on Certain Devices:
* <p>Some devices may fail to configure new CameraCaptureSessions
* if existing in-flight capture sequences haven't completed. This class helps you work around
* these issues.
* <p>Single capture requests may not receive a response if they are submitted
* simultaneously with repeating capture requests. Single capture requests fail to receive a
* response approximately 10% of the time when submitted within milliseconds of a repeating
* capture request.
*
* <p>How it works: Use `RequestMonitor#getRequestsProcessedFuture()` to get a ListenableFuture.
* This future signals when all in-flight capture sequences have been processed.
*
* @see CaptureNoResponseQuirk
* @see CaptureSessionStuckQuirk
* @see IncorrectCaptureStateQuirk
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class RequestMonitor {
private static final String TAG = "RequestMonitor";
private final boolean mQuirkEnabled;
private final List<ListenableFuture<Void>> mRequestTasks =
Collections.synchronizedList(new ArrayList<>());
/** Constructor of the RequestMonitor */
public RequestMonitor(boolean quirkEnabled) {
mQuirkEnabled = quirkEnabled;
}
/**
* Indicates whether capture sequence monitoring is enabled.
*
* <p>Returns true if a quirk is enabled that necessitates tracking in-flight capture requests.
* Returns false otherwise.
*/
public boolean shouldMonitorRequest() {
return mQuirkEnabled;
}
/**
* Returns a ListenableFuture that indicates whether all capture requests have been
* processed.
*/
@ExecutedBy("mExecutor")
@NonNull
public ListenableFuture<Void> getRequestsProcessedFuture() {
if (mRequestTasks.isEmpty()) {
return Futures.immediateFuture(null);
}
return Futures.nonCancellationPropagating(
Futures.transform(Futures.successfulAsList(new ArrayList<>(mRequestTasks)),
input -> null, CameraXExecutors.directExecutor()));
}
/**
* Creates a listener that monitors request completion for the `RequestMonitor`.
*
* <p>This listener should be assigned to the CameraCaptureSession via
* the `setSingleRepeatingRequest` or `captureBurstRequests` method to track when submitted
* requests are fully processed.
* The `RequestMonitor` can then use this information to ensure proper capture sequence
* handling.
*
* <p>Note: the created listener wraps the provided `originalListener`, ensuring any original
* capture callbacks still function as intended.
*
* @param originalListener The original CaptureCallback to combine with monitoring
* functionality.
* @return A new CaptureCallback that includes request completion tracking for the
* `RequestMonitor`.
*/
@ExecutedBy("mExecutor")
@NonNull
public CameraCaptureSession.CaptureCallback createMonitorListener(
@NonNull CameraCaptureSession.CaptureCallback originalListener) {
if (shouldMonitorRequest()) {
return Camera2CaptureCallbacks.createComboCallback(createMonitorListener(),
originalListener);
} else {
return originalListener;
}
}
private CameraCaptureSession.CaptureCallback createMonitorListener() {
RequestCompleteListener completeListener = new RequestCompleteListener();
ListenableFuture<Void> future = completeListener.mStartRequestFuture;
mRequestTasks.add(future);
Log.d(TAG, "RequestListener " + completeListener + " monitoring " + this);
future.addListener(() -> {
Log.d(TAG, "RequestListener " + completeListener + " done " + this);
mRequestTasks.remove(future);
}, CameraXExecutors.directExecutor());
return completeListener;
}
/** This should be called when a SynchronizedCaptureSession is stopped or closed. */
@ExecutedBy("mExecutor")
public void stop() {
LinkedList<ListenableFuture<Void>> tasks = new LinkedList<>(mRequestTasks);
while (!tasks.isEmpty()) {
Objects.requireNonNull(tasks.poll()).cancel(true);
}
}
static class RequestCompleteListener extends CameraCaptureSession.CaptureCallback {
@NonNull
final ListenableFuture<Void> mStartRequestFuture;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
CallbackToFutureAdapter.Completer<Void> mStartRequestCompleter;
RequestCompleteListener() {
mStartRequestFuture = CallbackToFutureAdapter.getFuture(completer -> {
mStartRequestCompleter = completer;
return "RequestCompleteListener[" + this + "]";
});
}
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request, long timestamp, long frameNumber) {
completeFuture();
}
@Override
public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
int sequenceId) {
completeFuture();
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
completeFuture();
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
completeFuture();
}
@Override
public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session,
int sequenceId, long frameNumber) {
completeFuture();
}
private void completeFuture() {
if (mStartRequestCompleter != null) {
mStartRequestCompleter.set(null);
mStartRequestCompleter = null;
}
}
}
}