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