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