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.lifecycle; 18 19 import android.annotation.SuppressLint; 20 import android.os.Build; 21 22 import androidx.annotation.GuardedBy; 23 import androidx.annotation.RestrictTo; 24 import androidx.annotation.VisibleForTesting; 25 import androidx.camera.core.Camera; 26 import androidx.camera.core.CameraControl; 27 import androidx.camera.core.CameraInfo; 28 import androidx.camera.core.UseCase; 29 import androidx.camera.core.impl.CameraConfig; 30 import androidx.camera.core.internal.CameraUseCaseAdapter; 31 import androidx.lifecycle.Lifecycle; 32 import androidx.lifecycle.Lifecycle.State; 33 import androidx.lifecycle.LifecycleObserver; 34 import androidx.lifecycle.LifecycleOwner; 35 import androidx.lifecycle.OnLifecycleEvent; 36 37 import org.jspecify.annotations.NonNull; 38 import org.jspecify.annotations.Nullable; 39 40 import java.util.ArrayList; 41 import java.util.Collection; 42 import java.util.Collections; 43 import java.util.List; 44 45 /** 46 * A {@link CameraUseCaseAdapter} whose starting and stopping is controlled by a 47 * {@link Lifecycle}. 48 */ 49 @SuppressLint("UsesNonDefaultVisibleForTesting") 50 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 51 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 52 public final class LifecycleCamera implements LifecycleObserver, Camera { 53 private final Object mLock = new Object(); 54 55 @GuardedBy("mLock") 56 // The lifecycle that controls the LifecycleCamera 57 private final LifecycleOwner mLifecycleOwner; 58 59 private final CameraUseCaseAdapter mCameraUseCaseAdapter; 60 61 @GuardedBy("mLock") 62 private volatile boolean mIsActive = false; 63 64 @GuardedBy("mLock") 65 private boolean mSuspended = false; 66 67 @GuardedBy("mLock") 68 private boolean mReleased = false; 69 70 /** 71 * Wraps an existing {@link CameraUseCaseAdapter} so it is controlled by lifecycle transitions. 72 */ LifecycleCamera(LifecycleOwner lifecycleOwner, CameraUseCaseAdapter cameraUseCaseAdaptor)73 LifecycleCamera(LifecycleOwner lifecycleOwner, CameraUseCaseAdapter cameraUseCaseAdaptor) { 74 mLifecycleOwner = lifecycleOwner; 75 mCameraUseCaseAdapter = cameraUseCaseAdaptor; 76 77 // Make sure that the attach state of mCameraUseCaseAdapter matches that of the lifecycle 78 if (mLifecycleOwner.getLifecycle().getCurrentState().isAtLeast(State.STARTED)) { 79 mCameraUseCaseAdapter.attachUseCases(); 80 } else { 81 mCameraUseCaseAdapter.detachUseCases(); 82 } 83 lifecycleOwner.getLifecycle().addObserver(this); 84 } 85 86 @OnLifecycleEvent(Lifecycle.Event.ON_START) onStart(@onNull LifecycleOwner lifecycleOwner)87 public void onStart(@NonNull LifecycleOwner lifecycleOwner) { 88 synchronized (mLock) { 89 if (!mSuspended && !mReleased) { 90 mCameraUseCaseAdapter.attachUseCases(); 91 mIsActive = true; 92 } 93 } 94 } 95 96 @OnLifecycleEvent(Lifecycle.Event.ON_STOP) onStop(@onNull LifecycleOwner lifecycleOwner)97 public void onStop(@NonNull LifecycleOwner lifecycleOwner) { 98 synchronized (mLock) { 99 if (!mSuspended && !mReleased) { 100 mCameraUseCaseAdapter.detachUseCases(); 101 mIsActive = false; 102 } 103 } 104 } 105 106 @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) onDestroy(@onNull LifecycleOwner lifecycleOwner)107 public void onDestroy(@NonNull LifecycleOwner lifecycleOwner) { 108 synchronized (mLock) { 109 mCameraUseCaseAdapter.removeUseCases(mCameraUseCaseAdapter.getUseCases()); 110 } 111 } 112 113 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) onResume(@onNull LifecycleOwner lifecycleOwner)114 public void onResume(@NonNull LifecycleOwner lifecycleOwner) { 115 // ActiveResumingMode is required for Multi-window which is supported since Android 7(N). 116 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 117 mCameraUseCaseAdapter.setActiveResumingMode(true); 118 } 119 } 120 121 @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) onPause(@onNull LifecycleOwner lifecycleOwner)122 public void onPause(@NonNull LifecycleOwner lifecycleOwner) { 123 // ActiveResumingMode is required for Multi-window which is supported since Android 7(N). 124 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 125 mCameraUseCaseAdapter.setActiveResumingMode(false); 126 } 127 } 128 129 130 /** 131 * Suspend the camera so that it ignore lifecycle events. 132 * 133 * <p> This will also close the {@link CameraUseCaseAdapter}. 134 * 135 * <p> This will be idempotent if the camera is already suspended. 136 */ suspend()137 public void suspend() { 138 synchronized (mLock) { 139 if (mSuspended) { 140 return; 141 } 142 143 onStop(mLifecycleOwner); 144 mSuspended = true; 145 } 146 } 147 148 /** 149 * Unsuspend the camera so it will start listening to lifecycle events. 150 * 151 * <p> This will also open the {@link CameraUseCaseAdapter} if the lifecycle is in a STARTED 152 * state or above. 153 * 154 * <p> This will be idempotent if the camera is already in an unsuspended state. 155 */ unsuspend()156 public void unsuspend() { 157 synchronized (mLock) { 158 if (!mSuspended) { 159 return; 160 } 161 162 mSuspended = false; 163 if (mLifecycleOwner.getLifecycle().getCurrentState().isAtLeast(State.STARTED)) { 164 onStart(mLifecycleOwner); 165 } 166 } 167 } 168 169 // TODO(b/154939118) remove when Extension.setExtension() is implemented since there no 170 // longer is a need to check if the camera is active. isActive()171 public boolean isActive() { 172 synchronized (mLock) { 173 return mIsActive; 174 } 175 } 176 isBound(@onNull UseCase useCase)177 public boolean isBound(@NonNull UseCase useCase) { 178 synchronized (mLock) { 179 return mCameraUseCaseAdapter.getUseCases().contains(useCase); 180 } 181 } 182 getUseCases()183 public @NonNull List<UseCase> getUseCases() { 184 synchronized (mLock) { 185 return Collections.unmodifiableList(mCameraUseCaseAdapter.getUseCases()); 186 } 187 } 188 189 /** 190 * Retrieves the lifecycle owner. 191 */ getLifecycleOwner()192 public @NonNull LifecycleOwner getLifecycleOwner() { 193 synchronized (mLock) { 194 return mLifecycleOwner; 195 } 196 } 197 getCameraUseCaseAdapter()198 public @NonNull CameraUseCaseAdapter getCameraUseCaseAdapter() { 199 return mCameraUseCaseAdapter; 200 } 201 202 /** 203 * Bind the UseCases to the lifecycle camera. 204 * 205 * <>This will attach the UseCases to the CameraUseCaseAdapter if successful. 206 * 207 * @throws CameraUseCaseAdapter.CameraException if unable to attach the UseCase to the camera. 208 */ bind(Collection<UseCase> useCases)209 void bind(Collection<UseCase> useCases) throws CameraUseCaseAdapter.CameraException { 210 synchronized (mLock) { 211 mCameraUseCaseAdapter.addUseCases(useCases); 212 } 213 } 214 215 /** 216 * Unbind the UseCases from the lifecycle camera. 217 * 218 * <>This will detach the UseCases from the CameraUseCaseAdapter. 219 */ unbind(Collection<UseCase> useCases)220 void unbind(Collection<UseCase> useCases) { 221 synchronized (mLock) { 222 List<UseCase> useCasesToRemove = new ArrayList<>(useCases); 223 useCasesToRemove.retainAll(mCameraUseCaseAdapter.getUseCases()); 224 mCameraUseCaseAdapter.removeUseCases(useCasesToRemove); 225 } 226 } 227 228 /** 229 * Unbind all of the UseCases from the lifecycle camera. 230 * 231 * <p>This will detach all UseCases from the CameraUseCaseAdapter. 232 */ unbindAll()233 void unbindAll() { 234 synchronized (mLock) { 235 mCameraUseCaseAdapter.removeUseCases(mCameraUseCaseAdapter.getUseCases()); 236 } 237 } 238 239 /** 240 * Stops observing lifecycle changes. 241 * 242 * <p>Once released the wrapped {@link LifecycleCamera} is still valid, but will no longer be 243 * triggered by lifecycle state transitions. In order to observe lifecycle changes again a new 244 * {@link LifecycleCamera} instance should be created. 245 * 246 * <p>Calls subsequent to the first time will do nothing. 247 */ release()248 void release() { 249 synchronized (mLock) { 250 mReleased = true; 251 mIsActive = false; 252 mLifecycleOwner.getLifecycle().removeObserver(this); 253 } 254 } 255 256 //////////////////////////////////////////////////////////////////////////////////////////////// 257 // Camera interface 258 //////////////////////////////////////////////////////////////////////////////////////////////// 259 @Override getCameraControl()260 public @NonNull CameraControl getCameraControl() { 261 return mCameraUseCaseAdapter.getCameraControl(); 262 } 263 264 @Override getCameraInfo()265 public @NonNull CameraInfo getCameraInfo() { 266 return mCameraUseCaseAdapter.getCameraInfo(); 267 } 268 getSecondaryCameraInfo()269 @Nullable CameraInfo getSecondaryCameraInfo() { 270 return mCameraUseCaseAdapter.getSecondaryCameraInfo(); 271 } 272 273 @Override getExtendedConfig()274 public @NonNull CameraConfig getExtendedConfig() { 275 return mCameraUseCaseAdapter.getExtendedConfig(); 276 } 277 278 @Override isUseCasesCombinationSupported(boolean withStreamSharing, UseCase @NonNull ... useCases)279 public boolean isUseCasesCombinationSupported(boolean withStreamSharing, 280 UseCase @NonNull ... useCases) { 281 return mCameraUseCaseAdapter.isUseCasesCombinationSupported(withStreamSharing, useCases); 282 } 283 } 284