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