1 /*
2  * Copyright (C) 2019 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 android.hardware.camera2.impl;
18 
19 import android.hardware.camera2.CameraAccessException;
20 import android.hardware.camera2.CameraCaptureSession;
21 import android.hardware.camera2.CameraCharacteristics;
22 import android.hardware.camera2.CameraDevice;
23 import android.hardware.camera2.CameraManager;
24 import android.hardware.camera2.CameraOfflineSession;
25 import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
26 import android.hardware.camera2.CaptureFailure;
27 import android.hardware.camera2.CaptureRequest;
28 import android.hardware.camera2.CaptureResult;
29 import android.hardware.camera2.ICameraDeviceCallbacks;
30 import android.hardware.camera2.ICameraOfflineSession;
31 import android.hardware.camera2.TotalCaptureResult;
32 import android.hardware.camera2.params.InputConfiguration;
33 import android.hardware.camera2.params.OutputConfiguration;
34 import android.os.Binder;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.util.Log;
39 import android.util.Range;
40 import android.util.SparseArray;
41 import android.view.Surface;
42 
43 import java.util.AbstractMap.SimpleEntry;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.concurrent.atomic.AtomicBoolean;
49 import java.util.concurrent.Executor;
50 
51 import static com.android.internal.util.Preconditions.*;
52 
53 public class CameraOfflineSessionImpl extends CameraOfflineSession
54         implements IBinder.DeathRecipient {
55     private static final String TAG = "CameraOfflineSessionImpl";
56     private static final int REQUEST_ID_NONE = -1;
57     private static final long NANO_PER_SECOND = 1000000000; //ns
58     private final boolean DEBUG = false;
59 
60     private ICameraOfflineSession mRemoteSession;
61     private final AtomicBoolean mClosing = new AtomicBoolean();
62 
63     private SimpleEntry<Integer, InputConfiguration> mOfflineInput =
64             new SimpleEntry<>(REQUEST_ID_NONE, null);
65     private SparseArray<OutputConfiguration> mOfflineOutputs = new SparseArray<>();
66     private SparseArray<OutputConfiguration> mConfiguredOutputs = new SparseArray<>();
67 
68     final Object mInterfaceLock = new Object(); // access from this class and Session only!
69 
70     private final String mCameraId;
71     private final CameraCharacteristics mCharacteristics;
72     private final int mTotalPartialCount;
73 
74     private final Executor mOfflineExecutor;
75     private final CameraOfflineSessionCallback mOfflineCallback;
76 
77     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
78 
79     /**
80      * A list tracking request and its expected last regular/reprocess/zslStill frame
81      * number.
82      */
83     private List<RequestLastFrameNumbersHolder> mOfflineRequestLastFrameNumbersList =
84             new ArrayList<>();
85 
86     /**
87      * An object tracking received frame numbers.
88      * Updated when receiving callbacks from ICameraDeviceCallbacks.
89      */
90     private FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
91 
92     /** map request IDs to callback/request data */
93     private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
94             new SparseArray<CaptureCallbackHolder>();
95 
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)96     public CameraOfflineSessionImpl(String cameraId, CameraCharacteristics characteristics,
97             Executor offlineExecutor, CameraOfflineSessionCallback offlineCallback,
98             SparseArray<OutputConfiguration> offlineOutputs,
99             SimpleEntry<Integer, InputConfiguration> offlineInput,
100             SparseArray<OutputConfiguration> configuredOutputs,
101             FrameNumberTracker frameNumberTracker, SparseArray<CaptureCallbackHolder> callbackMap,
102             List<RequestLastFrameNumbersHolder> frameNumberList) {
103         if ((cameraId == null) || (characteristics == null)) {
104             throw new IllegalArgumentException("Null argument given");
105         }
106 
107         mCameraId = cameraId;
108         mCharacteristics = characteristics;
109 
110         Integer partialCount =
111                 mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
112         if (partialCount == null) {
113             // 1 means partial result is not supported.
114             mTotalPartialCount = 1;
115         } else {
116             mTotalPartialCount = partialCount;
117         }
118 
119         mOfflineRequestLastFrameNumbersList.addAll(frameNumberList);
120         mFrameNumberTracker = frameNumberTracker;
121         mCaptureCallbackMap = callbackMap;
122         mConfiguredOutputs = configuredOutputs;
123         mOfflineOutputs = offlineOutputs;
124         mOfflineInput = offlineInput;
125         mOfflineExecutor = checkNotNull(offlineExecutor, "offline executor must not be null");
126         mOfflineCallback = checkNotNull(offlineCallback, "offline callback must not be null");
127 
128     }
129 
getCallbacks()130     public CameraDeviceCallbacks getCallbacks() {
131         return mCallbacks;
132     }
133 
134     public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
135         @Override
asBinder()136         public IBinder asBinder() {
137             return this;
138         }
139 
140         @Override
onDeviceError(final int errorCode, CaptureResultExtras resultExtras)141         public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
142             synchronized(mInterfaceLock) {
143 
144                 switch (errorCode) {
145                     case CameraDeviceCallbacks.ERROR_CAMERA_REQUEST:
146                     case CameraDeviceCallbacks.ERROR_CAMERA_RESULT:
147                     case CameraDeviceCallbacks.ERROR_CAMERA_BUFFER:
148                         onCaptureErrorLocked(errorCode, resultExtras);
149                         break;
150                     default: {
151                         Runnable errorDispatch = new Runnable() {
152                             @Override
153                             public void run() {
154                                 if (!isClosed()) {
155                                     mOfflineCallback.onError(CameraOfflineSessionImpl.this,
156                                             CameraOfflineSessionCallback.STATUS_INTERNAL_ERROR);
157                                 }
158                             }
159                         };
160 
161                         final long ident = Binder.clearCallingIdentity();
162                         try {
163                             mOfflineExecutor.execute(errorDispatch);
164                         } finally {
165                             Binder.restoreCallingIdentity(ident);
166                         }
167                     }
168                 }
169             }
170         }
171 
172         @Override
onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId)173         public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
174             Log.e(TAG, "Unexpected repeating request error received. Last frame number is " +
175                     lastFrameNumber);
176         }
177 
178         @Override
onDeviceIdle()179         public void onDeviceIdle() {
180             synchronized(mInterfaceLock) {
181                 if (mRemoteSession == null) {
182                     Log.v(TAG, "Ignoring idle state notifications during offline switches");
183                     return;
184                 }
185 
186                 // Remove all capture callbacks now that device has gone to IDLE state.
187                 removeCompletedCallbackHolderLocked(
188                         Long.MAX_VALUE, /*lastCompletedRegularFrameNumber*/
189                         Long.MAX_VALUE, /*lastCompletedReprocessFrameNumber*/
190                         Long.MAX_VALUE /*lastCompletedZslStillFrameNumber*/);
191 
192                 Runnable idleDispatch = new Runnable() {
193                     @Override
194                     public void run() {
195                         if (!isClosed()) {
196                             mOfflineCallback.onIdle(CameraOfflineSessionImpl.this);
197                         }
198                     }
199                 };
200 
201                 final long ident = Binder.clearCallingIdentity();
202                 try {
203                     mOfflineExecutor.execute(idleDispatch);
204                 } finally {
205                     Binder.restoreCallingIdentity(ident);
206                 }
207             }
208         }
209 
210         @Override
onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp)211         public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
212             int requestId = resultExtras.getRequestId();
213             final long frameNumber = resultExtras.getFrameNumber();
214             final long lastCompletedRegularFrameNumber =
215                     resultExtras.getLastCompletedRegularFrameNumber();
216             final long lastCompletedReprocessFrameNumber =
217                     resultExtras.getLastCompletedReprocessFrameNumber();
218             final long lastCompletedZslFrameNumber =
219                     resultExtras.getLastCompletedZslFrameNumber();
220 
221             final CaptureCallbackHolder holder;
222 
223             synchronized(mInterfaceLock) {
224                 // Check if it's okay to remove completed callbacks from mCaptureCallbackMap.
225                 // A callback is completed if the corresponding inflight request has been removed
226                 // from the inflight queue in cameraservice.
227                 removeCompletedCallbackHolderLocked(lastCompletedRegularFrameNumber,
228                         lastCompletedReprocessFrameNumber, lastCompletedZslFrameNumber);
229 
230                 // Get the callback for this frame ID, if there is one
231                 holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
232 
233                 if (holder == null) {
234                     return;
235                 }
236 
237                 final Executor executor = holder.getCallback().getExecutor();
238                 if (isClosed() || (executor == null)) return;
239 
240                 // Dispatch capture start notice
241                 final long ident = Binder.clearCallingIdentity();
242                 try {
243                     executor.execute(
244                         new Runnable() {
245                             @Override
246                             public void run() {
247                                 final CameraCaptureSession.CaptureCallback callback =
248                                         holder.getCallback().getSessionCallback();
249                                 if (!CameraOfflineSessionImpl.this.isClosed() &&
250                                         (callback != null)) {
251                                     final int subsequenceId = resultExtras.getSubsequenceId();
252                                     final CaptureRequest request = holder.getRequest(subsequenceId);
253 
254                                     if (holder.hasBatchedOutputs()) {
255                                         // Send derived onCaptureStarted for requests within the
256                                         // batch
257                                         final Range<Integer> fpsRange =
258                                                 request.get(
259                                                         CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
260                                         for (int i = 0; i < holder.getRequestCount(); i++) {
261                                             final CaptureRequest cbRequest = holder.getRequest(i);
262                                             final long cbTimestamp =
263                                                         timestamp - (subsequenceId - i) *
264                                                         NANO_PER_SECOND/fpsRange.getUpper();
265                                             final long cbFrameNumber =
266                                                     frameNumber - (subsequenceId - i);
267                                             callback.onCaptureStarted(CameraOfflineSessionImpl.this,
268                                                     cbRequest, cbTimestamp, cbFrameNumber);
269                                         }
270                                     } else {
271                                         callback.onCaptureStarted(CameraOfflineSessionImpl.this,
272                                                 holder.getRequest(
273                                                     resultExtras.getSubsequenceId()),
274                                                 timestamp, frameNumber);
275                                     }
276                                 }
277                             }
278                         });
279                 } finally {
280                     Binder.restoreCallingIdentity(ident);
281                 }
282             }
283         }
284 
285         @Override
onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])286         public void onResultReceived(CameraMetadataNative result,
287                 CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
288                 throws RemoteException {
289 
290             int requestId = resultExtras.getRequestId();
291             long frameNumber = resultExtras.getFrameNumber();
292 
293             synchronized(mInterfaceLock) {
294                 // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
295                 result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
296                         mCharacteristics.get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
297 
298                 final CaptureCallbackHolder holder =
299                         CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
300                 final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
301 
302                 boolean isPartialResult =
303                         (resultExtras.getPartialResultCount() < mTotalPartialCount);
304                 int requestType = request.getRequestType();
305 
306                 // Check if we have a callback for this
307                 if (holder == null) {
308                     mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
309                             requestType);
310 
311                     return;
312                 }
313 
314                 if (isClosed()) {
315                     mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
316                             requestType);
317                     return;
318                 }
319 
320 
321                 Runnable resultDispatch = null;
322 
323                 CaptureResult finalResult;
324                 // Make a copy of the native metadata before it gets moved to a CaptureResult
325                 // object.
326                 final CameraMetadataNative resultCopy;
327                 if (holder.hasBatchedOutputs()) {
328                     resultCopy = new CameraMetadataNative(result);
329                 } else {
330                     resultCopy = null;
331                 }
332 
333                 final Executor executor = holder.getCallback().getExecutor();
334                 // Either send a partial result or the final capture completed result
335                 if (isPartialResult) {
336                     final CaptureResult resultAsCapture =
337                             new CaptureResult(mCameraId, result, request, resultExtras);
338                     // Partial result
339                     resultDispatch = new Runnable() {
340                         @Override
341                         public void run() {
342                             final CameraCaptureSession.CaptureCallback callback =
343                                     holder.getCallback().getSessionCallback();
344                             if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
345                                 if (holder.hasBatchedOutputs()) {
346                                     // Send derived onCaptureProgressed for requests within
347                                     // the batch.
348                                     for (int i = 0; i < holder.getRequestCount(); i++) {
349                                         CameraMetadataNative resultLocal =
350                                                 new CameraMetadataNative(resultCopy);
351                                         final CaptureResult resultInBatch = new CaptureResult(
352                                                 mCameraId, resultLocal, holder.getRequest(i),
353                                                 resultExtras);
354 
355                                         final CaptureRequest cbRequest = holder.getRequest(i);
356                                         callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
357                                                 cbRequest, resultInBatch);
358                                     }
359                                 } else {
360                                     callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
361                                             request, resultAsCapture);
362                                 }
363                             }
364                         }
365                     };
366                     finalResult = resultAsCapture;
367                 } else {
368                     List<CaptureResult> partialResults =
369                             mFrameNumberTracker.popPartialResults(frameNumber);
370 
371                     final long sensorTimestamp =
372                             result.get(CaptureResult.SENSOR_TIMESTAMP);
373                     final Range<Integer> fpsRange =
374                             request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
375                     final int subsequenceId = resultExtras.getSubsequenceId();
376                     final TotalCaptureResult resultAsCapture = new TotalCaptureResult(mCameraId,
377                             result, request, resultExtras, partialResults, holder.getSessionId(),
378                             physicalResults);
379                     // Final capture result
380                     resultDispatch = new Runnable() {
381                         @Override
382                         public void run() {
383                             final CameraCaptureSession.CaptureCallback callback =
384                                     holder.getCallback().getSessionCallback();
385                             if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
386                                 if (holder.hasBatchedOutputs()) {
387                                     // Send derived onCaptureCompleted for requests within
388                                     // the batch.
389                                     for (int i = 0; i < holder.getRequestCount(); i++) {
390                                         resultCopy.set(CaptureResult.SENSOR_TIMESTAMP,
391                                                 sensorTimestamp - (subsequenceId - i) *
392                                                 NANO_PER_SECOND/fpsRange.getUpper());
393                                         CameraMetadataNative resultLocal =
394                                                 new CameraMetadataNative(resultCopy);
395                                         // No logical multi-camera support for batched output mode.
396                                         TotalCaptureResult resultInBatch = new TotalCaptureResult(
397                                                 mCameraId, resultLocal, holder.getRequest(i),
398                                                 resultExtras, partialResults, holder.getSessionId(),
399                                                 new PhysicalCaptureResultInfo[0]);
400 
401                                         final CaptureRequest cbRequest = holder.getRequest(i);
402                                         callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
403                                                 cbRequest, resultInBatch);
404                                     }
405                                 } else {
406                                     callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
407                                             request, resultAsCapture);
408                                 }
409                             }
410                         }
411                     };
412                     finalResult = resultAsCapture;
413                 }
414 
415                 if (executor != null) {
416                     final long ident = Binder.clearCallingIdentity();
417                     try {
418                         executor.execute(resultDispatch);
419                     } finally {
420                         Binder.restoreCallingIdentity(ident);
421                     }
422                 }
423 
424                 // Collect the partials for a total result; or mark the frame as totally completed
425                 mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
426                         requestType);
427 
428                 // Fire onCaptureSequenceCompleted
429                 if (!isPartialResult) {
430                     checkAndFireSequenceComplete();
431                 }
432             }
433         }
434 
435         @Override
onPrepared(int streamId)436         public void onPrepared(int streamId) {
437             Log.e(TAG, "Unexpected stream " + streamId + " is prepared");
438         }
439 
440         @Override
onRequestQueueEmpty()441         public void onRequestQueueEmpty() {
442             // No-op during offline mode
443             Log.v(TAG, "onRequestQueueEmpty");
444         }
445 
446         /**
447          * Called by onDeviceError for handling single-capture failures.
448          */
onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras)449         private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
450             final int requestId = resultExtras.getRequestId();
451             final int subsequenceId = resultExtras.getSubsequenceId();
452             final long frameNumber = resultExtras.getFrameNumber();
453             final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
454             final CaptureCallbackHolder holder =
455                     CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
456 
457             if (holder == null) {
458                 Log.e(TAG, String.format("Receive capture error on unknown request ID %d",
459                         requestId));
460                 return;
461             }
462 
463             final CaptureRequest request = holder.getRequest(subsequenceId);
464 
465             Runnable failureDispatch = null;
466             if (errorCode == ERROR_CAMERA_BUFFER) {
467                 // Because 1 stream id could map to multiple surfaces, we need to specify both
468                 // streamId and surfaceId.
469                 OutputConfiguration config;
470                 if ((mRemoteSession == null) && !isClosed()) {
471                     config = mConfiguredOutputs.get(resultExtras.getErrorStreamId());
472                 } else {
473                     config = mOfflineOutputs.get(resultExtras.getErrorStreamId());
474                 }
475                 if (config == null) {
476                     Log.v(TAG, String.format(
477                             "Stream %d has been removed. Skipping buffer lost callback",
478                             resultExtras.getErrorStreamId()));
479                     return;
480                 }
481                 for (Surface surface : config.getSurfaces()) {
482                     if (!request.containsTarget(surface)) {
483                         continue;
484                     }
485                     final Executor executor = holder.getCallback().getExecutor();
486                     failureDispatch = new Runnable() {
487                         @Override
488                         public void run() {
489                             final CameraCaptureSession.CaptureCallback callback =
490                                     holder.getCallback().getSessionCallback();
491                             if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
492                                 callback.onCaptureBufferLost( CameraOfflineSessionImpl.this,
493                                         request, surface, frameNumber);
494                             }
495                         }
496                     };
497                     if (executor != null) {
498                         // Dispatch the failure callback
499                         final long ident = Binder.clearCallingIdentity();
500                         try {
501                             executor.execute(failureDispatch);
502                         } finally {
503                             Binder.restoreCallingIdentity(ident);
504                         }
505                     }
506                 }
507             } else {
508                 boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
509                 int reason = CaptureFailure.REASON_ERROR;
510 
511                 final CaptureFailure failure = new CaptureFailure(
512                     request,
513                     reason,
514                     /*dropped*/ mayHaveBuffers,
515                     requestId,
516                     frameNumber,
517                     errorPhysicalCameraId);
518 
519                 final Executor executor = holder.getCallback().getExecutor();
520                 failureDispatch = new Runnable() {
521                     @Override
522                     public void run() {
523                         final CameraCaptureSession.CaptureCallback callback =
524                                 holder.getCallback().getSessionCallback();
525                         if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
526                             callback.onCaptureFailed(CameraOfflineSessionImpl.this, request,
527                                     failure);
528                         }
529                     }
530                 };
531 
532                 // Fire onCaptureSequenceCompleted if appropriate
533                 mFrameNumberTracker.updateTracker(frameNumber,
534                         /*error*/true, request.getRequestType());
535                 checkAndFireSequenceComplete();
536 
537                 if (executor != null) {
538                     // Dispatch the failure callback
539                     final long ident = Binder.clearCallingIdentity();
540                     try {
541                         executor.execute(failureDispatch);
542                     } finally {
543                         Binder.restoreCallingIdentity(ident);
544                     }
545                 }
546             }
547 
548         }
549 
550     }
551 
checkAndFireSequenceComplete()552     private void checkAndFireSequenceComplete() {
553         long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
554         long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
555         long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber();
556         Iterator<RequestLastFrameNumbersHolder> iter =
557                 mOfflineRequestLastFrameNumbersList.iterator();
558         while (iter.hasNext()) {
559             final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
560             boolean sequenceCompleted = false;
561             final int requestId = requestLastFrameNumbers.getRequestId();
562             final CaptureCallbackHolder holder;
563             final Executor executor;
564             final CameraCaptureSession.CaptureCallback callback;
565             synchronized(mInterfaceLock) {
566                 int index = mCaptureCallbackMap.indexOfKey(requestId);
567                 holder = (index >= 0) ?
568                         mCaptureCallbackMap.valueAt(index) : null;
569                 if (holder != null) {
570                     long lastRegularFrameNumber =
571                             requestLastFrameNumbers.getLastRegularFrameNumber();
572                     long lastReprocessFrameNumber =
573                             requestLastFrameNumbers.getLastReprocessFrameNumber();
574                     long lastZslStillFrameNumber =
575                             requestLastFrameNumbers.getLastZslStillFrameNumber();
576                     executor = holder.getCallback().getExecutor();
577                     callback = holder.getCallback().getSessionCallback();
578                     // check if it's okay to remove request from mCaptureCallbackMap
579                     if (lastRegularFrameNumber <= completedFrameNumber
580                             && lastReprocessFrameNumber <= completedReprocessFrameNumber
581                             && lastZslStillFrameNumber <= completedZslStillFrameNumber) {
582                         sequenceCompleted = true;
583                         mCaptureCallbackMap.removeAt(index);
584                     }
585                 } else {
586                     executor = null;
587                     callback = null;
588                 }
589             }
590 
591             // If no callback is registered for this requestId or sequence completed, remove it
592             // from the frame number->request pair because it's not needed anymore.
593             if (holder == null || sequenceCompleted) {
594                 iter.remove();
595             }
596 
597             // Call onCaptureSequenceCompleted
598             if ((sequenceCompleted) && (callback != null) && (executor != null)) {
599                 Runnable resultDispatch = new Runnable() {
600                     @Override
601                     public void run() {
602                         if (!isClosed()) {
603                             callback.onCaptureSequenceCompleted(CameraOfflineSessionImpl.this,
604                                     requestId, requestLastFrameNumbers.getLastFrameNumber());
605                         }
606                     }
607                 };
608 
609                 final long ident = Binder.clearCallingIdentity();
610                 try {
611                     executor.execute(resultDispatch);
612                 } finally {
613                     Binder.restoreCallingIdentity(ident);
614                 }
615 
616                 if (mCaptureCallbackMap.size() == 0) {
617                     getCallbacks().onDeviceIdle();
618                 }
619             }
620 
621         }
622     }
623 
removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber, long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber)624     private void removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber,
625             long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber) {
626         if (DEBUG) {
627             Log.v(TAG, String.format("remove completed callback holders for "
628                     + "lastCompletedRegularFrameNumber %d, "
629                     + "lastCompletedReprocessFrameNumber %d, "
630                     + "lastCompletedZslStillFrameNumber %d",
631                     lastCompletedRegularFrameNumber,
632                     lastCompletedReprocessFrameNumber,
633                     lastCompletedZslStillFrameNumber));
634         }
635 
636         boolean isReprocess = false;
637         Iterator<RequestLastFrameNumbersHolder> iter =
638                 mOfflineRequestLastFrameNumbersList.iterator();
639         while (iter.hasNext()) {
640             final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
641             final int requestId = requestLastFrameNumbers.getRequestId();
642             final CaptureCallbackHolder holder;
643 
644             int index = mCaptureCallbackMap.indexOfKey(requestId);
645             holder = (index >= 0) ?
646                     mCaptureCallbackMap.valueAt(index) : null;
647             if (holder != null) {
648                 long lastRegularFrameNumber =
649                         requestLastFrameNumbers.getLastRegularFrameNumber();
650                 long lastReprocessFrameNumber =
651                         requestLastFrameNumbers.getLastReprocessFrameNumber();
652                 long lastZslStillFrameNumber =
653                         requestLastFrameNumbers.getLastZslStillFrameNumber();
654                 if (lastRegularFrameNumber <= lastCompletedRegularFrameNumber
655                         && lastReprocessFrameNumber <= lastCompletedReprocessFrameNumber
656                         && lastZslStillFrameNumber <= lastCompletedZslStillFrameNumber) {
657                     if (requestLastFrameNumbers.isSequenceCompleted()) {
658                         mCaptureCallbackMap.removeAt(index);
659                         if (DEBUG) {
660                             Log.v(TAG, String.format(
661                                     "Remove holder for requestId %d, because lastRegularFrame %d "
662                                     + "is <= %d, lastReprocessFrame %d is <= %d, "
663                                     + "lastZslStillFrame %d is <= %d", requestId,
664                                     lastRegularFrameNumber, lastCompletedRegularFrameNumber,
665                                     lastReprocessFrameNumber, lastCompletedReprocessFrameNumber,
666                                     lastZslStillFrameNumber, lastCompletedZslStillFrameNumber));
667                         }
668 
669                         iter.remove();
670                     } else {
671                         Log.e(TAG, "Sequence not yet completed for request id " + requestId);
672                         continue;
673                     }
674                 }
675             }
676         }
677     }
678 
notifyFailedSwitch()679     public void notifyFailedSwitch() {
680         synchronized(mInterfaceLock) {
681             Runnable switchFailDispatch = new Runnable() {
682                 @Override
683                 public void run() {
684                     mOfflineCallback.onSwitchFailed(CameraOfflineSessionImpl.this);
685                 }
686             };
687 
688             final long ident = Binder.clearCallingIdentity();
689             try {
690                 mOfflineExecutor.execute(switchFailDispatch);
691             } finally {
692                 Binder.restoreCallingIdentity(ident);
693             }
694         }
695     }
696 
697     /**
698      * Set remote session.
699      *
700      */
setRemoteSession(ICameraOfflineSession remoteSession)701     public void setRemoteSession(ICameraOfflineSession remoteSession) throws CameraAccessException {
702         synchronized(mInterfaceLock) {
703             if (remoteSession == null) {
704                 notifyFailedSwitch();
705                 return;
706             }
707 
708             mRemoteSession = remoteSession;
709 
710             IBinder remoteSessionBinder = remoteSession.asBinder();
711             if (remoteSessionBinder == null) {
712                 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
713                         "The camera offline session has encountered a serious error");
714             }
715 
716             try {
717                 remoteSessionBinder.linkToDeath(this, /*flag*/ 0);
718             } catch (RemoteException e) {
719                 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
720                         "The camera offline session has encountered a serious error");
721             }
722 
723             Runnable readyDispatch = new Runnable() {
724                 @Override
725                 public void run() {
726                     if (!isClosed()) {
727                         mOfflineCallback.onReady(CameraOfflineSessionImpl.this);
728                     }
729                 }
730             };
731 
732             final long ident = Binder.clearCallingIdentity();
733             try {
734                 mOfflineExecutor.execute(readyDispatch);
735             } finally {
736                 Binder.restoreCallingIdentity(ident);
737             }
738         }
739     }
740 
741     /** Whether the offline session has started to close (may not yet have finished) */
isClosed()742     private boolean isClosed() {
743         return mClosing.get();
744     }
745 
disconnect()746     private void disconnect() {
747         synchronized (mInterfaceLock) {
748             if (mClosing.getAndSet(true)) {
749                 return;
750             }
751 
752             if (mRemoteSession != null) {
753                 mRemoteSession.asBinder().unlinkToDeath(this, /*flags*/0);
754 
755                 try {
756                     mRemoteSession.disconnect();
757                 } catch (RemoteException e) {
758                     Log.e(TAG, "Exception while disconnecting from offline session: ", e);
759                 }
760             } else {
761                 throw new IllegalStateException("Offline session is not yet ready");
762             }
763 
764             mRemoteSession = null;
765 
766             Runnable closeDispatch = new Runnable() {
767                 @Override
768                 public void run() {
769                     mOfflineCallback.onClosed(CameraOfflineSessionImpl.this);
770                 }
771             };
772 
773             final long ident = Binder.clearCallingIdentity();
774             try {
775                 mOfflineExecutor.execute(closeDispatch);
776             } finally {
777                 Binder.restoreCallingIdentity(ident);
778             }
779         }
780     }
781 
782     @Override
finalize()783     protected void finalize() throws Throwable {
784         try {
785             disconnect();
786         }
787         finally {
788             super.finalize();
789         }
790     }
791 
792     /**
793      * Listener for binder death.
794      *
795      * <p> Handle binder death for ICameraOfflineSession.</p>
796      */
797     @Override
binderDied()798     public void binderDied() {
799         Log.w(TAG, "CameraOfflineSession on device " + mCameraId + " died unexpectedly");
800         disconnect();
801     }
802 
803     @Override
getDevice()804     public CameraDevice getDevice() {
805         throw new UnsupportedOperationException("Operation not supported in offline mode");
806     }
807 
808     @Override
prepare(Surface surface)809     public void prepare(Surface surface) throws CameraAccessException {
810         throw new UnsupportedOperationException("Operation not supported in offline mode");
811     }
812 
813     @Override
prepare(int maxCount, Surface surface)814     public void prepare(int maxCount, Surface surface) throws CameraAccessException {
815         throw new UnsupportedOperationException("Operation not supported in offline mode");
816     }
817 
818     @Override
tearDown(Surface surface)819     public void tearDown(Surface surface) throws CameraAccessException {
820         throw new UnsupportedOperationException("Operation not supported in offline mode");
821     }
822 
823     @Override
finalizeOutputConfigurations( List<OutputConfiguration> outputConfigs)824     public void finalizeOutputConfigurations(
825             List<OutputConfiguration> outputConfigs) throws CameraAccessException {
826         throw new UnsupportedOperationException("Operation not supported in offline mode");
827     }
828 
829     @Override
capture(CaptureRequest request, CaptureCallback callback, Handler handler)830     public int capture(CaptureRequest request, CaptureCallback callback,
831             Handler handler) throws CameraAccessException {
832         throw new UnsupportedOperationException("Operation not supported in offline mode");
833     }
834 
835     @Override
captureSingleRequest(CaptureRequest request, Executor executor, CaptureCallback callback)836     public int captureSingleRequest(CaptureRequest request, Executor executor,
837             CaptureCallback callback) throws CameraAccessException {
838         throw new UnsupportedOperationException("Operation not supported in offline mode");
839     }
840 
841     @Override
captureBurst(List<CaptureRequest> requests, CaptureCallback callback, Handler handler)842     public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
843             Handler handler) throws CameraAccessException {
844         throw new UnsupportedOperationException("Operation not supported in offline mode");
845     }
846 
847     @Override
captureBurstRequests(List<CaptureRequest> requests, Executor executor, CaptureCallback callback)848     public int captureBurstRequests(List<CaptureRequest> requests, Executor executor,
849             CaptureCallback callback) throws CameraAccessException {
850         throw new UnsupportedOperationException("Operation not supported in offline mode");
851     }
852 
853     @Override
setRepeatingRequest(CaptureRequest request, CaptureCallback callback, Handler handler)854     public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
855             Handler handler) throws CameraAccessException {
856         throw new UnsupportedOperationException("Operation not supported in offline mode");
857     }
858 
859     @Override
setSingleRepeatingRequest(CaptureRequest request, Executor executor, CaptureCallback callback)860     public int setSingleRepeatingRequest(CaptureRequest request, Executor executor,
861             CaptureCallback callback) throws CameraAccessException {
862         throw new UnsupportedOperationException("Operation not supported in offline mode");
863     }
864 
865     @Override
setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback, Handler handler)866     public int setRepeatingBurst(List<CaptureRequest> requests,
867             CaptureCallback callback, Handler handler) throws CameraAccessException {
868         throw new UnsupportedOperationException("Operation not supported in offline mode");
869     }
870 
871     @Override
setRepeatingBurstRequests(List<CaptureRequest> requests, Executor executor, CaptureCallback callback)872     public int setRepeatingBurstRequests(List<CaptureRequest> requests, Executor executor,
873             CaptureCallback callback) throws CameraAccessException {
874         throw new UnsupportedOperationException("Operation not supported in offline mode");
875     }
876 
877     @Override
stopRepeating()878     public void stopRepeating() throws CameraAccessException {
879         throw new UnsupportedOperationException("Operation not supported in offline mode");
880     }
881 
882     @Override
abortCaptures()883     public void abortCaptures() throws CameraAccessException {
884         throw new UnsupportedOperationException("Operation not supported in offline mode");
885     }
886 
887     @Override
updateOutputConfiguration(OutputConfiguration config)888     public void updateOutputConfiguration(OutputConfiguration config)
889             throws CameraAccessException {
890         throw new UnsupportedOperationException("Operation not supported in offline mode");
891     }
892 
893     @Override
isReprocessable()894     public boolean isReprocessable() {
895         throw new UnsupportedOperationException("Operation not supported in offline mode");
896     }
897 
898     @Override
getInputSurface()899     public Surface getInputSurface() {
900         throw new UnsupportedOperationException("Operation not supported in offline mode");
901     }
902 
903     @Override
switchToOffline(Collection<Surface> offlineOutputs, Executor executor, CameraOfflineSessionCallback listener)904     public CameraOfflineSession switchToOffline(Collection<Surface> offlineOutputs,
905             Executor executor, CameraOfflineSessionCallback listener) throws CameraAccessException {
906         throw new UnsupportedOperationException("Operation not supported in offline mode");
907     }
908 
909     @Override
supportsOfflineProcessing(Surface surface)910     public boolean supportsOfflineProcessing(Surface surface) {
911         throw new UnsupportedOperationException("Operation not supported in offline mode");
912     }
913 
914     @Override
close()915     public void close() {
916         disconnect();
917     }
918 }
919