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