1 /*
2  * Copyright (C) 2014 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 package android.hardware.camera2.legacy;
17 
18 import android.hardware.camera2.impl.CameraDeviceImpl;
19 import android.util.Log;
20 import android.util.MutableLong;
21 import android.util.Pair;
22 import android.view.Surface;
23 import java.util.ArrayDeque;
24 import java.util.ArrayList;
25 import java.util.TreeSet;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.locks.Condition;
28 import java.util.concurrent.locks.ReentrantLock;
29 
30 /**
31  * Collect timestamps and state for each {@link CaptureRequest} as it passes through
32  * the Legacy camera pipeline.
33  */
34 public class CaptureCollector {
35     private static final String TAG = "CaptureCollector";
36 
37     private static final boolean DEBUG = false;
38 
39     private static final int FLAG_RECEIVED_JPEG = 1;
40     private static final int FLAG_RECEIVED_JPEG_TS = 2;
41     private static final int FLAG_RECEIVED_PREVIEW = 4;
42     private static final int FLAG_RECEIVED_PREVIEW_TS = 8;
43     private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS;
44     private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW |
45             FLAG_RECEIVED_PREVIEW_TS;
46 
47     private static final int MAX_JPEGS_IN_FLIGHT = 1;
48 
49     private class CaptureHolder implements Comparable<CaptureHolder>{
50         private final RequestHolder mRequest;
51         private final LegacyRequest mLegacy;
52         public final boolean needsJpeg;
53         public final boolean needsPreview;
54 
55         private long mTimestamp = 0;
56         private int mReceivedFlags = 0;
57         private boolean mHasStarted = false;
58         private boolean mFailedJpeg = false;
59         private boolean mFailedPreview = false;
60         private boolean mCompleted = false;
61         private boolean mPreviewCompleted = false;
62 
CaptureHolder(RequestHolder request, LegacyRequest legacyHolder)63         public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
64             mRequest = request;
65             mLegacy = legacyHolder;
66             needsJpeg = request.hasJpegTargets();
67             needsPreview = request.hasPreviewTargets();
68         }
69 
isPreviewCompleted()70         public boolean isPreviewCompleted() {
71             return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW;
72         }
73 
isJpegCompleted()74         public  boolean isJpegCompleted() {
75             return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG;
76         }
77 
isCompleted()78         public boolean isCompleted() {
79             return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted());
80         }
81 
tryComplete()82         public void tryComplete() {
83             if (!mPreviewCompleted && needsPreview && isPreviewCompleted()) {
84                 CaptureCollector.this.onPreviewCompleted();
85                 mPreviewCompleted = true;
86             }
87 
88             if (isCompleted() && !mCompleted) {
89                 if (mFailedPreview || mFailedJpeg) {
90                     if (!mHasStarted) {
91                         // Send a request error if the capture has not yet started.
92                         mRequest.failRequest();
93                         CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
94                                 CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_REQUEST);
95                     } else {
96                         // Send buffer dropped errors for each pending buffer if the request has
97                         // started.
98                         for (Surface targetSurface : mRequest.getRequest().getTargets() ) {
99                             try {
100                                 if (mRequest.jpegType(targetSurface)) {
101                                     if (mFailedJpeg) {
102                                         CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
103                                                 /*result*/null,
104                                                 CameraDeviceImpl.CameraDeviceCallbacks.
105                                                         ERROR_CAMERA_BUFFER,
106                                                 targetSurface);
107                                     }
108                                 } else {
109                                     // preview buffer
110                                     if (mFailedPreview) {
111                                         CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
112                                                 /*result*/null,
113                                                 CameraDeviceImpl.CameraDeviceCallbacks.
114                                                         ERROR_CAMERA_BUFFER,
115                                                 targetSurface);
116                                     }
117                                 }
118                             } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
119                                 Log.e(TAG, "Unexpected exception when querying Surface: " + e);
120                             }
121                         }
122                     }
123                 }
124                 CaptureCollector.this.onRequestCompleted(CaptureHolder.this);
125                 mCompleted = true;
126             }
127         }
128 
setJpegTimestamp(long timestamp)129         public void setJpegTimestamp(long timestamp) {
130             if (DEBUG) {
131                 Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId());
132             }
133             if (!needsJpeg) {
134                 throw new IllegalStateException(
135                         "setJpegTimestamp called for capture with no jpeg targets.");
136             }
137             if (isCompleted()) {
138                 throw new IllegalStateException(
139                         "setJpegTimestamp called on already completed request.");
140             }
141 
142             mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
143 
144             if (mTimestamp == 0) {
145                 mTimestamp = timestamp;
146             }
147 
148             if (!mHasStarted) {
149                 mHasStarted = true;
150                 CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
151                         CameraDeviceState.NO_CAPTURE_ERROR);
152             }
153 
154             tryComplete();
155         }
156 
setJpegProduced()157         public void setJpegProduced() {
158             if (DEBUG) {
159                 Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId());
160             }
161             if (!needsJpeg) {
162                 throw new IllegalStateException(
163                         "setJpegProduced called for capture with no jpeg targets.");
164             }
165             if (isCompleted()) {
166                 throw new IllegalStateException(
167                         "setJpegProduced called on already completed request.");
168             }
169 
170             mReceivedFlags |= FLAG_RECEIVED_JPEG;
171             tryComplete();
172         }
173 
setJpegFailed()174         public void setJpegFailed() {
175             if (DEBUG) {
176                 Log.d(TAG, "setJpegFailed - called for request " + mRequest.getRequestId());
177             }
178             if (!needsJpeg || isJpegCompleted()) {
179                 return;
180             }
181             mFailedJpeg = true;
182 
183             mReceivedFlags |= FLAG_RECEIVED_JPEG;
184             mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
185             tryComplete();
186         }
187 
setPreviewTimestamp(long timestamp)188         public void setPreviewTimestamp(long timestamp) {
189             if (DEBUG) {
190                 Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
191             }
192             if (!needsPreview) {
193                 throw new IllegalStateException(
194                         "setPreviewTimestamp called for capture with no preview targets.");
195             }
196             if (isCompleted()) {
197                 throw new IllegalStateException(
198                         "setPreviewTimestamp called on already completed request.");
199             }
200 
201             mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
202 
203             if (mTimestamp == 0) {
204                 mTimestamp = timestamp;
205             }
206 
207             if (!needsJpeg) {
208                 if (!mHasStarted) {
209                     mHasStarted = true;
210                     CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
211                             CameraDeviceState.NO_CAPTURE_ERROR);
212                 }
213             }
214 
215             tryComplete();
216         }
217 
setPreviewProduced()218         public void setPreviewProduced() {
219             if (DEBUG) {
220                 Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId());
221             }
222             if (!needsPreview) {
223                 throw new IllegalStateException(
224                         "setPreviewProduced called for capture with no preview targets.");
225             }
226             if (isCompleted()) {
227                 throw new IllegalStateException(
228                         "setPreviewProduced called on already completed request.");
229             }
230 
231             mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
232             tryComplete();
233         }
234 
setPreviewFailed()235         public void setPreviewFailed() {
236             if (DEBUG) {
237                 Log.d(TAG, "setPreviewFailed - called for request " + mRequest.getRequestId());
238             }
239             if (!needsPreview || isPreviewCompleted()) {
240                 return;
241             }
242             mFailedPreview = true;
243 
244             mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
245             mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
246             tryComplete();
247         }
248 
249         // Comparison and equals based on frame number.
250         @Override
compareTo(CaptureHolder captureHolder)251         public int compareTo(CaptureHolder captureHolder) {
252             return (mRequest.getFrameNumber() > captureHolder.mRequest.getFrameNumber()) ? 1 :
253                     ((mRequest.getFrameNumber() == captureHolder.mRequest.getFrameNumber()) ? 0 :
254                             -1);
255         }
256 
257         // Comparison and equals based on frame number.
258         @Override
equals(Object o)259         public boolean equals(Object o) {
260             return o instanceof CaptureHolder && compareTo((CaptureHolder) o) == 0;
261         }
262     }
263 
264     private final TreeSet<CaptureHolder> mActiveRequests;
265     private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
266     private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
267     private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
268     private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
269     private final ArrayList<CaptureHolder> mCompletedRequests = new ArrayList<>();
270 
271     private final ReentrantLock mLock = new ReentrantLock();
272     private final Condition mIsEmpty;
273     private final Condition mPreviewsEmpty;
274     private final Condition mNotFull;
275     private final CameraDeviceState mDeviceState;
276     private int mInFlight = 0;
277     private int mInFlightPreviews = 0;
278     private final int mMaxInFlight;
279 
280     /**
281      * Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}.
282      *
283      * @param maxInFlight max allowed in-flight requests.
284      * @param deviceState the {@link CameraDeviceState} to update as requests are processed.
285      */
CaptureCollector(int maxInFlight, CameraDeviceState deviceState)286     public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) {
287         mMaxInFlight = maxInFlight;
288         mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
289         mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
290         mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
291         mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
292         mActiveRequests = new TreeSet<>();
293         mIsEmpty = mLock.newCondition();
294         mNotFull = mLock.newCondition();
295         mPreviewsEmpty = mLock.newCondition();
296         mDeviceState = deviceState;
297     }
298 
299     /**
300      * Queue a new request.
301      *
302      * <p>
303      * For requests that use the Camera1 API preview output stream, this will block if there are
304      * already {@code maxInFlight} requests in progress (until at least one prior request has
305      * completed). For requests that use the Camera1 API jpeg callbacks, this will block until
306      * all prior requests have been completed to avoid stopping preview for
307      * {@link android.hardware.Camera#takePicture} before prior preview requests have been
308      * completed.
309      * </p>
310      * @param holder the {@link RequestHolder} for this request.
311      * @param legacy the {@link LegacyRequest} for this request; this will not be mutated.
312      * @param timeout a timeout to use for this call.
313      * @param unit the units to use for the timeout.
314      * @return {@code false} if this method timed out.
315      * @throws InterruptedException if this thread is interrupted.
316      */
queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout, TimeUnit unit)317     public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout,
318                                 TimeUnit unit)
319             throws InterruptedException {
320         CaptureHolder h = new CaptureHolder(holder, legacy);
321         long nanos = unit.toNanos(timeout);
322         final ReentrantLock lock = this.mLock;
323         lock.lock();
324         try {
325             if (DEBUG) {
326                 Log.d(TAG, "queueRequest  for request " + holder.getRequestId() +
327                         " - " + mInFlight + " requests remain in flight.");
328             }
329 
330             if (!(h.needsJpeg || h.needsPreview)) {
331                 throw new IllegalStateException("Request must target at least one output surface!");
332             }
333 
334             if (h.needsJpeg) {
335                 // Wait for all current requests to finish before queueing jpeg.
336                 while (mInFlight > 0) {
337                     if (nanos <= 0) {
338                         return false;
339                     }
340                     nanos = mIsEmpty.awaitNanos(nanos);
341                 }
342                 mJpegCaptureQueue.add(h);
343                 mJpegProduceQueue.add(h);
344             }
345             if (h.needsPreview) {
346                 while (mInFlight >= mMaxInFlight) {
347                     if (nanos <= 0) {
348                         return false;
349                     }
350                     nanos = mNotFull.awaitNanos(nanos);
351                 }
352                 mPreviewCaptureQueue.add(h);
353                 mPreviewProduceQueue.add(h);
354                 mInFlightPreviews++;
355             }
356             mActiveRequests.add(h);
357 
358             mInFlight++;
359             return true;
360         } finally {
361             lock.unlock();
362         }
363     }
364 
365     /**
366      * Wait all queued requests to complete.
367      *
368      * @param timeout a timeout to use for this call.
369      * @param unit the units to use for the timeout.
370      * @return {@code false} if this method timed out.
371      * @throws InterruptedException if this thread is interrupted.
372      */
waitForEmpty(long timeout, TimeUnit unit)373     public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException {
374         long nanos = unit.toNanos(timeout);
375         final ReentrantLock lock = this.mLock;
376         lock.lock();
377         try {
378             while (mInFlight > 0) {
379                 if (nanos <= 0) {
380                     return false;
381                 }
382                 nanos = mIsEmpty.awaitNanos(nanos);
383             }
384             return true;
385         } finally {
386             lock.unlock();
387         }
388     }
389 
390     /**
391      * Wait all queued requests that use the Camera1 API preview output to complete.
392      *
393      * @param timeout a timeout to use for this call.
394      * @param unit the units to use for the timeout.
395      * @return {@code false} if this method timed out.
396      * @throws InterruptedException if this thread is interrupted.
397      */
waitForPreviewsEmpty(long timeout, TimeUnit unit)398     public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException {
399         long nanos = unit.toNanos(timeout);
400         final ReentrantLock lock = this.mLock;
401         lock.lock();
402         try {
403             while (mInFlightPreviews > 0) {
404                 if (nanos <= 0) {
405                     return false;
406                 }
407                 nanos = mPreviewsEmpty.awaitNanos(nanos);
408             }
409             return true;
410         } finally {
411             lock.unlock();
412         }
413     }
414 
415     /**
416      * Wait for the specified request to be completed (all buffers available).
417      *
418      * <p>May not wait for the same request more than once, since a successful wait
419      * will erase the history of that request.</p>
420      *
421      * @param holder the {@link RequestHolder} for this request.
422      * @param timeout a timeout to use for this call.
423      * @param unit the units to use for the timeout.
424      * @param timestamp the timestamp of the request will be written out to here, in ns
425      *
426      * @return {@code false} if this method timed out.
427      *
428      * @throws InterruptedException if this thread is interrupted.
429      */
waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit, MutableLong timestamp)430     public boolean waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit,
431             MutableLong timestamp)
432             throws InterruptedException {
433         long nanos = unit.toNanos(timeout);
434         final ReentrantLock lock = this.mLock;
435         lock.lock();
436         try {
437             while (!removeRequestIfCompleted(holder, /*out*/timestamp)) {
438                 if (nanos <= 0) {
439                     return false;
440                 }
441                 nanos = mNotFull.awaitNanos(nanos);
442             }
443             return true;
444         } finally {
445             lock.unlock();
446         }
447     }
448 
removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp)449     private boolean removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp) {
450         int i = 0;
451         for (CaptureHolder h : mCompletedRequests) {
452             if (h.mRequest.equals(holder)) {
453                 timestamp.value = h.mTimestamp;
454                 mCompletedRequests.remove(i);
455                 return true;
456             }
457             i++;
458         }
459 
460         return false;
461     }
462 
463     /**
464      * Called to alert the {@link CaptureCollector} that the jpeg capture has begun.
465      *
466      * @param timestamp the time of the jpeg capture.
467      * @return the {@link RequestHolder} for the request associated with this capture.
468      */
jpegCaptured(long timestamp)469     public RequestHolder jpegCaptured(long timestamp) {
470         final ReentrantLock lock = this.mLock;
471         lock.lock();
472         try {
473             CaptureHolder h = mJpegCaptureQueue.poll();
474             if (h == null) {
475                 Log.w(TAG, "jpegCaptured called with no jpeg request on queue!");
476                 return null;
477             }
478             h.setJpegTimestamp(timestamp);
479             return h.mRequest;
480         } finally {
481             lock.unlock();
482         }
483     }
484 
485     /**
486      * Called to alert the {@link CaptureCollector} that the jpeg capture has completed.
487      *
488      * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
489      */
jpegProduced()490     public Pair<RequestHolder, Long> jpegProduced() {
491         final ReentrantLock lock = this.mLock;
492         lock.lock();
493         try {
494             CaptureHolder h = mJpegProduceQueue.poll();
495             if (h == null) {
496                 Log.w(TAG, "jpegProduced called with no jpeg request on queue!");
497                 return null;
498             }
499             h.setJpegProduced();
500             return new Pair<>(h.mRequest, h.mTimestamp);
501         } finally {
502             lock.unlock();
503         }
504     }
505 
506     /**
507      * Check if there are any pending capture requests that use the Camera1 API preview output.
508      *
509      * @return {@code true} if there are pending preview requests.
510      */
hasPendingPreviewCaptures()511     public boolean hasPendingPreviewCaptures() {
512         final ReentrantLock lock = this.mLock;
513         lock.lock();
514         try {
515             return !mPreviewCaptureQueue.isEmpty();
516         } finally {
517             lock.unlock();
518         }
519     }
520 
521     /**
522      * Called to alert the {@link CaptureCollector} that the preview capture has begun.
523      *
524      * @param timestamp the time of the preview capture.
525      * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
526      */
previewCaptured(long timestamp)527     public Pair<RequestHolder, Long> previewCaptured(long timestamp) {
528         final ReentrantLock lock = this.mLock;
529         lock.lock();
530         try {
531             CaptureHolder h = mPreviewCaptureQueue.poll();
532             if (h == null) {
533                 if (DEBUG) {
534                     Log.d(TAG, "previewCaptured called with no preview request on queue!");
535                 }
536                 return null;
537             }
538             h.setPreviewTimestamp(timestamp);
539             return new Pair<>(h.mRequest, h.mTimestamp);
540         } finally {
541             lock.unlock();
542         }
543     }
544 
545     /**
546      * Called to alert the {@link CaptureCollector} that the preview capture has completed.
547      *
548      * @return the {@link RequestHolder} for the request associated with this capture.
549      */
previewProduced()550     public RequestHolder previewProduced() {
551         final ReentrantLock lock = this.mLock;
552         lock.lock();
553         try {
554             CaptureHolder h = mPreviewProduceQueue.poll();
555             if (h == null) {
556                 Log.w(TAG, "previewProduced called with no preview request on queue!");
557                 return null;
558             }
559             h.setPreviewProduced();
560             return h.mRequest;
561         } finally {
562             lock.unlock();
563         }
564     }
565 
566     /**
567      * Called to alert the {@link CaptureCollector} that the next pending preview capture has failed.
568      */
failNextPreview()569     public void failNextPreview() {
570         final ReentrantLock lock = this.mLock;
571         lock.lock();
572         try {
573             CaptureHolder h1 = mPreviewCaptureQueue.peek();
574             CaptureHolder h2 = mPreviewProduceQueue.peek();
575 
576             // Find the request with the lowest frame number.
577             CaptureHolder h = (h1 == null) ? h2 :
578                               ((h2 == null) ? h1 :
579                               ((h1.compareTo(h2) <= 0) ? h1 :
580                               h2));
581 
582             if (h != null) {
583                 mPreviewCaptureQueue.remove(h);
584                 mPreviewProduceQueue.remove(h);
585                 mActiveRequests.remove(h);
586                 h.setPreviewFailed();
587             }
588         } finally {
589             lock.unlock();
590         }
591     }
592 
593     /**
594      * Called to alert the {@link CaptureCollector} that the next pending jpeg capture has failed.
595      */
failNextJpeg()596     public void failNextJpeg() {
597         final ReentrantLock lock = this.mLock;
598         lock.lock();
599         try {
600             CaptureHolder h1 = mJpegCaptureQueue.peek();
601             CaptureHolder h2 = mJpegProduceQueue.peek();
602 
603             // Find the request with the lowest frame number.
604             CaptureHolder h = (h1 == null) ? h2 :
605                               ((h2 == null) ? h1 :
606                               ((h1.compareTo(h2) <= 0) ? h1 :
607                               h2));
608 
609             if (h != null) {
610                 mJpegCaptureQueue.remove(h);
611                 mJpegProduceQueue.remove(h);
612                 mActiveRequests.remove(h);
613                 h.setJpegFailed();
614             }
615         } finally {
616             lock.unlock();
617         }
618     }
619 
620     /**
621      * Called to alert the {@link CaptureCollector} all pending captures have failed.
622      */
failAll()623     public void failAll() {
624         final ReentrantLock lock = this.mLock;
625         lock.lock();
626         try {
627             CaptureHolder h;
628             while ((h = mActiveRequests.pollFirst()) != null) {
629                 h.setPreviewFailed();
630                 h.setJpegFailed();
631             }
632             mPreviewCaptureQueue.clear();
633             mPreviewProduceQueue.clear();
634             mJpegCaptureQueue.clear();
635             mJpegProduceQueue.clear();
636         } finally {
637             lock.unlock();
638         }
639     }
640 
onPreviewCompleted()641     private void onPreviewCompleted() {
642         mInFlightPreviews--;
643         if (mInFlightPreviews < 0) {
644             throw new IllegalStateException(
645                     "More preview captures completed than requests queued.");
646         }
647         if (mInFlightPreviews == 0) {
648             mPreviewsEmpty.signalAll();
649         }
650     }
651 
onRequestCompleted(CaptureHolder capture)652     private void onRequestCompleted(CaptureHolder capture) {
653         RequestHolder request = capture.mRequest;
654 
655         mInFlight--;
656         if (DEBUG) {
657             Log.d(TAG, "Completed request " + request.getRequestId() +
658                     ", " + mInFlight + " requests remain in flight.");
659         }
660         if (mInFlight < 0) {
661             throw new IllegalStateException(
662                     "More captures completed than requests queued.");
663         }
664 
665         mCompletedRequests.add(capture);
666         mActiveRequests.remove(capture);
667 
668         mNotFull.signalAll();
669         if (mInFlight == 0) {
670             mIsEmpty.signalAll();
671         }
672     }
673 }
674