/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.camera.lifecycle; import android.annotation.SuppressLint; import android.os.Build; import androidx.annotation.GuardedBy; import androidx.annotation.RestrictTo; import androidx.annotation.VisibleForTesting; import androidx.camera.core.Camera; import androidx.camera.core.CameraControl; import androidx.camera.core.CameraInfo; import androidx.camera.core.UseCase; import androidx.camera.core.impl.CameraConfig; import androidx.camera.core.internal.CameraUseCaseAdapter; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle.State; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.OnLifecycleEvent; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * A {@link CameraUseCaseAdapter} whose starting and stopping is controlled by a * {@link Lifecycle}. */ @SuppressLint("UsesNonDefaultVisibleForTesting") @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public final class LifecycleCamera implements LifecycleObserver, Camera { private final Object mLock = new Object(); @GuardedBy("mLock") // The lifecycle that controls the LifecycleCamera private final LifecycleOwner mLifecycleOwner; private final CameraUseCaseAdapter mCameraUseCaseAdapter; @GuardedBy("mLock") private volatile boolean mIsActive = false; @GuardedBy("mLock") private boolean mSuspended = false; @GuardedBy("mLock") private boolean mReleased = false; /** * Wraps an existing {@link CameraUseCaseAdapter} so it is controlled by lifecycle transitions. */ LifecycleCamera(LifecycleOwner lifecycleOwner, CameraUseCaseAdapter cameraUseCaseAdaptor) { mLifecycleOwner = lifecycleOwner; mCameraUseCaseAdapter = cameraUseCaseAdaptor; // Make sure that the attach state of mCameraUseCaseAdapter matches that of the lifecycle if (mLifecycleOwner.getLifecycle().getCurrentState().isAtLeast(State.STARTED)) { mCameraUseCaseAdapter.attachUseCases(); } else { mCameraUseCaseAdapter.detachUseCases(); } lifecycleOwner.getLifecycle().addObserver(this); } @OnLifecycleEvent(Lifecycle.Event.ON_START) public void onStart(@NonNull LifecycleOwner lifecycleOwner) { synchronized (mLock) { if (!mSuspended && !mReleased) { mCameraUseCaseAdapter.attachUseCases(); mIsActive = true; } } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onStop(@NonNull LifecycleOwner lifecycleOwner) { synchronized (mLock) { if (!mSuspended && !mReleased) { mCameraUseCaseAdapter.detachUseCases(); mIsActive = false; } } } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void onDestroy(@NonNull LifecycleOwner lifecycleOwner) { synchronized (mLock) { mCameraUseCaseAdapter.removeUseCases(mCameraUseCaseAdapter.getUseCases()); } } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume(@NonNull LifecycleOwner lifecycleOwner) { // ActiveResumingMode is required for Multi-window which is supported since Android 7(N). if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mCameraUseCaseAdapter.setActiveResumingMode(true); } } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause(@NonNull LifecycleOwner lifecycleOwner) { // ActiveResumingMode is required for Multi-window which is supported since Android 7(N). if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mCameraUseCaseAdapter.setActiveResumingMode(false); } } /** * Suspend the camera so that it ignore lifecycle events. * *
This will also close the {@link CameraUseCaseAdapter}. * *
This will be idempotent if the camera is already suspended. */ public void suspend() { synchronized (mLock) { if (mSuspended) { return; } onStop(mLifecycleOwner); mSuspended = true; } } /** * Unsuspend the camera so it will start listening to lifecycle events. * *
This will also open the {@link CameraUseCaseAdapter} if the lifecycle is in a STARTED * state or above. * *
This will be idempotent if the camera is already in an unsuspended state.
*/
public void unsuspend() {
synchronized (mLock) {
if (!mSuspended) {
return;
}
mSuspended = false;
if (mLifecycleOwner.getLifecycle().getCurrentState().isAtLeast(State.STARTED)) {
onStart(mLifecycleOwner);
}
}
}
// TODO(b/154939118) remove when Extension.setExtension() is implemented since there no
// longer is a need to check if the camera is active.
public boolean isActive() {
synchronized (mLock) {
return mIsActive;
}
}
public boolean isBound(@NonNull UseCase useCase) {
synchronized (mLock) {
return mCameraUseCaseAdapter.getUseCases().contains(useCase);
}
}
public @NonNull List This will detach all UseCases from the CameraUseCaseAdapter.
*/
void unbindAll() {
synchronized (mLock) {
mCameraUseCaseAdapter.removeUseCases(mCameraUseCaseAdapter.getUseCases());
}
}
/**
* Stops observing lifecycle changes.
*
* Once released the wrapped {@link LifecycleCamera} is still valid, but will no longer be
* triggered by lifecycle state transitions. In order to observe lifecycle changes again a new
* {@link LifecycleCamera} instance should be created.
*
* Calls subsequent to the first time will do nothing.
*/
void release() {
synchronized (mLock) {
mReleased = true;
mIsActive = false;
mLifecycleOwner.getLifecycle().removeObserver(this);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Camera interface
////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public @NonNull CameraControl getCameraControl() {
return mCameraUseCaseAdapter.getCameraControl();
}
@Override
public @NonNull CameraInfo getCameraInfo() {
return mCameraUseCaseAdapter.getCameraInfo();
}
@Nullable CameraInfo getSecondaryCameraInfo() {
return mCameraUseCaseAdapter.getSecondaryCameraInfo();
}
@Override
public @NonNull CameraConfig getExtendedConfig() {
return mCameraUseCaseAdapter.getExtendedConfig();
}
@Override
public boolean isUseCasesCombinationSupported(boolean withStreamSharing,
UseCase @NonNull ... useCases) {
return mCameraUseCaseAdapter.isUseCasesCombinationSupported(withStreamSharing, useCases);
}
}