From ef879c2bbb1ebeeade4b6558bd63237736bf0547 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sun, 1 Sep 2019 18:28:56 +0200 Subject: [PATCH] Create Locker and locking.* parameters --- .../cameraview/engine/Camera2Engine.java | 17 +- .../cameraview/engine/Locker.java | 154 ++++++++++++++++++ .../engine/locking/AutoExposure.java | 91 +++++++++++ .../cameraview/engine/locking/AutoFocus.java | 101 ++++++++++++ .../engine/locking/AutoWhiteBalance.java | 80 +++++++++ .../cameraview/engine/locking/Parameter.java | 103 ++++++++++++ 6 files changed, 545 insertions(+), 1 deletion(-) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 7e2cfe6b..b294ce4e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -104,6 +104,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv // 3A metering private Meter mMeter; private Gesture mMeteringGesture; + private Locker mLocker; private PictureResult.Stub mDelayedPictureStub; private AspectRatio mDelayedPictureRatio; @@ -236,6 +237,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv if (mMeter != null && mMeter.isMetering()) { mMeter.onCapture(partialResult); } + if (mLocker != null && mLocker.isLocking()) { + mLocker.onCapture(partialResult); + } } @Override @@ -251,6 +255,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv if (mMeter != null && mMeter.isMetering()) { mMeter.onCapture(result); } + if (mLocker != null && mLocker.isLocking()) { + mLocker.onCapture(result); + } Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); Integer aeTriggerState = result.get(CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER); LOG.i("metering:", "aeState:", aeState, "aeTriggerState:", aeTriggerState); @@ -553,6 +560,10 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv mMeter.resetMetering(); mMeter = null; } + if (mLocker != null) { + mLocker.unlock(); + mLocker = null; + } if (hasFrameProcessors()) { getFrameManager().release(); } @@ -1206,7 +1217,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv // refers to "3A metering to a specific point". So if we have one, let's check. if (point != null && !mCameraOptions.isAutoFocusSupported()) return; - // Reset the old meter if present. + // Reset the old meter and locker if present. + if (mLocker != null) { + mLocker.unlock(); + mLocker = null; + } if (mMeter != null) { mMeter.resetMetering(); mMeter = null; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java new file mode 100644 index 00000000..e6d3edc1 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java @@ -0,0 +1,154 @@ +package com.otaliastudios.cameraview.engine; + +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.otaliastudios.cameraview.CameraLogger; +import com.otaliastudios.cameraview.engine.locking.AutoExposure; +import com.otaliastudios.cameraview.engine.locking.AutoFocus; +import com.otaliastudios.cameraview.engine.locking.AutoWhiteBalance; +import com.otaliastudios.cameraview.engine.locking.Parameter; + +/** + * Helps Camera2-based engines to perform 3A locking and unlocking. + * Users are required to: + * + * - Call {@link #lock(CaptureResult)} to start + * - Call {@link #onCapture(CaptureResult)} when they have partial or total results, as long as the + * locker is still in a locking operation, which can be checked through {@link #isLocking()} ()} + * - Call {@link #unlock()} to reset the locked parameters if needed. + */ +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public class Locker { + + /** + * The locker callback. + */ + public interface Callback extends Parameter.LockingChangeCallback { + + /** + * Notifies that locking has ended. No action is required for implementors. + * From now on, {@link #isLocking()} will return false. + * @param success success + */ + void onLocked(boolean success); + + /** + * Notifies that locking has been undone. From now on, this locker instance + * is done, although in theory it could be reused by calling + * {@link #lock(CaptureResult)} again. + */ + void onUnlocked(); + + /** + * Returns the currently used builder. This can change while a locking + * operation happens, so the locker will never cache this value. + * It is the engine responsibility to copy over values to the new builder + * when it changes. + * @return a builder + */ + @NonNull + CaptureRequest.Builder getLockingBuilder(); + } + + private static final String TAG = Locker.class.getSimpleName(); + private static final CameraLogger LOG = CameraLogger.create(TAG); + private static final int FORCED_END_DELAY = 2500; + + private final CameraCharacteristics mCharacteristics; + private final Callback mCallback; + + private boolean mIsLocking; + private Parameter mAutoFocus; + private Parameter mAutoWhiteBalance; + private Parameter mAutoExposure; + private long mLockingStartTime; + + /** + * Creates a new locker. + * @param characteristics the camera characteristics + * @param callback the callback + */ + @SuppressWarnings("WeakerAccess") + public Locker(@NonNull CameraCharacteristics characteristics, + @NonNull Callback callback) { + mCharacteristics = characteristics; + mCallback = callback; + mAutoFocus = new AutoFocus(callback); + mAutoExposure = new AutoExposure(callback); + mAutoWhiteBalance = new AutoWhiteBalance(callback); + } + + /** + * Locks 3A values. + * @param lastResult the last result + */ + @SuppressWarnings("WeakerAccess") + public void lock(@NonNull CaptureResult lastResult) { + mIsLocking = true; + mLockingStartTime = System.currentTimeMillis(); + mAutoFocus.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); + mAutoWhiteBalance.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); + mAutoExposure.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); + } + + /** + * True if we're locking. False if we're not, for example + * if {@link #lock(CaptureResult)} was never called. + * @return true if locking + */ + @SuppressWarnings("WeakerAccess") + public boolean isLocking() { + return mIsLocking; + } + + /** + * Should be called when we have partial or total CaptureResults, + * but only while {@link #isLocking()} returns true. + * @param result result + */ + @SuppressWarnings("WeakerAccess") + public void onCapture(@NonNull CaptureResult result) { + if (!mIsLocking) return; // We're not interested in results anymore + if (!(result instanceof TotalCaptureResult)) return; // Let's ignore these, contents are missing/wrong + + if (!mAutoFocus.isLocked()) mAutoFocus.onCapture(mCallback.getLockingBuilder(), result); + if (!mAutoExposure.isLocked()) mAutoExposure.onCapture(mCallback.getLockingBuilder(), result); + if (!mAutoWhiteBalance.isLocked()) mAutoWhiteBalance.onCapture(mCallback.getLockingBuilder(), result); + if (mAutoFocus.isLocked() && mAutoExposure.isLocked() && mAutoWhiteBalance.isLocked()) { + LOG.i("onCapture:", "all Parameters have converged. Dispatching onMeteringEnd"); + boolean success = mAutoFocus.isSuccessful() + && mAutoExposure.isSuccessful() + && mAutoWhiteBalance.isSuccessful(); + dispatchEnd(success); + } else if (System.currentTimeMillis() - mLockingStartTime >= FORCED_END_DELAY) { + LOG.i("onCapture:", "FORCED_END_DELAY was reached. Some Parameter is stuck. Forcing end."); + dispatchEnd(false); + } + } + + private void dispatchEnd(boolean success) { + mCallback.onLocked(success); + mIsLocking = false; + } + + /** + * Should be called to unlock. + * Note that {@link Callback#onUnlocked()} will be called immediately, + * we're not waiting for the results. + */ + @SuppressWarnings("WeakerAccess") + public void unlock() { + LOG.i("Unlocking."); + mAutoFocus.unlock(mCharacteristics, mCallback.getLockingBuilder()); + mAutoExposure.unlock(mCharacteristics, mCallback.getLockingBuilder()); + mAutoWhiteBalance.unlock(mCharacteristics, mCallback.getLockingBuilder()); + mCallback.onUnlocked(); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java new file mode 100644 index 00000000..19d205f2 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java @@ -0,0 +1,91 @@ +package com.otaliastudios.cameraview.engine.locking; + +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.otaliastudios.cameraview.CameraLogger; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public class AutoExposure extends Parameter { + + private static final String TAG = AutoExposure.class.getSimpleName(); + private static final CameraLogger LOG = CameraLogger.create(TAG); + + public AutoExposure(@NonNull LockingChangeCallback callback) { + super(callback); + } + + @Override + protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + boolean isNotLegacy = readCharacteristic(characteristics, + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) != + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + // Not sure we should check aeMode as well, probably all aeModes support locking, + // but this should not be a big issue since we're not even using different AE modes. + Integer aeMode = builder.get(CaptureRequest.CONTROL_AE_MODE); + boolean isAEOn = aeMode != null && + (aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON + || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH + || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH + || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE + || aeMode == 5 /* CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH, API 28 */); + boolean result = isNotLegacy && isAEOn; + LOG.i("checkSupportsProcessing:", result); + return result; + } + + @Override + protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { + Integer aeState = lastResult.get(CaptureResult.CONTROL_AE_STATE); + boolean result = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_LOCKED; + LOG.i("checkShouldSkip:", result); + return result; + } + + @Override + protected void onLock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + builder.set(CaptureRequest.CONTROL_AE_LOCK, true); + notifyBuilderChanged(); + } + + @Override + public void processCapture(@NonNull CaptureResult result) { + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + LOG.i("processCapture:", "aeState:", aeState); + if (aeState == null) return; + switch (aeState) { + case CaptureRequest.CONTROL_AE_STATE_LOCKED: { + isLocked = true; + isSuccessful = false; + break; + } + case CaptureRequest.CONTROL_AE_STATE_PRECAPTURE: + case CaptureRequest.CONTROL_AE_STATE_CONVERGED: + case CaptureRequest.CONTROL_AE_STATE_INACTIVE: + case CaptureRequest.CONTROL_AE_STATE_SEARCHING: + case CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED: { + // Wait... + break; + } + } + } + + @Override + protected void onLocked(@NonNull CaptureRequest.Builder builder) { + // Do nothing + } + + @Override + protected void onUnlock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + builder.set(CaptureRequest.CONTROL_AE_LOCK, false); + notifyBuilderChanged(); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java new file mode 100644 index 00000000..0a8eb721 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java @@ -0,0 +1,101 @@ +package com.otaliastudios.cameraview.engine.locking; + +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.otaliastudios.cameraview.CameraLogger; + +import java.util.List; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public class AutoFocus extends Parameter { + + private static final String TAG = AutoFocus.class.getSimpleName(); + private static final CameraLogger LOG = CameraLogger.create(TAG); + + private Integer oldAfMode; + + public AutoFocus(@NonNull LockingChangeCallback callback) { + super(callback); + } + + @Override + protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + // We'll lock by changing the AF mode to AUTO. + // In that mode, AF won't change unless someone starts a trigger operation. + int[] modes = readCharacteristic(characteristics, + CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{}); + for (int mode : modes) { + if (mode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) { + return true; + } + } + return false; + } + + @Override + protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { + Integer afState = lastResult.get(CaptureResult.CONTROL_AF_STATE); + boolean afStateOk = afState != null && + (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || + afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED); + oldAfMode = lastResult.get(CaptureResult.CONTROL_AF_MODE); + boolean afModeOk = oldAfMode != null && oldAfMode == CaptureResult.CONTROL_AF_MODE_AUTO; + boolean result = afStateOk && afModeOk; + LOG.i("checkShouldSkip:", result); + return result; + } + + @Override + protected void onLock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); + notifyBuilderChanged(); + } + + @Override + public void processCapture(@NonNull CaptureResult result) { + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE); + LOG.i("onCapture:", "afState:", afState, "afMode:", afMode); + if (afState == null || afMode == null) return; + if (afMode != CaptureResult.CONTROL_AF_MODE_AUTO) return; + switch (afState) { + case CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED: + case CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: + case CaptureRequest.CONTROL_AF_STATE_INACTIVE: + case CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED: + case CaptureRequest.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { + isLocked = true; + isSuccessful = true; + break; + } + case CaptureRequest.CONTROL_AF_STATE_ACTIVE_SCAN: + case CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN: { + // Wait... + break; + } + } + } + + @Override + protected void onLocked(@NonNull CaptureRequest.Builder builder) { + // Do nothing. + } + + @Override + protected void onUnlock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + builder.set(CaptureRequest.CONTROL_AF_MODE, oldAfMode); + notifyBuilderChanged(); + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java new file mode 100644 index 00000000..176c4f7f --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java @@ -0,0 +1,80 @@ +package com.otaliastudios.cameraview.engine.locking; + +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.otaliastudios.cameraview.CameraLogger; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public class AutoWhiteBalance extends Parameter { + + private static final String TAG = AutoWhiteBalance.class.getSimpleName(); + private static final CameraLogger LOG = CameraLogger.create(TAG); + + public AutoWhiteBalance(@NonNull LockingChangeCallback callback) { + super(callback); + } + + @Override + protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + boolean isNotLegacy = readCharacteristic(characteristics, + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) != + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + Integer awbMode = builder.get(CaptureRequest.CONTROL_AWB_MODE); + boolean result = isNotLegacy && awbMode != null && awbMode == CaptureRequest.CONTROL_AWB_MODE_AUTO; + LOG.i("checkSupportsLocking:", result); + return result; + } + + @Override + protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { + Integer awbState = lastResult.get(CaptureResult.CONTROL_AWB_STATE); + boolean result = awbState != null && awbState == CaptureRequest.CONTROL_AWB_STATE_LOCKED; + LOG.i("checkShouldSkip:", result); + return result; + } + + @Override + protected void onLock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + builder.set(CaptureRequest.CONTROL_AWB_LOCK, true); + notifyBuilderChanged(); + } + + @Override + public void processCapture(@NonNull CaptureResult result) { + Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); + LOG.i("processCapture:", "awbState:", awbState); + if (awbState == null) return; + switch (awbState) { + case CaptureRequest.CONTROL_AWB_STATE_LOCKED: { + isLocked = true; + isSuccessful = false; + break; + } + case CaptureRequest.CONTROL_AWB_STATE_CONVERGED: + case CaptureRequest.CONTROL_AWB_STATE_INACTIVE: + case CaptureRequest.CONTROL_AWB_STATE_SEARCHING: { + // Wait... + break; + } + } + } + + @Override + protected void onLocked(@NonNull CaptureRequest.Builder builder) { + // Do nothing + } + + @Override + protected void onUnlock(@NonNull CameraCharacteristics characteristics, @NonNull CaptureRequest.Builder builder) { + builder.set(CaptureRequest.CONTROL_AWB_LOCK, false); + notifyBuilderChanged(); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java new file mode 100644 index 00000000..191ed0ad --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java @@ -0,0 +1,103 @@ +package com.otaliastudios.cameraview.engine.locking; + +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.params.MeteringRectangle; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.List; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public abstract class Parameter { + + public interface LockingChangeCallback { + void onLockingChange(); + } + + @SuppressWarnings("WeakerAccess") + protected boolean isSuccessful; + + @SuppressWarnings("WeakerAccess") + protected boolean isLocked; + + private LockingChangeCallback callback; + private boolean shouldSkip; + private boolean supportsLocking; + + @SuppressWarnings("WeakerAccess") + protected Parameter(@NonNull LockingChangeCallback callback) { + this.callback = callback; + } + + @SuppressWarnings("WeakerAccess") + @NonNull + protected T readCharacteristic(@NonNull CameraCharacteristics characteristics, + @NonNull CameraCharacteristics.Key key, + @NonNull T fallback) { + T value = characteristics.get(key); + return value == null ? fallback : value; + } + + @SuppressWarnings("WeakerAccess") + protected void notifyBuilderChanged() { + callback.onLockingChange(); + } + + public final boolean isLocked() { + // A non supported parameter should always appear as metered + return isLocked || !supportsLocking || shouldSkip; + } + + public final boolean isSuccessful() { + // A non supported parameter should always appear as successful + return isSuccessful || !supportsLocking || shouldSkip; + } + + public final void lock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder, + @NonNull CaptureResult lastResult) { + isSuccessful = false; + isLocked = false; + shouldSkip = checkShouldSkip(lastResult); + supportsLocking = checkSupportsLocking(characteristics, builder); + if (!shouldSkip && supportsLocking) { + onLock(characteristics, builder); + } + } + + public final void onCapture(@NonNull CaptureRequest.Builder builder, + @NonNull CaptureResult result) { + if (isLocked()) return; + processCapture(result); + if (isLocked()) onLocked(builder); + } + + public final void unlock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder) { + if (supportsLocking) { + // Not checking shouldSkip. Though we skipped locking, we're + // now asked to unlock, so do it. + onUnlock(characteristics, builder); + } + } + + protected abstract boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder); + + protected abstract boolean checkShouldSkip(@NonNull CaptureResult lastResult); + + protected abstract void onLock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder); + + protected abstract void processCapture(@NonNull CaptureResult result); + + protected abstract void onLocked(@NonNull CaptureRequest.Builder builder); + + protected abstract void onUnlock(@NonNull CameraCharacteristics characteristics, + @NonNull CaptureRequest.Builder builder); +}