1 /*
2  * Copyright 2020 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.view;
18 
19 import androidx.annotation.GuardedBy;
20 import androidx.annotation.MainThread;
21 import androidx.camera.core.CameraInfo;
22 import androidx.camera.core.Logger;
23 import androidx.camera.core.impl.CameraCaptureCallback;
24 import androidx.camera.core.impl.CameraCaptureResult;
25 import androidx.camera.core.impl.CameraInfoInternal;
26 import androidx.camera.core.impl.CameraInternal;
27 import androidx.camera.core.impl.Observable;
28 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
29 import androidx.camera.core.impl.utils.futures.FutureCallback;
30 import androidx.camera.core.impl.utils.futures.FutureChain;
31 import androidx.camera.core.impl.utils.futures.Futures;
32 import androidx.concurrent.futures.CallbackToFutureAdapter;
33 import androidx.lifecycle.MutableLiveData;
34 
35 import com.google.common.util.concurrent.ListenableFuture;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 /**
44  * An observer to the camera state which when camera is opening/opened, it will start checking if
45  * preview is STREAMING and update the LiveData accordingly, when camera is closing/closed, it
46  * will reset to IDLE state.
47  *
48  * <p>To check if preview is STREAMING, it first checks if camera session capture result is
49  * received by {@link CameraInfoInternal#addSessionCaptureCallback}. And then it checks if the
50  * frame update is received by {@link PreviewViewImplementation#waitForNextFrame()}. If both
51  * happens, the state becomes {@link androidx.camera.view.PreviewView.StreamState#STREAMING}.
52  *
53  * <p>To activate the observer,  it should be registered to the CameraState obtained by
54  * {@link CameraInternal#getCameraState()} and the observer should be registered to run on main
55  * thread.
56  */
57 final class PreviewStreamStateObserver implements Observable.Observer<CameraInternal.State> {
58 
59     private static final String TAG = "StreamStateObserver";
60 
61     private final CameraInfoInternal mCameraInfoInternal;
62     private final MutableLiveData<PreviewView.StreamState> mPreviewStreamStateLiveData;
63     @GuardedBy("this")
64     private PreviewView.StreamState mPreviewStreamState;
65     private final PreviewViewImplementation mPreviewViewImplementation;
66     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
67     ListenableFuture<Void> mFlowFuture;
68     private boolean mHasStartedPreviewStreamFlow = false;
69 
PreviewStreamStateObserver(CameraInfoInternal cameraInfoInternal, MutableLiveData<PreviewView.StreamState> previewStreamLiveData, PreviewViewImplementation implementation)70     PreviewStreamStateObserver(CameraInfoInternal cameraInfoInternal,
71             MutableLiveData<PreviewView.StreamState> previewStreamLiveData,
72             PreviewViewImplementation implementation) {
73         mCameraInfoInternal = cameraInfoInternal;
74         mPreviewStreamStateLiveData = previewStreamLiveData;
75         mPreviewViewImplementation = implementation;
76 
77         synchronized (this) {
78             mPreviewStreamState = previewStreamLiveData.getValue();
79         }
80     }
81 
82     @Override
83     @MainThread
onNewData(CameraInternal.@ullable State value)84     public void onNewData(CameraInternal.@Nullable State value) {
85         if (value == CameraInternal.State.CLOSING
86                 || value == CameraInternal.State.CLOSED
87                 || value == CameraInternal.State.RELEASING
88                 || value == CameraInternal.State.RELEASED) {
89             updatePreviewStreamState(PreviewView.StreamState.IDLE);
90             if (mHasStartedPreviewStreamFlow) {
91                 mHasStartedPreviewStreamFlow = false;
92                 cancelFlow();
93             }
94         } else if (value == CameraInternal.State.OPENING
95                 || value == CameraInternal.State.OPEN
96                 || value == CameraInternal.State.PENDING_OPEN) {
97             if (!mHasStartedPreviewStreamFlow) {
98                 startPreviewStreamStateFlow(mCameraInfoInternal);
99                 mHasStartedPreviewStreamFlow = true;
100             }
101         }
102     }
103 
104     @Override
105     @MainThread
onError(@onNull Throwable t)106     public void onError(@NonNull Throwable t) {
107         clear();
108         updatePreviewStreamState(PreviewView.StreamState.IDLE);
109     }
110 
clear()111     void clear() {
112         cancelFlow();
113     }
114 
cancelFlow()115     private void cancelFlow() {
116         if (mFlowFuture != null) {
117             mFlowFuture.cancel(false);
118             mFlowFuture = null;
119         }
120     }
121 
122     @MainThread
startPreviewStreamStateFlow(CameraInfo cameraInfo)123     private void startPreviewStreamStateFlow(CameraInfo cameraInfo) {
124         updatePreviewStreamState(PreviewView.StreamState.IDLE);
125 
126         List<CameraCaptureCallback> callbacksToClear = new ArrayList<>();
127         mFlowFuture =
128                 FutureChain.from(waitForCaptureResult(cameraInfo, callbacksToClear))
129                         .transformAsync(v -> mPreviewViewImplementation.waitForNextFrame(),
130                                 CameraXExecutors.directExecutor())
131                         .transform(v -> {
132                             updatePreviewStreamState(PreviewView.StreamState.STREAMING);
133                             return null;
134                         }, CameraXExecutors.directExecutor());
135 
136         Futures.addCallback(mFlowFuture, new FutureCallback<Void>() {
137             @Override
138             public void onSuccess(@Nullable Void result) {
139                 mFlowFuture = null;
140             }
141 
142             @Override
143             public void onFailure(@NonNull Throwable t) {
144                 mFlowFuture = null;
145 
146                 if (!callbacksToClear.isEmpty()) {
147                     for (CameraCaptureCallback callback : callbacksToClear) {
148                         ((CameraInfoInternal) cameraInfo).removeSessionCaptureCallback(
149                                 callback);
150                     }
151                     callbacksToClear.clear();
152                 }
153             }
154         }, CameraXExecutors.directExecutor());
155     }
156 
updatePreviewStreamState(PreviewView.StreamState streamState)157     void updatePreviewStreamState(PreviewView.StreamState streamState) {
158         // Prevent from notifying same states.
159         synchronized (this) {
160             if (mPreviewStreamState.equals(streamState)) {
161                 return;
162             }
163             mPreviewStreamState = streamState;
164         }
165 
166         Logger.d(TAG, "Update Preview stream state to " + streamState);
167         mPreviewStreamStateLiveData.postValue(streamState);
168     }
169 
170     /**
171      * Returns a ListenableFuture which will complete when the session onCaptureCompleted happens.
172      * Please note that the future could complete in background thread.
173      */
waitForCaptureResult(CameraInfo cameraInfo, List<CameraCaptureCallback> callbacksToClear)174     private ListenableFuture<Void> waitForCaptureResult(CameraInfo cameraInfo,
175             List<CameraCaptureCallback> callbacksToClear) {
176         return CallbackToFutureAdapter.getFuture(
177                 completer -> {
178                     // The callback will be invoked in camera executor thread.
179                     CameraCaptureCallback callback = new CameraCaptureCallback() {
180                         @Override
181                         public void onCaptureCompleted(int captureConfigId,
182                                 @NonNull CameraCaptureResult result) {
183                             completer.set(null);
184                             ((CameraInfoInternal) cameraInfo).removeSessionCaptureCallback(
185                                     this);
186                         }
187                     };
188                     callbacksToClear.add(callback);
189                     ((CameraInfoInternal) cameraInfo).addSessionCaptureCallback(
190                             CameraXExecutors.directExecutor(), callback);
191                     return "waitForCaptureResult";
192                 }
193         );
194     }
195 }
196