[go: nahoru, domu]

blob: 6570bd0fa20de189c346de2eb7fff8cffc9b4d0c [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.graphics.Rect;
import android.media.ImageReader;
import android.util.Size;
import android.view.Surface;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.camera.core.impl.CameraControlInternal;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.Config.Option;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.core.impl.MutableConfig;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.UseCaseConfig;
import androidx.camera.core.impl.UseCaseConfig.Builder;
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.
*/
public abstract class UseCase {
/**
* The set of {@link StateChangeCallback} that are currently listening state transitions of this
* use case.
*/
private final Set<StateChangeCallback> mStateChangeCallbacks = new HashSet<>();
// The currently attached session config
private SessionConfig mAttachedSessionConfig = SessionConfig.defaultEmptySessionConfig();
/**
* The resolution assigned to the {@link UseCase} based on the attached camera.
*/
private Size mAttachedResolution;
/**
* The crop rect calculated at the time of binding based on {@link ViewPort}.
*/
@Nullable
private Rect mViewPortCropRect;
private State mState = State.INACTIVE;
private UseCaseConfig<?> mUseCaseConfig;
private final Object mCameraLock = new Object();
@GuardedBy("mCameraLock")
private CameraInternal mCamera;
/**
* Creates a named instance of the use case.
*
* @param useCaseConfig the configuration object used for this use case
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected UseCase(@NonNull UseCaseConfig<?> useCaseConfig) {
updateUseCaseConfig(useCaseConfig);
}
/**
* Returns a use case configuration pre-populated with default configuration
* options.
*
* <p>This is used to generate a final configuration by combining the user-supplied
* configuration with the default configuration. Subclasses can override this method to provide
* the pre-populated builder. If <code>null</code> is returned, then the user-supplied
* configuration will be used directly.
*
* @param cameraInfo The {@link CameraInfo} of the camera that the default builder will
* target to, null if it doesn't target to any camera.
* @return A builder pre-populated with use case default options.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
protected Builder<?, ?, ?> getDefaultBuilder(@Nullable CameraInfo cameraInfo) {
return null;
}
/**
* Updates the stored use case configuration.
*
* <p>This configuration will be combined with the default configuration that is contained in
* the pre-populated builder supplied by {@link #getDefaultBuilder}, if it exists and the
* behavior of {@link #applyDefaults(UseCaseConfig, Builder)} is not overridden. Once this
* method returns, the combined use case configuration can be retrieved with
* {@link #getUseCaseConfig()}.
*
* <p>This method alone will not make any changes to the {@link SessionConfig}, it is up to
* the use case to decide when to modify the session configuration.
*
* @param useCaseConfig Configuration which will be applied on top of use case defaults, if a
* default builder is provided by {@link #getDefaultBuilder}.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected final void updateUseCaseConfig(@NonNull UseCaseConfig<?> useCaseConfig) {
// Attempt to retrieve builder containing defaults for this use case's config
Builder<?, ?, ?> defaultBuilder =
getDefaultBuilder(getCamera() == null ? null : getCamera().getCameraInfo());
// Combine with default configuration.
mUseCaseConfig = applyDefaults(useCaseConfig, defaultBuilder);
}
/**
* Combines user-supplied configuration with use case default configuration.
*
* <p>Subclasses can override this method to
* modify the behavior of combining user-supplied values and default values.
*
* @param userConfig The user-supplied configuration.
* @param defaultConfigBuilder A builder containing use-case default values, or {@code null}
* if no default values exist.
* @return The configuration that will be used by this use case.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected UseCaseConfig<?> applyDefaults(
@NonNull UseCaseConfig<?> userConfig,
@Nullable Builder<?, ?, ?> defaultConfigBuilder) {
if (defaultConfigBuilder == null) {
// No default builder was retrieved, return config directly
return userConfig;
}
MutableConfig defaultMutableConfig = defaultConfigBuilder.getMutableConfig();
// If OPTION_TARGET_ASPECT_RATIO has been set by the user, remove
// OPTION_TARGET_ASPECT_RATIO_CUSTOM from defaultConfigBuilder. Otherwise, it may cause
// aspect ratio mismatched issue.
if (userConfig.containsOption(ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO)
&& defaultMutableConfig.containsOption(
ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO_CUSTOM)) {
defaultMutableConfig.removeOption(ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO_CUSTOM);
}
// 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 : userConfig.listOptions()) {
@SuppressWarnings("unchecked") // Options/values are being copied directly
Option<Object> objectOpt = (Option<Object>) opt;
defaultMutableConfig.insertOption(objectOpt, userConfig.retrieveOption(objectOpt));
}
// Since builder is a UseCaseConfig.Builder, it should produce a UseCaseConfig
@SuppressWarnings("unchecked")
UseCaseConfig<?> defaultConfig = defaultConfigBuilder.getUseCaseConfig();
return defaultConfig;
}
/**
* Sets the {@link SessionConfig} that will be used by the attached {@link Camera}.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected void updateSessionConfig(@NonNull SessionConfig sessionConfig) {
mAttachedSessionConfig = sessionConfig;
}
/**
* 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}.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public SessionConfig getSessionConfig() {
return mAttachedSessionConfig;
}
/**
* Notify all {@link StateChangeCallback} that are listening to this UseCase that it has
* transitioned to an active state.
*
* @hide
*/
@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.
*
* @hide
*/
@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.
*
* @hide
*/
@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.
*
* @hide
*/
@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.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected 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.
*
* @hide
*/
@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.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected boolean isCurrentCamera(@NonNull String cameraId) {
if (getCamera() == null) {
return false;
}
return Objects.equals(cameraId, getCameraId());
}
/**
* Clears internal state of this use case.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void clear() {
}
/**
* Called use case is unbound from lifecycle or the bound lifecycle is destroyed.
*
* TODO(b/152430679): remove this once UseCase can be reused. UseCase holds reference to user
* callbacks which causes memory leak. (see https://issuetracker.google.com/141188637) As
* long as the UseCase is never reused, it's safe to clear user callbacks when the lifecycle
* ends or unbinds. The proper fix should be breaking reference between Camera->UseCase
* when camera is detached (ON_STOP).
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void onDestroy() {
}
/** @hide */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public String getName() {
return mUseCaseConfig.getTargetName("<UnknownUseCase-" + this.hashCode() + ">");
}
/**
* Retrieves the configuration used by this use case.
*
* @return the configuration used by this use case.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public UseCaseConfig<?> getUseCaseConfig() {
return mUseCaseConfig;
}
/**
* Returns the currently attached {@link Camera} or {@code null} if none is attached.
*
* @hide
*/
@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.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Size getAttachedSurfaceResolution() {
return mAttachedResolution;
}
/**
* Offers suggested resolution for the UseCase.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void updateSuggestedResolution(@NonNull Size suggestedResolution) {
mAttachedResolution = onSuggestedResolutionUpdated(suggestedResolution);
}
/**
* 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 resolution.
*
* @param suggestedResolution The suggested resolution that depends on camera device
* capability and what and how many use cases will be bound.
* @return The resolution that finally used to create the SessionConfig to
* attach to the camera device.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected abstract Size onSuggestedResolutionUpdated(@NonNull Size suggestedResolution);
/**
* 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.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected void onCameraControlReady() {
}
/**
* Called when use case is attaching to a camera.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected void onAttach(@NonNull CameraInternal camera) {
synchronized (mCameraLock) {
mCamera = camera;
addStateChangeCallback(camera);
}
updateUseCaseConfig(mUseCaseConfig);
EventCallback eventCallback = mUseCaseConfig.getUseCaseEventCallback(null);
if (eventCallback != null) {
eventCallback.onBind(camera.getCameraInfoInternal().getCameraId());
}
onCameraControlReady();
}
/**
* Called when use case is detaching from a camera.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY)
public void onDetach() {
// Do any cleanup required by the UseCase implementation
clear();
// Cleanup required for any type of UseCase
EventCallback eventCallback = mUseCaseConfig.getUseCaseEventCallback(null);
if (eventCallback != null) {
eventCallback.onUnbind();
}
synchronized (mCameraLock) {
if (mCamera != null) {
mCamera.removeOnlineUseCase(Collections.singleton(this));
removeStateChangeCallback(mCamera);
mCamera = null;
}
}
}
/**
* Called when use case is online in camera. This method is called on main thread.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void onStateOnline() {
}
/**
* Called when use case is offline in camera. This method is called on main thread.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public void onStateOffline() {
}
/**
* Retrieves a previously attached {@link CameraControlInternal}.
*
* @hide
*/
@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.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
protected void setViewPortCropRect(@Nullable Rect viewPortCropRect) {
mViewPortCropRect = viewPortCropRect;
}
/**
* Gets the view port crop rect.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
protected Rect getViewPortCropRect() {
return mViewPortCropRect;
}
/**
* Get image format for the use case.
*
* @return image format for the use case
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public int getImageFormat() {
return mUseCaseConfig.getInputFormat();
}
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.
*
* @hide
*/
@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);
}
/**
* Callback for when a {@link UseCase} transitions between bind/unbind states.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public interface EventCallback {
/**
* Called when use case was bound to the life cycle.
*
* @param cameraId that current used.
*/
void onBind(@NonNull String cameraId);
/**
* Called when use case was unbind from the life cycle and clear the resource of the use
* case.
*/
void onUnbind();
}
}