blob: eb703aae93cb04f360aba8e5148cc89eb84d9b7f [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 static androidx.camera.core.MirrorMode.MIRROR_MODE_OFF;
import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON;
import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
import static androidx.camera.core.impl.ImageOutputConfig.OPTION_MAX_RESOLUTION;
import static androidx.camera.core.impl.ImageOutputConfig.OPTION_RESOLUTION_SELECTOR;
import static androidx.camera.core.impl.ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO;
import static androidx.camera.core.impl.ImageOutputConfig.OPTION_TARGET_RESOLUTION;
import static androidx.camera.core.impl.StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
import static androidx.camera.core.impl.utils.TransformUtils.within360;
import static androidx.camera.core.processing.TargetUtils.isSuperset;
import static androidx.core.util.Preconditions.checkArgument;
import static androidx.core.util.Preconditions.checkArgumentInRange;
import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.media.ImageReader;
import android.util.Range;
import android.util.Size;
import android.view.OrientationEventListener;
import android.view.Surface;
import androidx.annotation.CallSuper;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntRange;
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.impl.CameraControlInternal;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.Config;
import androidx.camera.core.impl.Config.Option;
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.core.impl.MutableOptionsBundle;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.StreamSpec;
import androidx.camera.core.impl.UseCaseConfig;
import androidx.camera.core.impl.UseCaseConfigFactory;
import androidx.camera.core.internal.TargetConfig;
import androidx.camera.core.internal.utils.UseCaseConfigUtil;
import androidx.camera.core.resolutionselector.ResolutionSelector;
import androidx.camera.core.streamsharing.StreamSharing;
import androidx.core.util.Preconditions;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* The use case which all other use cases are built on top of.
*
* <p>A UseCase provides functionality to map the set of arguments in a use case to arguments
* that are usable by a camera. UseCase also will communicate of the active/inactive state to
* the Camera.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public abstract class UseCase {
////////////////////////////////////////////////////////////////////////////////////////////
// [UseCase lifetime constant] - Stays constant for the lifetime of the UseCase. Which means
// they could be created in the constructor.
////////////////////////////////////////////////////////////////////////////////////////////
/**
* The set of {@link StateChangeCallback} that are currently listening state transitions of this
* use case.
*/
private final Set<StateChangeCallback> mStateChangeCallbacks = new HashSet<>();
private final Object mCameraLock = new Object();
////////////////////////////////////////////////////////////////////////////////////////////
// [UseCase lifetime dynamic] - Dynamic variables which could change during anytime during
// the UseCase lifetime.
////////////////////////////////////////////////////////////////////////////////////////////
private State mState = State.INACTIVE;
/** Extended config, applied on top of the app defined Config (mUseCaseConfig). */
@Nullable
private UseCaseConfig<?> mExtendedConfig;
/**
* Store the app defined {@link UseCaseConfig} used to create the use case.
*/
@NonNull
private UseCaseConfig<?> mUseCaseConfig;
/**
* The currently used Config.
*
* <p> This is the combination of the extended Config, app provided Config, and camera
* implementation Config (with decreasing priority).
*/
@NonNull
private UseCaseConfig<?> mCurrentConfig;
////////////////////////////////////////////////////////////////////////////////////////////
// [UseCase attached constant] - Is only valid when the UseCase is attached to a camera.
////////////////////////////////////////////////////////////////////////////////////////////
/**
* The {@link StreamSpec} assigned to the {@link UseCase} based on the attached camera.
*/
private StreamSpec mAttachedStreamSpec;
/**
* The camera implementation provided Config. Its options has lowest priority and will be
* overwritten by any app defined or extended configs.
*/
@Nullable
private UseCaseConfig<?> mCameraConfig;
/**
* The crop rect calculated at the time of binding based on {@link ViewPort}.
*/
@Nullable
private Rect mViewPortCropRect;
/**
* The sensor to image buffer transform matrix.
*/
@NonNull
private Matrix mSensorToBufferTransformMatrix = new Matrix();
@GuardedBy("mCameraLock")
private CameraInternal mCamera;
@Nullable
private CameraEffect mEffect;
@Nullable
private String mPhysicalCameraId;
////////////////////////////////////////////////////////////////////////////////////////////
// [UseCase attached dynamic] - Can change but is only available when the UseCase is attached.
////////////////////////////////////////////////////////////////////////////////////////////
// The currently attached session config
@NonNull
private SessionConfig mAttachedSessionConfig = SessionConfig.defaultEmptySessionConfig();
/**
* Creates a named instance of the use case.
*
* @param currentConfig the configuration object used for this use case
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected UseCase(@NonNull UseCaseConfig<?> currentConfig) {
mUseCaseConfig = currentConfig;
mCurrentConfig = currentConfig;
}
/**
* Retrieve the default {@link UseCaseConfig} for the UseCase.
*
* @param applyDefaultConfig true if this is the base config applied to a UseCase.
* @param factory the factory that contains the default UseCases.
* @return The UseCaseConfig or null if there is no default Config.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public abstract UseCaseConfig<?> getDefaultConfig(boolean applyDefaultConfig,
@NonNull UseCaseConfigFactory factory);
/**
* Create a {@link UseCaseConfig.Builder} for the UseCase.
*
* @param config the Config to initialize the builder
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public abstract UseCaseConfig.Builder<?, ?, ?> getUseCaseConfigBuilder(@NonNull Config config);
/**
* Create a merged {@link UseCaseConfig} from the UseCase, camera, and an extended config.
*
* @param cameraInfo info about the camera which may be used to resolve conflicts.
* @param extendedConfig configs that take priority over the UseCase's default config
* @param cameraDefaultConfig configs that have lower priority than the UseCase's default.
* This Config comes from the camera implementation.
* @throws IllegalArgumentException if there exists conflicts in the merged config that can
* not be resolved
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public UseCaseConfig<?> mergeConfigs(
@NonNull CameraInfoInternal cameraInfo,
@Nullable UseCaseConfig<?> extendedConfig,
@Nullable UseCaseConfig<?> cameraDefaultConfig) {
MutableOptionsBundle mergedConfig;
if (cameraDefaultConfig != null) {
mergedConfig = MutableOptionsBundle.from(cameraDefaultConfig);
mergedConfig.removeOption(TargetConfig.OPTION_TARGET_NAME);
} else {
mergedConfig = MutableOptionsBundle.create();
}
// Removes the default resolution selector setting to go for the legacy resolution
// selection logic flow if applications call the legacy setTargetAspectRatio and
// setTargetResolution APIs to do the setting.
if (mUseCaseConfig.containsOption(OPTION_TARGET_ASPECT_RATIO)
|| mUseCaseConfig.containsOption(OPTION_TARGET_RESOLUTION)) {
if (mergedConfig.containsOption(OPTION_RESOLUTION_SELECTOR)) {
mergedConfig.removeOption(OPTION_RESOLUTION_SELECTOR);
}
}
// Removes the default max resolution setting if application sets any ResolutionStrategy
// to override it.
if (mUseCaseConfig.containsOption(OPTION_RESOLUTION_SELECTOR)
&& mergedConfig.containsOption(OPTION_MAX_RESOLUTION)) {
ResolutionSelector resolutionSelector =
mUseCaseConfig.retrieveOption(OPTION_RESOLUTION_SELECTOR);
if (resolutionSelector.getResolutionStrategy() != null) {
mergedConfig.removeOption(OPTION_MAX_RESOLUTION);
}
}
// If any options need special handling, this is the place to do it. For now we'll just copy
// over all options.
for (Option<?> opt : mUseCaseConfig.listOptions()) {
Config.mergeOptionValue(mergedConfig, mergedConfig, mUseCaseConfig, opt);
}
if (extendedConfig != null) {
// If any options need special handling, this is the place to do it. For now we'll
// just copy over all options.
for (Option<?> opt : extendedConfig.listOptions()) {
@SuppressWarnings("unchecked") // Options/values are being copied directly
Option<Object> objectOpt = (Option<Object>) opt;
if (objectOpt.getId().equals(TargetConfig.OPTION_TARGET_NAME.getId())) {
continue;
}
Config.mergeOptionValue(mergedConfig, mergedConfig, extendedConfig, opt);
}
}
// If OPTION_TARGET_RESOLUTION has been set by the user, remove
// OPTION_TARGET_ASPECT_RATIO from defaultConfigBuilder because these two settings cannot be
// set at the same time.
if (mergedConfig.containsOption(ImageOutputConfig.OPTION_TARGET_RESOLUTION)
&& mergedConfig.containsOption(
ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO)) {
mergedConfig.removeOption(ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO);
}
// Forces disable ZSL when high resolution is enabled.
if (mergedConfig.containsOption(ImageOutputConfig.OPTION_RESOLUTION_SELECTOR)
&& mergedConfig.retrieveOption(
ImageOutputConfig.OPTION_RESOLUTION_SELECTOR).getAllowedResolutionMode()
!= ResolutionSelector.PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION) {
mergedConfig.insertOption(UseCaseConfig.OPTION_ZSL_DISABLED, true);
}
return onMergeConfig(cameraInfo, getUseCaseConfigBuilder(mergedConfig));
}
/**
* Called when a set of configs are merged so the UseCase can do additional handling.
*
* <p> This can be overridden by a UseCase which need to do additional verification of the
* configs to make sure there are no conflicting options.
*
* @param cameraInfo info about the camera which may be used to resolve conflicts.
* @param builder the builder containing the merged configs requiring addition conflict
* resolution
* @return the conflict resolved config
* @throws IllegalArgumentException if there exists conflicts in the merged config that can
* not be resolved
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected UseCaseConfig<?> onMergeConfig(@NonNull CameraInfoInternal cameraInfo,
@NonNull UseCaseConfig.Builder<?, ?, ?> builder) {
return builder.getUseCaseConfig();
}
/**
* A utility function that can convert the orientation degrees of
* {@link OrientationEventListener} to the nearest {@link Surface} rotation.
*
* <p>In general, it is best to use an {@link android.view.OrientationEventListener} to set
* the UseCase target rotation. This way, the rotation output will indicate which way is down
* for a given image or video. This is important since display orientation may be locked by
* device default, user setting, or app configuration, and some devices may not transition to a
* reverse-portrait display orientation. In these cases, set target rotation dynamically
* according to the {@link android.view.OrientationEventListener}, without re-creating the
* use case. The sample code is as below:
* <pre>{@code
* public class CameraXActivity extends AppCompatActivity {
*
* private OrientationEventListener mOrientationEventListener;
*
* @Override
* protected void onStart() {
* super.onStart();
* if (mOrientationEventListener == null) {
* mOrientationEventListener = new OrientationEventListener(this) {
* @Override
* public void onOrientationChanged(int orientation) {
* if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
* return;
* }
* int rotation = UseCase.snapToSurfaceRotation(orientation);
* mImageCapture.setTargetRotation(rotation);
* mImageAnalysis.setTargetRotation(rotation);
* mVideoCapture.setTargetRotation(rotation);
* }
* };
* }
* mOrientationEventListener.enable();
* }
*
* @Override
* protected void onStop() {
* super.onStop();
* mOrientationEventListener.disable();
* }
* }
* }</pre>
*
* @param orientation the orientation degrees in range [0, 359].
* @return surface rotation. One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
* {@link Surface#ROTATION_180} and {@link Surface#ROTATION_270}.
* @throws IllegalArgumentException if the input orientation degrees is not in range [0, 359].
* @see ImageCapture#setTargetRotation(int)
* @see ImageAnalysis#setTargetRotation(int)
*/
@ImageOutputConfig.RotationValue
public static int snapToSurfaceRotation(@IntRange(from = 0, to = 359) int orientation) {
checkArgumentInRange(orientation, 0, 359, "orientation");
if (orientation >= 315 || orientation < 45) {
return Surface.ROTATION_0;
} else if (orientation >= 225) {
return Surface.ROTATION_90;
} else if (orientation >= 135) {
return Surface.ROTATION_180;
} else {
return Surface.ROTATION_270;
}
}
@RestrictTo(Scope.LIBRARY_GROUP)
public void setPhysicalCameraId(@NonNull String physicalCameraId) {
mPhysicalCameraId = physicalCameraId;
}
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public String getPhysicalCameraId() {
return mPhysicalCameraId;
}
/**
* Updates the target rotation of the use case config.
*
* @param targetRotation Target rotation of the output image, expressed as one of
* {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
* {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}.
* @return true if the target rotation was changed.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected boolean setTargetRotationInternal(
@ImageOutputConfig.RotationValue int targetRotation) {
ImageOutputConfig oldConfig = (ImageOutputConfig) getCurrentConfig();
int oldRotation = oldConfig.getTargetRotation(ImageOutputConfig.INVALID_ROTATION);
if (oldRotation == ImageOutputConfig.INVALID_ROTATION || oldRotation != targetRotation) {
UseCaseConfig.Builder<?, ?, ?> builder = getUseCaseConfigBuilder(mUseCaseConfig);
UseCaseConfigUtil.updateTargetRotationAndRelatedConfigs(builder, targetRotation);
mUseCaseConfig = builder.getUseCaseConfig();
// Only merge configs if currently attached to a camera. Otherwise, set the current
// config to the use case config and mergeConfig() will be called once the use case
// is attached to a camera.
CameraInternal camera = getCamera();
if (camera == null) {
mCurrentConfig = mUseCaseConfig;
} else {
mCurrentConfig = mergeConfigs(camera.getCameraInfoInternal(), mExtendedConfig,
mCameraConfig);
}
return true;
}
return false;
}
/**
* Returns the rotation that the intended target resolution is expressed in.
*
* @return The rotation of the intended target.
*/
@SuppressLint("WrongConstant")
@RestrictTo(Scope.LIBRARY_GROUP)
@ImageOutputConfig.RotationValue
protected int getTargetRotationInternal() {
return ((ImageOutputConfig) mCurrentConfig).getTargetRotation(Surface.ROTATION_0);
}
/**
* Returns the target frame rate range for the associated VideoCapture use case.
*
* @return The target frame rate.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
protected Range<Integer> getTargetFrameRateInternal() {
return mCurrentConfig.getTargetFrameRate(FRAME_RATE_RANGE_UNSPECIFIED);
}
/**
* Returns the mirror mode.
*
* <p>If mirror mode is not set, defaults to {@link MirrorMode#MIRROR_MODE_OFF}.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@MirrorMode.Mirror
protected int getMirrorModeInternal() {
return ((ImageOutputConfig) mCurrentConfig).getMirrorMode(MIRROR_MODE_OFF);
}
/**
* Returns if the mirroring is required with the associated camera.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public boolean isMirroringRequired(@NonNull CameraInternal camera) {
int mirrorMode = getMirrorModeInternal();
switch (mirrorMode) {
case MIRROR_MODE_OFF:
return false;
case MIRROR_MODE_ON:
return true;
case MIRROR_MODE_ON_FRONT_ONLY:
return camera.isFrontFacing();
default:
throw new AssertionError("Unknown mirrorMode: " + mirrorMode);
}
}
/**
* Returns the target rotation set by apps explicitly.
*
* @return The rotation of the intended target.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@ImageOutputConfig.OptionalRotationValue
protected int getAppTargetRotation() {
return ((ImageOutputConfig) mCurrentConfig)
.getAppTargetRotation(ImageOutputConfig.ROTATION_NOT_SPECIFIED);
}
/**
* Gets the relative rotation degrees without mirroring.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@IntRange(from = 0, to = 359)
protected int getRelativeRotation(@NonNull CameraInternal cameraInternal) {
return getRelativeRotation(cameraInternal, /*requireMirroring=*/false);
}
/**
* Gets the relative rotation degrees given whether the output should be mirrored.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@IntRange(from = 0, to = 359)
protected int getRelativeRotation(@NonNull CameraInternal cameraInternal,
boolean requireMirroring) {
int rotation = cameraInternal.getCameraInfoInternal().getSensorRotationDegrees(
getTargetRotationInternal());
// Parent UseCase always mirror the stream if the child requires it. No camera transform
// means that the stream is copied by a parent, and if the child also requires mirroring,
// we know that the stream has been mirrored.
boolean inputStreamMirrored = !cameraInternal.getHasTransform() && requireMirroring;
if (inputStreamMirrored) {
// Flip rotation if the stream has been mirrored.
rotation = within360(-rotation);
}
return rotation;
}
/**
* Sets the {@link SessionConfig} that will be used by the attached {@link Camera}.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected void updateSessionConfig(@NonNull SessionConfig sessionConfig) {
mAttachedSessionConfig = sessionConfig;
for (DeferrableSurface surface : sessionConfig.getSurfaces()) {
if (surface.getContainerClass() == null) {
surface.setContainerClass(this.getClass());
}
}
}
/**
* Add a {@link StateChangeCallback}, which listens to this UseCase's active and inactive
* transition events.
*/
private void addStateChangeCallback(@NonNull StateChangeCallback callback) {
mStateChangeCallbacks.add(callback);
}
/**
* Remove a {@link StateChangeCallback} from listening to this UseCase's active and inactive
* transition events.
*
* <p>If the listener isn't currently listening to the UseCase then this call does nothing.
*/
private void removeStateChangeCallback(@NonNull StateChangeCallback callback) {
mStateChangeCallbacks.remove(callback);
}
/**
* Get the current {@link SessionConfig}.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public SessionConfig getSessionConfig() {
return mAttachedSessionConfig;
}
/**
* Notify all {@link StateChangeCallback} that are listening to this UseCase that it has
* transitioned to an active state.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected final void notifyActive() {
mState = State.ACTIVE;
notifyState();
}
/**
* Notify all {@link StateChangeCallback} that are listening to this UseCase that it has
* transitioned to an inactive state.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected final void notifyInactive() {
mState = State.INACTIVE;
notifyState();
}
/**
* Notify all {@link StateChangeCallback} that are listening to this UseCase that the
* settings have been updated.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected final void notifyUpdated() {
for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) {
stateChangeCallback.onUseCaseUpdated(this);
}
}
/**
* Notify all {@link StateChangeCallback} that are listening to this UseCase that the use
* case needs to be completely reset.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected final void notifyReset() {
for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) {
stateChangeCallback.onUseCaseReset(this);
}
}
/**
* Notify all {@link StateChangeCallback} that are listening to this UseCase of its current
* state.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public final void notifyState() {
switch (mState) {
case INACTIVE:
for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) {
stateChangeCallback.onUseCaseInactive(this);
}
break;
case ACTIVE:
for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) {
stateChangeCallback.onUseCaseActive(this);
}
break;
}
}
/**
* Returns the camera ID for the currently attached camera, or throws an exception if no
* camera is attached.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected String getCameraId() {
return Preconditions.checkNotNull(getCamera(),
"No camera attached to use case: " + this).getCameraInfoInternal().getCameraId();
}
/**
* Checks whether the provided camera ID is the currently attached camera ID.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected boolean isCurrentCamera(@NonNull String cameraId) {
if (getCamera() == null) {
return false;
}
return Objects.equals(cameraId, getCameraId());
}
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public String getName() {
return Objects.requireNonNull(
mCurrentConfig.getTargetName("<UnknownUseCase-" + hashCode() + ">"));
}
/**
* Retrieves the configuration set by applications.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected UseCaseConfig<?> getAppConfig() {
return mUseCaseConfig;
}
/**
* Retrieves the configuration used by this use case.
*
* @return the configuration used by this use case.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public UseCaseConfig<?> getCurrentConfig() {
return mCurrentConfig;
}
/**
* Returns the currently attached {@link Camera} or {@code null} if none is attached.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public CameraInternal getCamera() {
synchronized (mCameraLock) {
return mCamera;
}
}
/**
* Retrieves the currently attached surface resolution.
*
* @return the currently attached surface resolution for the given camera id.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Size getAttachedSurfaceResolution() {
return mAttachedStreamSpec != null ? mAttachedStreamSpec.getResolution() : null;
}
/**
* Retrieves the currently attached stream specification.
*
* @return the currently attached stream specification.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public StreamSpec getAttachedStreamSpec() {
return mAttachedStreamSpec;
}
/**
* Offers suggested stream specification for the UseCase.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void updateSuggestedStreamSpec(@NonNull StreamSpec suggestedStreamSpec) {
mAttachedStreamSpec = onSuggestedStreamSpecUpdated(suggestedStreamSpec);
}
/**
* Called when binding new use cases via {@code CameraX#bindToLifecycle(LifecycleOwner,
* CameraSelector, UseCase...)}.
*
* <p>Override to create necessary objects like {@link ImageReader} depending
* on the stream specification.
*
* @param suggestedStreamSpec The suggested stream specification that depends on camera device
* capability and what and how many use cases will be bound.
* @return The stream specification that finally used to create the SessionConfig to
* attach to the camera device.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected StreamSpec onSuggestedStreamSpecUpdated(@NonNull StreamSpec suggestedStreamSpec) {
return suggestedStreamSpec;
}
/**
* Update the implementation options of the stream specification for the UseCase.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void updateSuggestedStreamSpecImplementationOptions(@NonNull Config config) {
mAttachedStreamSpec = onSuggestedStreamSpecImplementationOptionsUpdated(config);
}
/**
* Called when updating the stream specifications' implementation options of existing use cases
* via {@code CameraUseCaseAdapter#updateUseCases}.
*
* @param config The new implementationOptions for the stream specification.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected StreamSpec onSuggestedStreamSpecImplementationOptionsUpdated(@NonNull Config config) {
if (mAttachedStreamSpec == null) {
throw new UnsupportedOperationException("Attempt to update the implementation options "
+ "for a use case without attached stream specifications.");
}
return mAttachedStreamSpec.toBuilder().setImplementationOptions(config).build();
}
/**
* Called when CameraControlInternal is attached into the UseCase. UseCase may need to
* override this method to configure the CameraControlInternal here. Ex. Setting correct flash
* mode by CameraControlInternal.setFlashMode to enable correct AE mode and flash state.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void onCameraControlReady() {
}
/**
* Binds use case to a camera.
*
* <p>Before a use case can receive frame data, it needs to establish association with the
* target camera first. An implementation of {@link CameraInternal} (e.g. a lifecycle camera
* or lifecycle-less camera) is provided when
* {@link #bindToCamera(CameraInternal, UseCaseConfig, UseCaseConfig)} is invoked, so that the
* use case can retrieve the necessary information from the camera to calculate and set up
* the configs.
*
* <p>The default, extended and camera config settings are also applied to the use case config
* in this stage. Subclasses can override {@link #onMergeConfig} to update the use case
* config for use case specific purposes.
*
* <p>Calling {@link #getCameraControl()} can retrieve a real {@link CameraControlInternal}
* implementation of the associated camera after this function is invoked. Otherwise, a fake
* no-op {@link CameraControlInternal} implementation is returned by
* {@link #getCameraControl()} function.
*/
@SuppressLint("WrongConstant")
@RestrictTo(Scope.LIBRARY_GROUP)
public final void bindToCamera(@NonNull CameraInternal camera,
@Nullable UseCaseConfig<?> extendedConfig,
@Nullable UseCaseConfig<?> cameraConfig) {
synchronized (mCameraLock) {
mCamera = camera;
addStateChangeCallback(camera);
}
mExtendedConfig = extendedConfig;
mCameraConfig = cameraConfig;
mCurrentConfig = mergeConfigs(camera.getCameraInfoInternal(), mExtendedConfig,
mCameraConfig);
onBind();
}
/**
* Called when use case is binding to a camera.
*
* <p>Subclasses can override this callback function to create the necessary objects to
* make the use case work correctly.
*
* <p>After this function is invoked, CameraX will also provide the selected resolution
* information to subclasses via {@link #onSuggestedStreamSpecUpdated}. Subclasses should
* override it to set up the pipeline according to the selected resolution, so that UseCase
* becomes ready to receive data from the camera.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void onBind() {
}
/**
* Unbinds use case from a camera.
*
* <p>The use case de-associates from the camera. Before this function is invoked, the use
* case must have been detached from the camera. So that the {@link CameraInternal}
* implementation can remove the related resource (e.g. surface) from the working capture
* session. Then, when this function is invoked, the use case can also clear all objects and
* settings to initial state like it is never bound to a camera.
*
* <p>After this function is invoked, calling {@link #getCameraControl()} returns a fake no-op
* {@link CameraControlInternal} implementation.
*
*/
@RestrictTo(Scope.LIBRARY)
public final void unbindFromCamera(@NonNull CameraInternal camera) {
// Do any cleanup required by the UseCase implementation
onUnbind();
synchronized (mCameraLock) {
checkArgument(camera == mCamera);
removeStateChangeCallback(mCamera);
mCamera = null;
}
mAttachedStreamSpec = null;
mViewPortCropRect = null;
// Resets the mUseCaseConfig to the initial status when the use case was created to make
// the use case reusable.
mCurrentConfig = mUseCaseConfig;
mExtendedConfig = null;
mCameraConfig = null;
}
/**
* Called when use case is unbinding from a camera.
*
* <p>Subclasses can override this callback function to clear the objects created for
* their specific purposes.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void onUnbind() {
}
/**
* Called when use case is attached to the camera. This method is called on main thread.
*
* <p>Once this function is invoked, the use case is attached to the {@link CameraInternal}
* implementation of the associated camera. CameraX starts to open the camera and capture
* session with the use case session config. The use case can receive the frame data from the
* camera after the capture session is configured.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@CallSuper
public void onStateAttached() {
}
/**
* Called when use case is detached from the camera. This method is called on main thread.
*
* <p>Once this function is invoked, the use case is detached from the {@link CameraInternal}
* implementation of the associated camera. The use case no longer receives frame data from
* the camera.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void onStateDetached() {
}
/**
* Retrieves a previously attached {@link CameraControlInternal}.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected CameraControlInternal getCameraControl() {
synchronized (mCameraLock) {
if (mCamera == null) {
return CameraControlInternal.DEFAULT_EMPTY_INSTANCE;
}
return mCamera.getCameraControlInternal();
}
}
/**
* Sets the view port crop rect calculated at the time of binding.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@CallSuper
public void setViewPortCropRect(@NonNull Rect viewPortCropRect) {
mViewPortCropRect = viewPortCropRect;
}
/**
* Sets the {@link CameraEffect} associated with this use case.
*
* @throws IllegalArgumentException if the effect targets are not supported by this use case.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void setEffect(@Nullable CameraEffect effect) {
checkArgument(effect == null || isEffectTargetsSupported(effect.getTargets()));
mEffect = effect;
}
/**
* Gets the {@link CameraEffect} associated with this use case.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public CameraEffect getEffect() {
return mEffect;
}
/**
* Gets the view port crop rect.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Rect getViewPortCropRect() {
return mViewPortCropRect;
}
/**
* Sets the sensor to image buffer transform matrix.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@CallSuper
public void setSensorToBufferTransformMatrix(@NonNull Matrix sensorToBufferTransformMatrix) {
mSensorToBufferTransformMatrix = new Matrix(sensorToBufferTransformMatrix);
}
/**
* Gets the sensor to image buffer transform matrix.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public Matrix getSensorToBufferTransformMatrix() {
return mSensorToBufferTransformMatrix;
}
/**
* Get image format for the use case.
*
* @return image format for the use case
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public int getImageFormat() {
return mCurrentConfig.getInputFormat();
}
/**
* Returns a new {@link ResolutionInfo} according to the latest settings of the use case, or
* null if the use case is not bound yet.
*
* <p>This allows the subclasses to return different {@link ResolutionInfo} according to its
* different design.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
protected ResolutionInfo getResolutionInfoInternal() {
CameraInternal camera = getCamera();
Size resolution = getAttachedSurfaceResolution();
if (camera == null || resolution == null) {
return null;
}
Rect cropRect = getViewPortCropRect();
if (cropRect == null) {
cropRect = new Rect(0, 0, resolution.getWidth(), resolution.getHeight());
}
int rotationDegrees = getRelativeRotation(camera);
return new ResolutionInfo(resolution, cropRect, rotationDegrees);
}
/**
* A set of {@link CameraEffect.Targets} bitmasks supported by the {@link UseCase}.
*
* <p>To apply the {@link CameraEffect} on the {@link UseCase} or one of its ancestors,
* {@link CameraEffect#getTargets()} must be a superset of at least one of the bitmask. For
* example:
* <ul>
* <li>For {@link Preview}, the set only contains [PREVIEW]. {@link Preview} and its ancestors
* supports effects that are supersets of [PREVIEW]: PREVIEW, PREVIEW|VIDEO_CAPTURE, or
* PREVIEW|VIDEO_CAPTURE|IMAGE_CAPTURE. A {@link CameraEffect} that does not target PREVIEW
* cannot be applied to {@link Preview} or its ancestors.
* <li>For {@link StreamSharing}, the set contains [PREVIEW|VIDEO_CAPTURE].
* {@link StreamSharing} supports effects with targets PREVIEW|VIDEO_CAPTURE or
* PREVIEW|VIDEO_CAPTURE|IMAGE_CAPTURE.
* </ul>
*
* <p>The method returns an empty set if this {@link UseCase} does not support effects. By
* default, this method returns an empty set.
*
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
protected Set<Integer> getSupportedEffectTargets() {
return Collections.emptySet();
}
/**
* Returns whether the targets can be applied to this {@link UseCase} or one of its ancestors.
*
* @see #getSupportedEffectTargets()
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public boolean isEffectTargetsSupported(@CameraEffect.Targets int effectTargets) {
for (Integer useCaseTargets : getSupportedEffectTargets()) {
if (isSuperset(effectTargets, useCaseTargets)) {
return true;
}
}
return false;
}
enum State {
/** Currently waiting for image data. */
ACTIVE,
/** Currently not waiting for image data. */
INACTIVE
}
/**
* Callback for when a {@link UseCase} transitions between active/inactive states.
*
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public interface StateChangeCallback {
/**
* Called when a {@link UseCase} becomes active.
*
* <p>When a UseCase is active it expects that all data producers attached to itself
* should start producing data for it to consume. In addition the UseCase will start
* producing data that other classes can be consumed.
*/
void onUseCaseActive(@NonNull UseCase useCase);
/**
* Called when a {@link UseCase} becomes inactive.
*
* <p>When a UseCase is inactive it no longer expects data to be produced for it. In
* addition the UseCase will stop producing data for other classes to consume.
*/
void onUseCaseInactive(@NonNull UseCase useCase);
/**
* Called when a {@link UseCase} has updated settings.
*
* <p>When a {@link UseCase} has updated settings, it is expected that the listener will
* use these updated settings to reconfigure the listener's own state. A settings update is
* orthogonal to the active/inactive state change.
*/
void onUseCaseUpdated(@NonNull UseCase useCase);
/**
* Called when a {@link UseCase} has updated settings that require complete reset of the
* camera.
*
* <p>Updating certain parameters of the use case require a full reset of the camera. This
* includes updating the {@link Surface} used by the use case.
*/
void onUseCaseReset(@NonNull UseCase useCase);
}
}