[go: nahoru, domu]

Merge changes I4e2a5704,If8ae0958 into androidx-main

* changes:
  Enable testlib advanced type testing in camera-extensions
  Implement the postview and captureProcessProgressed on testlib advanced extender
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
index dc55aec..e448993 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
@@ -146,11 +146,25 @@
     }
 
     /**
-     * Convert a YUV_420_888 ImageProxy to a JPEG bytes data as an Image into the Surface.
+     * Convert a YUV_420_888 Image to a JPEG bytes data as an Image into the Surface.
      *
      * <p>Returns true if it succeeds and false otherwise.
      */
     public static boolean convertYuvToJpegBytesIntoSurface(
+            @NonNull Image image,
+            @IntRange(from = 1, to = 100) int jpegQuality,
+            @ImageOutputConfig.RotationDegreesValue int rotationDegrees,
+            @NonNull Surface outputSurface) {
+        return convertYuvToJpegBytesIntoSurface(new AndroidImageProxy(image), jpegQuality,
+                rotationDegrees, outputSurface);
+    }
+
+        /**
+         * Convert a YUV_420_888 ImageProxy to a JPEG bytes data as an Image into the Surface.
+         *
+         * <p>Returns true if it succeeds and false otherwise.
+         */
+    public static boolean convertYuvToJpegBytesIntoSurface(
             @NonNull ImageProxy imageProxy,
             @IntRange(from = 1, to = 100) int jpegQuality,
             @ImageOutputConfig.RotationDegreesValue int rotationDegrees,
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
index 926cad2..5dc613d 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
@@ -27,6 +27,7 @@
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.MutableStateObservable
+import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.internal.ClientVersion
 import androidx.camera.extensions.internal.ExtensionVersion
 import androidx.camera.extensions.internal.VendorExtender
@@ -60,6 +61,7 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class ExtensionsManagerTest(
+    private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
@@ -92,6 +94,7 @@
         )
 
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
+        ExtensionsTestlibControl.getInstance().setImplementationType(implType)
     }
 
     @After
@@ -107,9 +110,9 @@
 
     companion object {
         @JvmStatic
-        @get:Parameterized.Parameters(name = "extension = {0}, facing = {1}")
+        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
         val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllExtensionsLensFacingCombinations()
+            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations()
     }
 
     @Test
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
index 2fb9866..935c0e1 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
@@ -30,6 +30,7 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.impl.ImageFormatConstants
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.internal.VendorExtender
 import androidx.camera.extensions.util.ExtensionsTestUtil
 import androidx.camera.lifecycle.ProcessCameraProvider
@@ -58,14 +59,15 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class ImageAnalysisTest(
+    private val implType: ExtensionsTestlibControl.ImplementationType,
     @ExtensionMode.Mode private val extensionMode: Int,
     @CameraSelector.LensFacing private val lensFacing: Int
 ) {
     companion object {
         @JvmStatic
-        @get:Parameterized.Parameters(name = "extension = {0}, facing = {1}")
+        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
         val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllExtensionsLensFacingCombinations()
+            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations()
     }
 
     @get:Rule
@@ -91,6 +93,7 @@
         )
 
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
+        ExtensionsTestlibControl.getInstance().setImplementationType(implType)
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
index 0328edf..96616ad 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
@@ -31,6 +31,7 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability
+import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.util.ExtensionsTestUtil
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.impl.CameraUtil
@@ -66,6 +67,7 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class ImageCaptureTest(
+    private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
@@ -102,6 +104,7 @@
 
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
+        ExtensionsTestlibControl.getInstance().setImplementationType(implType)
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
             cameraProvider
@@ -132,9 +135,9 @@
 
     companion object {
         @JvmStatic
-        @get:Parameterized.Parameters(name = "extension = {0}, facing = {1}")
+        @get:Parameterized.Parameters(name = "impl= {0}, mode = {1}, facing = {2}")
         val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllExtensionsLensFacingCombinations()
+            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations()
     }
 
     @Test
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
index 7ba9407..ef828d0 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
@@ -24,6 +24,7 @@
 import androidx.camera.camera2.Camera2Config
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.Preview
+import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.util.ExtensionsTestUtil
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.impl.CameraUtil
@@ -53,6 +54,7 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class PreviewTest(
+    private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
@@ -119,6 +121,7 @@
         )
 
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
+        ExtensionsTestlibControl.getInstance().setImplementationType(implType)
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
@@ -150,9 +153,9 @@
 
     companion object {
         @JvmStatic
-        @get:Parameterized.Parameters(name = "extension = {0}, facing = {1}")
+        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
         val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllExtensionsLensFacingCombinations()
+            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations()
     }
 
     @UiThreadTest
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt
index 8348eeb..69bcb78 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/VideoCaptureTest.kt
@@ -29,6 +29,7 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.Preview.SurfaceProvider
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.util.ExtensionsTestUtil
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.impl.AndroidUtil.skipVideoRecordingTestIfNotSupportedByEmulator
@@ -66,6 +67,7 @@
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = 21)
 class VideoCaptureTest(
+    private val implType: ExtensionsTestlibControl.ImplementationType,
     @field:ExtensionMode.Mode @param:ExtensionMode.Mode private val extensionMode: Int,
     @field:CameraSelector.LensFacing @param:CameraSelector.LensFacing private val lensFacing: Int
 ) {
@@ -130,6 +132,7 @@
         skipVideoRecordingTestIfNotSupportedByEmulator()
 
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
+        ExtensionsTestlibControl.getInstance().setImplementationType(implType)
         baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
@@ -280,8 +283,8 @@
         private const val TAG = "VideoCaptureTest"
 
         @JvmStatic
-        @get:Parameterized.Parameters(name = "extension = {0}, facing = {1}")
+        @get:Parameterized.Parameters(name = "implType = {0}, mode = {1}, facing = {2}")
         val parameters: Collection<Array<Any>>
-            get() = ExtensionsTestUtil.getAllExtensionsLensFacingCombinations()
+            get() = ExtensionsTestUtil.getAllImplExtensionsLensFacingCombinations()
     }
 }
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
index a9f652d..8f92a27 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
@@ -21,6 +21,9 @@
 import static androidx.camera.extensions.ExtensionMode.FACE_RETOUCH;
 import static androidx.camera.extensions.ExtensionMode.HDR;
 import static androidx.camera.extensions.ExtensionMode.NIGHT;
