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