| /* |
| * 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); |
| } |
| } |