+import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.OEM_IMPL;
+import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.TESTLIB_ADVANCED;
+import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.TESTLIB_BASIC;
 
 import android.hardware.camera2.CameraCharacteristics;
 import android.os.Build;
@@ -29,6 +32,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.extensions.ExtensionMode;
+import androidx.camera.extensions.impl.ExtensionsTestlibControl;
 import androidx.camera.extensions.internal.AdvancedVendorExtender;
 import androidx.camera.extensions.internal.BasicVendorExtender;
 import androidx.camera.extensions.internal.ExtensionVersion;
@@ -37,28 +41,65 @@
 import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator;
 import androidx.camera.testing.impl.CameraUtil;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * Extension test util functions.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class ExtensionsTestUtil {
+
+    /**
+     * Returns the parameters which contains the combination of implementationType, extensions
+     * mode and lens facing.
+     */
     @NonNull
-    public static Collection<Object[]> getAllExtensionsLensFacingCombinations() {
-        return Arrays.asList(new Object[][]{
-                {BOKEH, CameraSelector.LENS_FACING_FRONT},
-                {BOKEH, CameraSelector.LENS_FACING_BACK},
-                {HDR, CameraSelector.LENS_FACING_FRONT},
-                {HDR, CameraSelector.LENS_FACING_BACK},
-                {FACE_RETOUCH, CameraSelector.LENS_FACING_FRONT},
-                {FACE_RETOUCH, CameraSelector.LENS_FACING_BACK},
-                {NIGHT, CameraSelector.LENS_FACING_FRONT},
-                {NIGHT, CameraSelector.LENS_FACING_BACK},
-                {AUTO, CameraSelector.LENS_FACING_FRONT},
-                {AUTO, CameraSelector.LENS_FACING_BACK}
+    public static Collection<Object[]> getAllImplExtensionsLensFacingCombinations() {
+        ExtensionsTestlibControl.ImplementationType implType =
+                ExtensionsTestlibControl.getInstance().getImplementationType();
+
+        if (implType == TESTLIB_ADVANCED) {
+            ExtensionsTestlibControl.getInstance().setImplementationType(TESTLIB_BASIC);
+            implType = TESTLIB_BASIC;
+        }
+
+        List<Object[]> basicOrOemImplList = Arrays.asList(new Object[][]{
+                {implType, BOKEH, CameraSelector.LENS_FACING_FRONT},
+                {implType, BOKEH, CameraSelector.LENS_FACING_BACK},
+                {implType, HDR, CameraSelector.LENS_FACING_FRONT},
+                {implType, HDR, CameraSelector.LENS_FACING_BACK},
+                {implType, FACE_RETOUCH, CameraSelector.LENS_FACING_FRONT},
+                {implType, FACE_RETOUCH, CameraSelector.LENS_FACING_BACK},
+                {implType, NIGHT, CameraSelector.LENS_FACING_FRONT},
+                {implType, NIGHT, CameraSelector.LENS_FACING_BACK},
+                {implType, AUTO, CameraSelector.LENS_FACING_FRONT},
+                {implType, AUTO, CameraSelector.LENS_FACING_BACK}
         });
+
+        if (implType == OEM_IMPL) {
+            return basicOrOemImplList;
+        }
+
+        List<Object[]> advancedList = Arrays.asList(new Object[][]{
+                {TESTLIB_ADVANCED, BOKEH, CameraSelector.LENS_FACING_FRONT},
+                {TESTLIB_ADVANCED, BOKEH, CameraSelector.LENS_FACING_BACK},
+                {TESTLIB_ADVANCED, HDR, CameraSelector.LENS_FACING_FRONT},
+                {TESTLIB_ADVANCED, HDR, CameraSelector.LENS_FACING_BACK},
+                {TESTLIB_ADVANCED, FACE_RETOUCH, CameraSelector.LENS_FACING_FRONT},
+                {TESTLIB_ADVANCED, FACE_RETOUCH, CameraSelector.LENS_FACING_BACK},
+                {TESTLIB_ADVANCED, NIGHT, CameraSelector.LENS_FACING_FRONT},
+                {TESTLIB_ADVANCED, NIGHT, CameraSelector.LENS_FACING_BACK},
+                {TESTLIB_ADVANCED, AUTO, CameraSelector.LENS_FACING_FRONT},
+                {TESTLIB_ADVANCED, AUTO, CameraSelector.LENS_FACING_BACK}
+        });
+
+        List<Object[]> allList = new ArrayList<>();
+        allList.addAll(basicOrOemImplList);
+        allList.addAll(advancedList);
+        return allList;
     }
 
     /**
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
index 5235e59..7df0fe4 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
@@ -87,6 +87,15 @@
      * @since 1.2
      */
     public boolean isAdvancedExtenderImplemented() {
-        return false;
+        return ExtensionsTestlibControl.getInstance().getImplementationType()
+                == ExtensionsTestlibControl.ImplementationType.TESTLIB_ADVANCED;
+    }
+
+    /**
+     * This method is used to check if test lib is running. If OEM implementation exists, invoking
+     * this method will throw {@link NoSuchMethodError}. This can be used to determine if OEM
+     * implementation is used or not.
+     */
+    public void checkTestlibRunning() {
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java
index 6cf35b5..344d9ac 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java
@@ -21,19 +21,25 @@
 
 /**
  * An internal utility class that allows tests to specify whether to enable basic extender or
- * advanced extender of this testlib.
+ * advanced extender of this testlib. If OEM implementation exists on the device, the
+ * implementation type is always {@link ImplementationType#OEM_IMPL} and can't be changed to
+ * other types.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class ExtensionsTestlibControl {
     public enum ImplementationType {
-        ADVANCED_EXTENDER,
-        BASIC_EXTENDER
+        OEM_IMPL,
+        TESTLIB_ADVANCED,
+        TESTLIB_BASIC
     }
 
     private static ExtensionsTestlibControl sInstance;
     private static Object sLock = new Object();
+    private volatile ImplementationType mImplementationType = ImplementationType.TESTLIB_BASIC;
 
     private ExtensionsTestlibControl() {
+        mImplementationType = doesOEMImplementationExist()
+                ? ImplementationType.OEM_IMPL : ImplementationType.TESTLIB_BASIC;
     }
 
     /**
@@ -49,15 +55,43 @@
         }
     }
 
-    private ImplementationType mImplementationType = ImplementationType.BASIC_EXTENDER;
-
     /**
      * Set the implementation type.
+     *
+     * <p>When OEM implementation exists on the device, the only possible type is
+     * {@link ImplementationType#OEM_IMPL}. Setting the implementation type to
+     * {@link ImplementationType#TESTLIB_BASIC} or {@link ImplementationType#TESTLIB_ADVANCED}
+     *  when OEM implementation exist will throw an {@link IllegalArgumentException}.
+     *
+     * <p>When OEM implementation doesn't exist on the device, it is allowed to set it to
+     * {@link ImplementationType#TESTLIB_BASIC} or {@link ImplementationType#TESTLIB_ADVANCED}.
+     * Setting it to {@link ImplementationType#OEM_IMPL} in this case will throw an
+     * {@link IllegalArgumentException}.
      */
     public void setImplementationType(@NonNull ImplementationType type) {
+        if (mImplementationType != ImplementationType.OEM_IMPL) { // OEM impl doesn't exist
+            if (type == ImplementationType.OEM_IMPL) {
+                throw new IllegalArgumentException("OEM_IMPL is not supported on this device.");
+            }
+        } else { // OEM impl exists
+            if (type != ImplementationType.OEM_IMPL) {
+                throw new IllegalArgumentException("Can't change the implementation type because "
+                        + "OEM implementation exists on the device");
+            }
+        }
+
         mImplementationType = type;
     }
 
+    private boolean doesOEMImplementationExist() {
+        try {
+            new ExtensionVersionImpl().checkTestlibRunning();
+            return false;
+        } catch (NoSuchMethodError e) { // checkTestlibRunning doesn't exist in OEM implementation.
+            return true;
+        }
+    }
+
     /**
      * Gets the implementation type;
      */
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index ac5fdce..0c0cf09 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -19,7 +19,6 @@
 
 import android.annotation.SuppressLint;
 import android.content.Context;
-import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
@@ -40,7 +39,7 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -385,8 +384,7 @@
     @Nullable
     @Override
     public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
-        Pair<Integer, Size[]> pair = new Pair<>(ImageFormat.JPEG, new Size[] {captureSize});
-        return Arrays.asList(pair);
+        return Collections.emptyList();
     }
 
     @Override
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index 093d409..0de93ff 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -179,11 +179,11 @@
     @NonNull
     @Override
     public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        List<CaptureRequest.Key> keys = Arrays.asList(
+        List<CaptureRequest.Key> keys = new ArrayList<>(Arrays.asList(
                 CaptureRequest.CONTROL_AF_MODE,
                 CaptureRequest.CONTROL_AF_TRIGGER,
                 CaptureRequest.CONTROL_AF_REGIONS,
-                CaptureRequest.CONTROL_AE_REGIONS);
+                CaptureRequest.CONTROL_AE_REGIONS));
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             keys.add(CaptureRequest.CONTROL_ZOOM_RATIO);
         } else {
@@ -195,12 +195,12 @@
     @NonNull
     @Override
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        List<CaptureResult.Key> keys = Arrays.asList(
+        List<CaptureResult.Key> keys = new ArrayList<>(Arrays.asList(
                 CaptureResult.CONTROL_AF_MODE,
                 CaptureResult.CONTROL_AE_REGIONS,
                 CaptureResult.CONTROL_AF_REGIONS,
                 CaptureResult.CONTROL_AE_STATE,
-                CaptureResult.CONTROL_AF_STATE);
+                CaptureResult.CONTROL_AF_STATE));
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             keys.add(CaptureResult.CONTROL_ZOOM_RATIO);
         } else {
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
index 9f567ba..520ea90 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
@@ -42,13 +42,12 @@
     @Override
     public boolean isExtensionAvailable(@NonNull String cameraId,
             @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
+        return false;
     }
 
     @Override
     public void init(@NonNull String cameraId,
             @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
index 40bbb93..d99e44c 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
@@ -42,13 +42,12 @@
     @Override
     public boolean isExtensionAvailable(@NonNull String cameraId,
             @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
+        return false;
     }
 
     @Override
     public void init(@NonNull String cameraId,
             @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
index 4093211..743b8d9 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
@@ -42,13 +42,12 @@
     @Override
     public boolean isExtensionAvailable(@NonNull String cameraId,
             @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
+        return false;
     }
 
     @Override
     public void init(@NonNull String cameraId,
             @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/DefaultRequestProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/DefaultRequestProcessorImpl.java
new file mode 100644
index 0000000..c6c58a3
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/DefaultRequestProcessorImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 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.extensions.impl.advanced;
+
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+
+import androidx.annotation.NonNull;
+
+public class DefaultRequestProcessorImpl implements RequestProcessorImpl.Callback {
+    @Override
+    public void onCaptureStarted(@NonNull RequestProcessorImpl.Request request, long frameNumber,
+            long timestamp) {
+
+    }
+
+    @Override
+    public void onCaptureProgressed(@NonNull RequestProcessorImpl.Request request,
+            @NonNull CaptureResult partialResult) {
+
+    }
+
+    @Override
+    public void onCaptureCompleted(@NonNull RequestProcessorImpl.Request request,
+            @NonNull TotalCaptureResult totalCaptureResult) {
+
+    }
+
+    @Override
+    public void onCaptureFailed(@NonNull RequestProcessorImpl.Request request,
+            @NonNull CaptureFailure captureFailure) {
+
+    }
+
+    @Override
+    public void onCaptureBufferLost(@NonNull RequestProcessorImpl.Request request, long frameNumber,
+            int outputStreamId) {
+
+    }
+
+    @Override
+    public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+
+    }
+
+    @Override
+    public void onCaptureSequenceAborted(int sequenceId) {
+
+    }
+}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
index 3d682ec..e4b038c 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
@@ -16,101 +16,19 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.util.Range;
-import android.util.Size;
+import android.graphics.ImageFormat;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.Map;
+import androidx.annotation.RequiresApi;
 
 /**
- * Stub advanced extender implementation for hdr.
- *
- * <p>This class should be implemented by OEM and deployed to the target devices.
+ * A sample HDR implementation for testing long processing capture. It is capable of outputting
+ * the postview(JPEG format) and the process progress event. ImageAnalysis is not supported.
  *
  * @since 1.2
  */
-public class HdrAdvancedExtenderImpl implements AdvancedExtenderImpl {
+@RequiresApi(21)
+public class HdrAdvancedExtenderImpl extends LongCaptureAdvancedExtenderImpl {
     public HdrAdvancedExtenderImpl() {
-    }
-
-    @Override
-    public boolean isExtensionAvailable(@NonNull String cameraId,
-            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    public void init(@NonNull String cameraId,
-            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @Nullable
-    public Range<Long> getEstimatedCaptureLatencyRange(
-            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
-            @NonNull String cameraId) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-
-    @Override
-    @NonNull
-    public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
-            @NonNull String cameraId) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
-            @NonNull Size captureSize) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @Nullable
-    public List<Size> getSupportedYuvAnalysisResolutions(@NonNull String cameraId) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public SessionProcessorImpl createSessionProcessor() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    public boolean isCaptureProcessProgressAvailable() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    public boolean isPostviewAvailable() {
-        throw new RuntimeException("Stub, replace with implementation.");
+        super(/* postviewFormat */ ImageFormat.JPEG);
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/LongCaptureAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/LongCaptureAdvancedExtenderImpl.java
new file mode 100644
index 0000000..d28067c
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/LongCaptureAdvancedExtenderImpl.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright 2023 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.extensions.impl.advanced;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageProcessingUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An {@link AdvancedExtenderImpl} implementation that have long processing time for capture and
+ * is capable of outputting the postview and the process progress event.
+ */
+@RequiresApi(21)
+public class LongCaptureAdvancedExtenderImpl implements AdvancedExtenderImpl {
+    private static final int EV_INDEX = 10;
+
+    private CameraCharacteristics mCameraCharacteristics;
+    private final int mPostviewFormat;
+
+    public LongCaptureAdvancedExtenderImpl(int postviewFormat) {
+        mPostviewFormat = postviewFormat;
+    }
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        return true;
+    }
+
+    @Override
+    public void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        mCameraCharacteristics = characteristicsMap.get(cameraId);
+    }
+
+    @Override
+    @Nullable
+    public Range<Long> getEstimatedCaptureLatencyRange(
+            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
+        return new Range<>(1000L, 4000L);
+    }
+
+    @Override
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
+            @NonNull String cameraId) {
+        HashMap<Integer, List<Size>> map = new HashMap<>();
+        map.put(ImageFormat.PRIVATE, getOutputSizes(ImageFormat.PRIVATE));
+        return map;
+    }
+
+    private List<Size> getOutputSizes(int imageFormat) {
+        StreamConfigurationMap map = mCameraCharacteristics
+                .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        return Arrays.asList(map.getOutputSizes(imageFormat));
+    }
+
+    @Override
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
+            @NonNull String cameraId) {
+        HashMap<Integer, List<Size>> map = new HashMap<>();
+        map.put(ImageFormat.JPEG, getOutputSizes(ImageFormat.JPEG));
+        return map;
+    }
+
+    @Override
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            @NonNull Size captureSize) {
+        HashMap<Integer, List<Size>> map = new HashMap<>();
+        // Here it intentionally contains JPEG or YUV instead of both so that we can test
+        // the different path.
+        if (mPostviewFormat == ImageFormat.YUV_420_888) {
+            map.put(ImageFormat.YUV_420_888, getCompatibleYuvSizes(captureSize));
+        } else if (mPostviewFormat == ImageFormat.JPEG) {
+            // it will configure YUV stream and convert it to JPEG.
+            map.put(ImageFormat.JPEG, getCompatibleYuvSizes(captureSize));
+        }
+        return map;
+    }
+
+    private List<Size> getCompatibleYuvSizes(Size captureSize) {
+        List<Size> yuvSizes = getOutputSizes(ImageFormat.YUV_420_888);
+        List<Size> results = new ArrayList<>();
+
+        for (Size yuvSize : yuvSizes) {
+            int area = yuvSize.getWidth() * yuvSize.getHeight();
+            if (area <= captureSize.getWidth() * captureSize.getHeight()
+                    && area <= 1920 * 1080 /* 1080P */) {
+                results.add(yuvSize);
+            }
+        }
+        return results;
+    }
+
+    @Override
+    @Nullable
+    public List<Size> getSupportedYuvAnalysisResolutions(
+            @NonNull String cameraId) {
+        return Collections.emptyList();
+    }
+
+    private LongCaptureSessionProcessor mNightSessionProcessor = new LongCaptureSessionProcessor();
+    @Override
+    @NonNull
+    public SessionProcessorImpl createSessionProcessor() {
+        return mNightSessionProcessor;
+    }
+
+    @Override
+    @NonNull
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        List<CaptureRequest.Key> keys = new ArrayList<>(Arrays.asList(
+                CaptureRequest.CONTROL_AF_MODE,
+                CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_REGIONS,
+                CaptureRequest.CONTROL_AE_REGIONS)
+        );
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            keys.add(CaptureRequest.CONTROL_ZOOM_RATIO);
+        } else {
+            keys.add(CaptureRequest.SCALER_CROP_REGION);
+        }
+        return Collections.unmodifiableList(keys);
+    }
+
+    @Override
+    @NonNull
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        List<CaptureResult.Key> keys = new ArrayList<>(Arrays.asList(
+                CaptureResult.CONTROL_AF_MODE,
+                CaptureResult.CONTROL_AE_REGIONS,
+                CaptureResult.CONTROL_AF_REGIONS,
+                CaptureResult.CONTROL_AE_STATE,
+                CaptureResult.CONTROL_AF_STATE));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            keys.add(CaptureResult.CONTROL_ZOOM_RATIO);
+        } else {
+            keys.add(CaptureResult.SCALER_CROP_REGION);
+        }
+        return Collections.unmodifiableList(keys);
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return true;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return true;
+    }
+
+    private class LongCaptureSessionProcessor implements SessionProcessorImpl {
+        private HandlerThread mHandlerThread;
+        private Handler mBackgroundHandler;
+
+        private final Object mLock = new Object();
+        @GuardedBy("mLock")
+        private Map<CaptureRequest.Key<?>, Object> mParameters = new LinkedHashMap<>();
+
+        private Camera2OutputConfigImpl mPreviewOutputConfig;
+        private Camera2OutputConfigImpl mCaptureOutputConfig;
+        private Camera2OutputConfigImpl mPostviewOutputConfig;
+
+        private Surface mPostviewJpegOutputSurface;
+
+        private RequestProcessorImpl mRequestProcessor;
+        private AtomicInteger mNextCaptureSequenceId = new AtomicInteger(1);
+
+        @NonNull
+        @Override
+        public Camera2SessionConfigImpl initSession(@NonNull String cameraId,
+                @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+                @NonNull Context context, @NonNull OutputSurfaceConfigurationImpl surfaceConfigs) {
+            return initSessionInternal(
+                    surfaceConfigs.getPreviewOutputSurface(),
+                    surfaceConfigs.getImageCaptureOutputSurface(),
+                    surfaceConfigs.getPostviewOutputSurface());
+        }
+
+        @NonNull
+        @Override
+        public Camera2SessionConfigImpl initSession(@NonNull String cameraId,
+                @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+                @NonNull Context context, @NonNull OutputSurfaceImpl previewSurfaceConfig,
+                @NonNull OutputSurfaceImpl imageCaptureSurfaceConfig,
+                @Nullable OutputSurfaceImpl imageAnalysisSurfaceConfig) {
+            return initSessionInternal(previewSurfaceConfig,
+                    imageCaptureSurfaceConfig,
+                    null);
+        }
+
+        private Camera2SessionConfigImpl initSessionInternal(
+                @NonNull OutputSurfaceImpl previewSurfaceConfig,
+                @NonNull OutputSurfaceImpl imageCaptureSurfaceConfig,
+                @Nullable OutputSurfaceImpl postviewSurfaceConfig) {
+
+            mHandlerThread = new HandlerThread("LongCapture advanced extender impl");
+            mHandlerThread.start();
+            mBackgroundHandler = new Handler(mHandlerThread.getLooper());
+            Camera2SessionConfigImplBuilder builder =
+                    new Camera2SessionConfigImplBuilder()
+                            .setSessionTemplateId(CameraDevice.TEMPLATE_PREVIEW);
+
+            // Preview
+            if (previewSurfaceConfig.getSurface() != null) {
+                mPreviewOutputConfig = Camera2OutputConfigImplBuilder
+                        .newSurfaceConfig(previewSurfaceConfig.getSurface()).build();
+
+                builder.addOutputConfig(mPreviewOutputConfig);
+            }
+
+            // Image Capture
+            if (imageCaptureSurfaceConfig.getSurface() != null) {
+                mCaptureOutputConfig = Camera2OutputConfigImplBuilder.newSurfaceConfig(
+                        imageCaptureSurfaceConfig.getSurface()).build();
+                builder.addOutputConfig(mCaptureOutputConfig);
+            }
+
+            // postview
+            if (postviewSurfaceConfig != null) {
+                if (postviewSurfaceConfig.getImageFormat() == ImageFormat.YUV_420_888) {
+                    // For YUV format postview, it just configures the YUV surface into the camera.
+                    mPostviewOutputConfig = Camera2OutputConfigImplBuilder.newSurfaceConfig(
+                            postviewSurfaceConfig.getSurface()).build();
+                } else if (postviewSurfaceConfig.getImageFormat() == ImageFormat.JPEG) {
+                    // For JPEG format postview, because we can't configure two JPEG streams on
+                    // most devices, alternatively, we configure a YUV stream and convert it to
+                    // JPEG to the postview output surface.
+                    mPostviewOutputConfig = Camera2OutputConfigImplBuilder.newImageReaderConfig(
+                            postviewSurfaceConfig.getSize(), ImageFormat.YUV_420_888, 2
+                    ).build();
+                    mPostviewJpegOutputSurface = postviewSurfaceConfig.getSurface();
+                }
+
+                builder.addOutputConfig(mPostviewOutputConfig);
+            }
+
+            return builder.build();
+        }
+
+        @Override
+        public void deInitSession() {
+            mHandlerThread.quitSafely();
+        }
+
+        @Override
+        public void setParameters(@NonNull Map<CaptureRequest.Key<?>, Object> parameters) {
+            synchronized (mLock) {
+                for (CaptureRequest.Key<?> key : parameters.keySet()) {
+                    Object value = parameters.get(key);
+                    if (value != null) {
+                        mParameters.put(key, value);
+                    }
+                }
+            }
+        }
+
+        private void applyParameters(@NonNull RequestBuilder requestBuilder) {
+            synchronized (mLock) {
+                for (CaptureRequest.Key<?> key : mParameters.keySet()) {
+                    requestBuilder.setParameters(key, mParameters.get(key));
+                }
+            }
+        }
+
+        @Override
+        public int startTrigger(@NonNull Map<CaptureRequest.Key<?>, Object> triggers,
+                @NonNull CaptureCallback captureCallback) {
+            RequestBuilder builder = new RequestBuilder(mPreviewOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_PREVIEW);
+            applyParameters(builder);
+            builder.setParameters(
+                    CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, EV_INDEX);
+
+            List<CaptureRequest.Key> availableKeys = getAvailableCaptureRequestKeys();
+            for (CaptureRequest.Key<?> key : triggers.keySet()) {
+                if (availableKeys.contains(key)) {
+                    builder.setParameters(key, triggers.get(key));
+                }
+            }
+
+            int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+                @Override
+                public void onCaptureStarted(@NonNull RequestProcessorImpl.Request request,
+                        long frameNumber,
+                        long timestamp) {
+                    captureCallback.onCaptureStarted(seqId, timestamp);
+                }
+
+                @Override
+                public void onCaptureProgressed(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull TotalCaptureResult totalCaptureResult) {
+                    captureCallback.onCaptureProcessStarted(seqId);
+                }
+
+                @Override
+                public void onCaptureFailed(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(@NonNull RequestProcessorImpl.Request request,
+                        long frameNumber, int outputStreamId) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+                    captureCallback.onCaptureSequenceCompleted(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceAborted(int sequenceId) {
+                    captureCallback.onCaptureSequenceAborted(seqId);
+                }
+            };
+
+            mRequestProcessor.submit(builder.build(), callback);
+            return seqId;
+        }
+
+        private int getJpegOrientation() {
+            synchronized (mLock) {
+                if (mParameters.get(CaptureRequest.JPEG_ORIENTATION) != null) {
+                    return (int) mParameters.get(CaptureRequest.JPEG_ORIENTATION);
+                }
+            }
+            return 0;
+        }
+
+        @RequiresApi(21)
+        @Override
+        public void onCaptureSessionStart(@NonNull RequestProcessorImpl requestProcessor) {
+            mRequestProcessor = requestProcessor;
+
+            if (mPostviewFormat == ImageFormat.JPEG && mPostviewJpegOutputSurface != null) {
+                requestProcessor.setImageProcessor(mPostviewOutputConfig.getId(),
+                        new ImageProcessorImpl() {
+                            @Override
+                            public void onNextImageAvailable(int outputConfigId, long timestampNs,
+                                    ImageReferenceImpl imageReference, String physicalCameraId) {
+                                ImageProcessingUtil.convertYuvToJpegBytesIntoSurface(
+                                        imageReference.get(),
+                                        90,
+                                        getJpegOrientation(),
+                                        mPostviewJpegOutputSurface
+                                );
+
+                                imageReference.decrement();
+                            }
+                        }
+                );
+            }
+        }
+
+        @Override
+        public void onCaptureSessionEnd() {
+
+        }
+
+        @Override
+        public int startRepeating(@NonNull CaptureCallback captureCallback) {
+            RequestBuilder builder = new RequestBuilder(mPreviewOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_PREVIEW);
+            applyParameters(builder);
+            builder.setParameters(
+                    CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, EV_INDEX);
+            final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+                @Override
+                public void onCaptureStarted(@NonNull RequestProcessorImpl.Request request,
+                        long frameNumber,
+                        long timestamp) {
+                    captureCallback.onCaptureStarted(seqId, timestamp);
+                }
+
+                @Override
+                public void onCaptureProgressed(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull TotalCaptureResult totalCaptureResult) {
+                    captureCallback.onCaptureProcessStarted(seqId);
+                }
+
+                @Override
+                public void onCaptureFailed(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(@NonNull RequestProcessorImpl.Request request,
+                        long frameNumber, int outputStreamId) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+                    captureCallback.onCaptureSequenceCompleted(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceAborted(int sequenceId) {
+                    captureCallback.onCaptureSequenceAborted(seqId);
+                }
+            };
+
+            mRequestProcessor.setRepeating(builder.build(), callback);
+
+            return seqId;
+        }
+
+        @Override
+        public void stopRepeating() {
+            mRequestProcessor.stopRepeating();
+        }
+
+        @Override
+        public int startCapture(@NonNull CaptureCallback callback) {
+            return startCaptureInternal(false, callback);
+        }
+
+        @Override
+        public int startCaptureWithPostview(@NonNull CaptureCallback callback) {
+            return startCaptureInternal(true, callback);
+        }
+
+        private int startCaptureInternal(boolean enablePostivew,
+                @NonNull CaptureCallback captureCallback) {
+
+            // Send postview request
+            if (enablePostivew) {
+                RequestBuilder builderPostview = new RequestBuilder(mPostviewOutputConfig.getId(),
+                        CameraDevice.TEMPLATE_PREVIEW);
+                applyParameters(builderPostview);
+                RequestProcessorImpl.Request postviewRequest = builderPostview.build();
+
+                mRequestProcessor.submit(postviewRequest, new DefaultRequestProcessorImpl());
+            }
+
+            captureCallback.onCaptureProcessProgressed(10);
+
+            // send still capture request
+            final int seqId = mNextCaptureSequenceId.getAndIncrement();
+            updateProcessProgressDelayed(captureCallback, 40, 1000);
+            updateProcessProgressDelayed(captureCallback, 70, 2000);
+            updateProcessProgressDelayed(captureCallback, 100, 3000);
+
+            mBackgroundHandler.postDelayed(() -> {
+                submitStillCapture(seqId, captureCallback);
+            }, 3000);
+            return seqId;
+        }
+
+        private void updateProcessProgressDelayed(CaptureCallback callback,
+                int progress, long delayInMs) {
+            mBackgroundHandler.postDelayed(() -> {
+                callback.onCaptureProcessProgressed(progress);
+            }, delayInMs);
+        }
+
+        private int submitStillCapture(int seqId, @NonNull CaptureCallback captureCallback) {
+            if (mRequestProcessor == null) {
+                return -1;
+            }
+            RequestBuilder builderStillCapture = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE);
+            applyParameters(builderStillCapture);
+            RequestProcessorImpl.Request stillcaptureRequest = builderStillCapture.build();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+                private boolean mOnCaptureStartedInvokded = false;
+
+                @Override
+                public void onCaptureStarted(@NonNull RequestProcessorImpl.Request request,
+                        long frameNumber, long timestamp) {
+                    if (!mOnCaptureStartedInvokded) {
+                        mOnCaptureStartedInvokded = true;
+                        captureCallback.onCaptureStarted(seqId, timestamp);
+                    }
+                }
+
+                @Override
+                public void onCaptureProgressed(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull TotalCaptureResult totalCaptureResult) {
+                }
+
+                @Override
+                public void onCaptureFailed(@NonNull RequestProcessorImpl.Request request,
+                        @NonNull CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(@NonNull RequestProcessorImpl.Request request,
+                        long frameNumber, int outputStreamId) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+                    captureCallback.onCaptureSequenceCompleted(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceAborted(int sequenceId) {
+                    captureCallback.onCaptureSequenceAborted(seqId);
+                }
+            };
+
+            mRequestProcessor.submit(stillcaptureRequest, callback);
+
+            return seqId;
+        }
+        @Override
+        public void abortCapture(int captureSequenceId) {
+
+        }
+
+        @Nullable
+        @Override
+        public Pair<Long, Long> getRealtimeCaptureLatency() {
+            return null;
+        }
+    }
+}
+
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
index 5c32de8..415a8c5 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
@@ -16,101 +16,19 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.util.Range;
-import android.util.Size;
+import android.graphics.ImageFormat;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.Map;
+import androidx.annotation.RequiresApi;
 
 /**
- * Stub advanced extender implementation for night.
- *
- * <p>This class should be implemented by OEM and deployed to the target devices.
+ * A sample night implementation for testing long processing capture. It is capable of outputting
+ * the postview(YUV format) and the process progress event. ImageAnalysis is not supported.
  *
  * @since 1.2
  */
-public class NightAdvancedExtenderImpl implements AdvancedExtenderImpl {
+@RequiresApi(21)
+public class NightAdvancedExtenderImpl extends LongCaptureAdvancedExtenderImpl {
     public NightAdvancedExtenderImpl() {
-    }
-
-    @Override
-    public boolean isExtensionAvailable(@NonNull String cameraId,
-            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    public void init(@NonNull String cameraId,
-            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @Nullable
-    public Range<Long> getEstimatedCaptureLatencyRange(
-            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
-            @NonNull String cameraId) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
-            @NonNull String cameraId) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
-            @NonNull Size captureSize) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @Nullable
-    public List<Size> getSupportedYuvAnalysisResolutions(
-            @NonNull String cameraId) {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public SessionProcessorImpl createSessionProcessor() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    @NonNull
-    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    public boolean isCaptureProcessProgressAvailable() {
-        throw new RuntimeException("Stub, replace with implementation.");
-    }
-
-    @Override
-    public boolean isPostviewAvailable() {
-        throw new RuntimeException("Stub, replace with implementation.");
+        super(/* postviewFormat */ ImageFormat.YUV_420_888);
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/RequestBuilder.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/RequestBuilder.java
new file mode 100644
index 0000000..b83328b
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/RequestBuilder.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 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.extensions.impl.advanced;
+
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder to build the {@link RequestProcessorImpl.Request} instance.
+ */
+public class RequestBuilder {
+    List<Integer> mTargetOutputConfigIds = new ArrayList<>();
+    Map<CaptureRequest.Key<?>, Object> mParameters = new HashMap<>();
+    int mTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+
+    /**
+     * Construct the builder with default settings.
+     */
+    public RequestBuilder() {
+    }
+
+    /**
+     * Construct the builder.
+     */
+    public RequestBuilder(int targetOutputConfigId, int templateId) {
+        addTargetOutputConfigIds(targetOutputConfigId);
+        setTemplateId(templateId);
+    }
+
+
+    /**
+     * Adds the target outputconfig ids.
+     */
+    @NonNull
+    public RequestBuilder addTargetOutputConfigIds(int targetOutputConfigId) {
+        mTargetOutputConfigIds.add(targetOutputConfigId);
+        return this;
+    }
+
+    /**
+     * Sets the parameters
+     */
+    @NonNull
+    public RequestBuilder setParameters(@NonNull CaptureRequest.Key<?> key,
+            @NonNull Object value) {
+        mParameters.put(key, value);
+        return this;
+    }
+
+    /**
+     * Sets the template id.
+     */
+    @NonNull
+    public RequestBuilder setTemplateId(int templateId) {
+        mTemplateId = templateId;
+        return this;
+    }
+
+    /**
+     * construct {@link RequestProcessorImpl.Request} instance.
+     */
+    @NonNull
+    public RequestProcessorImpl.Request build() {
+        return new RequestProcessorRequest(
+                mTargetOutputConfigIds, mParameters, mTemplateId);
+    }
+
+    static class RequestProcessorRequest implements RequestProcessorImpl.Request {
+        final List<Integer> mTargetOutputConfigIds;
+        final Map<CaptureRequest.Key<?>, Object> mParameters;
+        final int mTemplateId;
+
+        RequestProcessorRequest(List<Integer> targetOutputConfigIds,
+                Map<CaptureRequest.Key<?>, Object> parameters,
+                int templateId) {
+            mTargetOutputConfigIds = targetOutputConfigIds;
+            mParameters = parameters;
+            mTemplateId = templateId;
+        }
+
+        @Override
+        @NonNull
+        public List<Integer> getTargetOutputConfigIds() {
+            return mTargetOutputConfigIds;
+        }
+
+        @Override
+        @NonNull
+        public Map<CaptureRequest.Key<?>, Object> getParameters() {
+            return mParameters;
+        }
+
+        @Override
+        @NonNull
+        public Integer getTemplateId() {
+            return mTemplateId;
+        }
+    }
+}