[go: nahoru, domu]

blob: ff8644a044899be8d8fc52c45b1c476b7307e15d [file] [log] [blame]
/*
* Copyright (C) 2019 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.core;
import android.app.Application;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.util.Size;
import androidx.annotation.GuardedBy;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.camera.core.impl.CameraDeviceConfig;
import androidx.camera.core.impl.CameraDeviceSurfaceManager;
import androidx.camera.core.impl.CameraFactory;
import androidx.camera.core.impl.CameraIdFilter;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.CameraRepository;
import androidx.camera.core.impl.UseCaseConfigFactory;
import androidx.camera.core.impl.utils.Threads;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.FutureChain;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.util.Preconditions;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Main interface for accessing CameraX library.
*
* <p>This is a singleton class responsible for managing the set of camera instances and
* attached use cases (such as {@link Preview}, {@link ImageAnalysis}, or {@link ImageCapture}.
* Use cases are bound to a {@link LifecycleOwner} by calling
* {@code #bindToLifecycle(LifecycleOwner, CameraSelector, UseCase...)} Once bound, the
* lifecycle of the {@link LifecycleOwner} determines when the camera is started and stopped, and
* when camera data is available to the use case.
*
* <p>It is often sufficient to just bind the use cases once when the activity is created, and
* let the lifecycle handle the rest, so application code generally does not need to call
* {@code #unbind(UseCase...)} nor call {@code #bindToLifecycle} more than once.
*
* <p>A lifecycle transition from {@link Lifecycle.State#CREATED} to {@link Lifecycle.State#STARTED}
* state (via {@link Lifecycle.Event#ON_START}) initializes the camera asynchronously on a
* CameraX managed thread. After initialization, the camera is opened and a camera capture
* session is created. If a {@link Preview} or {@link ImageAnalysis} is bound, those use cases
* will begin to receive camera data after initialization completes. {@link ImageCapture} can
* receive data via specific calls (such as {@link ImageCapture#takePicture}) after initialization
* completes. Calling #bindToLifecycle with no Use Cases does nothing.
*
* <p>Binding to a {@link LifecycleOwner} when the state is {@link Lifecycle.State#STARTED} or
* greater will also initialize and start data capture as though an
* {@link Lifecycle.Event#ON_START} transition had occurred. If the camera was already running
* this may cause a new initialization to occur, temporarily stopping data from the camera before
* restarting it.
*
* <p>After a lifecycle transition from {@link Lifecycle.State#STARTED} to
* {@link Lifecycle.State#CREATED} state (via {@link Lifecycle.Event#ON_STOP}), use cases will no
* longer receive camera data. The camera capture session is destroyed and the camera device is
* closed. Use cases can remain bound and will become active again on the next
* {@link Lifecycle.Event#ON_START} transition.
*
* <p>When the lifecycle transitions from {@link Lifecycle.State#CREATED} to the
* {@link Lifecycle.State#DESTROYED} state (via {@link Lifecycle.Event#ON_DESTROY}) any
* bound use cases are unbound and use case resources are freed. Calls to #bindToLifecycle
* when the lifecycle is in the {@link Lifecycle.State#DESTROYED} state will fail.
* A call to #bindToLifecycle will need to be made with another lifecycle to rebind the
* UseCase that has been unbound.
*
* <p>If the camera is not already closed, unbinding all use cases will cause the camera to close
* asynchronously.
*
* <pre>{@code
* public void setup() {
* // Initialize UseCase
* useCase = ...;
*
* // Select a camera
* cameraSelector = ...;
*
* // UseCase binding event
* CameraX.bindToLifecycle(lifecycleOwner, cameraSelector, useCase);
*
* // Make calls on useCase
* }
*
* public void operateOnUseCase() {
* if (CameraX.isBound(useCase)) {
* // Make calls on useCase
* }
* }
*
* public void prematureTearDown() {
* // Not required since the lifecycle automatically stops the use case. Can be used to
* // disassociate use cases from the lifecycle to move a use case to a different lifecycle.
* CameraX.unbindAll();
* }
* }</pre>
*
* <p>All operations on a use case, including binding and unbinding, should be done on the main
* thread. This is because lifecycle events are triggered on main thread and so accessing the use
* case on the main thread guarantees that lifecycle state changes will not occur during execution
* of a method call or binding/unbinding.
*
* @hide
*/
@MainThread
@RestrictTo(Scope.LIBRARY_GROUP)
public final class CameraX {
private static final String TAG = "CameraX";
private static final long WAIT_INITIALIZED_TIMEOUT = 3L;
static final Object sInitializeLock = new Object();
@GuardedBy("sInitializeLock")
@Nullable
static CameraX sInstance = null;
@GuardedBy("sInitializeLock")
private static boolean sTargetInitialized = false;
@GuardedBy("sInitializeLock")
@NonNull
private static ListenableFuture<Void> sInitializeFuture = Futures.immediateFailedFuture(
new IllegalStateException("CameraX is not initialized."));
@GuardedBy("sInitializeLock")
@NonNull
private static ListenableFuture<Void> sShutdownFuture = Futures.immediateFuture(null);
final CameraRepository mCameraRepository = new CameraRepository();
private final Object mInitializeLock = new Object();
private final UseCaseGroupRepository mUseCaseGroupRepository = new UseCaseGroupRepository();
private final Executor mCameraExecutor;
private CameraFactory mCameraFactory;
private CameraDeviceSurfaceManager mSurfaceManager;
private UseCaseConfigFactory mDefaultConfigFactory;
private Context mContext;
@GuardedBy("mInitializeLock")
private InternalInitState mInitState = InternalInitState.UNINITIALIZED;
@GuardedBy("mInitializeLock")
private ListenableFuture<Void> mShutdownInternalFuture = Futures.immediateFuture(null);
/** Prevents construction. */
CameraX(@NonNull Executor executor) {
Preconditions.checkNotNull(executor);
mCameraExecutor = executor;
}
/**
* Binds the collection of {@link UseCase} to a {@link LifecycleOwner}.
*
* <p>The state of the lifecycle will determine when the cameras are open, started, stopped
* and closed. When started, the use cases receive camera data.
*
* <p>Binding to a lifecycleOwner in state currently in {@link Lifecycle.State#STARTED} or
* greater will also initialize and start data capture. If the camera was already running
* this may cause a new initialization to occur temporarily stopping data from the camera
* before restarting it.
*
* <p>Multiple use cases can be bound via adding them all to a single bindToLifecycle call, or
* by using multiple bindToLifecycle calls. Using a single call that includes all the use
* cases helps to set up a camera session correctly for all uses cases, such as by allowing
* determination of resolutions depending on all the use cases bound being bound.
* If the use cases are bound separately, it will find the supported resolution with the
* priority depending on the binding sequence. If the use cases are bound with a single call,
* it will find the supported resolution with the priority in sequence of {@link ImageCapture},
* {@link Preview} and then {@link ImageAnalysis}. The resolutions that can be supported depends
* on the camera device hardware level that there are some default guaranteed resolutions
* listed in {@link android.hardware.camera2.CameraDevice#createCaptureSession(List,
* android.hardware.camera2.CameraCaptureSession.StateCallback, Handler)}.
*
* <p>Currently up to 3 use cases may be bound to a {@link Lifecycle} at any time. Exceeding
* capability of target camera device will throw an IllegalArgumentException.
*
* <p>A UseCase should only be bound to a single lifecycle and camera selector a time.
* Attempting to bind a use case to a lifecycle when it is already bound to another lifecycle
* is an error, and the use case binding will not change. Attempting to bind the same use case
* to multiple camera selectors is also an error and will not change the binding.
*
* <p>If different use cases are bound to different camera selectors that resolve to distinct
* cameras, but the same lifecycle, only one of the cameras will operate at a time. The
* non-operating camera will not become active until it is the only camera with use cases bound.
*
* <p>Only {@link UseCase} bound to latest active {@link Lifecycle} can keep alive.
* {@link UseCase} bound to other {@link Lifecycle} will be stopped.
*
* @param lifecycleOwner The lifecycleOwner which controls the lifecycle transitions of the use
* cases.
* @param cameraSelector The camera selector which determines the camera to use for set of
* use cases.
* @param useCases The use cases to bind to a lifecycle.
* @return The {@link Camera} instance which is determined by the camera selector.
* @throws IllegalStateException If the use case has already been bound to another lifecycle
* or method is not called on main thread.
* @throws IllegalArgumentException If the provided camera selector is unable to resolve a
* camera to be used for the given use cases.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@SuppressWarnings("lambdaLast")
@NonNull
public static Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector, @NonNull UseCase... useCases) {
Threads.checkMainThread();
CameraX cameraX = checkInitialized();
UseCaseGroupLifecycleController useCaseGroupLifecycleController =
cameraX.getOrCreateUseCaseGroup(lifecycleOwner);
UseCaseGroup useCaseGroupToBind = useCaseGroupLifecycleController.getUseCaseGroup();
Collection<UseCaseGroupLifecycleController> controllers =
cameraX.mUseCaseGroupRepository.getUseCaseGroups();
for (UseCase useCase : useCases) {
for (UseCaseGroupLifecycleController controller : controllers) {
UseCaseGroup useCaseGroup = controller.getUseCaseGroup();
if (useCaseGroup.contains(useCase) && useCaseGroup != useCaseGroupToBind) {
throw new IllegalStateException(
String.format(
"Use case %s already bound to a different lifecycle.",
useCase));
}
}
}
// TODO(b/142840814): Camera should be selected here and bound to the use case.
// Should not need to use CameraDeviceConfig anymore.
CameraSelector.Builder selectorBuilder =
CameraSelector.Builder.fromSelector(cameraSelector);
// Copy existing filters from use cases into the new selector
for (UseCase useCase : useCases) {
CameraIdFilter filter =
((CameraDeviceConfig) useCase.getUseCaseConfig()).getCameraIdFilter(null);
if (filter != null) {
selectorBuilder.appendFilter(filter);
}
}
String newCameraId = CameraX.getCameraWithCameraSelector(selectorBuilder.build());
// Try to get the camera before bind to the use case, and throw the IllegalArgumentException
// if the camera not found.
CameraInternal camera = cameraX.getCameraRepository().getCamera(newCameraId);
for (UseCase useCase : useCases) {
// Sets bound camera to use case.
useCase.onBind(camera);
}
calculateSuggestedResolutions(lifecycleOwner, newCameraId, useCases);
for (UseCase useCase : useCases) {
useCaseGroupToBind.addUseCase(useCase);
for (String cameraId : useCase.getAttachedCameraIds()) {
attach(cameraId, useCase);
}
}
useCaseGroupLifecycleController.notifyState();
return camera;
}
/**
* Returns true if the {@link UseCase} is bound to a lifecycle. Otherwise returns false.
*
* <p>After binding a use case with {@link #bindToLifecycle}, use cases remain bound until the
* lifecycle reaches a {@link Lifecycle.State#DESTROYED} state or if is unbound by calls to
* {@link #unbind(UseCase...)} or {@link #unbindAll()}.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public static boolean isBound(@NonNull UseCase useCase) {
CameraX cameraX = checkInitialized();
Collection<UseCaseGroupLifecycleController> controllers =
cameraX.mUseCaseGroupRepository.getUseCaseGroups();
for (UseCaseGroupLifecycleController controller : controllers) {
UseCaseGroup useCaseGroup = controller.getUseCaseGroup();
if (useCaseGroup.contains(useCase)) {
return true;
}
}
return false;
}
/**
* Unbinds all specified use cases from the lifecycle.
*
* <p>This will initiate a close of every open camera which has zero {@link UseCase}
* associated with it at the end of this call.
*
* <p>If a use case in the argument list is not bound, then it is simply ignored.
*
* <p>After unbinding a UseCase, the UseCase can be and bound to another {@link Lifecycle}
* however listeners and settings should be reset by the application.
*
* @param useCases The collection of use cases to remove.
* @throws IllegalStateException If not called on main thread.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public static void unbind(@NonNull UseCase... useCases) {
Threads.checkMainThread();
CameraX cameraX = checkInitialized();
Collection<UseCaseGroupLifecycleController> useCaseGroups =
cameraX.mUseCaseGroupRepository.getUseCaseGroups();
Map<String, List<UseCase>> detachingUseCaseMap = new HashMap<>();
for (UseCase useCase : useCases) {
for (UseCaseGroupLifecycleController useCaseGroupLifecycleController : useCaseGroups) {
UseCaseGroup useCaseGroup = useCaseGroupLifecycleController.getUseCaseGroup();
if (useCaseGroup.removeUseCase(useCase)) {
// Saves all detaching use cases and detach them at once.
for (String cameraId : useCase.getAttachedCameraIds()) {
List<UseCase> useCasesOnCameraId = detachingUseCaseMap.get(cameraId);
if (useCasesOnCameraId == null) {
useCasesOnCameraId = new ArrayList<>();
detachingUseCaseMap.put(cameraId, useCasesOnCameraId);
}
useCasesOnCameraId.add(useCase);
}
}
}
}
for (String cameraId : detachingUseCaseMap.keySet()) {
detach(cameraId, detachingUseCaseMap.get(cameraId));
}
for (UseCase useCase : useCases) {
useCase.clear();
}
}
/**
* Unbinds all use cases from the lifecycle and removes them from CameraX.
*
* <p>This will initiate a close of every currently open camera.
*
* @throws IllegalStateException If not called on main thread.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public static void unbindAll() {
Threads.checkMainThread();
CameraX cameraX = checkInitialized();
Collection<UseCaseGroupLifecycleController> useCaseGroups =
cameraX.mUseCaseGroupRepository.getUseCaseGroups();
List<UseCase> useCases = new ArrayList<>();
for (UseCaseGroupLifecycleController useCaseGroupLifecycleController : useCaseGroups) {
UseCaseGroup useCaseGroup = useCaseGroupLifecycleController.getUseCaseGroup();
useCases.addAll(useCaseGroup.getUseCases());
}
unbind(useCases.toArray(new UseCase[0]));
}
/**
* Checks if the device supports at least one camera that meets the requirements from a
* {@link CameraSelector}.
*
* @param cameraSelector the {@link CameraSelector} that filters available cameras.
* @return true if the device has at least one available camera, otherwise false.
* @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
* insufficient permissions.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public static boolean hasCamera(@NonNull CameraSelector cameraSelector)
throws CameraInfoUnavailableException {
checkInitialized();
try {
cameraSelector.select(getCameraFactory().getAvailableCameraIds());
} catch (IllegalArgumentException e) {
return false;
}
return true;
}
/**
* Returns the camera id for a camera with the specified lens facing.
*
* <p>This only gives the first (primary) camera found with the specified facing.
*
* @param lensFacing the lens facing of the camera
* @return the camera id if camera exists or {@code null} if no camera with specified facing
* exists
* @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
* insufficient permissions.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public static String getCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing)
throws CameraInfoUnavailableException {
checkInitialized();
return getCameraFactory().cameraIdForLensFacing(lensFacing);
}
/**
* Returns the camera id for a camera defined by the given {@link CameraSelector}.
*
* @param cameraSelector the camera selector
* @return the camera id if camera exists or {@code null} if no camera can be resolved with
* the camera selector
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public static String getCameraWithCameraSelector(@NonNull CameraSelector cameraSelector) {
checkInitialized();
Set<String> availableCameraIds;
String resultCameraId = null;
try {
availableCameraIds = getCameraFactory().getAvailableCameraIds();
resultCameraId = cameraSelector.select(availableCameraIds);
} catch (CameraInfoUnavailableException e) {
return null;
}
return resultCameraId;
}
/**
* Gets the default lens facing, or throws a {@link IllegalStateException} if there is no
* available camera.
*
* @return The default lens facing.
* @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
* insufficient permissions.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@CameraSelector.LensFacing
public static int getDefaultLensFacing() throws CameraInfoUnavailableException {
checkInitialized();
Integer lensFacingCandidate = null;
List<Integer> lensFacingList = Arrays.asList(CameraSelector.LENS_FACING_BACK,
CameraSelector.LENS_FACING_FRONT);
for (Integer lensFacing : lensFacingList) {
String cameraId = getCameraFactory().cameraIdForLensFacing(lensFacing);
if (cameraId != null) {
lensFacingCandidate = lensFacing;
break;
}
}
if (lensFacingCandidate == null) {
throw new IllegalStateException("Unable to get default lens facing.");
}
return lensFacingCandidate;
}
/**
* Returns the camera info for the camera with the given camera id.
*
* @param cameraId the internal id of the camera
* @return the camera info if it can be retrieved for the given id.
* @throws IllegalArgumentException if unable to access cameras, perhaps due to
* insufficient permissions.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static CameraInfoInternal getCameraInfo(String cameraId) {
CameraX cameraX = checkInitialized();
return cameraX.getCameraRepository().getCamera(cameraId).getCameraInfoInternal();
}
/**
* Returns the {@link CameraDeviceSurfaceManager} which can be used to query for valid surface
* configurations.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static CameraDeviceSurfaceManager getSurfaceManager() {
CameraX cameraX = checkInitialized();
return cameraX.getCameraDeviceSurfaceManager();
}
/**
* Returns the default configuration for the given use case configuration type.
*
* <p>The options contained in this configuration serve as fallbacks if they are not included in
* the user-provided configuration used to create a use case.
*
* @param configType the configuration type
* @param lensFacing The {@link CameraSelector.LensFacing} that the default configuration
* will target to.
* @return the default configuration for the given configuration type
* @throws IllegalStateException if Camerax has not yet been initialized.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public static <C extends UseCaseConfig<?>> C getDefaultUseCaseConfig(Class<C> configType,
@Nullable Integer lensFacing) {
CameraX cameraX = checkInitialized();
return cameraX.getDefaultConfigFactory().getConfig(configType, lensFacing);
}
/**
* Initializes CameraX with the given context and application configuration.
*
* <p>The context enables CameraX to obtain access to necessary services, including the camera
* service. For example, the context can be provided by the application.
*
* @param context to attach
* @param cameraXConfig configuration options for this application session.
* @return A {@link ListenableFuture} representing the initialization task.
*/
@NonNull
public static ListenableFuture<Void> initialize(@NonNull Context context,
@NonNull CameraXConfig cameraXConfig) {
synchronized (sInitializeLock) {
return initializeLocked(context, cameraXConfig);
}
}
@GuardedBy("sInitializeLock")
@NonNull
private static ListenableFuture<Void> initializeLocked(@NonNull Context context,
@NonNull CameraXConfig cameraXConfig) {
Preconditions.checkNotNull(context);
Preconditions.checkNotNull(cameraXConfig);
Preconditions.checkState(!sTargetInitialized, "Must call CameraX.shutdown() first.");
sTargetInitialized = true;
Executor executor = cameraXConfig.getCameraExecutor(null);
// Set a default camera executor if not set.
if (executor == null) {
executor = new CameraExecutor();
}
CameraX cameraX = new CameraX(executor);
sInstance = cameraX;
sInitializeFuture = CallbackToFutureAdapter.getFuture(completer -> {
synchronized (sInitializeLock) {
// The sShutdownFuture should always be successful, otherwise it will not
// propagate to transformAsync() due to the behavior of FutureChain.
ListenableFuture<Void> future = FutureChain.from(sShutdownFuture)
.transformAsync(input -> cameraX.initInternal(context, cameraXConfig),
CameraXExecutors.directExecutor());
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
completer.set(null);
}
@Override
public void onFailure(Throwable t) {
Log.w(TAG, "CameraX initialize() failed", t);
// Call shutdown() automatically, if initialization fails.
synchronized (sInitializeLock) {
// Make sure it is the same instance to prevent reinitialization
// during initialization.
if (sInstance == cameraX) {
shutdown();
}
}
completer.setException(t);
}
}, CameraXExecutors.directExecutor());
return "CameraX-initialize";
}
});
return sInitializeFuture;
}
/**
* Shutdown CameraX so that it can be initialized again.
*
* @return A {@link ListenableFuture} representing the shutdown task.
*/
@NonNull
public static ListenableFuture<Void> shutdown() {
synchronized (sInitializeLock) {
return shutdownLocked();
}
}
@GuardedBy("sInitializeLock")
@NonNull
private static ListenableFuture<Void> shutdownLocked() {
if (!sTargetInitialized) {
// If it is already or will be shutdown, return the future directly.
return sShutdownFuture;
}
sTargetInitialized = false;
CameraX cameraX = sInstance;
sInstance = null;
// Do not use FutureChain to chain the initFuture, because FutureChain.transformAsync()
// will not propagate if the input initFuture is failed. We want to always
// shutdown the CameraX instance to ensure that resources are freed.
sShutdownFuture = CallbackToFutureAdapter.getFuture(
completer -> {
synchronized (sInitializeLock) {
// Wait initialize complete
sInitializeFuture.addListener(() -> {
// Wait shutdownInternal complete
Futures.propagate(cameraX.shutdownInternal(), completer);
}, CameraXExecutors.directExecutor());
return "CameraX shutdown";
}
});
return sShutdownFuture;
}
/**
* Returns the context used for CameraX.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static Context getContext() {
CameraX cameraX = checkInitialized();
return cameraX.mContext;
}
/**
* Returns true if CameraX is initialized.
*
* <p>Any previous call to {@link #initialize(Context, CameraXConfig)} would have initialized
* CameraX.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public static boolean isInitialized() {
synchronized (sInitializeLock) {
return sInstance != null && sInstance.isInitializedInternal();
}
}
/**
* Returns currently active {@link UseCase}
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public static Collection<UseCase> getActiveUseCases() {
CameraX cameraX = checkInitialized();
Collection<UseCase> activeUseCases = null;
Collection<UseCaseGroupLifecycleController> controllers =
cameraX.mUseCaseGroupRepository.getUseCaseGroups();
for (UseCaseGroupLifecycleController controller : controllers) {
if (controller.getUseCaseGroup().isActive()) {
activeUseCases = controller.getUseCaseGroup().getUseCases();
break;
}
}
return activeUseCases;
}
/**
* Returns the {@link CameraFactory} instance.
*
* @throws IllegalStateException if the {@link CameraFactory} has not been set, due to being
* uninitialized.
* @hide
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public static CameraFactory getCameraFactory() {
CameraX cameraX = checkInitialized();
if (cameraX.mCameraFactory == null) {
throw new IllegalStateException("CameraX not initialized yet.");
}
return cameraX.mCameraFactory;
}
/**
* Wait for the initialize or shutdown task finished and then check if it is initialized.
*
* @return CameraX instance
* @throws IllegalStateException if it is not initialized
*/
@NonNull
private static CameraX checkInitialized() {
CameraX cameraX = waitInitialized();
Preconditions.checkState(cameraX.isInitializedInternal(),
"Must call CameraX.initialize() first");
return cameraX;
}
/**
* Returns a future which contains a CameraX instance after initialization is complete.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static ListenableFuture<CameraX> getOrCreateInstance(@NonNull Context context) {
Preconditions.checkNotNull(context, "Context must not be null.");
synchronized (sInitializeLock) {
ListenableFuture<CameraX> instanceFuture = getInstanceLocked();
if (instanceFuture.isDone()) {
try {
instanceFuture.get();
} catch (InterruptedException e) {
// Should not be possible since future is complete.
throw new RuntimeException("Unexpected thread interrupt. Should not be "
+ "possible since future is already complete.", e);
} catch (ExecutionException e) {
// Either initialization failed or initialize() has not been called, ensure we
// can try to reinitialize.
shutdownLocked();
instanceFuture = null;
}
}
// Attempt initialization through Application
if (instanceFuture == null) {
Application app = (Application) context.getApplicationContext();
if (app instanceof CameraXConfig.Provider) {
initializeLocked(app, ((CameraXConfig.Provider) app).getCameraXConfig());
instanceFuture = getInstanceLocked();
} else {
throw new IllegalStateException("CameraX is not initialized properly. Either "
+ "CameraX.initialize() needs to have been called or the CameraXConfig"
+ ".Provider interface must be implemented by your Application class.");
}
}
return instanceFuture;
}
}
@NonNull
private static ListenableFuture<CameraX> getInstance() {
synchronized (sInitializeLock) {
return getInstanceLocked();
}
}
@GuardedBy("sInitializeLock")
@NonNull
private static ListenableFuture<CameraX> getInstanceLocked() {
if (!sTargetInitialized) {
return Futures.immediateFailedFuture(new IllegalStateException("Must "
+ "call CameraX.initialize() first"));
}
CameraX cameraX = sInstance;
return Futures.transform(sInitializeFuture, nullVoid -> cameraX,
CameraXExecutors.directExecutor());
}
/**
* Wait for the initialize or shutdown task finished.
*
* @throws IllegalStateException if the initialization is fail or timeout
*/
@NonNull
private static CameraX waitInitialized() {
ListenableFuture<CameraX> future = getInstance();
try {
return future.get(WAIT_INITIALIZED_TIMEOUT, TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw new IllegalStateException(e);
} catch (TimeoutException e) {
throw new IllegalStateException(e);
} catch (InterruptedException e) {
// Throw exception when get interrupted because the initialization could not be
// finished yet.
throw new IllegalStateException(e);
}
}
/**
* Registers the callbacks for the {@link CameraInternal} to the {@link UseCase}.
*
* @param cameraId the id for the {@link CameraInternal}
* @param useCase the use case to register the callback for
*/
private static void attach(String cameraId, UseCase useCase) {
CameraX cameraX = checkInitialized();
CameraInternal cameraInternal = cameraX.getCameraRepository().getCamera(cameraId);
useCase.addStateChangeCallback(cameraInternal);
useCase.attachCameraControl(cameraId, cameraInternal.getCameraControlInternal());
}
/**
* Removes the callbacks registered by the {@link CameraInternal} to the {@link UseCase}.
*
* @param cameraId the id for the {@link CameraInternal}
* @param useCases the list of use case to remove the callback from.
*/
private static void detach(String cameraId, List<UseCase> useCases) {
CameraX cameraX = checkInitialized();
CameraInternal cameraInternal = cameraX.getCameraRepository().getCamera(cameraId);
for (UseCase useCase : useCases) {
useCase.removeStateChangeCallback(cameraInternal);
useCase.detachCameraControl(cameraId);
}
cameraInternal.removeOnlineUseCase(useCases);
}
private static void calculateSuggestedResolutions(@NonNull LifecycleOwner lifecycleOwner,
@NonNull String newCameraId, @NonNull UseCase... useCases) {
CameraX cameraX = checkInitialized();
// There will only one lifecycleOwner active. Therefore, only collect use cases belong to
// same lifecycleOwner and calculate the suggested resolutions.
UseCaseGroupLifecycleController useCaseGroupLifecycleController =
cameraX.getOrCreateUseCaseGroup(lifecycleOwner);
UseCaseGroup useCaseGroupToBind = useCaseGroupLifecycleController.getUseCaseGroup();
Map<String, List<UseCase>> originalCameraIdUseCaseMap = new HashMap<>();
Map<String, List<UseCase>> newCameraIdUseCaseMap = new HashMap<>();
// Collect original use cases for different camera devices
for (UseCase useCase : useCaseGroupToBind.getUseCases()) {
for (String cameraId : useCase.getAttachedCameraIds()) {
List<UseCase> useCaseList = originalCameraIdUseCaseMap.get(cameraId);
if (useCaseList == null) {
useCaseList = new ArrayList<>();
originalCameraIdUseCaseMap.put(cameraId, useCaseList);
}
useCaseList.add(useCase);
}
}
// Collect new use cases for different camera devices
for (UseCase useCase : useCases) {
List<UseCase> useCaseList = newCameraIdUseCaseMap.get(newCameraId);
if (useCaseList == null) {
useCaseList = new ArrayList<>();
newCameraIdUseCaseMap.put(newCameraId, useCaseList);
}
useCaseList.add(useCase);
}
// Get suggested resolutions and update the use case session configuration
for (String cameraId : newCameraIdUseCaseMap.keySet()) {
Map<UseCase, Size> suggestResolutionsMap =
getSurfaceManager()
.getSuggestedResolutions(
cameraId,
originalCameraIdUseCaseMap.get(cameraId),
newCameraIdUseCaseMap.get(cameraId));
for (UseCase useCase : newCameraIdUseCaseMap.get(cameraId)) {
Size resolution = suggestResolutionsMap.get(useCase);
Map<String, Size> suggestedCameraSurfaceResolutionMap = new HashMap<>();
suggestedCameraSurfaceResolutionMap.put(cameraId, resolution);
useCase.updateSuggestedResolution(suggestedCameraSurfaceResolutionMap);
}
}
}
/**
* Returns the {@link CameraDeviceSurfaceManager} instance.
*
* @throws IllegalStateException if the {@link CameraDeviceSurfaceManager} has not been set, due
* to being uninitialized.
*/
private CameraDeviceSurfaceManager getCameraDeviceSurfaceManager() {
if (mSurfaceManager == null) {
throw new IllegalStateException("CameraX not initialized yet.");
}
return mSurfaceManager;
}
private UseCaseConfigFactory getDefaultConfigFactory() {
if (mDefaultConfigFactory == null) {
throw new IllegalStateException("CameraX not initialized yet.");
}
return mDefaultConfigFactory;
}
private ListenableFuture<Void> initInternal(Context context, CameraXConfig cameraXConfig) {
synchronized (mInitializeLock) {
Preconditions.checkState(mInitState == InternalInitState.UNINITIALIZED,
"CameraX.initInternal() should only be called once per instance");
mInitState = InternalInitState.INITIALIZING;
return CallbackToFutureAdapter.getFuture(
completer -> {
mCameraExecutor.execute(() -> {
Exception e = null;
try {
mContext = context.getApplicationContext();
CameraFactory.Provider cameraFactoryProvider =
cameraXConfig.getCameraFactoryProvider(null);
if (cameraFactoryProvider == null) {
e = new IllegalArgumentException(
"Invalid app configuration provided. Missing "
+ "CameraFactory.");
return;
}
mCameraFactory = cameraFactoryProvider.newInstance(context);
CameraDeviceSurfaceManager.Provider surfaceManagerProvider =
cameraXConfig.getDeviceSurfaceManagerProvider(null);
if (surfaceManagerProvider == null) {
e = new IllegalArgumentException(
"Invalid app configuration provided. Missing "
+ "CameraDeviceSurfaceManager.");
return;
}
mSurfaceManager = surfaceManagerProvider.newInstance(context);
UseCaseConfigFactory.Provider configFactoryProvider =
cameraXConfig.getUseCaseConfigFactoryProvider(
null);
if (configFactoryProvider == null) {
e = new IllegalArgumentException(
"Invalid app configuration provided. Missing "
+ "UseCaseConfigFactory.");
return;
}
mDefaultConfigFactory = configFactoryProvider.newInstance(context);
if (mCameraExecutor instanceof CameraExecutor) {
CameraExecutor executor = (CameraExecutor) mCameraExecutor;
executor.init(mCameraFactory);
}
mCameraRepository.init(mCameraFactory);
} finally {
synchronized (mInitializeLock) {
mInitState = InternalInitState.INITIALIZED;
}
if (e != null) {
completer.setException(e);
} else {
completer.set(null);
}
}
});
return "CameraX initInternal";
});
}
}
@NonNull
private ListenableFuture<Void> shutdownInternal() {
synchronized (mInitializeLock) {
switch (mInitState) {
case UNINITIALIZED:
mInitState = InternalInitState.SHUTDOWN;
return Futures.immediateFuture(null);
case INITIALIZING:
throw new IllegalStateException(
"CameraX could not be shutdown when it is initializing.");
case INITIALIZED:
mInitState = InternalInitState.SHUTDOWN;
mShutdownInternalFuture = CallbackToFutureAdapter.getFuture(
completer -> {
ListenableFuture<Void> future = mCameraRepository.deinit();
// Deinit camera executor at last to avoid RejectExecutionException.
future.addListener(() -> {
if (mCameraExecutor instanceof CameraExecutor) {
CameraExecutor executor = (CameraExecutor) mCameraExecutor;
executor.deinit();
}
completer.set(null);
}, mCameraExecutor);
return "CameraX shutdownInternal";
}
);
// Fall through
case SHUTDOWN:
break;
}
// Already shutdown. Return the shutdown future.
return mShutdownInternalFuture;
}
}
private boolean isInitializedInternal() {
synchronized (mInitializeLock) {
return mInitState == InternalInitState.INITIALIZED;
}
}
private UseCaseGroupLifecycleController getOrCreateUseCaseGroup(LifecycleOwner lifecycleOwner) {
return mUseCaseGroupRepository.getOrCreateUseCaseGroup(
lifecycleOwner, new UseCaseGroupRepository.UseCaseGroupSetup() {
@Override
public void setup(UseCaseGroup useCaseGroup) {
useCaseGroup.setListener(mCameraRepository);
}
});
}
private CameraRepository getCameraRepository() {
return mCameraRepository;
}
/** Internal initialization state. */
private enum InternalInitState {
/** The CameraX instance has not yet been initialized. */
UNINITIALIZED,
/** The CameraX instance is initializing. */
INITIALIZING,
/** The CameraX instance has been initialized. */
INITIALIZED,
/**
* The CameraX instance has been shutdown.
*
* <p>Once the CameraX instance has been shutdown, it can't be used or re-initialized.
*/
SHUTDOWN
}
}