1 /*
2  * Copyright 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 androidx.camera.camera2.internal;
18 
19 import android.graphics.ImageFormat;
20 import android.hardware.camera2.CameraAccessException;
21 import android.hardware.camera2.CameraCaptureSession;
22 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
23 import android.hardware.camera2.CameraDevice;
24 import android.hardware.camera2.CaptureRequest;
25 import android.hardware.camera2.TotalCaptureResult;
26 import android.hardware.camera2.params.DynamicRangeProfiles;
27 import android.hardware.camera2.params.MultiResolutionStreamInfo;
28 import android.hardware.camera2.params.OutputConfiguration;
29 import android.hardware.camera2.params.SessionConfiguration;
30 import android.os.Build;
31 import android.view.Surface;
32 
33 import androidx.annotation.GuardedBy;
34 import androidx.annotation.OptIn;
35 import androidx.annotation.RequiresApi;
36 import androidx.camera.camera2.impl.Camera2ImplConfig;
37 import androidx.camera.camera2.internal.compat.params.DynamicRangeConversions;
38 import androidx.camera.camera2.internal.compat.params.DynamicRangesCompat;
39 import androidx.camera.camera2.internal.compat.params.InputConfigurationCompat;
40 import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
41 import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
42 import androidx.camera.camera2.internal.compat.quirk.CaptureNoResponseQuirk;
43 import androidx.camera.camera2.internal.compat.workaround.RequestMonitor;
44 import androidx.camera.camera2.internal.compat.workaround.StillCaptureFlow;
45 import androidx.camera.camera2.internal.compat.workaround.TemplateParamsOverride;
46 import androidx.camera.camera2.internal.compat.workaround.TorchStateReset;
47 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
48 import androidx.camera.core.DynamicRange;
49 import androidx.camera.core.Logger;
50 import androidx.camera.core.MirrorMode;
51 import androidx.camera.core.impl.CameraCaptureCallback;
52 import androidx.camera.core.impl.CaptureConfig;
53 import androidx.camera.core.impl.DeferrableSurface;
54 import androidx.camera.core.impl.Quirks;
55 import androidx.camera.core.impl.SessionConfig;
56 import androidx.camera.core.impl.utils.SurfaceUtil;
57 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
58 import androidx.camera.core.impl.utils.futures.FutureCallback;
59 import androidx.camera.core.impl.utils.futures.FutureChain;
60 import androidx.camera.core.impl.utils.futures.Futures;
61 import androidx.concurrent.futures.CallbackToFutureAdapter;
62 import androidx.core.util.Preconditions;
63 import androidx.tracing.Trace;
64 
65 import com.google.common.util.concurrent.ListenableFuture;
66 
67 import org.jspecify.annotations.NonNull;
68 import org.jspecify.annotations.Nullable;
69 
70 import java.util.ArrayList;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.HashMap;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Objects;
77 import java.util.concurrent.CancellationException;
78 
79 /**
80  * A basic implementation of {@link CaptureSessionInterface} for capturing images from the camera
81  * which is tied to a specific {@link CameraDevice}.
82  */
83 final class CaptureSession implements CaptureSessionInterface {
84     private static final String TAG = "CaptureSession";
85 
86     // TODO: Find a proper timeout threshold.
87     private static final long TIMEOUT_GET_SURFACE_IN_MS = 5000L;
88     /** Lock to ensure session operations run atomically. */
89     final Object mSessionLock = new Object();
90     /** The configuration for the currently issued single capture requests. */
91     @GuardedBy("mSessionLock")
92     private final List<CaptureConfig> mCaptureConfigs = new ArrayList<>();
93     @GuardedBy("mSessionLock")
94     private final StateCallback mCaptureSessionStateCallback;
95     /** The Opener to help on creating the SynchronizedCaptureSession. */
96     @GuardedBy("mSessionLock")
97     SynchronizedCaptureSession.@Nullable Opener mSessionOpener;
98     /** The framework camera capture session held by this session. */
99     @GuardedBy("mSessionLock")
100     @Nullable SynchronizedCaptureSession mSynchronizedCaptureSession;
101     /** The configuration for the currently issued capture requests. */
102     @GuardedBy("mSessionLock")
103     @Nullable SessionConfig mSessionConfig;
104     /**
105      * The map of DeferrableSurface to Surface. It is both for restoring the surfaces used to
106      * configure the current capture session and for getting the configured surface from a
107      * DeferrableSurface.
108      */
109     @GuardedBy("mSessionLock")
110     private final Map<DeferrableSurface, Surface> mConfiguredSurfaceMap = new HashMap<>();
111 
112     /** The list of DeferrableSurface used to notify surface detach events */
113     @GuardedBy("mSessionLock")
114     List<DeferrableSurface> mConfiguredDeferrableSurfaces = Collections.emptyList();
115     /** Maximum state this session achieved (for debugging) */
116     @GuardedBy("mSessionLock")
117     State mHighestState = State.UNINITIALIZED;
118     /** Tracks the current state of the session. */
119     @GuardedBy("mSessionLock")
120     State mState = State.UNINITIALIZED;
121     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
122     @GuardedBy("mSessionLock")
123     ListenableFuture<Void> mReleaseFuture;
124     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
125     @GuardedBy("mSessionLock")
126     CallbackToFutureAdapter.Completer<Void> mReleaseCompleter;
127     @GuardedBy("mSessionLock")
128     private @NonNull Map<DeferrableSurface, Long> mStreamUseCaseMap = new HashMap<>();
129     private final StillCaptureFlow mStillCaptureFlow = new StillCaptureFlow();
130     private final TorchStateReset mTorchStateReset = new TorchStateReset();
131     private final RequestMonitor mRequestMonitor;
132     private final DynamicRangesCompat mDynamicRangesCompat;
133     private final TemplateParamsOverride mTemplateParamsOverride;
134     private final boolean mCanUseMultiResolutionImageReader;
135 
136     /**
137      * Constructor for CaptureSession without CameraQuirk.
138      */
CaptureSession(@onNull DynamicRangesCompat dynamicRangesCompat)139     CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat) {
140         this(dynamicRangesCompat, false);
141     }
142 
143     /**
144      * Constructor for CaptureSession without CameraQuirk.
145      */
CaptureSession(@onNull DynamicRangesCompat dynamicRangesCompat, boolean canUseMultiResolutionImageReader)146     CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat,
147             boolean canUseMultiResolutionImageReader) {
148         this(dynamicRangesCompat, new Quirks(Collections.emptyList()),
149                 canUseMultiResolutionImageReader);
150     }
151 
152     /**
153      * Constructor for CaptureSession with CameraQuirk.
154      */
CaptureSession(@onNull DynamicRangesCompat dynamicRangesCompat, @NonNull Quirks cameraQuirks)155     CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat,
156             @NonNull Quirks cameraQuirks) {
157         this(dynamicRangesCompat, cameraQuirks, false);
158     }
159 
160     /**
161      * Constructor for CaptureSession.
162      */
CaptureSession(@onNull DynamicRangesCompat dynamicRangesCompat, @NonNull Quirks cameraQuirks, boolean canUseMultiResolutionImageReader)163     CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat,
164             @NonNull Quirks cameraQuirks, boolean canUseMultiResolutionImageReader) {
165         setState(State.INITIALIZED);
166         mDynamicRangesCompat = dynamicRangesCompat;
167         mCaptureSessionStateCallback = new StateCallback();
168         mRequestMonitor = new RequestMonitor(cameraQuirks.contains(CaptureNoResponseQuirk.class));
169         mTemplateParamsOverride = new TemplateParamsOverride(cameraQuirks);
170         mCanUseMultiResolutionImageReader = canUseMultiResolutionImageReader;
171     }
172 
173     @Override
setStreamUseCaseMap(@onNull Map<DeferrableSurface, Long> streamUseCaseMap)174     public void setStreamUseCaseMap(@NonNull Map<DeferrableSurface, Long> streamUseCaseMap) {
175         synchronized (mSessionLock) {
176             mStreamUseCaseMap = streamUseCaseMap;
177         }
178     }
179 
180     /**
181      * {@inheritDoc}
182      */
183     @Override
getSessionConfig()184     public @Nullable SessionConfig getSessionConfig() {
185         synchronized (mSessionLock) {
186             return mSessionConfig;
187         }
188     }
189 
190     /**
191      * {@inheritDoc}
192      */
193     @Override
setSessionConfig(@ullable SessionConfig sessionConfig)194     public void setSessionConfig(@Nullable SessionConfig sessionConfig) {
195         synchronized (mSessionLock) {
196             switch (mState) {
197                 case UNINITIALIZED:
198                     throw new IllegalStateException(
199                             "setSessionConfig() should not be possible in state: " + mState);
200                 case INITIALIZED:
201                 case GET_SURFACE:
202                 case OPENING:
203                     mSessionConfig = sessionConfig;
204                     break;
205                 case OPENED:
206                     mSessionConfig = sessionConfig;
207                     if (sessionConfig == null) {
208                         return;
209                     }
210 
211                     if (!mConfiguredSurfaceMap.keySet().containsAll(sessionConfig.getSurfaces())) {
212                         Logger.e(TAG, "Does not have the proper configured lists");
213                         return;
214                     }
215 
216                     Logger.d(TAG, "Attempting to submit CaptureRequest after setting");
217                     issueRepeatingCaptureRequests(mSessionConfig);
218                     break;
219                 case CLOSED:
220                 case RELEASING:
221                 case RELEASED:
222                     throw new IllegalStateException(
223                             "Session configuration cannot be set on a closed/released session.");
224             }
225         }
226     }
227 
228     /**
229      * {@inheritDoc}
230      */
231     @Override
open(@onNull SessionConfig sessionConfig, @NonNull CameraDevice cameraDevice, SynchronizedCaptureSession.@NonNull Opener opener)232     public @NonNull ListenableFuture<Void> open(@NonNull SessionConfig sessionConfig,
233             @NonNull CameraDevice cameraDevice,
234             SynchronizedCaptureSession.@NonNull Opener opener) {
235         synchronized (mSessionLock) {
236             switch (mState) {
237                 case INITIALIZED:
238                     setState(State.GET_SURFACE);
239                     mConfiguredDeferrableSurfaces = new ArrayList<>(sessionConfig.getSurfaces());
240                     mSessionOpener = opener;
241                     ListenableFuture<Void> openFuture = FutureChain.from(
242                             mSessionOpener.startWithDeferrableSurface(
243                                     mConfiguredDeferrableSurfaces, TIMEOUT_GET_SURFACE_IN_MS)
244                     ).transformAsync(
245                             surfaces -> openCaptureSession(surfaces, sessionConfig, cameraDevice),
246                             mSessionOpener.getExecutor());
247 
248                     Futures.addCallback(openFuture, new FutureCallback<Void>() {
249                         @Override
250                         public void onSuccess(@Nullable Void result) {
251                             // Nothing to do.
252                         }
253 
254                         @Override
255                         public void onFailure(@NonNull Throwable t) {
256                             synchronized (mSessionLock) {
257                                 // Stop the Opener if we get any failure during opening.
258                                 mSessionOpener.stop();
259                                 switch (mState) {
260                                     case OPENING:
261                                     case CLOSED:
262                                     case RELEASING:
263                                         if (!(t instanceof CancellationException)) {
264                                             Logger.w(TAG, "Opening session with fail " + mState, t);
265                                             finishClose();
266                                         }
267                                         break;
268                                     default:
269                                 }
270                             }
271                         }
272                     }, mSessionOpener.getExecutor());
273 
274                     // The cancellation of the external ListenableFuture cannot actually stop
275                     // the open session since we can't cancel the camera2 flow. The underlying
276                     // Future is used to track the session is configured, we don't want to
277                     // propagate the cancellation event to it. Wrap the Future in a
278                     // NonCancellationPropagatingFuture, so that if the external caller cancels
279                     // the Future it won't affect the underlying Future.
280                     return Futures.nonCancellationPropagating(openFuture);
281                 default:
282                     Logger.e(TAG, "Open not allowed in state: " + mState);
283             }
284 
285             return Futures.immediateFailedFuture(
286                     new IllegalStateException("open() should not allow the state: " + mState));
287         }
288     }
289 
290     @OptIn(markerClass = ExperimentalCamera2Interop.class)
openCaptureSession( @onNull List<Surface> configuredSurfaces, @NonNull SessionConfig sessionConfig, @NonNull CameraDevice cameraDevice)291     private @NonNull ListenableFuture<Void> openCaptureSession(
292             @NonNull List<Surface> configuredSurfaces, @NonNull SessionConfig sessionConfig,
293             @NonNull CameraDevice cameraDevice) {
294         synchronized (mSessionLock) {
295             switch (mState) {
296                 case UNINITIALIZED:
297                 case INITIALIZED:
298                 case OPENED:
299                     return Futures.immediateFailedFuture(new IllegalStateException(
300                             "openCaptureSession() should not be possible in state: " + mState));
301                 case GET_SURFACE:
302                     // Establishes the mapping of DeferrableSurface to Surface. Capture request
303                     // will use this mapping to get the Surface from DeferrableSurface.
304                     mConfiguredSurfaceMap.clear();
305                     for (int i = 0; i < configuredSurfaces.size(); i++) {
306                         mConfiguredSurfaceMap.put(mConfiguredDeferrableSurfaces.get(i),
307                                 configuredSurfaces.get(i));
308                     }
309 
310                     setState(State.OPENING);
311                     Logger.d(TAG, "Opening capture session.");
312                     SynchronizedCaptureSession.StateCallback callbacks =
313                             SynchronizedCaptureSessionStateCallbacks.createComboCallback(
314                                     mCaptureSessionStateCallback,
315                                     new SynchronizedCaptureSessionStateCallbacks.Adapter(
316                                             sessionConfig.getSessionStateCallbacks())
317                             );
318 
319                     Camera2ImplConfig camera2Config =
320                             new Camera2ImplConfig(sessionConfig.getImplementationOptions());
321                     // Generate the CaptureRequest builder from repeating request since Android
322                     // recommend use the same template type as the initial capture request. The
323                     // tag and output targets would be ignored by default.
324                     CaptureConfig.Builder sessionParameterConfigBuilder =
325                             CaptureConfig.Builder.from(sessionConfig.getRepeatingCaptureConfig());
326 
327                     Map<SessionConfig.OutputConfig, OutputConfigurationCompat>
328                             mrirOutputConfigurationCompatMap = new HashMap<>();
329                     if (mCanUseMultiResolutionImageReader && Build.VERSION.SDK_INT >= 35) {
330                         Map<Integer, List<SessionConfig.OutputConfig>> groupIdToOutputConfigsMap =
331                                 groupMrirOutputConfigs(sessionConfig.getOutputConfigs());
332                         mrirOutputConfigurationCompatMap =
333                                 createMultiResolutionOutputConfigurationCompats(
334                                         groupIdToOutputConfigsMap, mConfiguredSurfaceMap);
335                     }
336 
337                     List<OutputConfigurationCompat> outputConfigList = new ArrayList<>();
338                     String physicalCameraIdForAllStreams =
339                             camera2Config.getPhysicalCameraId(null);
340                     for (SessionConfig.OutputConfig outputConfig :
341                             sessionConfig.getOutputConfigs()) {
342                         OutputConfigurationCompat outputConfiguration = null;
343 
344                         // If an OutputConfiguration has been created via the MRIR approach,
345                         // retrieves it from the map
346                         if (mCanUseMultiResolutionImageReader && Build.VERSION.SDK_INT >= 35) {
347                             outputConfiguration = mrirOutputConfigurationCompatMap.get(
348                                     outputConfig);
349                         }
350 
351                         // Otherwise, uses the original approach to create the
352                         // OutputConfigurationCompat.
353                         if (outputConfiguration == null) {
354                             outputConfiguration = getOutputConfigurationCompat(
355                                     outputConfig,
356                                     mConfiguredSurfaceMap,
357                                     physicalCameraIdForAllStreams);
358                             if (mStreamUseCaseMap.containsKey(outputConfig.getSurface())) {
359                                 outputConfiguration.setStreamUseCase(
360                                         mStreamUseCaseMap.get(outputConfig.getSurface()));
361                             }
362                         }
363                         outputConfigList.add(outputConfiguration);
364                     }
365 
366                     // Some DeferrableSurfaces might actually point to the same Surface. For
367                     // example, a Surface(ImageReader) could be shared between use cases.
368                     // Therefore, there might be duplicate surfaces that need to be removed.
369                     // We might consider removing this logic if this is no longer necessary.
370                     outputConfigList = getUniqueOutputConfigurations(outputConfigList);
371 
372                     SessionConfigurationCompat sessionConfigCompat =
373                             mSessionOpener.createSessionConfigurationCompat(
374                                     sessionConfig.getSessionType(), outputConfigList,
375                                     callbacks);
376 
377                     if (sessionConfig.getTemplateType() == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
378                             && sessionConfig.getInputConfiguration() != null) {
379                         sessionConfigCompat.setInputConfiguration(
380                                 InputConfigurationCompat.wrap(
381                                         sessionConfig.getInputConfiguration()));
382                     }
383 
384                     try {
385                         CaptureRequest captureRequest =
386                                 Camera2CaptureRequestBuilder.buildWithoutTarget(
387                                         sessionParameterConfigBuilder.build(), cameraDevice,
388                                         mTemplateParamsOverride);
389                         if (captureRequest != null) {
390                             sessionConfigCompat.setSessionParameters(captureRequest);
391                         }
392                     } catch (CameraAccessException e) {
393                         return Futures.immediateFailedFuture(e);
394                     }
395 
396                     return mSessionOpener.openCaptureSession(cameraDevice,
397                             sessionConfigCompat, mConfiguredDeferrableSurfaces);
398                 default:
399                     return Futures.immediateFailedFuture(new CancellationException(
400                             "openCaptureSession() not execute in state: " + mState));
401             }
402         }
403     }
404 
getUniqueOutputConfigurations( @onNull List<OutputConfigurationCompat> outputConfigurations)405     private @NonNull List<OutputConfigurationCompat> getUniqueOutputConfigurations(
406             @NonNull List<OutputConfigurationCompat> outputConfigurations) {
407         List<Surface> addedSurfaces = new ArrayList<>();
408         List<OutputConfigurationCompat> results = new ArrayList<>();
409         for (OutputConfigurationCompat outputConfiguration : outputConfigurations) {
410             if (addedSurfaces.contains(outputConfiguration.getSurface())) {
411                 // Surface already added,  ignore this outputConfiguration.
412                 continue;
413             }
414             addedSurfaces.add(outputConfiguration.getSurface());
415             results.add(outputConfiguration);
416         }
417         return results;
418     }
419 
getOutputConfigurationCompat( SessionConfig.@onNull OutputConfig outputConfig, @NonNull Map<DeferrableSurface, Surface> configuredSurfaceMap, @Nullable String physicalCameraIdForAllStreams)420     private @NonNull OutputConfigurationCompat getOutputConfigurationCompat(
421             SessionConfig.@NonNull OutputConfig outputConfig,
422             @NonNull Map<DeferrableSurface, Surface> configuredSurfaceMap,
423             @Nullable String physicalCameraIdForAllStreams) {
424         Surface surface = configuredSurfaceMap.get(outputConfig.getSurface());
425         Preconditions.checkNotNull(surface,
426                 "Surface in OutputConfig not found in configuredSurfaceMap.");
427 
428         OutputConfigurationCompat outputConfiguration =
429                 new OutputConfigurationCompat(outputConfig.getSurfaceGroupId(),
430                         surface);
431         // Set the desired physical camera ID, or null to use the logical stream.
432         // TODO(b/219414502): Configure different streams with different physical
433         //  camera IDs.
434         if (physicalCameraIdForAllStreams != null) {
435             outputConfiguration.setPhysicalCameraId(physicalCameraIdForAllStreams);
436         } else {
437             outputConfiguration.setPhysicalCameraId(
438                     outputConfig.getPhysicalCameraId());
439         }
440 
441         // No need to map MIRROR_MODE_ON_FRONT_ONLY to MIRROR_MODE_AUTO
442         // since its default value in framework
443         if (outputConfig.getMirrorMode() == MirrorMode.MIRROR_MODE_OFF) {
444             outputConfiguration.setMirrorMode(OutputConfiguration.MIRROR_MODE_NONE);
445         } else if (outputConfig.getMirrorMode() == MirrorMode.MIRROR_MODE_ON) {
446             outputConfiguration.setMirrorMode(OutputConfiguration.MIRROR_MODE_H);
447         }
448 
449         if (!outputConfig.getSharedSurfaces().isEmpty()) {
450             outputConfiguration.enableSurfaceSharing();
451             for (DeferrableSurface sharedDeferSurface : outputConfig.getSharedSurfaces()) {
452                 Surface sharedSurface = configuredSurfaceMap.get(sharedDeferSurface);
453                 Preconditions.checkNotNull(sharedSurface,
454                         "Surface in OutputConfig not found in configuredSurfaceMap.");
455                 outputConfiguration.addSurface(sharedSurface);
456             }
457         }
458 
459         long dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
460         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
461             DynamicRangeProfiles dynamicRangeProfiles =
462                     mDynamicRangesCompat.toDynamicRangeProfiles();
463             if (dynamicRangeProfiles != null) {
464                 DynamicRange requestedDynamicRange = outputConfig.getDynamicRange();
465                 Long dynamicRangeProfileOrNull =
466                         DynamicRangeConversions.dynamicRangeToFirstSupportedProfile(
467                                 requestedDynamicRange, dynamicRangeProfiles);
468                 if (dynamicRangeProfileOrNull == null) {
469                     Logger.e(TAG,
470                             "Requested dynamic range is not supported. Defaulting to STANDARD "
471                                     + "dynamic range profile.\nRequested dynamic range:\n  "
472                                     + requestedDynamicRange);
473                 } else {
474                     dynamicRangeProfile = dynamicRangeProfileOrNull;
475                 }
476             }
477         }
478         outputConfiguration.setDynamicRangeProfile(dynamicRangeProfile);
479         return outputConfiguration;
480     }
481 
482     /**
483      * {@inheritDoc}
484      */
485     @Override
close()486     public void close() {
487         synchronized (mSessionLock) {
488             switch (mState) {
489                 case UNINITIALIZED:
490                     throw new IllegalStateException(
491                             "close() should not be possible in state: " + mState);
492                 case GET_SURFACE:
493                     Preconditions.checkNotNull(mSessionOpener,
494                             "The Opener shouldn't null in state:" + mState);
495                     mSessionOpener.stop();
496                     // Fall through
497                 case INITIALIZED:
498                     setState(State.RELEASED);
499                     break;
500                 case OPENED:
501                     // Not break close flow. Fall through
502                 case OPENING:
503                     Preconditions.checkNotNull(mSessionOpener,
504                             "The Opener shouldn't null in state:" + mState);
505                     mSessionOpener.stop();
506                     setState(State.CLOSED);
507                     mRequestMonitor.stop();
508                     mSessionConfig = null;
509 
510                     break;
511                 case CLOSED:
512                 case RELEASING:
513                 case RELEASED:
514                     break;
515             }
516         }
517     }
518 
519     /**
520      * {@inheritDoc}
521      */
522     @SuppressWarnings("ObjectToString")
523     @Override
release(boolean abortInFlightCaptures)524     public @NonNull ListenableFuture<Void> release(boolean abortInFlightCaptures) {
525         synchronized (mSessionLock) {
526             switch (mState) {
527                 case UNINITIALIZED:
528                     throw new IllegalStateException(
529                             "release() should not be possible in state: " + mState);
530                 case OPENED:
531                 case CLOSED:
532                     if (mSynchronizedCaptureSession != null) {
533                         if (abortInFlightCaptures) {
534                             try {
535                                 mSynchronizedCaptureSession.abortCaptures();
536                             } catch (CameraAccessException e) {
537                                 // We couldn't abort the captures, but we should continue on to
538                                 // release the session.
539                                 Logger.e(TAG, "Unable to abort captures.", e);
540                             }
541                         }
542                         mSynchronizedCaptureSession.close();
543                     }
544                     // Fall through
545                 case OPENING:
546                     setState(State.RELEASING);
547                     mRequestMonitor.stop();
548                     Preconditions.checkNotNull(mSessionOpener,
549                             "The Opener shouldn't null in state:" + mState);
550                     if (mSessionOpener.stop()) {
551                         // The CameraCaptureSession doesn't created finish the release flow
552                         // directly.
553                         finishClose();
554                         break;
555                     }
556                     // Fall through
557                 case RELEASING:
558                     if (mReleaseFuture == null) {
559                         mReleaseFuture = CallbackToFutureAdapter.getFuture(
560                                 completer -> {
561                                     synchronized (mSessionLock) {
562                                         Preconditions.checkState(mReleaseCompleter == null,
563                                                 "Release completer expected to be null");
564                                         mReleaseCompleter = completer;
565                                         return "Release[session=" + CaptureSession.this + "]";
566                                     }
567                                 });
568                     }
569 
570                     return mReleaseFuture;
571                 case GET_SURFACE:
572                     Preconditions.checkNotNull(mSessionOpener,
573                             "The Opener shouldn't null in state:" + mState);
574                     mSessionOpener.stop();
575                     // Fall through
576                 case INITIALIZED:
577                     setState(State.RELEASED);
578                     // Fall through
579                 case RELEASED:
580                     break;
581             }
582         }
583 
584         // Already released. Return success immediately.
585         return Futures.immediateFuture(null);
586     }
587 
588     /**
589      * {@inheritDoc}
590      */
591     @Override
issueCaptureRequests(@onNull List<CaptureConfig> captureConfigs)592     public void issueCaptureRequests(@NonNull List<CaptureConfig> captureConfigs) {
593         synchronized (mSessionLock) {
594             switch (mState) {
595                 case UNINITIALIZED:
596                     throw new IllegalStateException(
597                             "issueCaptureRequests() should not be possible in state: "
598                                     + mState);
599                 case INITIALIZED:
600                 case GET_SURFACE:
601                 case OPENING:
602                     mCaptureConfigs.addAll(captureConfigs);
603                     break;
604                 case OPENED:
605                     mCaptureConfigs.addAll(captureConfigs);
606                     issuePendingCaptureRequest();
607                     break;
608                 case CLOSED:
609                 case RELEASING:
610                 case RELEASED:
611                     throw new IllegalStateException(
612                             "Cannot issue capture request on a closed/released session.");
613             }
614         }
615     }
616 
617     /**
618      * {@inheritDoc}
619      */
620     @Override
getCaptureConfigs()621     public @NonNull List<CaptureConfig> getCaptureConfigs() {
622         synchronized (mSessionLock) {
623             return Collections.unmodifiableList(mCaptureConfigs);
624         }
625     }
626 
627     /** Returns the current state of the session. */
getState()628     State getState() {
629         synchronized (mSessionLock) {
630             return mState;
631         }
632     }
633 
634     @Override
isInOpenState()635     public boolean isInOpenState() {
636         synchronized (mSessionLock) {
637             return mState == State.OPENED || mState == State.OPENING;
638         }
639     }
640 
641     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
642     @GuardedBy("mSessionLock")
finishClose()643     void finishClose() {
644         if (mState == State.RELEASED) {
645             Logger.d(TAG, "Skipping finishClose due to being state RELEASED.");
646             return;
647         }
648 
649         setState(State.RELEASED);
650         mSynchronizedCaptureSession = null;
651 
652         if (mReleaseCompleter != null) {
653             mReleaseCompleter.set(null);
654             mReleaseCompleter = null;
655         }
656     }
657 
658     /**
659      * Sets the {@link CaptureRequest} so that the camera will start producing data.
660      *
661      * <p>It will stop running repeating if there are no surfaces.
662      */
663     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
issueRepeatingCaptureRequests(@ullable SessionConfig sessionConfig)664     int issueRepeatingCaptureRequests(@Nullable SessionConfig sessionConfig) {
665         synchronized (mSessionLock) {
666             if (sessionConfig == null) {
667                 Logger.d(TAG, "Skipping issueRepeatingCaptureRequests for no configuration case.");
668                 return -1;
669             }
670 
671             if (mState != State.OPENED) {
672                 Logger.d(TAG, "Skipping issueRepeatingCaptureRequests due to session closed");
673                 return -1;
674             }
675 
676             CaptureConfig captureConfig = sessionConfig.getRepeatingCaptureConfig();
677             if (captureConfig.getSurfaces().isEmpty()) {
678                 Logger.d(TAG, "Skipping issueRepeatingCaptureRequests for no surface.");
679                 try {
680                     // At least from Android L, framework will ignore the stopRepeating() if
681                     // there is no ongoing repeating request, so it should be safe to always call
682                     // stopRepeating() without checking if there is a repeating request.
683                     mSynchronizedCaptureSession.stopRepeating();
684                 } catch (CameraAccessException e) {
685                     Logger.e(TAG, "Unable to access camera: " + e.getMessage());
686                     Thread.dumpStack();
687                 }
688                 return -1;
689             }
690 
691             try {
692                 Logger.d(TAG, "Issuing request for session.");
693                 CaptureRequest captureRequest = Camera2CaptureRequestBuilder.build(
694                         captureConfig, mSynchronizedCaptureSession.getDevice(),
695                         mConfiguredSurfaceMap, true, mTemplateParamsOverride);
696                 if (captureRequest == null) {
697                     Logger.d(TAG, "Skipping issuing empty request for session.");
698                     return -1;
699                 }
700 
701                 CameraCaptureSession.CaptureCallback comboCaptureCallback =
702                         mRequestMonitor.createMonitorListener(createCamera2CaptureCallback(
703                                 captureConfig.getCameraCaptureCallbacks()));
704 
705                 if (sessionConfig.getSessionType() == SessionConfiguration.SESSION_HIGH_SPEED) {
706                     List<CaptureRequest> requests =
707                             mSynchronizedCaptureSession.createHighSpeedRequestList(captureRequest);
708                     return mSynchronizedCaptureSession.setRepeatingBurstRequests(requests,
709                             comboCaptureCallback);
710                 } else {  // SessionConfiguration.SESSION_REGULAR
711                     return mSynchronizedCaptureSession.setSingleRepeatingRequest(captureRequest,
712                             comboCaptureCallback);
713                 }
714             } catch (CameraAccessException e) {
715                 Logger.e(TAG, "Unable to access camera: " + e.getMessage());
716                 Thread.dumpStack();
717             }
718 
719             return -1;
720         }
721     }
722 
723     /** Issues mCaptureConfigs to {@link CameraCaptureSession}. */
724     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
725     @GuardedBy("mSessionLock")
issuePendingCaptureRequest()726     void issuePendingCaptureRequest() {
727         mRequestMonitor.getRequestsProcessedFuture().addListener(() -> {
728             synchronized (mSessionLock) {
729                 if (mCaptureConfigs.isEmpty()) {
730                     return;
731                 }
732                 try {
733                     issueBurstCaptureRequest(mCaptureConfigs);
734                 } finally {
735                     mCaptureConfigs.clear();
736                 }
737             }
738         }, CameraXExecutors.directExecutor());
739     }
740 
741     /**
742      * Issues input CaptureConfig list to {@link CameraCaptureSession}.
743      *
744      * @return A unique capture sequence ID or -1 if request is not submitted.
745      */
746     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
issueBurstCaptureRequest(List<CaptureConfig> captureConfigs)747     int issueBurstCaptureRequest(List<CaptureConfig> captureConfigs) {
748         synchronized (mSessionLock) {
749             if (mState != State.OPENED) {
750                 Logger.d(TAG, "Skipping issueBurstCaptureRequest due to session closed");
751                 return -1;
752             }
753             if (captureConfigs.isEmpty()) {
754                 return -1;
755             }
756             try {
757                 CameraBurstCaptureCallback callbackAggregator = new CameraBurstCaptureCallback();
758                 List<CaptureRequest> captureRequests = new ArrayList<>();
759                 boolean isStillCapture = false;
760                 Logger.d(TAG, "Issuing capture request.");
761                 for (CaptureConfig captureConfig : captureConfigs) {
762                     if (captureConfig.getSurfaces().isEmpty()) {
763                         Logger.d(TAG, "Skipping issuing empty capture request.");
764                         continue;
765                     }
766 
767                     // Validate all surfaces belong to configured surfaces map
768                     boolean surfacesValid = true;
769                     for (DeferrableSurface surface : captureConfig.getSurfaces()) {
770                         if (!mConfiguredSurfaceMap.containsKey(surface)) {
771                             Logger.d(TAG,
772                                     "Skipping capture request with invalid surface: " + surface);
773                             surfacesValid = false;
774                             break;
775                         }
776                     }
777 
778                     if (!surfacesValid) {
779                         // An invalid surface was detected in this request.
780                         // Skip it and go on to the next request.
781                         // TODO (b/133710422): Report this request as an error.
782                         continue;
783                     }
784 
785                     if (captureConfig.getTemplateType() == CameraDevice.TEMPLATE_STILL_CAPTURE) {
786                         isStillCapture = true;
787                     }
788                     CaptureConfig.Builder captureConfigBuilder = CaptureConfig.Builder.from(
789                             captureConfig);
790 
791                     if (captureConfig.getTemplateType() == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
792                             && captureConfig.getCameraCaptureResult() != null) {
793                         captureConfigBuilder.setCameraCaptureResult(
794                                 captureConfig.getCameraCaptureResult());
795                     }
796 
797                     // The override priority for implementation options
798                     // P1 Single capture options
799                     // P2 SessionConfig options
800                     if (mSessionConfig != null) {
801                         captureConfigBuilder.addImplementationOptions(
802                                 mSessionConfig.getRepeatingCaptureConfig()
803                                         .getImplementationOptions());
804                     }
805 
806                     // Need to override again since single capture options has highest priority.
807                     captureConfigBuilder.addImplementationOptions(
808                             captureConfig.getImplementationOptions());
809 
810                     CaptureRequest captureRequest = Camera2CaptureRequestBuilder.build(
811                             captureConfigBuilder.build(), mSynchronizedCaptureSession.getDevice(),
812                             mConfiguredSurfaceMap, false, mTemplateParamsOverride);
813                     if (captureRequest == null) {
814                         Logger.d(TAG, "Skipping issuing request without surface.");
815                         return -1;
816                     }
817 
818                     List<CameraCaptureSession.CaptureCallback> cameraCallbacks = new ArrayList<>();
819                     for (CameraCaptureCallback callback :
820                             captureConfig.getCameraCaptureCallbacks()) {
821                         CaptureCallbackConverter.toCaptureCallback(callback, cameraCallbacks);
822                     }
823                     callbackAggregator.addCamera2Callbacks(captureRequest, cameraCallbacks);
824                     captureRequests.add(captureRequest);
825                 }
826 
827                 if (!captureRequests.isEmpty()) {
828                     if (mStillCaptureFlow
829                             .shouldStopRepeatingBeforeCapture(captureRequests, isStillCapture)) {
830                         mSynchronizedCaptureSession.stopRepeating();
831                         callbackAggregator.setCaptureSequenceCallback(
832                                 (session, sequenceId, isAborted) -> {
833                                     synchronized (mSessionLock) {
834                                         if (mState == State.OPENED) {
835                                             issueRepeatingCaptureRequests(mSessionConfig);
836                                         }
837                                     }
838                                 });
839                     }
840                     if (mTorchStateReset.isTorchResetRequired(captureRequests, isStillCapture)) {
841                         callbackAggregator.addCamera2Callbacks(
842                                 captureRequests.get(captureRequests.size() - 1),
843                                 Collections.singletonList(new CaptureCallback() {
844 
845                                     @Override
846                                     public void onCaptureCompleted(
847                                             @NonNull CameraCaptureSession session,
848                                             @NonNull CaptureRequest request,
849                                             @NonNull TotalCaptureResult result) {
850                                         synchronized (mSessionLock) {
851                                             if (mSessionConfig == null) {
852                                                 return;
853                                             }
854                                             CaptureConfig repeatingConfig =
855                                                     mSessionConfig.getRepeatingCaptureConfig();
856                                             Logger.d(TAG, "Submit FLASH_MODE_OFF request");
857                                             issueCaptureRequests(Collections.singletonList(
858                                                     mTorchStateReset.createTorchResetRequest(
859                                                             repeatingConfig)));
860                                         }
861                                     }
862                                 }));
863                     }
864                     if (mSessionConfig != null && mSessionConfig.getSessionType()
865                             == SessionConfiguration.SESSION_HIGH_SPEED) {
866                         return captureHighSpeedBurst(captureRequests, callbackAggregator);
867                     } else {  // SessionConfiguration.SESSION_REGULAR
868                         return mSynchronizedCaptureSession.captureBurstRequests(captureRequests,
869                                 callbackAggregator);
870                     }
871                 } else {
872                     Logger.d(TAG,
873                             "Skipping issuing burst request due to no valid request elements");
874                 }
875             } catch (CameraAccessException e) {
876                 Logger.e(TAG, "Unable to access camera: " + e.getMessage());
877                 Thread.dumpStack();
878             }
879 
880             return -1;
881         }
882     }
883 
884     @GuardedBy("mSessionLock")
captureHighSpeedBurst(@onNull List<CaptureRequest> captureRequests, @NonNull CameraBurstCaptureCallback callbackAggregator)885     private int captureHighSpeedBurst(@NonNull List<CaptureRequest> captureRequests,
886             @NonNull CameraBurstCaptureCallback callbackAggregator)
887             throws CameraAccessException {
888         // Create a new CameraBurstCaptureCallback to handle callbacks from high-speed requests.
889         // This is necessary because high-speed capture sessions generate multiple requests for
890         // each original request, and we need to map the callbacks back to the original requests.
891         CameraBurstCaptureCallback highSpeedCallbackAggregator = new CameraBurstCaptureCallback();
892 
893         int sequenceId = -1;
894 
895         for (CaptureRequest captureRequest : captureRequests) {
896             List<CaptureRequest> highSpeedRequests =
897                     Objects.requireNonNull(mSynchronizedCaptureSession)
898                             .createHighSpeedRequestList(captureRequest);
899 
900             // For each high-speed request, create a forwarding callback that maps the high-speed
901             // request back to the original request and forwards the callback to the original
902             // callback aggregator.
903             for (CaptureRequest highSpeedRequest : highSpeedRequests) {
904                 CaptureCallback forwardingCallback = new RequestForwardingCaptureCallback(
905                         captureRequest, callbackAggregator);
906                 highSpeedCallbackAggregator.addCamera2Callbacks(highSpeedRequest,
907                         Collections.singletonList(forwardingCallback));
908             }
909 
910             sequenceId = mSynchronizedCaptureSession.captureBurstRequests(
911                     highSpeedRequests, highSpeedCallbackAggregator);
912         }
913 
914         // Return the sequence ID of the last burst capture as a representative ID.
915         return sequenceId;
916     }
917 
918     /**
919      * Discards all captures currently pending and in-progress as fast as possible.
920      */
abortCaptures()921     void abortCaptures() {
922         synchronized (mSessionLock) {
923             if (mState != State.OPENED) {
924                 Logger.e(TAG, "Unable to abort captures. Incorrect state:" + mState);
925                 return;
926             }
927 
928             try {
929                 mSynchronizedCaptureSession.abortCaptures();
930             } catch (CameraAccessException e) {
931                 Logger.e(TAG, "Unable to abort captures.", e);
932             }
933         }
934     }
935 
936     /**
937      * Cancels any ongoing repeating capture.
938      */
stopRepeating()939     void stopRepeating() {
940         synchronized (mSessionLock) {
941             if (mState != State.OPENED) {
942                 Logger.e(TAG, "Unable to stop repeating. Incorrect state:" + mState);
943                 return;
944             }
945 
946             try {
947                 mSynchronizedCaptureSession.stopRepeating();
948             } catch (CameraAccessException e) {
949                 Logger.e(TAG, "Unable to stop repeating.", e);
950             }
951         }
952     }
953 
954     /**
955      * {@inheritDoc}
956      */
957     @Override
cancelIssuedCaptureRequests()958     public void cancelIssuedCaptureRequests() {
959         List<CaptureConfig> captureConfigs = null;
960         synchronized (mSessionLock) {
961             if (!mCaptureConfigs.isEmpty()) {
962                 captureConfigs = new ArrayList<>(mCaptureConfigs);
963                 mCaptureConfigs.clear();
964             }
965         }
966 
967         if (captureConfigs != null) {
968             for (CaptureConfig captureConfig : captureConfigs) {
969                 for (CameraCaptureCallback cameraCaptureCallback :
970                         captureConfig.getCameraCaptureCallbacks()) {
971                     cameraCaptureCallback.onCaptureCancelled(captureConfig.getId());
972                 }
973             }
974         }
975     }
976 
977     @GuardedBy("mSessionLock")
setState(@onNull State state)978     private void setState(@NonNull State state) {
979         if (state.ordinal() > mHighestState.ordinal()) {
980             mHighestState = state;
981         }
982         mState = state;
983         // Some sessions are created and immediately destroyed, so only trace those sessions
984         // that are actually used, which we distinguish by capture sessions that have gone to
985         // at least a GET_SURFACE state.
986         if (Trace.isEnabled() && mHighestState.ordinal() >= State.GET_SURFACE.ordinal()) {
987             String counterName = "CX:C2State[" + String.format("CaptureSession@%x", hashCode())
988                     + "]";
989             Trace.setCounter(counterName, state.ordinal());
990         }
991     }
992 
993     @GuardedBy("mSessionLock")
createCamera2CaptureCallback( List<CameraCaptureCallback> cameraCaptureCallbacks, CameraCaptureSession.CaptureCallback... additionalCallbacks)994     private CameraCaptureSession.CaptureCallback createCamera2CaptureCallback(
995             List<CameraCaptureCallback> cameraCaptureCallbacks,
996             CameraCaptureSession.CaptureCallback... additionalCallbacks) {
997         List<CameraCaptureSession.CaptureCallback> camera2Callbacks =
998                 new ArrayList<>(cameraCaptureCallbacks.size() + additionalCallbacks.length);
999         for (CameraCaptureCallback callback : cameraCaptureCallbacks) {
1000             camera2Callbacks.add(CaptureCallbackConverter.toCaptureCallback(callback));
1001         }
1002         Collections.addAll(camera2Callbacks, additionalCallbacks);
1003         return Camera2CaptureCallbacks.createComboCallback(camera2Callbacks);
1004     }
1005 
1006     /**
1007      * Returns the map which contains the data by mapping surface group id to OutputConfig list.
1008      */
groupMrirOutputConfigs( @onNull Collection<SessionConfig.OutputConfig> outputConfigs)1009     private static @NonNull Map<Integer, List<SessionConfig.OutputConfig>> groupMrirOutputConfigs(
1010             @NonNull Collection<SessionConfig.OutputConfig> outputConfigs) {
1011         Map<Integer, List<SessionConfig.OutputConfig>> groupResult = new HashMap<>();
1012 
1013         for (SessionConfig.OutputConfig outputConfig : outputConfigs) {
1014             // When shared surfaces is not empty, surface sharing will be enabled on the
1015             // OutputConfiguration. In that case, MultiResolutionImageReader shouldn't be used.
1016             if (outputConfig.getSurfaceGroupId() <= 0
1017                     || !outputConfig.getSharedSurfaces().isEmpty()) {
1018                 continue;
1019             }
1020             List<SessionConfig.OutputConfig> groupedOutputConfigs = groupResult.get(
1021                     outputConfig.getSurfaceGroupId());
1022             if (groupedOutputConfigs == null) {
1023                 groupedOutputConfigs = new ArrayList<>();
1024                 groupResult.put(outputConfig.getSurfaceGroupId(), groupedOutputConfigs);
1025             }
1026             groupedOutputConfigs.add(outputConfig);
1027         }
1028 
1029         // Double-check that the list size of each group is at least 2. It is the necessary
1030         // condition to create a MRIR.
1031         Map<Integer, List<SessionConfig.OutputConfig>> mrirGroupResult = new HashMap<>();
1032         for (int groupId : groupResult.keySet()) {
1033             if (groupResult.get(groupId).size() >= 2) {
1034                 mrirGroupResult.put(groupId, groupResult.get(groupId));
1035             }
1036         }
1037 
1038         return mrirGroupResult;
1039     }
1040 
1041     @RequiresApi(35)
1042     private static @NonNull Map<SessionConfig.OutputConfig, OutputConfigurationCompat>
createMultiResolutionOutputConfigurationCompats( @onNull Map<Integer, List<SessionConfig.OutputConfig>> groupIdToOutputConfigsMap, @NonNull Map<DeferrableSurface, Surface> configuredSurfaceMap)1043             createMultiResolutionOutputConfigurationCompats(
1044             @NonNull Map<Integer, List<SessionConfig.OutputConfig>> groupIdToOutputConfigsMap,
1045             @NonNull Map<DeferrableSurface, Surface> configuredSurfaceMap) {
1046         Map<SessionConfig.OutputConfig, OutputConfigurationCompat>
1047                 outputConfigToOutputConfigurationCompatMap = new HashMap<>();
1048 
1049         for (int groupId : groupIdToOutputConfigsMap.keySet()) {
1050             List<MultiResolutionStreamInfo> streamInfos = new ArrayList<>();
1051             int imageFormat = ImageFormat.UNKNOWN;
1052             for (SessionConfig.OutputConfig outputConfig : groupIdToOutputConfigsMap.get(groupId)) {
1053                 Surface surface = configuredSurfaceMap.get(outputConfig.getSurface());
1054                 SurfaceUtil.SurfaceInfo surfaceInfo = SurfaceUtil.getSurfaceInfo(surface);
1055                 if (imageFormat == ImageFormat.UNKNOWN) {
1056                     imageFormat = surfaceInfo.format;
1057                 }
1058                 streamInfos.add(new MultiResolutionStreamInfo(surfaceInfo.width, surfaceInfo.height,
1059                         Objects.requireNonNull(outputConfig.getPhysicalCameraId())));
1060             }
1061             if (imageFormat == ImageFormat.UNKNOWN || streamInfos.isEmpty()) {
1062                 Logger.e(TAG, "Skips to create instances for multi-resolution output. imageFormat: "
1063                         + imageFormat + ", streamInfos size: " + streamInfos.size());
1064                 continue;
1065             }
1066             List<OutputConfiguration> outputConfigurations =
1067                     OutputConfiguration.createInstancesForMultiResolutionOutput(streamInfos,
1068                             imageFormat);
1069             if (outputConfigurations != null) {
1070                 for (SessionConfig.OutputConfig outputConfig : groupIdToOutputConfigsMap.get(
1071                         groupId)) {
1072                     OutputConfiguration outputConfiguration = outputConfigurations.remove(0);
1073                     Surface surface = configuredSurfaceMap.get(outputConfig.getSurface());
1074                     outputConfiguration.addSurface(surface);
1075                     outputConfigToOutputConfigurationCompatMap.put(outputConfig,
1076                             new OutputConfigurationCompat(outputConfiguration));
1077                 }
1078             }
1079         }
1080         return outputConfigToOutputConfigurationCompatMap;
1081     }
1082 
1083     // Debugging note: these states are kept in ordinal order. Any additions or changes should try
1084     // to maintain the same order such that the highest ordinal is the state of largest resource
1085     // utilization.
1086     enum State {
1087         /** The default state of the session before construction. */
1088         UNINITIALIZED,
1089         /**
1090          * Terminal state where the session has been cleaned up. At this point the session should
1091          * not be used as nothing will happen in this state.
1092          */
1093         RELEASED,
1094         /**
1095          * Stable state once the session has been constructed, but prior to the {@link
1096          * CameraCaptureSession} being opened.
1097          */
1098         INITIALIZED,
1099         /**
1100          * Transitional state to get the configured surface from the configuration. Once the
1101          * surfaces is ready, we can create the {@link CameraCaptureSession}.
1102          */
1103         GET_SURFACE,
1104         /** Transitional state where the resources are being cleaned up. */
1105         RELEASING,
1106         /**
1107          * Stable state where the session has been closed. However the {@link CameraCaptureSession}
1108          * is still valid. It will remain valid until a new instance is opened at which point {@link
1109          * CameraCaptureSession.StateCallback#onClosed(CameraCaptureSession)} will be called to do
1110          * final cleanup.
1111          */
1112         CLOSED,
1113         /**
1114          * Transitional state when the {@link CameraCaptureSession} is in the process of being
1115          * opened.
1116          */
1117         OPENING,
1118         /**
1119          * Stable state where the {@link CameraCaptureSession} has been successfully opened. During
1120          * this state if a valid {@link SessionConfig} has been set then the {@link
1121          * CaptureRequest} will be issued.
1122          */
1123         OPENED
1124     }
1125 
1126     /**
1127      * Callback for handling state changes to the {@link CameraCaptureSession}.
1128      *
1129      * <p>State changes are ignored once the CaptureSession has been closed.
1130      */
1131     final class StateCallback extends SynchronizedCaptureSession.StateCallback {
1132 
1133         /**
1134          * {@inheritDoc}
1135          *
1136          * <p>Once the {@link CameraCaptureSession} has been configured then the capture request
1137          * will be immediately issued.
1138          */
1139         @Override
onConfigured(@onNull SynchronizedCaptureSession session)1140         public void onConfigured(@NonNull SynchronizedCaptureSession session) {
1141             synchronized (mSessionLock) {
1142                 switch (mState) {
1143                     case UNINITIALIZED:
1144                     case INITIALIZED:
1145                     case GET_SURFACE:
1146                     case OPENED:
1147                     case RELEASED:
1148                         throw new IllegalStateException(
1149                                 "onConfigured() should not be possible in state: " + mState);
1150                     case OPENING:
1151                         setState(State.OPENED);
1152                         mSynchronizedCaptureSession = session;
1153                         Logger.d(TAG, "Attempting to send capture request onConfigured");
1154                         issueRepeatingCaptureRequests(mSessionConfig);
1155                         issuePendingCaptureRequest();
1156                         break;
1157                     case CLOSED:
1158                         mSynchronizedCaptureSession = session;
1159                         break;
1160                     case RELEASING:
1161                         session.close();
1162                         break;
1163                 }
1164                 Logger.d(TAG, "CameraCaptureSession.onConfigured() mState=" + mState);
1165             }
1166         }
1167 
1168         @Override
onReady(@onNull SynchronizedCaptureSession session)1169         public void onReady(@NonNull SynchronizedCaptureSession session) {
1170             synchronized (mSessionLock) {
1171                 switch (mState) {
1172                     case UNINITIALIZED:
1173                         throw new IllegalStateException(
1174                                 "onReady() should not be possible in state: " + mState);
1175                     default:
1176                 }
1177                 Logger.d(TAG, "CameraCaptureSession.onReady() " + mState);
1178             }
1179         }
1180 
1181         @Override
onSessionFinished(@onNull SynchronizedCaptureSession session)1182         public void onSessionFinished(@NonNull SynchronizedCaptureSession session) {
1183             synchronized (mSessionLock) {
1184                 if (mState == State.UNINITIALIZED) {
1185                     throw new IllegalStateException(
1186                             "onSessionFinished() should not be possible in state: " + mState);
1187                 }
1188                 Logger.d(TAG, "onSessionFinished()");
1189 
1190                 finishClose();
1191             }
1192         }
1193 
1194         @Override
onConfigureFailed(@onNull SynchronizedCaptureSession session)1195         public void onConfigureFailed(@NonNull SynchronizedCaptureSession session) {
1196             synchronized (mSessionLock) {
1197                 switch (mState) {
1198                     case UNINITIALIZED:
1199                     case INITIALIZED:
1200                     case GET_SURFACE:
1201                     case OPENED:
1202                         throw new IllegalStateException(
1203                                 "onConfigureFailed() should not be possible in state: " + mState);
1204                     case OPENING:
1205                     case CLOSED:
1206                     case RELEASING:
1207                         // For CaptureSession onConfigureFailed in framework, it will not allow
1208                         // any close function or callback work. Calling session.close() will not
1209                         // trigger StateCallback.onClosed(). It has to complete the close flow
1210                         // internally. Check b/147402661 for detail.
1211                         finishClose();
1212                         break;
1213                     case RELEASED:
1214                         Logger.d(TAG, "ConfigureFailed callback after change to RELEASED state");
1215                         break;
1216                 }
1217                 Logger.e(TAG, "CameraCaptureSession.onConfigureFailed() " + mState);
1218             }
1219         }
1220     }
1221 }
1222