java.lang.Object
↳androidx.camera.lifecycle.ProcessCameraProvider
Overview
A singleton which can be used to bind the lifecycle of cameras to any LifecycleOwner
within an application's process.
Only a single process camera provider can exist within a process, and it can be retrieved
with ProcessCameraProvider.getInstance(Context).
Heavyweight resources, such as open and running camera devices, will be scoped to the
lifecycle provided to ProcessCameraProvider.bindToLifecycle(LifecycleOwner, CameraSelector, UseCase...).
Other lightweight resources, such as static camera characteristics, may be retrieved and
cached upon first retrieval of this provider with ProcessCameraProvider.getInstance(Context), and will
persist for the lifetime of the process.
This is the standard provider for applications to use.
Summary
Methods |
---|
public Camera | bindToLifecycle(LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCase useCases[])
Binds the collection of UseCase to a LifecycleOwner. |
public Camera | bindToLifecycle(LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCaseGroup useCaseGroup)
Binds a UseCaseGroup to a LifecycleOwner. |
public static void | configureInstance(CameraXConfig cameraXConfig)
Perform one-time configuration of the ProcessCameraProvider singleton with the
given CameraXConfig. |
public java.util.List<CameraInfo> | getAvailableCameraInfos()
Returns CameraInfo instances of the available cameras. |
public static <any> | getInstance(Context context)
Retrieves the ProcessCameraProvider associated with the current process. |
public boolean | hasCamera(CameraSelector cameraSelector)
|
public boolean | isBound(UseCase useCase)
Returns true if the UseCase is bound to a lifecycle. |
public <any> | shutdown()
Allows shutting down this ProcessCameraProvider instance so a new instance can be
retrieved by ProcessCameraProvider.getInstance(Context). |
public void | unbind(UseCase useCases[])
Unbinds all specified use cases from the lifecycle. |
public void | unbindAll()
Unbinds all use cases from the lifecycle and removes them from CameraX. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Methods
public static <any>
getInstance(Context context)
Retrieves the ProcessCameraProvider associated with the current process.
The instance returned here can be used to bind use cases to any
LifecycleOwner with
ProcessCameraProvider.bindToLifecycle(LifecycleOwner, CameraSelector, UseCase...).
The instance's configuration may be customized by subclassing the application's
Application
class and implementing . For example, the
following will initialize this process camera provider with a
Camera2 implementation from
androidx.camera.camera2, and with a custom executor.
public class MyApplication extends Application implements CameraXConfig.Provider {
@Override
public CameraXConfig getCameraXConfig() {
return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setCameraExecutor(myExecutor)
.setSchedulerHandler(mySchedulerHandler)
.build();
}
. . .
}
If it isn't possible to subclass the Application
class, such as in library
code, then the singleton can be configured via ProcessCameraProvider.configureInstance(CameraXConfig)
before the first invocation of getInstance(context), as in the following example.
class MyCustomizedCameraProvider {
private static boolean configured = false;
static ListenableFuture getInstance(Context context) {
synchronized(MyCustomizedCameraProvider.class) {
if (!configured) {
configured = true;
ProcessCameraProvider.configureInstance(
CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setCameraExecutor(myExecutor)
.setSchedulerHandler(mySchedulerHandler)
.build());
}
}
return ProcessCameraProvider.getInstance(context);
}
}
If no is implemented by Application
, or if the
singleton has not been configured via ProcessCameraProvider.configureInstance(CameraXConfig) a default
configuration will be used.
Returns:
A future which will contain the ProcessCameraProvider. Cancellation of
this future is a no-op. This future may fail with an InitializationException and
associated cause that can be retrieved by getCause
. The cause will be
a CameraUnavailableException if it fails to access any camera
during initialization.
See also: ProcessCameraProvider.configureInstance(CameraXConfig)
public static void
configureInstance(
CameraXConfig cameraXConfig)
Perform one-time configuration of the ProcessCameraProvider singleton with the
given CameraXConfig.
This method allows configuration of the camera provider via CameraXConfig. All
initialization tasks, such as communicating with the camera service, will be executed
on the java.util.concurrent.Executor
set by
, or by an internally defined
executor if none is provided.
This method is not required for every application. If the method is not called and
is not implemented in Application
, default
configuration will be used.
Once this method is called, the instance configured by the given CameraXConfig can
be retrieved with ProcessCameraProvider.getInstance(Context).
implemented in Application
will be ignored.
Configuration can only occur once. Once the ProcessCameraProvider has been configured with
configureInstance() or ProcessCameraProvider.getInstance(Context), this method will throw
an java.lang.IllegalStateException
. Because configuration can only occur once, usage of this
method from library code is not recommended as the application owner should ultimately
be in control of singleton configuration.
Parameters:
cameraXConfig: configuration options for the singleton process camera provider
instance.
Allows shutting down this ProcessCameraProvider instance so a new instance can be
retrieved by ProcessCameraProvider.getInstance(Context).
Once shutdown, a new instance can be retrieved with
ProcessCameraProvider.getInstance(Context).
This method, along with ProcessCameraProvider.configureInstance(CameraXConfig) allows the process
camera provider to be used in test suites which may need to initialize CameraX in
different ways in between tests.
Returns:
A representing the shutdown status. Cancellation of this
future is a no-op.
Binds the collection of UseCase to a LifecycleOwner.
The state of the lifecycle will determine when the cameras are open, started, stopped
and closed. When started, the use cases receive camera data.
Binding to a lifecycleOwner in state currently in 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.
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 ImageCapture,
Preview and then ImageAnalysis. The resolutions that can be supported depends
on the camera device hardware level that there are some default guaranteed resolutions
listed in
android.hardware.camera2.CameraDevice
.
Currently up to 3 use cases may be bound to a Lifecycle at any time. Exceeding
capability of target camera device will throw an IllegalArgumentException.
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.
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.
The Camera returned is determined by the given camera selector, plus other
internal requirements, possibly from use case configurations. The camera returned from
bindToLifecycle may differ from the camera determined solely by a camera selector. If the
camera selector can't resolve a valid camera under the requirements, an
IllegalArgumentException will be thrown.
Only UseCase bound to latest active Lifecycle can keep alive.
UseCase bound to other Lifecycle will be stopped.
Parameters:
lifecycleOwner: The lifecycleOwner which controls the lifecycle transitions of the use
cases.
cameraSelector: The camera selector which determines the camera to use for set of
use cases.
useCases: The use cases to bind to a lifecycle.
Returns:
The Camera instance which is determined by the camera selector and
internal requirements.
Binds a UseCaseGroup to a LifecycleOwner.
Similar to ProcessCameraProvider.bindToLifecycle(LifecycleOwner, CameraSelector, UseCase...),
with the addition that the bound collection of UseCase share parameters
defined by UseCaseGroup such as consistent camera sensor rect across all
UseCases.
If one UseCase is in multiple UseCaseGroups, it will be linked to
the UseCaseGroup in the latest
ProcessCameraProvider.bindToLifecycle(LifecycleOwner, CameraSelector, UseCaseGroup) call.
public boolean
isBound(
UseCase useCase)
Returns true if the UseCase is bound to a lifecycle. Otherwise returns false.
After binding a use case with ProcessCameraProvider.bindToLifecycle(LifecycleOwner, CameraSelector, UseCase...), use cases remain bound until the
lifecycle reaches a state or if is unbound by calls to
ProcessCameraProvider.unbind(UseCase...) or ProcessCameraProvider.unbindAll().
public void
unbind(
UseCase useCases[])
Unbinds all specified use cases from the lifecycle.
This will initiate a close of every open camera which has zero UseCase
associated with it at the end of this call.
If a use case in the argument list is not bound, then it is simply ignored.
After unbinding a UseCase, the UseCase can be and bound to another Lifecycle
however listeners and settings should be reset by the application.
Parameters:
useCases: The collection of use cases to remove.
Unbinds all use cases from the lifecycle and removes them from CameraX.
This will initiate a close of every currently open camera.
public java.util.List<CameraInfo>
getAvailableCameraInfos()
Returns CameraInfo instances of the available cameras.
The available cameras include all the available cameras on the device, or only those
selected through
CameraXConfig.Builder.setAvailableCamerasLimiter(CameraSelector)
While iterating through all the available CameraInfo, if one of them meets some
predefined requirements, a CameraSelector that uniquely identifies its camera
can be retrieved using CameraInfo.getCameraSelector(), which can then be used to bind
use cases to that camera.
Returns:
A list of CameraInfo instances for the available cameras.
Source
/*
* Copyright 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.lifecycle;
import android.app.Application;
import android.content.Context;
import android.os.Handler;
import androidx.annotation.GuardedBy;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraFilter;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraX;
import androidx.camera.core.CameraXConfig;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.InitializationException;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.core.ViewPort;
import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
import androidx.camera.core.impl.utils.ContextUtil;
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.camera.core.internal.CameraUseCaseAdapter;
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.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
/**
* A singleton which can be used to bind the lifecycle of cameras to any {@link LifecycleOwner}
* within an application's process.
*
* <p>Only a single process camera provider can exist within a process, and it can be retrieved
* with {@link #getInstance(Context)}.
*
* <p>Heavyweight resources, such as open and running camera devices, will be scoped to the
* lifecycle provided to {@link #bindToLifecycle(LifecycleOwner, CameraSelector, UseCase...)}.
* Other lightweight resources, such as static camera characteristics, may be retrieved and
* cached upon first retrieval of this provider with {@link #getInstance(Context)}, and will
* persist for the lifetime of the process.
*
* <p>This is the standard provider for applications to use.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class ProcessCameraProvider implements LifecycleCameraProvider {
private static final ProcessCameraProvider sAppInstance = new ProcessCameraProvider();
private final Object mLock = new Object();
@GuardedBy("mLock")
private CameraXConfig.Provider mCameraXConfigProvider = null;
@GuardedBy("mLock")
private ListenableFuture<CameraX> mCameraXInitializeFuture;
@GuardedBy("mLock")
private ListenableFuture<Void> mCameraXShutdownFuture = Futures.immediateFuture(null);
private final LifecycleCameraRepository mLifecycleCameraRepository =
new LifecycleCameraRepository();
private CameraX mCameraX;
private Context mContext;
/**
* Retrieves the {@link ProcessCameraProvider} associated with the current process.
*
* <p>The instance returned here can be used to bind use cases to any
* {@link LifecycleOwner} with
* {@link #bindToLifecycle(LifecycleOwner, CameraSelector, UseCase...)}.
* <p>The instance's configuration may be customized by subclassing the application's
* {@link Application} class and implementing {@link CameraXConfig.Provider}. For example, the
* following will initialize this process camera provider with a
* {@linkplain androidx.camera.camera2.Camera2Config Camera2 implementation} from
* {@link androidx.camera.camera2}, and with a custom executor.
* <p/>
* <pre>
* public class MyApplication extends Application implements CameraXConfig.Provider {
* {@literal @}Override
* public CameraXConfig getCameraXConfig() {
* return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
* .setCameraExecutor(myExecutor)
* .setSchedulerHandler(mySchedulerHandler)
* .build();
* }
*
* . . .
* }
* </pre>
* <p>If it isn't possible to subclass the {@link Application} class, such as in library
* code, then the singleton can be configured via {@link #configureInstance(CameraXConfig)}
* before the first invocation of {@code getInstance(context)}, as in the following example.
* <p/>
* <pre>{@code
* class MyCustomizedCameraProvider {
*
* private static boolean configured = false;
*
* static ListenableFuture<ProcessCameraProvider> getInstance(Context context) {
* synchronized(MyCustomizedCameraProvider.class) {
* if (!configured) {
* configured = true;
* ProcessCameraProvider.configureInstance(
* CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
* .setCameraExecutor(myExecutor)
* .setSchedulerHandler(mySchedulerHandler)
* .build());
* }
* }
* return ProcessCameraProvider.getInstance(context);
* }
* }
* }</pre>
* <p>If no {@link CameraXConfig.Provider} is implemented by {@link Application}, or if the
* singleton has not been configured via {@link #configureInstance(CameraXConfig)} a default
* configuration will be used.
*
* @return A future which will contain the {@link ProcessCameraProvider}. Cancellation of
* this future is a no-op. This future may fail with an {@link InitializationException} and
* associated cause that can be retrieved by {@link Throwable#getCause()}. The cause will be
* a {@link androidx.camera.core.CameraUnavailableException} if it fails to access any camera
* during initialization.
* @throws IllegalStateException if CameraX fails to initialize via a default provider or a
* CameraXConfig.Provider.
* @see #configureInstance(CameraXConfig)
*/
@NonNull
public static ListenableFuture<ProcessCameraProvider> getInstance(@NonNull Context context) {
Preconditions.checkNotNull(context);
return Futures.transform(sAppInstance.getOrCreateCameraXInstance(context),
cameraX -> {
sAppInstance.setCameraX(cameraX);
sAppInstance.setContext(ContextUtil.getApplicationContext(context));
return sAppInstance;
}, CameraXExecutors.directExecutor());
}
private ListenableFuture<CameraX> getOrCreateCameraXInstance(@NonNull Context context) {
synchronized (mLock) {
if (mCameraXInitializeFuture != null) {
return mCameraXInitializeFuture;
}
CameraX cameraX = new CameraX(context, mCameraXConfigProvider);
mCameraXInitializeFuture = CallbackToFutureAdapter.getFuture(completer -> {
synchronized (mLock) {
ListenableFuture<Void> future =
FutureChain.from(mCameraXShutdownFuture).transformAsync(
input -> cameraX.getInitializeFuture(),
CameraXExecutors.directExecutor());
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
completer.set(cameraX);
}
@Override
public void onFailure(Throwable t) {
completer.setException(t);
}
}, CameraXExecutors.directExecutor());
}
return "ProcessCameraProvider-initializeCameraX";
});
return mCameraXInitializeFuture;
}
}
/**
* Perform one-time configuration of the {@link ProcessCameraProvider} singleton with the
* given {@link CameraXConfig}.
*
* <p>This method allows configuration of the camera provider via {@link CameraXConfig}. All
* initialization tasks, such as communicating with the camera service, will be executed
* on the {@link java.util.concurrent.Executor} set by
* {@link CameraXConfig.Builder#setCameraExecutor(Executor)}, or by an internally defined
* executor if none is provided.
*
* <p>This method is not required for every application. If the method is not called and
* {@link CameraXConfig.Provider} is not implemented in {@link Application}, default
* configuration will be used.
*
* <p>Once this method is called, the instance configured by the given {@link CameraXConfig} can
* be retrieved with {@link #getInstance(Context)}. {@link CameraXConfig.Provider}
* implemented in {@link Application} will be ignored.
*
* <p>Configuration can only occur once. Once the ProcessCameraProvider has been configured with
* {@code configureInstance()} or {@link #getInstance(Context)}, this method will throw
* an {@link IllegalStateException}. Because configuration can only occur once, <b>usage of this
* method from library code is not recommended</b> as the application owner should ultimately
* be in control of singleton configuration.
*
* @param cameraXConfig configuration options for the singleton process camera provider
* instance.
* @throws IllegalStateException if the camera provider has already been configured by a
* previous call to {@code configureInstance()} or
* {@link #getInstance(Context)}.
*/
@ExperimentalCameraProviderConfiguration
public static void configureInstance(@NonNull CameraXConfig cameraXConfig) {
sAppInstance.configureInstanceInternal(cameraXConfig);
}
private void configureInstanceInternal(@NonNull CameraXConfig cameraXConfig) {
synchronized (mLock) {
Preconditions.checkNotNull(cameraXConfig);
Preconditions.checkState(mCameraXConfigProvider == null, "CameraX has "
+ "already been configured. To use a different configuration, shutdown() must"
+ " be called.");
mCameraXConfigProvider = () -> cameraXConfig;
}
}
/**
* Allows shutting down this {@link ProcessCameraProvider} instance so a new instance can be
* retrieved by {@link #getInstance(Context)}.
*
* <p>Once shutdown, a new instance can be retrieved with
* {@link ProcessCameraProvider#getInstance(Context)}.
*
* <p>This method, along with {@link #configureInstance(CameraXConfig)} allows the process
* camera provider to be used in test suites which may need to initialize CameraX in
* different ways in between tests.
*
* @return A {@link ListenableFuture} representing the shutdown status. Cancellation of this
* future is a no-op.
* @hide
*/
@RestrictTo(Scope.TESTS)
@NonNull
public ListenableFuture<Void> shutdown() {
mLifecycleCameraRepository.clear();
ListenableFuture<Void> shutdownFuture = mCameraX != null ? mCameraX.shutdown() :
Futures.immediateFuture(null);
synchronized (mLock) {
mCameraXConfigProvider = null;
mCameraXInitializeFuture = null;
mCameraXShutdownFuture = shutdownFuture;
}
mCameraX = null;
mContext = null;
return shutdownFuture;
}
private void setCameraX(CameraX cameraX) {
mCameraX = cameraX;
}
private void setContext(Context context) {
mContext = context;
}
/**
* 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>The {@link Camera} returned is determined by the given camera selector, plus other
* internal requirements, possibly from use case configurations. The camera returned from
* bindToLifecycle may differ from the camera determined solely by a camera selector. If the
* camera selector can't resolve a valid camera under the requirements, an
* IllegalArgumentException will be thrown.
*
* <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 and
* internal requirements.
* @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.
*/
@SuppressWarnings({"lambdaLast"})
@MainThread
@NonNull
public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector,
@NonNull UseCase... useCases) {
return bindToLifecycle(lifecycleOwner, cameraSelector, null, useCases);
}
/**
* Binds a {@link UseCaseGroup} to a {@link LifecycleOwner}.
*
* <p> Similar to {@link #bindToLifecycle(LifecycleOwner, CameraSelector, UseCase[])},
* with the addition that the bound collection of {@link UseCase} share parameters
* defined by {@link UseCaseGroup} such as consistent camera sensor rect across all
* {@link UseCase}s.
*
* <p> If one {@link UseCase} is in multiple {@link UseCaseGroup}s, it will be linked to
* the {@link UseCaseGroup} in the latest
* {@link #bindToLifecycle(LifecycleOwner, CameraSelector, UseCaseGroup)} call.
*/
@SuppressWarnings({"lambdaLast"})
@MainThread
@NonNull
public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector,
@NonNull UseCaseGroup useCaseGroup) {
return bindToLifecycle(lifecycleOwner, cameraSelector,
useCaseGroup.getViewPort(), useCaseGroup.getUseCases().toArray(new UseCase[0]));
}
/**
* Binds {@link ViewPort} and a 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>The {@link Camera} returned is determined by the given camera selector, plus other
* internal requirements, possibly from use case configurations. The camera returned from
* bindToLifecycle may differ from the camera determined solely by a camera selector. If the
* camera selector can't resolve a camera under the requirements, an IllegalArgumentException
* will be thrown.
*
* <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 viewPort The viewPort which represents the visible camera sensor rect.
* @param useCases The use cases to bind to a lifecycle.
* @return The {@link Camera} instance which is determined by the camera selector and
* internal requirements.
* @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.
*/
@SuppressWarnings({"lambdaLast", "unused"})
@NonNull
Camera bindToLifecycle(
@NonNull LifecycleOwner lifecycleOwner,
@NonNull CameraSelector cameraSelector,
@Nullable ViewPort viewPort,
@NonNull UseCase... useCases) {
Threads.checkMainThread();
// TODO(b/153096869): override UseCase's target rotation.
// TODO(b/154939118) The filter appending should be removed after extensions are moved to
// the CheckedCameraInternal
CameraSelector.Builder selectorBuilder =
CameraSelector.Builder.fromSelector(cameraSelector);
// Append the camera filter required internally if there's any.
for (UseCase useCase : useCases) {
CameraSelector selector = useCase.getCurrentConfig().getCameraSelector(null);
if (selector != null) {
for (CameraFilter filter : selector.getCameraFilterSet()) {
selectorBuilder.addCameraFilter(filter);
}
}
}
CameraSelector modifiedSelector = selectorBuilder.build();
LinkedHashSet<CameraInternal> cameraInternals =
modifiedSelector.filter(mCameraX.getCameraRepository().getCameras());
if (cameraInternals.isEmpty()) {
throw new IllegalArgumentException("Provided camera selector unable to resolve a "
+ "camera for the given use case");
}
CameraUseCaseAdapter.CameraId cameraId =
CameraUseCaseAdapter.generateCameraId(cameraInternals);
LifecycleCamera lifecycleCameraToBind =
mLifecycleCameraRepository.getLifecycleCamera(lifecycleOwner, cameraId);
Collection<LifecycleCamera> lifecycleCameras =
mLifecycleCameraRepository.getLifecycleCameras();
for (UseCase useCase : useCases) {
for (LifecycleCamera lifecycleCamera : lifecycleCameras) {
if (lifecycleCamera.isBound(useCase)
&& lifecycleCamera != lifecycleCameraToBind) {
throw new IllegalStateException(
String.format(
"Use case %s already bound to a different lifecycle.",
useCase));
}
}
}
// Try to get the camera before binding to the use case, and throw IllegalArgumentException
// if the camera not found.
if (lifecycleCameraToBind == null) {
lifecycleCameraToBind =
mLifecycleCameraRepository.createLifecycleCamera(lifecycleOwner,
new CameraUseCaseAdapter(cameraInternals,
mCameraX.getCameraDeviceSurfaceManager(),
mCameraX.getDefaultConfigFactory()));
}
CameraConfig cameraConfig = null;
// Retrieves extended camera configs from ExtendedCameraConfigProviderStore
for (CameraFilter cameraFilter : cameraSelector.getCameraFilterSet()) {
if (cameraFilter.getIdentifier() != CameraFilter.DEFAULT_ID) {
CameraConfig extendedCameraConfig =
ExtendedCameraConfigProviderStore.getConfigProvider(
cameraFilter.getIdentifier()).getConfig(
lifecycleCameraToBind.getCameraInfo(), mContext);
if (extendedCameraConfig == null) { // ignore IDs unrelated to camera configs.
continue;
}
// Only allows one camera config now.
if (cameraConfig != null) {
throw new IllegalArgumentException(
"Cannot apply multiple extended camera configs at the same time.");
}
cameraConfig = extendedCameraConfig;
}
}
// Applies extended camera configs to the camera
lifecycleCameraToBind.setExtendedConfig(cameraConfig);
if (useCases.length == 0) {
return lifecycleCameraToBind;
}
mLifecycleCameraRepository.bindToLifecycleCamera(lifecycleCameraToBind, viewPort,
Arrays.asList(useCases));
return lifecycleCameraToBind;
}
/**
* 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()}.
*/
@Override
public boolean isBound(@NonNull UseCase useCase) {
for (LifecycleCamera lifecycleCamera :
mLifecycleCameraRepository.getLifecycleCameras()) {
if (lifecycleCamera.isBound(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.
*/
@MainThread
@Override
public void unbind(@NonNull UseCase... useCases) {
Threads.checkMainThread();
mLifecycleCameraRepository.unbind(Arrays.asList(useCases));
}
/**
* 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.
*/
@MainThread
@Override
public void unbindAll() {
Threads.checkMainThread();
mLifecycleCameraRepository.unbindAll();
}
/** {@inheritDoc} */
@Override
public boolean hasCamera(@NonNull CameraSelector cameraSelector)
throws CameraInfoUnavailableException {
try {
cameraSelector.select(mCameraX.getCameraRepository().getCameras());
} catch (IllegalArgumentException e) {
return false;
}
return true;
}
/**
* Returns {@link CameraInfo} instances of the available cameras.
*
* <p>The available cameras include all the available cameras on the device, or only those
* selected through
* {@link androidx.camera.core.CameraXConfig.Builder#setAvailableCamerasLimiter(CameraSelector)}
*
* <p>While iterating through all the available {@link CameraInfo}, if one of them meets some
* predefined requirements, a {@link CameraSelector} that uniquely identifies its camera
* can be retrieved using {@link CameraInfo#getCameraSelector()}, which can then be used to bind
* {@linkplain UseCase use cases} to that camera.
*
* @return A list of {@link CameraInfo} instances for the available cameras.
*/
@NonNull
@Override
public List<CameraInfo> getAvailableCameraInfos() {
final List<CameraInfo> availableCameraInfos = new ArrayList<>();
final Set<CameraInternal> cameras = mCameraX.getCameraRepository().getCameras();
for (final CameraInternal camera : cameras) {
availableCameraInfos.add(camera.getCameraInfo());
}
return availableCameraInfos;
}
private ProcessCameraProvider() {
}
}