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.util.Range;
20 
21 import androidx.annotation.GuardedBy;
22 import androidx.annotation.VisibleForTesting;
23 import androidx.camera.core.CameraEffect;
24 import androidx.camera.core.Logger;
25 import androidx.camera.core.UseCase;
26 import androidx.camera.core.ViewPort;
27 import androidx.camera.core.concurrent.CameraCoordinator;
28 import androidx.camera.core.impl.CameraInternal;
29 import androidx.camera.core.internal.CameraUseCaseAdapter;
30 import androidx.core.util.Preconditions;
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 com.google.auto.value.AutoValue;
38 
39 import org.jspecify.annotations.NonNull;
40 import org.jspecify.annotations.Nullable;
41 
42 import java.util.ArrayDeque;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 
51 /**
52  * A repository of {@link LifecycleCamera} instances.
53  *
54  * <p> This repository maps each unique pair of {@link LifecycleOwner} and set of
55  * {@link CameraInternal} to a single LifecycleCamera.
56  *
57  * <p> The repository ensures that a LifecycleCamera can be active only when there is any use
58  * case bound on it. And, only a single LifecycleCamera is active at a time. A Lifecycle can
59  * control multiple LifecycleCameras. For the LifecycleCameras controlled by a single Lifecycle,
60  * only one LifecycleCamera among them can have use cases bound on it.
61  *
62  * <p> LifecycleCameras managed by the repository can be controlled by multiple Lifecycles. The
63  * repository ensures that a Lifecycle can be active only when any LifecycleCamera controlled by
64  * the Lifecycle has any use case bound on it. More than one Lifecycle can become ON_START at
65  * the same time. Only a single Lifecycle can be active at a time so if a Lifecycle becomes ON_START
66  * then it will take over as the current active Lifecycle. The original active Lifecycle will
67  * become inactive but is kept in the active Lifecycle array. When the Lifecycle of the most
68  * recently active camera stops then it will make sure that the next most recently started Lifecycle
69  * becomes the active Lifecycle.
70  *
71  * <p> A LifecycleCamera associated with the repository can also be released from the repository.
72  * When it is released, all UseCases bound to the LifecycleCamera will be unbound and the
73  * LifecycleCamera will be released.
74  */
75 final class LifecycleCameraRepository {
76     private static final String TAG = "LifecycleCameraRepository";
77     private static final Object INSTANCE_LOCK = new Object();
78     @GuardedBy("INSTANCE_LOCK")
79     private static LifecycleCameraRepository sInstance = null;
80 
81     private final Object mLock = new Object();
82 
83     @GuardedBy("mLock")
84     private final Map<Key, LifecycleCamera> mCameraMap = new HashMap<>();
85 
86     @GuardedBy("mLock")
87     private final Map<LifecycleCameraRepositoryObserver, Set<Key>> mLifecycleObserverMap =
88             new HashMap<>();
89 
90     @GuardedBy("mLock")
91     private final ArrayDeque<LifecycleOwner> mActiveLifecycleOwners = new ArrayDeque<>();
92 
93     @GuardedBy("mLock")
94 @Nullable CameraCoordinator mCameraCoordinator;
95 
96     @VisibleForTesting
LifecycleCameraRepository()97     LifecycleCameraRepository() {
98         // LifecycleCameraRepository is designed to be used as a singleton and the constructor
99         // should only be called for testing purpose.
100     }
101 
getInstance()102     static @NonNull LifecycleCameraRepository getInstance() {
103         synchronized (INSTANCE_LOCK) {
104             if (sInstance == null) {
105                 sInstance = new LifecycleCameraRepository();
106             }
107             return sInstance;
108         }
109     }
110 
111     /**
112      * Create a new {@link LifecycleCamera} associated with the given {@link LifecycleOwner}.
113      *
114      * <p>The {@link LifecycleCamera} is set to be an observer of the {@link
115      * LifecycleOwner}.
116      *
117      * @param lifecycleOwner       to associate with the LifecycleCamera
118      * @param cameraUseCaseAdaptor the CameraUseCaseAdapter to wrap in a LifecycleCamera
119      *
120      * @throws IllegalArgumentException if the LifecycleOwner is already in a destroyed state or
121      * if the repository already contains a LifecycleCamera that has the same LifecycleOwner and
122      * CameraInternal set as the CameraUseCaseAdapter.
123      */
createLifecycleCamera( @onNull LifecycleOwner lifecycleOwner, @NonNull CameraUseCaseAdapter cameraUseCaseAdaptor)124     LifecycleCamera createLifecycleCamera(
125             @NonNull LifecycleOwner lifecycleOwner,
126             @NonNull CameraUseCaseAdapter cameraUseCaseAdaptor) {
127         LifecycleCamera lifecycleCamera;
128         synchronized (mLock) {
129             Key key = Key.create(lifecycleOwner, cameraUseCaseAdaptor.getCameraId());
130             Preconditions.checkArgument(mCameraMap.get(key) == null, "LifecycleCamera already "
131                     + "exists for the given LifecycleOwner and set of cameras");
132 
133             // Need to add observer before creating LifecycleCamera to make sure
134             // it can be stopped before the latest active one is started.'
135             lifecycleCamera = new LifecycleCamera(lifecycleOwner, cameraUseCaseAdaptor);
136             // Suspend the LifecycleCamera if there is no use case bound.
137             if (cameraUseCaseAdaptor.getUseCases().isEmpty()) {
138                 lifecycleCamera.suspend();
139             }
140 
141             // If the lifecycle is already DESTROYED, we don't need to register the camera.
142             if (lifecycleOwner.getLifecycle().getCurrentState() == State.DESTROYED) {
143                 return lifecycleCamera;
144             }
145 
146             registerCamera(lifecycleCamera);
147         }
148         return lifecycleCamera;
149     }
150 
151     /**
152      * Get the {@link LifecycleCamera} which contains the same LifecycleOwner and a
153      * CameraUseCaseAdapter.CameraId.
154      *
155      * @return null if no such LifecycleCamera exists.
156      */
getLifecycleCamera(LifecycleOwner lifecycleOwner, CameraUseCaseAdapter.@NonNull CameraId cameraId )157     @Nullable LifecycleCamera getLifecycleCamera(LifecycleOwner lifecycleOwner,
158             CameraUseCaseAdapter.@NonNull CameraId cameraId
159     ) {
160         synchronized (mLock) {
161             return mCameraMap.get(Key.create(lifecycleOwner, cameraId));
162         }
163     }
164 
165     /**
166      * Returns all the {@link LifecycleCamera}s that have been created by the repository which
167      * haven't been destroyed yet.
168      */
getLifecycleCameras()169     Collection<LifecycleCamera> getLifecycleCameras() {
170         synchronized (mLock) {
171             return Collections.unmodifiableCollection(mCameraMap.values());
172         }
173     }
174 
175     /**
176      * Removes the {@link LifecycleCamera} from the repository.
177      */
removeLifecycleCameras(@ullable Set<Key> lifecycleCameraKeys)178     void removeLifecycleCameras(@Nullable Set<Key> lifecycleCameraKeys) {
179         synchronized (mLock) {
180             Set<Key> keysToUnbind =
181                     lifecycleCameraKeys == null ? mCameraMap.keySet() : lifecycleCameraKeys;
182 
183             for (Key key : keysToUnbind) {
184                 if (mCameraMap.containsKey(key)) {
185                     unregisterCamera(mCameraMap.get(key));
186                 }
187             }
188         }
189     }
190 
191     /**
192      * Clears out all of the {@link LifecycleCamera}s from the repository.
193      */
clear()194     void clear() {
195         synchronized (mLock) {
196             Set<LifecycleCameraRepositoryObserver> keySet =
197                     new HashSet<>(mLifecycleObserverMap.keySet());
198             for (LifecycleCameraRepositoryObserver observer : keySet) {
199                 unregisterLifecycle(observer.getLifecycleOwner());
200             }
201         }
202     }
203 
204     /**
205      * Registers the {@link LifecycleCamera} in the repository so that the repository ensures that
206      * only one camera is active at one time.
207      *
208      * <p>Multiple {@link LifecycleCamera}s may be controlled by a single {@link LifecycleOwner}.
209      * Only one lifecycle event observer will be created to monitor a LifecycleOwner's state events.
210      * When receiving a state event, the corresponding operations will be applied onto all
211      * {@link LifecycleCamera}s controlled by the same {@link LifecycleOwner}.
212      */
registerCamera(@onNull LifecycleCamera lifecycleCamera)213     private void registerCamera(@NonNull LifecycleCamera lifecycleCamera) {
214         synchronized (mLock) {
215             LifecycleOwner lifecycleOwner = lifecycleCamera.getLifecycleOwner();
216             Key key = Key.create(lifecycleOwner,
217                     lifecycleCamera.getCameraUseCaseAdapter().getCameraId());
218 
219             LifecycleCameraRepositoryObserver observer =
220                     getLifecycleCameraRepositoryObserver(lifecycleOwner);
221             Set<Key> lifecycleCameraKeySet;
222 
223             // Retrieves original or creates new key set.
224             if (observer != null) {
225                 lifecycleCameraKeySet = mLifecycleObserverMap.get(observer);
226             } else {
227                 lifecycleCameraKeySet = new HashSet<>();
228             }
229 
230             lifecycleCameraKeySet.add(key);
231             mCameraMap.put(key, lifecycleCamera);
232 
233             // Create and put new observer and key set into the map if it didn't exist.
234             if (observer == null) {
235                 observer = new LifecycleCameraRepositoryObserver(lifecycleOwner, this);
236                 mLifecycleObserverMap.put(observer, lifecycleCameraKeySet);
237                 lifecycleOwner.getLifecycle().addObserver(observer);
238             }
239         }
240     }
241 
unregisterCamera(@onNull LifecycleCamera lifecycleCamera)242     private void unregisterCamera(@NonNull LifecycleCamera lifecycleCamera) {
243         synchronized (mLock) {
244             LifecycleOwner lifecycleOwner = lifecycleCamera.getLifecycleOwner();
245             Key key = Key.create(lifecycleOwner,
246                     lifecycleCamera.getCameraUseCaseAdapter().getCameraId());
247             mCameraMap.remove(key);
248 
249             Set<LifecycleOwner> lifecycleOwnerToUnregister = new HashSet<>();
250             for (LifecycleCameraRepositoryObserver observer : mLifecycleObserverMap.keySet()) {
251                 if (lifecycleOwner.equals(observer.getLifecycleOwner())) {
252                     Set<Key> keySet = mLifecycleObserverMap.get(observer);
253                     keySet.remove(key);
254                     if (keySet.isEmpty()) {
255                         lifecycleOwnerToUnregister.add(observer.getLifecycleOwner());
256                     }
257                 }
258             }
259             for (LifecycleOwner owner : lifecycleOwnerToUnregister) {
260                 unregisterLifecycle(owner);
261             }
262         }
263     }
264 
265     /**
266      * Unregisters the Lifecycle from the repository so it is no longer tracked by the repository.
267      *
268      * <p>This does not stop the camera from receiving its lifecycle events. For the
269      * LifecycleCamera to stop receiving lifecycle event it must be done manually either before
270      * or after.
271      */
272     @SuppressWarnings("WeakerAccess") /* synthetic access */
unregisterLifecycle(LifecycleOwner lifecycleOwner)273     void unregisterLifecycle(LifecycleOwner lifecycleOwner) {
274         synchronized (mLock) {
275             LifecycleCameraRepositoryObserver observer =
276                     getLifecycleCameraRepositoryObserver(lifecycleOwner);
277 
278             // There is an error condition that can happen where onDestroy() can possibly be
279             // called twice if using Robolectric. The observer for the lifecycle would be removed
280             // in the first onDestroy() call and become null in the second onDestroy() call. It
281             // will cause an exception when executing the code after the null checker.
282             if (observer == null) {
283                 return;
284             }
285 
286             setInactive(lifecycleOwner);
287 
288             for (Key key : mLifecycleObserverMap.get(observer)) {
289                 mCameraMap.remove(key);
290             }
291 
292             mLifecycleObserverMap.remove(observer);
293             observer.getLifecycleOwner().getLifecycle().removeObserver(observer);
294 
295         }
296     }
297 
getLifecycleCameraRepositoryObserver( LifecycleOwner lifecycleOwner)298     private LifecycleCameraRepositoryObserver getLifecycleCameraRepositoryObserver(
299             LifecycleOwner lifecycleOwner) {
300         synchronized (mLock) {
301             for (LifecycleCameraRepositoryObserver observer : mLifecycleObserverMap.keySet()) {
302                 if (lifecycleOwner.equals(observer.getLifecycleOwner())) {
303                     return observer;
304                 }
305             }
306 
307             return null;
308         }
309     }
310 
311     /**
312      * Binds the use cases to the specified LifecycleCamera.
313      *
314      * <p>The LifecycleCamera will become active if its Lifecycle state is ON_START. When
315      * multiple LifecycleCameras are controlled by the same Lifecycle, only one LifecycleCamera
316      * can have use cases bound and become active. When multiple Lifecycles are in ON_START
317      * state, only the most recently started Lifecycle which has any LifecycleCamera with use
318      * case bound can become active.
319      *
320      * @param lifecycleCamera The LifecycleCamera which the use cases will be bound to.
321      * @param viewPort The viewport which represents the visible camera sensor rect.
322      * @param effects The effects applied to the camera outputs.
323      * @param targetHighSpeedFrameRate The target high speed frame rate.
324      * @param useCases The use cases to bind to a lifecycle.
325      * @param cameraCoordinator The {@link CameraCoordinator} for concurrent camera mode.
326      *
327      * @throws IllegalArgumentException If multiple LifecycleCameras with use cases are
328      * registered to the same LifecycleOwner. Or all use cases will exceed the capability of the
329      * camera after binding them to the LifecycleCamera.
330      */
bindToLifecycleCamera( @onNull LifecycleCamera lifecycleCamera, @Nullable ViewPort viewPort, @NonNull List<CameraEffect> effects, @NonNull Range<Integer> targetHighSpeedFrameRate, @NonNull Collection<UseCase> useCases, @Nullable CameraCoordinator cameraCoordinator)331     void bindToLifecycleCamera(
332             @NonNull LifecycleCamera lifecycleCamera,
333             @Nullable ViewPort viewPort,
334             @NonNull List<CameraEffect> effects,
335             @NonNull Range<Integer> targetHighSpeedFrameRate,
336             @NonNull Collection<UseCase> useCases,
337             @Nullable CameraCoordinator cameraCoordinator) {
338         synchronized (mLock) {
339             Preconditions.checkArgument(!useCases.isEmpty());
340             mCameraCoordinator = cameraCoordinator;
341             LifecycleOwner lifecycleOwner = lifecycleCamera.getLifecycleOwner();
342             // Disallow multiple LifecycleCameras with use cases to be registered to the same
343             // LifecycleOwner.
344             LifecycleCameraRepositoryObserver observer =
345                     getLifecycleCameraRepositoryObserver(lifecycleOwner);
346             if (observer == null) {
347                 // LifecycleCamera is not registered due to lifecycle destroyed, simply do nothing.
348                 return;
349             }
350             Set<Key> lifecycleCameraKeySet = mLifecycleObserverMap.get(observer);
351 
352             // Bypass the use cases lifecycle owner validation when concurrent camera mode is on.
353             // In concurrent camera mode, we allow multiple cameras registered to the same
354             // lifecycle owner.
355             if (mCameraCoordinator == null || (mCameraCoordinator.getCameraOperatingMode()
356                     != CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT)) {
357                 for (Key key : lifecycleCameraKeySet) {
358                     LifecycleCamera camera = Preconditions.checkNotNull(mCameraMap.get(key));
359                     if (!camera.equals(lifecycleCamera) && !camera.getUseCases().isEmpty()) {
360                         throw new IllegalArgumentException(
361                                 "Multiple LifecycleCameras with use cases "
362                                         + "are registered to the same LifecycleOwner.");
363                     }
364                 }
365             }
366 
367             try {
368                 lifecycleCamera.getCameraUseCaseAdapter().setViewPort(viewPort);
369                 lifecycleCamera.getCameraUseCaseAdapter().setEffects(effects);
370                 lifecycleCamera.getCameraUseCaseAdapter()
371                         .setTargetHighSpeedFrameRate(targetHighSpeedFrameRate);
372                 lifecycleCamera.bind(useCases);
373             } catch (CameraUseCaseAdapter.CameraException e) {
374                 throw new IllegalArgumentException(e);
375             }
376 
377             // The target LifecycleCamera has use case bound. If the target LifecycleOwner has been
378             // started, set the target LifecycleOwner as active.
379             if (lifecycleOwner.getLifecycle().getCurrentState().isAtLeast(
380                     Lifecycle.State.STARTED)) {
381                 setActive(lifecycleOwner);
382             }
383         }
384     }
385 
386     /**
387      * Unbinds all specified use cases from the LifecycleCameras managed by the repository.
388      *
389      * <p>If a LifecycleCamera is active but all use cases are removed at the end of this call,
390      * the LifecycleCamera will become inactive. This will also initiate a close of the existing
391      * open camera since there is zero {@link UseCase} associated with it.
392      *
393      * <p>If a use case in the argument list is not bound, then it is simply ignored.
394      *
395      * @param useCases The collection of use cases to remove.
396      */
unbind(@onNull Collection<UseCase> useCases)397     void unbind(@NonNull Collection<UseCase> useCases) {
398         unbind(useCases, null);
399     }
400 
401     /**
402      * Unbinds all specified use cases from the given {@link LifecycleCamera}s.
403      *
404      * <p>If the given {@link LifecycleCamera} set is {@code null}, this method will unbind the use
405      * cases from all the {@link LifecycleCamera}s managed by the repository.
406      *
407      * <p>If the {@link LifecycleCamera} isn't contained in the repository, it will be no-op for
408      * that {@link LifecycleCamera}.
409      *
410      * @param useCases The collection of use cases to remove.
411      * @param lifecycleCameraKeys The keys of {@link LifecycleCamera} to unbind the use cases from.
412      */
unbind(@onNull Collection<UseCase> useCases, @Nullable Set<Key> lifecycleCameraKeys)413     void unbind(@NonNull Collection<UseCase> useCases, @Nullable Set<Key> lifecycleCameraKeys) {
414         synchronized (mLock) {
415             Set<Key> keysToUnbind =
416                     lifecycleCameraKeys == null ? mCameraMap.keySet() : lifecycleCameraKeys;
417             for (Key key : keysToUnbind) {
418                 if (mCameraMap.containsKey(key)) {
419                     LifecycleCamera lifecycleCamera = mCameraMap.get(key);
420                     boolean hasUseCase = !lifecycleCamera.getUseCases().isEmpty();
421                     lifecycleCamera.unbind(useCases);
422 
423                     // For a LifecycleOwner, there can be only one LifecycleCamera with use cases
424                     // bound. Set the target LifecycleOwner as inactive if the LifecycleCamera
425                     // originally had use cases bound but now all use cases have been unbound.
426                     if (hasUseCase && lifecycleCamera.getUseCases().isEmpty()) {
427                         setInactive(lifecycleCamera.getLifecycleOwner());
428                     }
429                 } else {
430                     Logger.w(TAG, "Attempt to unbind use cases from an invalid camera.");
431                 }
432             }
433         }
434     }
435 
436     /**
437      * Unbinds all use cases from all LifecycleCameras managed by the repository.
438      *
439      * <p>All LifecycleCameras will become inactive after all use cases are unbound. All
440      * Lifecycles that control LifecycleCameras will also be removed from the active Lifecycle
441      * array.
442      *
443      * <p>This will initiate a close of the existing open camera.
444      */
unbindAll()445     void unbindAll() {
446         unbindAll(null);
447     }
448 
449     /**
450      * Unbinds all use cases from the given {@link LifecycleCamera}s.
451      *
452      * <p>If the given {@link LifecycleCamera} set is {@code null}, this method will unbind all use
453      * cases from all the {@link LifecycleCamera}s managed by the repository.
454      *
455      * @param lifecycleCameraKeys The keys of {@link LifecycleCamera} to unbind all use cases from.
456      */
unbindAll(@ullable Set<Key> lifecycleCameraKeys)457     void unbindAll(@Nullable Set<Key> lifecycleCameraKeys) {
458         synchronized (mLock) {
459             Set<Key> keysToUnbind =
460                     lifecycleCameraKeys == null ? mCameraMap.keySet() : lifecycleCameraKeys;
461             for (Key key : keysToUnbind) {
462                 LifecycleCamera lifecycleCamera = mCameraMap.get(key);
463                 if (lifecycleCamera != null) {
464                     lifecycleCamera.unbindAll();
465                     setInactive(lifecycleCamera.getLifecycleOwner());
466                 }
467             }
468         }
469     }
470 
471     /**
472      * Makes the LifecycleCamera which is controlled by the LifecycleOwner and has use case bound
473      * become active.
474      *
475      * <p>If there is another LifecycleOwner still in ON_START state, it will be put into the
476      * active LifecycleOwner array. The LifecycleCamera controlled by that LifecycleOwner will be
477      * suspended and become inactive.
478      *
479      * <p>If no use case is bound to any LifecycleCamera controlled by the target LifecycleOwner,
480      * all LifecycleCameras will keep their original status.
481      */
482     @SuppressWarnings("WeakerAccess") /* synthetic access */
setActive(LifecycleOwner lifecycleOwner)483     void setActive(LifecycleOwner lifecycleOwner) {
484         synchronized (mLock) {
485             // Returns if no use case is bound to any LifecycleCamera controlled by the target
486             // LifecycleOwner
487             if (!hasUseCaseBound(lifecycleOwner)) {
488                 return;
489             }
490 
491             // Only keep LifecycleCameras controlled by the last {@link LifecycleOwner} active.
492             // Stop the others.
493             if (mActiveLifecycleOwners.isEmpty()) {
494                 mActiveLifecycleOwners.push(lifecycleOwner);
495             } else {
496                 // Bypass the use cases suspending when concurrent camera mode is on.
497                 // In concurrent camera mode, we allow multiple cameras registered to the same
498                 // lifecycle owner.
499                 if (mCameraCoordinator == null || (mCameraCoordinator.getCameraOperatingMode()
500                         != CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT)) {
501                     LifecycleOwner currentActiveLifecycleOwner = mActiveLifecycleOwners.peek();
502                     if (!lifecycleOwner.equals(currentActiveLifecycleOwner)) {
503                         suspendUseCases(currentActiveLifecycleOwner);
504 
505                         mActiveLifecycleOwners.remove(lifecycleOwner);
506                         mActiveLifecycleOwners.push(lifecycleOwner);
507                     }
508                 }
509             }
510 
511             unsuspendUseCases(lifecycleOwner);
512         }
513     }
514 
515     /**
516      * Makes all LifecycleCameras controlled by the LifecycleOwner become inactive.
517      *
518      * <p>If the LifecycleOwner was the current active LifecycleOwner then the next most recent
519      * active LifecycleOwner in the active LifecycleOwner array will replace it to become the
520      * active one.
521      */
522     @SuppressWarnings("WeakerAccess") /* synthetic access */
setInactive(LifecycleOwner lifecycleOwner)523     void setInactive(LifecycleOwner lifecycleOwner) {
524         synchronized (mLock) {
525             // Removes stopped lifecycleOwner from active list.
526             mActiveLifecycleOwners.remove(lifecycleOwner);
527             suspendUseCases(lifecycleOwner);
528 
529             // Start up LifecycleCameras controlled by the next LifecycleOwner if there are still
530             // active LifecycleOwners.
531             if (!mActiveLifecycleOwners.isEmpty()) {
532                 LifecycleOwner newActiveLifecycleOwner = mActiveLifecycleOwners.peek();
533                 unsuspendUseCases(newActiveLifecycleOwner);
534             }
535         }
536     }
537 
538     /**
539      * Checks whether any LifecycleCamera controlled by the LifecycleOwner has any use case bound.
540      */
hasUseCaseBound(LifecycleOwner lifecycleOwner)541     private boolean hasUseCaseBound(LifecycleOwner lifecycleOwner) {
542         synchronized (mLock) {
543             LifecycleCameraRepositoryObserver observer =
544                     getLifecycleCameraRepositoryObserver(lifecycleOwner);
545 
546             if (observer == null) {
547                 return false;
548             }
549 
550             Set<Key> lifecycleCameraKeySet = mLifecycleObserverMap.get(observer);
551 
552             // Checks whether any LifecycleCamera controlled by the LifecycleOwner has any
553             // use case bound.
554             for (Key key : lifecycleCameraKeySet) {
555                 if (!Preconditions.checkNotNull(mCameraMap.get(key)).getUseCases().isEmpty()) {
556                     return true;
557                 }
558             }
559 
560             return false;
561         }
562     }
563 
564     /**
565      * Suspends all LifecycleCameras controlled by the LifecycleOwner.
566      */
suspendUseCases(LifecycleOwner lifecycleOwner)567     private void suspendUseCases(LifecycleOwner lifecycleOwner) {
568         synchronized (mLock) {
569             LifecycleCameraRepositoryObserver observer =
570                     getLifecycleCameraRepositoryObserver(lifecycleOwner);
571 
572             // An observer should be created when the lifecycleOwner was first time used to bind
573             // use cases to a camera. It should be removed from the mLifecycleObserverMap when an
574             // ON_DESTROY event is received from the observer. Normally, this should be not null
575             // when this function is called from LifecycleCameraRepositoryObserver#onStop, but it
576             // seems like it might happen in the real world. See b/222105787.
577             if (observer == null) {
578                 return;
579             }
580 
581             for (Key key : mLifecycleObserverMap.get(observer)) {
582                 Preconditions.checkNotNull(mCameraMap.get(key)).suspend();
583             }
584         }
585     }
586 
587     /**
588      * Unsuspends all LifecycleCameras controlled by the LifecycleOwner.
589      *
590      * <p>A LifecycleCamera can be unsuspended only when there is any use case bound on it.
591      */
unsuspendUseCases(LifecycleOwner lifecycleOwner)592     private void unsuspendUseCases(LifecycleOwner lifecycleOwner) {
593         synchronized (mLock) {
594             LifecycleCameraRepositoryObserver observer =
595                     getLifecycleCameraRepositoryObserver(lifecycleOwner);
596 
597             for (Key key : mLifecycleObserverMap.get(observer)) {
598                 LifecycleCamera lifecycleCamera = mCameraMap.get(key);
599                 // Only LifecycleCamera with use cases bound can be active.
600                 if (!Preconditions.checkNotNull(lifecycleCamera).getUseCases().isEmpty()) {
601                     lifecycleCamera.unsuspend();
602                 }
603             }
604         }
605     }
606 
607     /**
608      * A key for mapping a {@link LifecycleOwner} and a {@link CameraUseCaseAdapter.CameraId} to a
609      * {@link LifecycleCamera}.
610      */
611     @AutoValue
612     abstract static class Key {
create(@onNull LifecycleOwner lifecycleOwner, CameraUseCaseAdapter.@NonNull CameraId cameraId)613         static Key create(@NonNull LifecycleOwner lifecycleOwner,
614                 CameraUseCaseAdapter.@NonNull CameraId cameraId) {
615             return new AutoValue_LifecycleCameraRepository_Key(
616                     lifecycleOwner, cameraId);
617         }
618 
getLifecycleOwner()619         public abstract @NonNull LifecycleOwner getLifecycleOwner();
620 
getCameraId()621         public abstract CameraUseCaseAdapter.@NonNull CameraId getCameraId();
622     }
623 
624     private static class LifecycleCameraRepositoryObserver implements LifecycleObserver {
625         private final LifecycleCameraRepository mLifecycleCameraRepository;
626         private final LifecycleOwner mLifecycleOwner;
627 
LifecycleCameraRepositoryObserver(LifecycleOwner lifecycleOwner, LifecycleCameraRepository lifecycleCameraRepository)628         LifecycleCameraRepositoryObserver(LifecycleOwner lifecycleOwner,
629                 LifecycleCameraRepository lifecycleCameraRepository) {
630             mLifecycleOwner = lifecycleOwner;
631             mLifecycleCameraRepository = lifecycleCameraRepository;
632         }
633 
getLifecycleOwner()634         LifecycleOwner getLifecycleOwner() {
635             return mLifecycleOwner;
636         }
637 
638         /**
639          * Monitors which {@link LifecycleOwner} receives an ON_START event and then stop
640          * other {@link LifecycleCamera} to keep only one active at a time.
641          */
642         @SuppressWarnings("unused") // Called via OnLifecycleEvent
643         @OnLifecycleEvent(Lifecycle.Event.ON_START)
onStart(LifecycleOwner lifecycleOwner)644         public void onStart(LifecycleOwner lifecycleOwner) {
645             mLifecycleCameraRepository.setActive(lifecycleOwner);
646         }
647 
648         /**
649          * Monitors which {@link LifecycleOwner} receives an ON_STOP event.
650          */
651         @SuppressWarnings("unused") // Called via OnLifecycleEvent
652         @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
onStop(LifecycleOwner lifecycleOwner)653         public void onStop(LifecycleOwner lifecycleOwner) {
654             mLifecycleCameraRepository.setInactive(lifecycleOwner);
655         }
656 
657         /**
658          * Monitors which {@link LifecycleOwner} receives an ON_DESTROY event and then
659          * removes any {@link LifecycleCamera} associated with it from this
660          * repository when that lifecycle is destroyed.
661          */
662         @SuppressWarnings("unused") // Called via OnLifecycleEvent
663         @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
onDestroy(LifecycleOwner lifecycleOwner)664         public void onDestroy(LifecycleOwner lifecycleOwner) {
665             mLifecycleCameraRepository.unregisterLifecycle(lifecycleOwner);
666         }
667     }
668 }
669