1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.camera2.internal.compat.workaround; 18 19 import android.hardware.camera2.CameraCaptureSession; 20 import android.hardware.camera2.CaptureFailure; 21 import android.hardware.camera2.CaptureRequest; 22 import android.hardware.camera2.TotalCaptureResult; 23 import android.util.Log; 24 25 import androidx.camera.camera2.internal.Camera2CaptureCallbacks; 26 import androidx.camera.camera2.internal.compat.quirk.CaptureNoResponseQuirk; 27 import androidx.camera.camera2.internal.compat.quirk.CaptureSessionStuckQuirk; 28 import androidx.camera.camera2.internal.compat.quirk.IncorrectCaptureStateQuirk; 29 import androidx.camera.core.impl.annotation.ExecutedBy; 30 import androidx.camera.core.impl.utils.executor.CameraXExecutors; 31 import androidx.camera.core.impl.utils.futures.Futures; 32 import androidx.concurrent.futures.CallbackToFutureAdapter; 33 34 import com.google.common.util.concurrent.ListenableFuture; 35 36 import org.jspecify.annotations.NonNull; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.LinkedList; 41 import java.util.List; 42 import java.util.Objects; 43 44 /** 45 * Monitors in-flight capture sequences on devices with specific quirks. 46 * 47 * <p>Quirks on Certain Devices: 48 * <p>Some devices may fail to configure new CameraCaptureSessions 49 * if existing in-flight capture sequences haven't completed. This class helps you work around 50 * these issues. 51 * <p>Single capture requests may not receive a response if they are submitted 52 * simultaneously with repeating capture requests. Single capture requests fail to receive a 53 * response approximately 10% of the time when submitted within milliseconds of a repeating 54 * capture request. 55 * 56 * <p>How it works: Use `RequestMonitor#getRequestsProcessedFuture()` to get a ListenableFuture. 57 * This future signals when all in-flight capture sequences have been processed. 58 * 59 * @see CaptureNoResponseQuirk 60 * @see CaptureSessionStuckQuirk 61 * @see IncorrectCaptureStateQuirk 62 */ 63 public class RequestMonitor { 64 65 private static final String TAG = "RequestMonitor"; 66 private final boolean mQuirkEnabled; 67 private final List<ListenableFuture<Void>> mRequestTasks = 68 Collections.synchronizedList(new ArrayList<>()); 69 70 /** Constructor of the RequestMonitor */ RequestMonitor(boolean quirkEnabled)71 public RequestMonitor(boolean quirkEnabled) { 72 mQuirkEnabled = quirkEnabled; 73 } 74 75 /** 76 * Indicates whether capture sequence monitoring is enabled. 77 * 78 * <p>Returns true if a quirk is enabled that necessitates tracking in-flight capture requests. 79 * Returns false otherwise. 80 */ shouldMonitorRequest()81 public boolean shouldMonitorRequest() { 82 return mQuirkEnabled; 83 } 84 85 /** 86 * Returns a ListenableFuture that indicates whether all capture requests have been 87 * processed. 88 */ 89 @ExecutedBy("mExecutor") getRequestsProcessedFuture()90 public @NonNull ListenableFuture<Void> getRequestsProcessedFuture() { 91 if (mRequestTasks.isEmpty()) { 92 return Futures.immediateFuture(null); 93 } 94 95 return Futures.nonCancellationPropagating( 96 Futures.transform(Futures.successfulAsList(new ArrayList<>(mRequestTasks)), 97 input -> null, CameraXExecutors.directExecutor())); 98 } 99 100 /** 101 * Creates a listener that monitors request completion for the `RequestMonitor`. 102 * 103 * <p>This listener should be assigned to the CameraCaptureSession via 104 * the `setSingleRepeatingRequest` or `captureBurstRequests` method to track when submitted 105 * requests are fully processed. 106 * The `RequestMonitor` can then use this information to ensure proper capture sequence 107 * handling. 108 * 109 * <p>Note: the created listener wraps the provided `originalListener`, ensuring any original 110 * capture callbacks still function as intended. 111 * 112 * @param originalListener The original CaptureCallback to combine with monitoring 113 * functionality. 114 * @return A new CaptureCallback that includes request completion tracking for the 115 * `RequestMonitor`. 116 */ 117 @ExecutedBy("mExecutor") createMonitorListener( CameraCaptureSession.@onNull CaptureCallback originalListener)118 public CameraCaptureSession.@NonNull CaptureCallback createMonitorListener( 119 CameraCaptureSession.@NonNull CaptureCallback originalListener) { 120 if (shouldMonitorRequest()) { 121 return Camera2CaptureCallbacks.createComboCallback(createMonitorListener(), 122 originalListener); 123 } else { 124 return originalListener; 125 } 126 } 127 createMonitorListener()128 private CameraCaptureSession.CaptureCallback createMonitorListener() { 129 RequestCompleteListener completeListener = new RequestCompleteListener(); 130 ListenableFuture<Void> future = completeListener.mStartRequestFuture; 131 132 mRequestTasks.add(future); 133 Log.d(TAG, "RequestListener " + completeListener + " monitoring " + this); 134 future.addListener(() -> { 135 Log.d(TAG, "RequestListener " + completeListener + " done " + this); 136 mRequestTasks.remove(future); 137 }, CameraXExecutors.directExecutor()); 138 return completeListener; 139 } 140 141 /** This should be called when a SynchronizedCaptureSession is stopped or closed. */ 142 @ExecutedBy("mExecutor") stop()143 public void stop() { 144 LinkedList<ListenableFuture<Void>> tasks = new LinkedList<>(mRequestTasks); 145 while (!tasks.isEmpty()) { 146 Objects.requireNonNull(tasks.poll()).cancel(true); 147 } 148 } 149 150 static class RequestCompleteListener extends CameraCaptureSession.CaptureCallback { 151 final @NonNull ListenableFuture<Void> mStartRequestFuture; 152 @SuppressWarnings("WeakerAccess") /* synthetic accessor */ 153 CallbackToFutureAdapter.Completer<Void> mStartRequestCompleter; 154 RequestCompleteListener()155 RequestCompleteListener() { 156 mStartRequestFuture = CallbackToFutureAdapter.getFuture(completer -> { 157 mStartRequestCompleter = completer; 158 return "RequestCompleteListener[" + this + "]"; 159 }); 160 } 161 162 @Override onCaptureStarted(@onNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber)163 public void onCaptureStarted(@NonNull CameraCaptureSession session, 164 @NonNull CaptureRequest request, long timestamp, long frameNumber) { 165 completeFuture(); 166 } 167 168 @Override onCaptureSequenceAborted(@onNull CameraCaptureSession session, int sequenceId)169 public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, 170 int sequenceId) { 171 completeFuture(); 172 } 173 174 @Override onCaptureCompleted(@onNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result)175 public void onCaptureCompleted(@NonNull CameraCaptureSession session, 176 @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { 177 completeFuture(); 178 } 179 180 @Override onCaptureFailed(@onNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure)181 public void onCaptureFailed(@NonNull CameraCaptureSession session, 182 @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { 183 completeFuture(); 184 } 185 186 @Override onCaptureSequenceCompleted(@onNull CameraCaptureSession session, int sequenceId, long frameNumber)187 public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, 188 int sequenceId, long frameNumber) { 189 completeFuture(); 190 } 191 completeFuture()192 private void completeFuture() { 193 if (mStartRequestCompleter != null) { 194 mStartRequestCompleter.set(null); 195 mStartRequestCompleter = null; 196 } 197 } 198 } 199 } 200