blob: 6cc162e948abc1d69e7806b16d6fe7e38b0d20ef [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.testing.fakes;
import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
import static androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE;
import android.graphics.Rect;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Logger;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraCaptureFailure;
import androidx.camera.core.impl.CameraCaptureResult;
import androidx.camera.core.impl.CameraControlInternal;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.Config;
import androidx.camera.core.impl.MutableOptionsBundle;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.util.Pair;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* A fake implementation for the {@link CameraControlInternal} interface which is capable of
* notifying submitted requests using the associated {@link CameraCaptureCallback} instances or
* {@link ControlUpdateCallback}.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class FakeCameraControl implements CameraControlInternal {
private static final String TAG = "FakeCameraControl";
private static final ControlUpdateCallback NO_OP_CALLBACK = new ControlUpdateCallback() {
@Override
public void onCameraControlUpdateSessionConfig() {
// No-op
}
@Override
public void onCameraControlCaptureRequests(
@NonNull List<CaptureConfig> captureConfigs) {
// No-op
}
};
/**
* The executor used to invoke any callback/listener which doesn't have a dedicated executor
* for it.
* <p> {@link CameraXExecutors#directExecutor} via default, unless some other executor is set
* via {@link #FakeCameraControl(Executor, CameraControlInternal.ControlUpdateCallback)}.
*/
@NonNull private final Executor mExecutor;
private final ControlUpdateCallback mControlUpdateCallback;
private final SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder();
@ImageCapture.FlashMode
private int mFlashMode = FLASH_MODE_OFF;
private final ArrayList<CaptureConfig> mSubmittedCaptureRequests = new ArrayList<>();
private Pair<Executor, OnNewCaptureRequestListener> mOnNewCaptureRequestListener;
private MutableOptionsBundle mInteropConfig = MutableOptionsBundle.create();
private final ArrayList<CallbackToFutureAdapter.Completer<Void>> mSubmittedCompleterList =
new ArrayList<>();
private boolean mIsZslDisabledByUseCaseConfig = false;
private boolean mIsZslConfigAdded = false;
private float mZoomRatio = -1;
private float mLinearZoom = -1;
private boolean mTorchEnabled = false;
private int mExposureCompensation = -1;
@Nullable
private FocusMeteringAction mLastSubmittedFocusMeteringAction = null;
/**
* Constructs an instance of {@link FakeCameraControl} with a no-op
* {@link ControlUpdateCallback}.
*
* @see #FakeCameraControl(CameraControlInternal.ControlUpdateCallback)
* @see #FakeCameraControl(Executor, CameraControlInternal.ControlUpdateCallback)
*/
public FakeCameraControl() {
this(NO_OP_CALLBACK);
}
/**
* Constructs an instance of {@link FakeCameraControl} with the
* provided {@link ControlUpdateCallback}.
*
* <p> Note that callbacks will be executed on the calling thread directly via
* {@link CameraXExecutors#directExecutor}. To specify the execution thread, use
* {@link #FakeCameraControl(Executor, CameraControlInternal.ControlUpdateCallback)}.
*
* @param controlUpdateCallback {@link ControlUpdateCallback} to notify events.
*/
public FakeCameraControl(@NonNull ControlUpdateCallback controlUpdateCallback) {
this(CameraXExecutors.directExecutor(), controlUpdateCallback);
}
/**
* Constructs an instance of {@link FakeCameraControl} with the
* provided {@link ControlUpdateCallback}.
*
* @param executor {@link Executor} used to invoke the {@code controlUpdateCallback}.
* @param controlUpdateCallback {@link ControlUpdateCallback} to notify events.
*/
public FakeCameraControl(@NonNull Executor executor,
@NonNull ControlUpdateCallback controlUpdateCallback) {
mExecutor = executor;
mControlUpdateCallback = controlUpdateCallback;
}
/**
* Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureCancelled},
* which is invoked in the thread denoted by {@link #mExecutor}.
*/
public void notifyAllRequestsOnCaptureCancelled() {
for (CaptureConfig captureConfig : mSubmittedCaptureRequests) {
for (CameraCaptureCallback cameraCaptureCallback :
captureConfig.getCameraCaptureCallbacks()) {
mExecutor.execute(cameraCaptureCallback::onCaptureCancelled);
}
}
for (CallbackToFutureAdapter.Completer<Void> completer : mSubmittedCompleterList) {
completer.setException(
new ImageCaptureException(ImageCapture.ERROR_CAMERA_CLOSED, "Simulate "
+ "capture cancelled", null));
}
mSubmittedCompleterList.clear();
mSubmittedCaptureRequests.clear();
}
/**
* Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureFailed},
* which is invoked in the thread denoted by {@link #mExecutor}.
*/
public void notifyAllRequestsOnCaptureFailed() {
for (CaptureConfig captureConfig : mSubmittedCaptureRequests) {
for (CameraCaptureCallback cameraCaptureCallback :
captureConfig.getCameraCaptureCallbacks()) {
mExecutor.execute(() -> cameraCaptureCallback.onCaptureFailed(
new CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)));
}
}
for (CallbackToFutureAdapter.Completer<Void> completer : mSubmittedCompleterList) {
completer.setException(new ImageCaptureException(ImageCapture.ERROR_CAPTURE_FAILED,
"Simulate capture fail", null));
}
mSubmittedCompleterList.clear();
mSubmittedCaptureRequests.clear();
}
/**
* Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureCompleted},
* which is invoked in the thread denoted by {@link #mExecutor}.
*
* @param result The {@link CameraCaptureResult} which is notified to all the callbacks.
*/
public void notifyAllRequestsOnCaptureCompleted(@NonNull CameraCaptureResult result) {
for (CaptureConfig captureConfig : mSubmittedCaptureRequests) {
for (CameraCaptureCallback cameraCaptureCallback :
captureConfig.getCameraCaptureCallbacks()) {
mExecutor.execute(() -> cameraCaptureCallback.onCaptureCompleted(result));
}
}
for (CallbackToFutureAdapter.Completer<Void> completer : mSubmittedCompleterList) {
completer.set(null);
}
mSubmittedCompleterList.clear();
mSubmittedCaptureRequests.clear();
}
@ImageCapture.FlashMode
@Override
public int getFlashMode() {
return mFlashMode;
}
@Override
public void setFlashMode(@ImageCapture.FlashMode int flashMode) {
mFlashMode = flashMode;
Logger.d(TAG, "setFlashMode(" + mFlashMode + ")");
}
@Override
public void setZslDisabledByUserCaseConfig(boolean disabled) {
mIsZslDisabledByUseCaseConfig = disabled;
}
@Override
public boolean isZslDisabledByByUserCaseConfig() {
return mIsZslDisabledByUseCaseConfig;
}
@Override
public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
// Override if Zero-Shutter Lag needs to add config to session config.
mIsZslConfigAdded = true;
}
/**
* Checks if {@link FakeCameraControl#addZslConfig(SessionConfig.Builder)} has been triggered.
*/
public boolean isZslConfigAdded() {
return mIsZslConfigAdded;
}
/**
* Sets the torch status.
*
* @param torch The torch status is set as enabled if true, disabled if false.
* @return Returns a {@link Futures#immediateFuture} which immediately contains a result.
*/
@Override
@NonNull
public ListenableFuture<Void> enableTorch(boolean torch) {
Logger.d(TAG, "enableTorch(" + torch + ")");
mTorchEnabled = torch;
return Futures.immediateFuture(null);
}
/** Returns if torch is set as enabled. */
public boolean getTorchEnabled() {
return mTorchEnabled;
}
/**
* Sets the exposure compensation index.
*
* @param value The exposure compensation value to be set.
* @return Returns a {@link Futures#immediateFuture} which immediately contains a result.
*/
@NonNull
@Override
public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
mExposureCompensation = value;
return Futures.immediateFuture(null);
}
/** Returns the exposure compensation index. */
public int getExposureCompensationIndex() {
return mExposureCompensation;
}
@NonNull
@Override
public ListenableFuture<List<Void>> submitStillCaptureRequests(
@NonNull List<CaptureConfig> captureConfigs,
int captureMode, int flashType) {
mSubmittedCaptureRequests.addAll(captureConfigs);
mExecutor.execute(
() -> mControlUpdateCallback.onCameraControlCaptureRequests(captureConfigs));
List<ListenableFuture<Void>> fakeFutures = new ArrayList<>();
for (int i = 0; i < captureConfigs.size(); i++) {
fakeFutures.add(CallbackToFutureAdapter.getFuture(completer -> {
mSubmittedCompleterList.add(completer);
return "fakeFuture";
}));
}
if (mOnNewCaptureRequestListener != null) {
Executor executor = Objects.requireNonNull(mOnNewCaptureRequestListener.first);
OnNewCaptureRequestListener listener =
Objects.requireNonNull(mOnNewCaptureRequestListener.second);
executor.execute(() -> listener.onNewCaptureRequests(captureConfigs));
}
return Futures.allAsList(fakeFutures);
}
@NonNull
@Override
public SessionConfig getSessionConfig() {
return mSessionConfigBuilder.build();
}
/**
* Returns a {@link Rect} corresponding to
* {@link FakeCameraDeviceSurfaceManager#MAX_OUTPUT_SIZE}.
*/
@NonNull
@Override
public Rect getSensorRect() {
return new Rect(0, 0, MAX_OUTPUT_SIZE.getWidth(), MAX_OUTPUT_SIZE.getHeight());
}
/**
* Stores the last submitted {@link FocusMeteringAction}.
*
* @param action The {@link FocusMeteringAction} to be used.
* @return Returns a {@link Futures#immediateFuture} which immediately contains a empty
* {@link FocusMeteringResult}.
*/
@NonNull
@Override
public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
@NonNull FocusMeteringAction action) {
mLastSubmittedFocusMeteringAction = action;
return Futures.immediateFuture(FocusMeteringResult.emptyInstance());
}
/** Returns a {@link Futures#immediateFuture} which immediately contains a result. */
@NonNull
@Override
public ListenableFuture<Void> cancelFocusAndMetering() {
return Futures.immediateFuture(null);
}
/**
* Sets a listener to be notified when there are new capture requests submitted.
*
* <p> Note that the listener will be executed on the calling thread directly using
* {@link CameraXExecutors#directExecutor}. To specify the execution thread, use
* {@link #setOnNewCaptureRequestListener(Executor, OnNewCaptureRequestListener)}.
*
* @param listener {@link OnNewCaptureRequestListener} that is notified with the submitted
* {@link CaptureConfig} parameters when new capture requests are submitted.
*/
public void setOnNewCaptureRequestListener(@NonNull OnNewCaptureRequestListener listener) {
setOnNewCaptureRequestListener(CameraXExecutors.directExecutor(), listener);
}
/**
* Sets a listener to be notified when there are new capture requests submitted.
*
* @param executor {@link Executor} used to notify the {@code listener}.
* @param listener {@link OnNewCaptureRequestListener} that is notified with the submitted
* {@link CaptureConfig} parameters when new capture requests are submitted.
*/
public void setOnNewCaptureRequestListener(@NonNull Executor executor,
@NonNull OnNewCaptureRequestListener listener) {
mOnNewCaptureRequestListener = new Pair<>(executor, listener);
}
/**
* Clears any listener set via {@link #setOnNewCaptureRequestListener}.
*/
public void clearNewCaptureRequestListener() {
mOnNewCaptureRequestListener = null;
}
@NonNull
@Override
public ListenableFuture<Void> setZoomRatio(float ratio) {
mZoomRatio = ratio;
return Futures.immediateFuture(null);
}
/** Gets the linear zoom value set with {@link #setZoomRatio}. */
public float getZoomRatio() {
return mZoomRatio;
}
@NonNull
@Override
public ListenableFuture<Void> setLinearZoom(float linearZoom) {
mLinearZoom = linearZoom;
return Futures.immediateFuture(null);
}
/** Gets the linear zoom value set with {@link #setLinearZoom}. */
public float getLinearZoom() {
return mLinearZoom;
}
/** Gets the last focus metering action submitted with {@link #startFocusAndMetering}. */
@Nullable
public FocusMeteringAction getLastSubmittedFocusMeteringAction() {
return mLastSubmittedFocusMeteringAction;
}
@Override
public void addInteropConfig(@NonNull Config config) {
for (Config.Option<?> option : config.listOptions()) {
@SuppressWarnings("unchecked")
Config.Option<Object> objectOpt = (Config.Option<Object>) option;
mInteropConfig.insertOption(objectOpt, config.retrieveOption(objectOpt));
}
}
@Override
public void clearInteropConfig() {
mInteropConfig = MutableOptionsBundle.create();
}
@NonNull
@Override
public Config getInteropConfig() {
return MutableOptionsBundle.from(mInteropConfig);
}
/** A listener which is used to notify when there are new submitted capture requests */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public interface OnNewCaptureRequestListener {
/** Called when there are new submitted capture request */
void onNewCaptureRequests(@NonNull List<CaptureConfig> captureConfigs);
}
}