Snap for 10453563 from cb5a327830ee0325116392b3dc3d796afa1c4215 to mainline-os-statsd-release

Change-Id: Ia8dad1b301d78081ce9b547fbfcc8afdfe8516c9
diff --git a/camera2/extensions/advancedSample/Android.bp b/camera2/extensions/advancedSample/Android.bp
new file mode 100644
index 0000000..9f0df8e
--- /dev/null
+++ b/camera2/extensions/advancedSample/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "androidx.camera.extensions.impl.advanced",
+    installable: true,
+    static_libs: [
+        "androidx.annotation_annotation"
+    ],
+    exclude_kotlinc_generated_files: true,
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    vendor: true,
+    jarjar_rules: "jarjar-rules.txt",
+}
+
+prebuilt_etc {
+    name: "advancedSample_camera_extensions.xml",
+    src: "advancedSample_camera_extensions.xml",
+    sub_dir: "permissions",
+    vendor: true,
+}
diff --git a/camera2/extensions/advancedSample/advancedSample_camera_extensions.xml b/camera2/extensions/advancedSample/advancedSample_camera_extensions.xml
new file mode 100644
index 0000000..4fcbb48
--- /dev/null
+++ b/camera2/extensions/advancedSample/advancedSample_camera_extensions.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<permissions>
+    <library name="androidx.camera.extensions.impl"
+            file="/vendor/framework/androidx.camera.extensions.impl.advanced.jar"/>
+</permissions>
diff --git a/camera2/extensions/advancedSample/jarjar-rules.txt b/camera2/extensions/advancedSample/jarjar-rules.txt
new file mode 100644
index 0000000..7442cef
--- /dev/null
+++ b/camera2/extensions/advancedSample/jarjar-rules.txt
@@ -0,0 +1 @@
+rule kotlin.** androidx.camera.extensions.impl.advanced.@0
\ No newline at end of file
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
new file mode 100755
index 0000000..bd60570
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for auto image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class AutoImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
+    public AutoImageCaptureExtenderImpl() {}
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureProcessorImpl getCaptureProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<CaptureStageImpl> getCaptureStages() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int getMaxCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
new file mode 100755
index 0000000..0c4577a
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for auto preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class AutoPreviewExtenderImpl implements PreviewExtenderImpl {
+    public AutoPreviewExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl getCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorImpl getProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
new file mode 100755
index 0000000..50c8040
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for beauty image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BeautyImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
+    public BeautyImageCaptureExtenderImpl() {}
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureProcessorImpl getCaptureProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<CaptureStageImpl> getCaptureStages() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int getMaxCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
new file mode 100755
index 0000000..1f50174
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for beauty preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BeautyPreviewExtenderImpl implements PreviewExtenderImpl {
+    public BeautyPreviewExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl getCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorImpl getProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
new file mode 100644
index 0000000..ee777cf
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for bokeh image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BokehImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
+    public BokehImageCaptureExtenderImpl() {}
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureProcessorImpl getCaptureProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<CaptureStageImpl> getCaptureStages() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int getMaxCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
new file mode 100644
index 0000000..1dc5ed7
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for bokeh preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BokehPreviewExtenderImpl implements PreviewExtenderImpl {
+    public BokehPreviewExtenderImpl() {}
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl getCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorImpl getProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
new file mode 100644
index 0000000..f4719b8
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.util.Pair;
+import android.util.Size;
+import android.view.Surface;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * The interface for processing a set of {@link Image}s that have captured.
+ *
+ * @since 1.0
+ */
+@SuppressLint("UnknownNullness")
+public interface CaptureProcessorImpl extends ProcessorImpl {
+    /**
+     * Process a set images captured that were requested.
+     *
+     * <p> The result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}.
+     *
+     * @param results The map of {@link ImageFormat#YUV_420_888} format images and metadata to
+     *                process. The {@link Image} that are contained within the map will become
+     *                invalid after this method completes, so no references to them should be kept.
+     */
+    void process(Map<Integer, Pair<Image, TotalCaptureResult>> results);
+
+    /**
+     * Informs the CaptureProcessorImpl where it should write the postview output to.
+     * This will only be invoked once if a valid postview surface was set.
+     *
+     * @param surface A valid {@link ImageFormat#YUV_420_888} {@link Surface}
+     *                that the CaptureProcessorImpl should write data into.
+     * @since 1.4
+     */
+    void onPostviewOutputSurface(Surface surface);
+
+    /**
+     * Invoked when the Camera Framework changes the configured output resolution for
+     * still capture and postview.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
+     * input for still capture and postview to be at the specified resolutions.
+     *
+     * @param size for the surface for still capture.
+     * @param postviewSize for the surface for postview.
+     * @since 1.4
+     */
+    void onResolutionUpdate(Size size, Size postviewSize);
+
+    /**
+     * Process a set images captured that were requested.
+     *
+     * <p> The result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}.
+     *
+     * @param results        The map of {@link ImageFormat#YUV_420_888} format images and metadata
+     *                       to process. The {@link Image} that are contained within the map will
+     *                       become invalid after this method completes, so no references to them
+     *                       should be kept.
+     * @param resultCallback Capture result callback to be called once the capture result
+     *                       values of the processed image are ready.
+     * @param executor       The executor to run the callback on. If null then the callback will
+     *                       run on any arbitrary executor.
+     * @since 1.3
+     */
+    void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            ProcessResultImpl resultCallback, Executor executor);
+
+    /**
+     * Process a set images captured that were requested for both postview and
+     * still capture.
+     *
+     * <p> This processing method will be called if a postview was requested, therefore the
+     * processed postview should be written to the
+     * {@link Surface} received by {@link #onPostviewOutputSurface(Surface, int)}.
+     * The final result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}. Since postview should be available
+     * before the capture, it should be processed and written to the surface before
+     * the final capture is processed.
+     *
+     * @param results             The map of {@link ImageFormat#YUV_420_888} format images and
+     *                            metadata to process. The {@link Image} that are contained within
+     *                            the map will become invalid after this method completes, so no
+     *                            references to them should be kept.
+     * @param resultCallback      Capture result callback to be called once the capture result
+     *                            values of the processed image are ready.
+     * @param executor            The executor to run the callback on. If null then the callback
+     *                            will run on any arbitrary executor.
+     * @throws RuntimeException   if postview feature is not supported
+     * @since 1.4
+     */
+    void processWithPostview(Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            ProcessResultImpl resultCallback, Executor executor);
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/CaptureStageImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/CaptureStageImpl.java
new file mode 100644
index 0000000..268a49d
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/CaptureStageImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.hardware.camera2.CaptureRequest;
+import android.util.Pair;
+
+import java.util.List;
+
+/**
+ * The set of parameters that defines a single capture that will be sent to the camera.
+ *
+ * @since 1.0
+     * @hide
+ */
+public interface CaptureStageImpl {
+    /** Returns the identifier for the {@link CaptureStageImpl}. */
+    /**
+     * @hide
+     */
+    int getId();
+
+    /**
+     * Returns the set of {@link CaptureRequest.Key} and the corresponding values that will be
+     * set for a single {@link CaptureRequest}.
+     */
+    /**
+     * @hide
+     */
+    List<Pair<CaptureRequest.Key, Object>> getParameters();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java
new file mode 100644
index 0000000..3a88ab2
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
+
+/**
+ * Provides interfaces that the OEM needs to implement to handle the state change.
+ *
+ * @since 1.0
+ */
+public interface ExtenderStateListener {
+
+    /**
+     * Notify to initialize the extension. This will be called after bindToLifeCycle. This is
+     * where the use case is started and would be able to allocate resources here. After onInit() is
+     * called, the camera ID, cameraCharacteristics and context will not change until onDeInit()
+     * has been called.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     * @param context The {@link Context} used for CameraX.
+     */
+    void onInit(String cameraId, CameraCharacteristics cameraCharacteristics, Context context);
+
+    /**
+     * Notify to de-initialize the extension. This callback will be invoked after unbind.
+     * After onDeInit() was called, it is expected that the camera ID, cameraCharacteristics will
+     * no longer hold, this should be where to clear all resources allocated for this use case.
+     */
+    void onDeInit();
+
+    /**
+     * This will be invoked before creating a
+     * {@link android.hardware.camera2.CameraCaptureSession}. The {@link CaptureRequest}
+     * parameters returned via {@link CaptureStageImpl} will be passed to the camera device as
+     * part of the capture session initialization via
+     * {@link SessionConfiguration#setSessionParameters(CaptureRequest)} which only supported from
+     * API level 28. The valid parameter is a subset of the available capture request parameters.
+     *
+     * @return The request information to set the session wide camera parameters.
+     */
+    CaptureStageImpl onPresetSession();
+
+    /**
+     * This will be invoked once after the {@link android.hardware.camera2.CameraCaptureSession}
+     * has been created. The {@link CaptureRequest} parameters returned via
+     * {@link CaptureStageImpl} will be used to generate a single request to the current
+     * configured {@link CameraDevice}. The generated request will be submitted to camera before
+     * processing other single requests.
+     *
+     * @return The request information to create a single capture request to camera device.
+     */
+    CaptureStageImpl onEnableSession();
+
+    /**
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+     * closed. The {@link CaptureRequest} parameters returned via {@link CaptureStageImpl} will
+     * be used to generate a single request to the currently configured {@link CameraDevice}. The
+     * generated request will be submitted to camera before the CameraCaptureSession is closed.
+     *
+     * @return The request information to customize the session.
+     */
+    CaptureStageImpl onDisableSession();
+
+    /**
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+     * initialized and must return a valid camera session type
+     * {@link android.hardware.camera2.params.SessionConfiguration#getSessionType}
+     * to be used to configure camera capture session. Both the preview and the image capture
+     * extender must return the same session type value for a specific extension type. If there
+     * is inconsistency between the session type values from preview and image extenders, then
+     * the session configuration will fail.
+     *
+     *
+     * @since 1.4
+     * @return Camera capture session type. Regular and vendor specific types are supported but
+     * not high speed values. The extension can return -1 in which case the camera capture session
+     * will be configured to use the default regular type.
+     */
+    int onSessionType();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
new file mode 100644
index 0000000..1f28572
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.util.Log;
+
+/**
+ * Implementation for extension version check.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices. 3P developers
+ * don't need to implement this, unless this is used for related testing usage.
+ *
+ * @since 1.0
+ * @hide
+ */
+public class ExtensionVersionImpl {
+    private static final String TAG = "ExtenderVersionImpl";
+    private static final String VERSION = "1.4.0";
+
+    /**
+     * @hide
+     */
+    public ExtensionVersionImpl() {
+    }
+
+    /**
+     * Provide the current CameraX extension library version to vendor library and vendor would
+     * need to return the supported version for this device. If the returned version is not
+     * supported by CameraX library, the Preview and ImageCapture would not be able to enable the
+     * specific effects provided by the vendor.
+     *
+     * <p>CameraX library provides the Semantic Versioning string in a form of
+     * MAJOR.MINOR.PATCH-description
+     * We will increment the
+     * MAJOR version when make incompatible API changes,
+     * MINOR version when add functionality in a backwards-compatible manner, and
+     * PATCH version when make backwards-compatible bug fixes. And the description can be ignored.
+     *
+     * <p>Vendor library should provide MAJOR.MINOR.PATCH to CameraX. The MAJOR and MINOR
+     * version is used to map to the version of CameraX that it supports, and CameraX extension
+     * would only available when MAJOR version is matched with CameraX current version. The PATCH
+     * version does not indicate compatibility. The patch version should be incremented whenever
+     * the vendor library makes bug fixes or updates to the algorithm.
+     *
+     * @param version the version of CameraX library formatted as MAJOR.MINOR.PATCH-description.
+     * @return the version that vendor supported in this device. The MAJOR.MINOR.PATCH format
+     * should be used.
+     */
+    /**
+     * @hide
+     */
+    public String checkApiVersion(String version) {
+        Log.d(TAG, "Extension device library version " + VERSION);
+        return VERSION;
+    }
+
+    public boolean isAdvancedExtenderImplemented() {
+        return true;
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
new file mode 100644
index 0000000..f3fd2f3
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for HDR image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class HdrImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
+    public HdrImageCaptureExtenderImpl() {}
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureProcessorImpl getCaptureProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<CaptureStageImpl> getCaptureStages() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int getMaxCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
new file mode 100644
index 0000000..af48464
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for HDR preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class HdrPreviewExtenderImpl implements PreviewExtenderImpl {
+    public HdrPreviewExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl getCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorImpl getProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
new file mode 100644
index 0000000..70c1804
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import java.util.List;
+
+/**
+ * Provides abstract methods that the OEM needs to implement to enable extensions for image capture.
+ *
+ * @since 1.0
+ */
+@SuppressLint("UnknownNullness")
+public interface ImageCaptureExtenderImpl extends ExtenderStateListener {
+    /**
+     * Indicates whether the extension is supported on the device.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     * @return true if the extension is supported, otherwise false
+     */
+    boolean isExtensionAvailable(String cameraId, CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * Initializes the extender to be used with the specified camera.
+     *
+     * <p>This should be called before any other method on the extender. The exception is {@link
+     * #isExtensionAvailable(String, CameraCharacteristics)}.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     */
+    void init(String cameraId, CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * The processing that will be done on a set of captures to create and image with the effect.
+     */
+    CaptureProcessorImpl getCaptureProcessor();
+
+    /** The set of captures that are needed to create an image with the effect. */
+    List<CaptureStageImpl> getCaptureStages();
+
+    /**
+     * Returns the maximum size of the list returned by {@link #getCaptureStages()}.
+     * @return the maximum count.
+     */
+    int getMaxCaptureStage();
+
+    /**
+     * Returns the customized supported resolutions.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     *
+     * <p>The returned resolutions should be subset of the supported sizes retrieved from
+     * {@link android.hardware.camera2.params.StreamConfigurationMap} for the camera device. If the
+     * returned list is not null, it will be used to find the best resolutions combination for
+     * the bound use cases.
+     *
+     * @return the customized supported resolutions, or null to support all sizes retrieved from
+     *         {@link android.hardware.camera2.params.StreamConfigurationMap}.
+     * @since 1.1
+     */
+    List<Pair<Integer, Size[]>> getSupportedResolutions();
+
+    /**
+     * Returns the customized supported postview resolutions for a still capture using
+     * its size.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     *
+     * <p>The returned resolutions should be subset of the supported sizes retrieved from
+     * {@link android.hardware.camera2.params.StreamConfigurationMap} for the camera device.
+     *
+     * @return the customized supported resolutions, or null to support all sizes retrieved from
+     *         {@link android.hardware.camera2.params.StreamConfigurationMap}.
+     * @since 1.4
+     */
+    List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize);
+
+    /**
+     * Returns the estimated capture latency range in milliseconds for the target capture
+     * resolution.
+     *
+     * <p>This includes the time spent processing the multi-frame capture request along with any
+     * additional time for encoding of the processed buffer in the framework if necessary.</p>
+     *
+     * @param captureOutputSize size of the capture output surface. If it is null or not in the
+     *                          supported output sizes, maximum capture output size is used for
+     *                          the estimation.
+     * @return the range of estimated minimal and maximal capture latency in milliseconds, or
+     * null if no capture latency info can be provided.
+     * @since 1.2
+     */
+    Range<Long> getEstimatedCaptureLatencyRange(Size captureOutputSize);
+
+    /**
+     * Return a list of orthogonal capture request keys.
+     *
+     * <p>Any keys included in the list will be configurable by clients of the extension and will
+     * affect the extension functionality.</p>
+     *
+     * <p>Do note that the list of keys applies to {@link PreviewExtenderImpl} as well.</p>
+     *
+     * <p>Also note that the keys {@link CaptureRequest#JPEG_QUALITY} and
+     * {@link CaptureRequest#JPEG_ORIENTATION} are always supported regardless being added in the
+     * list or not. To support common camera operations like zoom, tap-to-focus, flash and
+     * exposure compensation, we recommend supporting the following keys if possible.
+     * <pre>
+     *  zoom:  {@link CaptureRequest#CONTROL_ZOOM_RATIO}
+     *         {@link CaptureRequest#SCALER_CROP_REGION}
+     *  tap-to-focus:
+     *         {@link CaptureRequest#CONTROL_AF_MODE}
+     *         {@link CaptureRequest#CONTROL_AF_TRIGGER}
+     *         {@link CaptureRequest#CONTROL_AF_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AE_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AWB_REGIONS}
+     *  flash:
+     *         {@link CaptureRequest#CONTROL_AE_MODE}
+     *         {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER}
+     *         {@link CaptureRequest#FLASH_MODE}
+     *  exposure compensation:
+     *         {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION}
+     * </pre>
+     * On basic extensions that implement 1.2 or prior version, the above keys are all supported
+     * explicitly. When migrating from 1.2 or prior to 1.3, please note that both CameraX and
+     * Camera2 will honor the returned list and support only the keys contained in it. For
+     * example, if OEM decides to return only {@link CaptureRequest#CONTROL_ZOOM_RATIO} and
+     * {@link CaptureRequest#SCALER_CROP_REGION} in the 1.3 implementation, it means only zoom is
+     * supported for the app while tap-to-focus , flash and exposure compensation are not allowed.
+     *
+     * @return List of supported orthogonal capture keys, or an empty list if no capture settings
+     * are not supported.
+     * @since 1.3
+     */
+    List<CaptureRequest.Key> getAvailableCaptureRequestKeys();
+
+    /**
+     * Return a list of supported capture result keys.
+     *
+     * <p>Any keys included in this list must be available as part of the registered
+     * {@link ProcessResultImpl} callback. In case frame processing is not supported,
+     * then the Camera2/CameraX framework will use the list to filter and notify camera clients
+     * using the respective camera results.</p>
+     *
+     * <p>At the very minimum, it is expected that the result key list is a superset of the
+     * capture request keys.</p>
+     *
+     * <p>Do note that the list of keys applies to {@link PreviewExtenderImpl} as well.</p>
+     *
+     * @return List of supported capture result keys, or an empty list if capture results are not
+     * supported.
+     * @since 1.3
+     */
+    List<CaptureResult.Key> getAvailableCaptureResultKeys();
+
+    /**
+     * Advertise support for {@link ProcessResultImpl#onCaptureProcessProgressed}.
+     *
+     * @return {@code true} in case the process progress callback is supported and is expected to
+     * be triggered, {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isCaptureProcessProgressAvailable();
+
+    /**
+     * Returns the dynamically calculated capture latency pair in milliseconds.
+     *
+     * <p>In contrast to {@link #getEstimatedCaptureLatencyRange} this method is guaranteed to be
+     * called after the camera capture session is initialized and camera preview is enabled.
+     * The measurement is expected to take in to account dynamic parameters such as the current
+     * scene, the state of 3A algorithms, the state of internal HW modules and return a more
+     * accurate assessment of the still capture latency.</p>
+     *
+     * @return pair that includes the estimated input frame/frames camera capture latency as the
+     * first field and the estimated post-processing latency {@link CaptureProcessorImpl#process}
+     * as the second pair field. Both first and second fields will be in milliseconds. The total
+     * still capture latency will be the sum of both the first and second values.
+     * The pair is expected to be null if the dynamic latency estimation is not supported.
+     * If clients have not configured a still capture output, then this method can also return a
+     * null pair.
+     * @since 1.4
+     */
+    Pair<Long, Long> getRealtimeCaptureLatency();
+
+    /**
+     * Indicates whether the extension supports the postview for still capture feature.
+     * If the extension is using HAL processing, false should be returned since the
+     * postview feature is not currently supported for this case.
+     *
+     * @return {@code true} in case postview for still capture is supported
+     * {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isPostviewAvailable();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/InitializerImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/InitializerImpl.java
new file mode 100644
index 0000000..fe2afd5
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/InitializerImpl.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Used for initializing the extensions library.
+ *
+ * @since 1.1
+ * @hide
+ */
+public class InitializerImpl {
+    private InitializerImpl() {
+    }
+
+    private static final String TAG = "InitializerImpl";
+    /**
+     * An unknown error has occurred.
+     * @hide
+     */
+    public static final int ERROR_UNKNOWN = 0;
+    /**
+     * Error reported if the application version of extensions is incompatible with the on device
+     * library version.
+     * @hide
+     */
+    public static final int ERROR_INITIALIZE_VERSION_INCOMPATIBLE = 1;
+    private static Executor sExecutor = Executors.newSingleThreadExecutor();
+
+    /**
+     * Initializes the {@link Context}.
+     *
+     * <p>Before this call has been made no calls to the extensions library should be made except
+     * for {@link ExtensionVersionImpl#checkApiVersion(String)}.
+     *
+     * @param version  The version of the extension used by the application.
+     * @param context  The {@link Context} of the calling application.
+     * @param executor The executor to run the callback on. If null then the callback will run on
+     *                 any arbitrary executor.
+     * @hide
+     */
+    public static void init(String version, Context context,
+            OnExtensionsInitializedCallback callback, Executor executor) {
+        Log.d(TAG, "initializing extensions");
+        if (executor == null) {
+            sExecutor.execute(callback::onSuccess);
+        } else {
+            executor.execute(callback::onSuccess);
+        }
+    }
+
+    /**
+     * Deinitializes the extensions to release resources.
+     *
+     * <p>After this call has been made no calls to the extensions library should be made except
+     * for {@link ExtensionVersionImpl#checkApiVersion(String)}.
+     *
+     * @param executor The executor to run the callback on. If null then the callback will run on
+     *                 any arbitrary executor.
+     * @hide
+     */
+    public static void deinit(OnExtensionsDeinitializedCallback callback,
+            Executor executor) {
+        Log.d(TAG, "deinitializing extensions");
+        if (executor == null) {
+            sExecutor.execute(callback::onSuccess);
+        } else {
+            executor.execute(callback::onSuccess);
+        }
+    }
+
+    /**
+     * Callback that gets called when the library has finished initializing and is ready for used.
+     * @hide
+     */
+    public interface OnExtensionsInitializedCallback {
+        /** Called if the library successfully initializes. */
+        void onSuccess();
+
+        /**
+         * Called if the library is unable to successfully initialize.
+         *
+         * @param error The reason for failing to initialize.
+         */
+        void onFailure(int error);
+    }
+
+    /**
+     * Callback that gets called when the library has finished deinitialized.
+     *
+     * <p> Once this interface has been called then
+     * {@link #init(String, Context, OnExtensionsInitializedCallback, Executor)} can be called
+     * again regardless of whether or not the deinitialization has succeeded or failed.
+     * @hide
+     */
+    public interface OnExtensionsDeinitializedCallback {
+        /** Called if the library successfully deinitializes. */
+        void onSuccess();
+
+        /**
+         * Called if the library encountered some error during the deinitialization.
+         *
+         * <p>Even if the library fails to deinitialize it is now valid for {@link
+         * #init(String, Context, OnExtensionsInitializedCallback, Executor)} to be called
+         * again.
+         *
+         * @param error The reason for failing to deinitialize.
+         */
+        void onFailure(int error);
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
new file mode 100755
index 0000000..6f0eaef
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for night image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class NightImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
+    public NightImageCaptureExtenderImpl() {}
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureProcessorImpl getCaptureProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<CaptureStageImpl> getCaptureStages() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int getMaxCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Nullable
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
new file mode 100755
index 0000000..825994f
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Stub implementation for night preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class NightPreviewExtenderImpl implements PreviewExtenderImpl {
+    public NightPreviewExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @Nullable CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl getCaptureStage() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public ProcessorImpl getProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public void onDeInit() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
new file mode 100644
index 0000000..4324987
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.TotalCaptureResult;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Provides abstract methods that the OEM needs to implement to enable extensions in the preview.
+ *
+ * @since 1.0
+ */
+public interface PreviewExtenderImpl extends ExtenderStateListener {
+    /** The different types of the preview processing. */
+    enum ProcessorType {
+        /** Processor which only updates the {@link CaptureStageImpl}. */
+        PROCESSOR_TYPE_REQUEST_UPDATE_ONLY,
+        /** Processor which updates the received {@link android.media.Image}. */
+        PROCESSOR_TYPE_IMAGE_PROCESSOR,
+        /** No processor, only a {@link CaptureStageImpl} is defined. */
+        PROCESSOR_TYPE_NONE
+    }
+
+    /**
+     * Indicates whether the extension is supported on the device.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     * @return true if the extension is supported, otherwise false
+     */
+    boolean isExtensionAvailable(String cameraId, CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * Initializes the extender to be used with the specified camera.
+     *
+     * <p>This should be called before any other method on the extender. The exception is {@link
+     * #isExtensionAvailable(String, CameraCharacteristics)}.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     */
+    void init(String cameraId, CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * The set of parameters required to produce the effect on the preview stream.
+     *
+     * <p> This will be the initial set of parameters used for the preview
+     * {@link android.hardware.camera2.CaptureRequest}. If the {@link ProcessorType} is defined as
+     * {@link ProcessorType#PROCESSOR_TYPE_REQUEST_UPDATE_ONLY} then this will be updated when
+     * the {@link RequestUpdateProcessorImpl#process(TotalCaptureResult)} from {@link
+     * #getProcessor()} has been called, this should be updated to reflect the new {@link
+     * CaptureStageImpl}. If the processing step returns a {@code null}, meaning the required
+     * parameters has not changed, then calling this will return the previous non-null value.
+     */
+    CaptureStageImpl getCaptureStage();
+
+    /** The type of preview processing to use. */
+    ProcessorType getProcessorType();
+
+    /**
+     * Returns a processor which only updates the {@link CaptureStageImpl}.
+     *
+     * <p>The type of processor is dependent on the return of {@link #getProcessorType()}. The
+     * type of ProcessorImpl returned will be according to the following table.
+     *
+     * <table>
+     * <tr><th> ProcessorType </th> <th> ProcessorImpl </th> </tr>
+     * <tr><td> PROCESSOR_TYPE_REQUEST_UPDATE_ONLY </td> <td> RequestUpdateProcessorImpl </td> </tr>
+     * <tr><td> PROCESSOR_TYPE_IMAGE_PROCESSOR </td> <td> PreviewImageProcessorImpl </td> </tr>
+     * <tr><td> PROCESSOR_TYPE_NONE </td> <td> null </td> </tr>
+     * </table>
+     */
+    ProcessorImpl getProcessor();
+
+    /**
+     * Returns the customized supported resolutions.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     *
+     * <p>The returned resolutions should be subset of the supported sizes retrieved from
+     * {@link android.hardware.camera2.params.StreamConfigurationMap} for the camera device. If the
+     * returned list is not null, it will be used to find the best resolutions combination for
+     * the bound use cases.
+     *
+     * @return the customized supported resolutions.
+     * @since 1.1
+     */
+    @Nullable
+    List<Pair<Integer, Size[]>> getSupportedResolutions();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java
new file mode 100644
index 0000000..f203eba
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Processes a single {@link Image} and {@link TotalCaptureResult} to produce an output to a
+ * stream.
+ *
+ * @since 1.0
+ */
+@SuppressLint("UnknownNullness")
+public interface PreviewImageProcessorImpl extends ProcessorImpl {
+    /**
+     * Processes the requested image capture.
+     *
+     * <p> The result of the processing step should be written to the {@link android.view.Surface}
+     * that was received by {@link ProcessorImpl#onOutputSurface(android.view.Surface, int)}.
+     *
+     * @param image  The {@link ImageFormat#YUV_420_888} format image to process. This will be
+     *               invalid after the method completes so no reference to it should be kept.
+     * @param result The metadata associated with the image to process.
+     */
+    void process(Image image, TotalCaptureResult result);
+
+    /**
+     * Processes the requested image capture.
+     *
+     * <p> The result of the processing step should be written to the {@link android.view.Surface}
+     * that was received by {@link ProcessorImpl#onOutputSurface(android.view.Surface, int)}.
+     *
+     * @param image          The {@link ImageFormat#YUV_420_888} format image to process. This will
+     *                       be invalid after the method completes so no reference to it should be
+     *                       kept.
+     * @param result         The metadata associated with the image to process.
+     * @param resultCallback Capture result callback to be called once the capture result
+     *                       values of the processed image are ready.
+     * @param executor       The executor to run the callback on. If null then the callback will
+     *                       run on any arbitrary executor.
+     * @since 1.3
+     */
+    void process(Image image, TotalCaptureResult result, ProcessResultImpl resultCallback,
+            Executor executor);
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java
new file mode 100644
index 0000000..0e15445
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022 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;
+
+import android.annotation.SuppressLint;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+
+import java.util.List;
+
+/**
+ * Allows clients to receive information about the capture result values of processed frames.
+ *
+ */
+@SuppressLint("UnknownNullness")
+public interface ProcessResultImpl {
+    /**
+     * Capture result callback that needs to be called when the process capture results are
+     * ready as part of frame post-processing.
+     *
+     * @param shutterTimestamp     The shutter time stamp of the processed frame.
+     * @param result               Key value pairs for all supported capture results. Do note that
+     *                             if results 'android.jpeg.quality' and 'android.jpeg.orientation'
+     *                             are present in the process capture input results, then the values
+     *                             must also be passed as part of this callback. Both Camera2 and
+     *                             CameraX guarantee that those two settings and results are always
+     *                             supported and applied by the corresponding framework.
+     * @since 1.3
+     */
+    void onCaptureCompleted(long shutterTimestamp, List<Pair<CaptureResult.Key, Object>> result);
+
+    /**
+     * Capture progress callback that needs to be called when the process capture is
+     * ongoing and includes the estimated progress of the processing.
+     *
+     * <p>Extensions must ensure that they always call this callback with monotonically increasing
+     * values.</p>
+     *
+     * <p>Extensions are allowed to trigger this callback multiple times but at the minimum the
+     * callback is expected to be called once when processing is done with value 100.</p>
+     *
+     * @param progress             Value between 0 and 100.
+     * @since 1.4
+     */
+    void onCaptureProcessProgressed(int progress);
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ProcessorImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ProcessorImpl.java
new file mode 100644
index 0000000..6be328b
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/ProcessorImpl.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.util.Size;
+import android.view.Surface;
+
+/**
+ * Processes an input image stream and produces an output image stream.
+ *
+ * @since 1.0
+ */
+public interface ProcessorImpl {
+    /**
+     * Updates where the ProcessorImpl should write the output to.
+     *
+     * @param surface     The {@link Surface} that the ProcessorImpl should write data into.
+     * @param imageFormat The format of that the surface expects.
+     */
+    void onOutputSurface(Surface surface, int imageFormat);
+
+    /**
+     * Invoked when CameraX changes the configured output resolution.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
+     * input to be at the specified resolution.
+     *
+     * @param size for the surface.
+     */
+    void onResolutionUpdate(Size size);
+
+    /**
+     * Invoked when CameraX changes the configured input image format.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
+     * input to have the specified image format.
+     *
+     * @param imageFormat for the surface.
+     */
+    void onImageFormatUpdate(int imageFormat);
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
new file mode 100644
index 0000000..14637d7
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.hardware.camera2.TotalCaptureResult;
+
+/**
+ * Processes a {@link TotalCaptureResult} to update a CaptureStage.
+ *
+ * @since 1.0
+ */
+public interface RequestUpdateProcessorImpl extends ProcessorImpl {
+    /**
+     * Process the {@link TotalCaptureResult} to update the {@link CaptureStageImpl}
+     *
+     * @param result The metadata associated with the image. Can be null if the image and meta have
+     *               not been synced.
+     * @return The updated parameters used for the repeating requests. If this is {@code null} then
+     * the previous parameters will be used.
+     */
+    CaptureStageImpl process(TotalCaptureResult result);
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
new file mode 100644
index 0000000..abcbf5f
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2022 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.camera.extensions.impl.ExtensionVersionImpl;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Advanced OEM contract for implementing Extensions. ImageCapture/Preview Extensions are both
+ * implemented on this interface.
+ *
+ * <p>This advanced OEM contract empowers OEM to gain access to more Camera2 capability. This
+ * includes: (1) Add custom surfaces with specific formats like YUV, RAW, RAW_DEPTH. (2) Access to
+ * the capture request callbacks as well as all the images retrieved of various image formats. (3)
+ * Able to triggers single or repeating request with the capabilities to specify target surfaces,
+ * template id and parameters.
+ *
+ * <p>OEM needs to implement it with class name HdrAdvancedExtenderImpl for HDR,
+ * NightAdvancedExtenderImpl for night mode, BeautyAdvancedExtenderImpl for beauty mode,
+ * BokehAdvancedExtenderImpl for bokeh mode and AutoAdvancedExtenderImpl for auto mode.
+ *
+ * <p>OEMs are required to return true in
+ * {@link ExtensionVersionImpl#isAdvancedExtenderImplemented()} in order to request CameraX to
+ * use advanced extender over basic extender. OEM is okay to implement advanced
+ * extender only Or basic extender only. However the caveat of advanced-only implementation is,
+ * extensions will be unavailable on the apps using interfaces prior to 1.2.
+ *
+ * @since 1.2
+ */
+@SuppressLint("UnknownNullness")
+public interface AdvancedExtenderImpl {
+
+    /**
+     * Indicates whether the extension is supported on the device.
+     *
+     * @param cameraId           The camera2 id string of the camera.
+     * @param characteristicsMap A map consisting of the camera ids and the
+     *                           {@link CameraCharacteristics}s. For every camera, the map
+     *                           contains at least the CameraCharacteristics for the camera id.
+     *                           If the camera is logical camera, it will also contain associated
+     *                           physical camera ids and their CameraCharacteristics.
+     * @return true if the extension is supported, otherwise false
+     */
+    boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap);
+
+    /**
+     * Initializes the extender to be used with the specified camera.
+     *
+     * <p>This should be called before any other method on the extender. The exception is {@link
+     * #isExtensionAvailable}.
+     *
+     * @param cameraId           The camera2 id string of the camera.
+     * @param characteristicsMap A map consisting of the camera ids and the
+     *                           {@link CameraCharacteristics}s. For every camera, the map
+     *                           contains at least the CameraCharacteristics for the camera id.
+     *                           If the camera is logical camera, it will also contain associated
+     *                           physical camera ids and their CameraCharacteristics.
+     */
+    void init(String cameraId, Map<String, CameraCharacteristics> characteristicsMap);
+
+    /**
+     * Returns the estimated capture latency range in milliseconds for the
+     * target capture resolution during the calls to
+     * {@link SessionProcessorImpl#startCapture}. This
+     * includes the time spent processing the multi-frame capture request along with any additional
+     * time for encoding of the processed buffer in the framework if necessary.
+     *
+     * @param cameraId          the camera id
+     * @param captureOutputSize size of the capture output surface. If it is null or not in the
+     *                          supported output sizes, maximum capture output size is used for
+     *                          the estimation.
+     * @param imageFormat the image format of the capture output surface.
+     * @return the range of estimated minimal and maximal capture latency in milliseconds.
+     * Returns null if no capture latency info can be provided.
+     */
+    Range<Long> getEstimatedCaptureLatencyRange(String cameraId,
+            Size captureOutputSize, int imageFormat);
+
+    /**
+     * Returns supported output format/size map for preview. The format could be PRIVATE or
+     * YUV_420_888. OEM must support PRIVATE format at least. CameraX will only use resolutions
+     * for preview from the list.
+     *
+     * <p>The preview surface format in the CameraCaptureSession may not be identical to the
+     * supported preview output format returned here. Like in the basic extender interface, the
+     * preview PRIVATE surface could be added to the CameraCaptureSession and OEM processes it in
+     * the HAL. Alternatively OEM can configure a intermediate YUV surface of the same size and
+     * writes the output to the preview output surface.
+     */
+    Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(String cameraId);
+
+    /**
+     * Returns supported output format/size map for image capture. OEM is required to support
+     * both JPEG and YUV_420_888 format output.
+     *
+     * <p>Like in the basic extender interface, the surface created with this supported
+     * format/size could be either added in CameraCaptureSession with HAL processing OR it
+     * configures intermediate surfaces(YUV/RAW..) and writes the output to the output surface.
+     */
+    Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(String cameraId);
+
+    /**
+     * Returns supported output format/size map for postview image. OEM is required to support
+     * both JPEG and YUV_420_888 format output.
+     *
+     * <p>The surface created with this supported format/size could configure
+     * intermediate surfaces(YUV/RAW..) and write the output to the output surface.</p>
+     *
+     * @since 1.4
+     */
+    Map<Integer, List<Size>> getSupportedPostviewResolutions(Size captureSize);
+
+    /**
+     * Returns supported output sizes for Image Analysis (YUV_420_888 format).
+     *
+     * <p>OEM can optionally support a YUV surface for ImageAnalysis along with Preview/ImageCapture
+     * output surfaces. If imageAnalysis YUV surface is not supported, OEM should return null or
+     * empty list.
+     */
+    List<Size> getSupportedYuvAnalysisResolutions(String cameraId);
+
+    /**
+     * Returns a processor for activating extension sessions. It implements all the interactions
+     * required for starting a extension and cleanup.
+     */
+    SessionProcessorImpl createSessionProcessor();
+
+    /**
+     * Returns a list of orthogonal capture request keys.
+     *
+     * <p>Any keys included in the list will be configurable by clients of the extension and will
+     * affect the extension functionality.</p>
+     *
+     * <p>Please note that the keys {@link CaptureRequest#JPEG_QUALITY} and
+     * {@link CaptureRequest#JPEG_ORIENTATION} are always supported regardless being added in the
+     * list or not. To support common camera operations like zoom, tap-to-focus, flash and
+     * exposure compensation, we recommend supporting the following keys if possible.
+     * <pre>
+     *  zoom:  {@link CaptureRequest#CONTROL_ZOOM_RATIO}
+     *         {@link CaptureRequest#SCALER_CROP_REGION}
+     *  tap-to-focus:
+     *         {@link CaptureRequest#CONTROL_AF_MODE}
+     *         {@link CaptureRequest#CONTROL_AF_TRIGGER}
+     *         {@link CaptureRequest#CONTROL_AF_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AE_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AWB_REGIONS}
+     *  flash:
+     *         {@link CaptureRequest#CONTROL_AE_MODE}
+     *         {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER}
+     *         {@link CaptureRequest#FLASH_MODE}
+     *  exposure compensation:
+     *         {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION}
+     * </pre>
+     *
+     * @return List of supported orthogonal capture keys, or an empty list if no capture settings
+     * are not supported.
+     * @since 1.3
+     */
+    List<CaptureRequest.Key> getAvailableCaptureRequestKeys();
+
+    /**
+     * Returns a list of supported capture result keys.
+     *
+     * <p>Any keys included in this list must be available as part of the registered
+     * {@link SessionProcessorImpl.CaptureCallback#onCaptureCompleted} callback.</p>
+     *
+     * <p>At the very minimum, it is expected that the result key list is a superset of the
+     * capture request keys.</p>
+     *
+     * @return List of supported capture result keys, or
+     * an empty list if capture results are not supported.
+     * @since 1.3
+     */
+    List<CaptureResult.Key> getAvailableCaptureResultKeys();
+
+    /**
+     * Advertise support for {@link SessionProcessorImpl#onCaptureProcessProgressed}.
+     *
+     * @return {@code true} in case the process progress callback is supported and is expected to
+     * be triggered, {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isCaptureProcessProgressAvailable();
+
+    /**
+     * Indicates whether the extension supports the postview for still capture feature.
+     * If the extension is using HAL processing, false should be returned since the
+     * postview feature is not currently supported for this case.
+     *
+     * @return {@code true} in case postview for still capture is supported
+     * {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isPostviewAvailable();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
new file mode 100644
index 0000000..06c8c44
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 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 androidx.camera.extensions.impl.advanced.BaseAdvancedExtenderImpl.BaseAdvancedSessionProcessor;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.os.Build;
+import android.util.Log;
+
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+@SuppressLint("UnknownNullness")
+public class AutoAdvancedExtenderImpl extends BaseAdvancedExtenderImpl {
+
+    protected static final int AWB_MODE_DAYLIGHT = CaptureRequest.CONTROL_AWB_MODE_DAYLIGHT;
+
+    public AutoAdvancedExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap) {
+        // Requires API 23 for ImageWriter
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        CameraCharacteristics cameraCharacteristics = characteristicsMap.get(cameraId);
+
+        if (cameraCharacteristics == null) {
+            return false;
+        }
+
+        return CameraCharacteristicAvailability.isWBModeAvailable(cameraCharacteristics,
+                AWB_MODE_DAYLIGHT);
+    }
+
+    public class AutoAdvancedSessionProcessor extends BaseAdvancedSessionProcessor {
+
+        public AutoAdvancedSessionProcessor() {
+            appendTag("::Auto");
+        }
+
+        @Override
+        protected void addSessionParameter(Camera2SessionConfigImplBuilder builder) {
+            builder.addSessionParameter(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_DAYLIGHT);
+        }
+
+        @Override
+        protected void addCaptureRequestParameters(List<RequestProcessorImpl.Request> requestList) {
+            RequestBuilder build = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, DEFAULT_CAPTURE_ID);
+            build.setParameters(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_DAYLIGHT);
+            applyParameters(build);
+
+            requestList.add(build.build());
+        }
+    }
+
+    @Override
+    public SessionProcessorImpl createSessionProcessor() {
+        return new AutoAdvancedSessionProcessor();
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BaseAdvancedExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BaseAdvancedExtenderImpl.java
new file mode 100644
index 0000000..00b5705
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BaseAdvancedExtenderImpl.java
@@ -0,0 +1,699 @@
+/*
+ * Copyright 2021 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 androidx.camera.extensions.impl.advanced.JpegEncoder;
+
+import static  androidx.camera.extensions.impl.advanced.JpegEncoder.JPEG_DEFAULT_QUALITY;
+import static androidx.camera.extensions.impl.advanced.JpegEncoder.JPEG_DEFAULT_ROTATION;
+
+import android.annotation.SuppressLint;
+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.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageWriter;
+import android.os.Build;
+import android.util.Log;
+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 java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Executor;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressLint("UnknownNullness")
+public abstract class BaseAdvancedExtenderImpl implements AdvancedExtenderImpl {
+
+    static {
+        try {
+            System.loadLibrary("encoderjpeg_jni");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e("BaseAdvancedExtenderImpl", "libencoderjpeg_jni not loaded");
+        }
+    }
+
+    protected CameraCharacteristics mCameraCharacteristics;
+
+    public BaseAdvancedExtenderImpl() {
+    }
+
+    @Override
+    public abstract boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap);
+
+    @Override
+    public void init(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap) {
+        mCameraCharacteristics = characteristicsMap.get(cameraId);
+    }
+
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(
+            String cameraId, Size size, int imageFormat) {
+        return null;
+    }
+
+    protected Map<Integer, List<Size>> filterOutputResolutions(List<Integer> formats) {
+        Map<Integer, List<Size>> formatResolutions = new HashMap<>();
+
+        StreamConfigurationMap map =
+                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        if (map != null) {
+            for (Integer format : formats) {
+                if (map.getOutputSizes(format) != null) {
+                    formatResolutions.put(format, Arrays.asList(map.getOutputSizes(format)));
+                }
+            }
+        }
+
+        return formatResolutions;
+    }
+
+    @Override
+    public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(String cameraId) {
+        return filterOutputResolutions(Arrays.asList(ImageFormat.PRIVATE, ImageFormat.YUV_420_888));
+    }
+
+    @Override
+    public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(String cameraId) {
+        return filterOutputResolutions(Arrays.asList(ImageFormat.JPEG, ImageFormat.YUV_420_888));
+    }
+
+    @Override
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(Size captureSize) {
+        return new HashMap<>();
+    }
+
+    @Override
+    public List<Size> getSupportedYuvAnalysisResolutions(
+            String cameraId) {
+        return null;
+    }
+
+    public class BaseAdvancedSessionProcessor implements SessionProcessorImpl {
+        protected String TAG = "BaseAdvancedSessionProcessor";
+
+        protected static final int DEFAULT_CAPTURE_ID = 0;
+        protected static final int BASIC_CAPTURE_PROCESS_MAX_IMAGES = 3;
+        protected static final int MAX_NUM_IMAGES = 1;
+
+        protected Camera2OutputConfigImpl mPreviewOutputConfig;
+        protected Camera2OutputConfigImpl mCaptureOutputConfig;
+
+        protected OutputSurfaceImpl mPreviewOutputSurfaceConfig;
+        protected OutputSurfaceImpl mCaptureOutputSurfaceConfig;
+
+        protected final Object mLock = new Object();
+        @GuardedBy("mLock")
+        protected Map<CaptureRequest.Key<?>, Object> mParameters = new LinkedHashMap<>();
+
+        protected final Object mLockCaptureSurfaceImageWriter = new Object();
+        @GuardedBy("mLockCaptureSurfaceImageWriter")
+        protected ImageWriter mCaptureSurfaceImageWriter;
+
+        protected CaptureResultImageMatcher mImageCaptureCaptureResultImageMatcher =
+                new CaptureResultImageMatcher();
+        protected HashMap<Integer, Pair<ImageReferenceImpl, TotalCaptureResult>> mCaptureResults =
+                new HashMap<>();
+        protected RequestProcessorImpl mRequestProcessor;
+
+        protected List<Integer> mCaptureIdList = List.of(DEFAULT_CAPTURE_ID);
+
+        protected AtomicInteger mNextCaptureSequenceId = new AtomicInteger(1);
+
+        protected void appendTag(String tag) {
+            TAG += tag;
+        }
+
+        @Override
+        @NonNull
+        public Camera2SessionConfigImpl initSession(@NonNull String cameraId,
+                @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+                @NonNull Context context,
+                @NonNull OutputSurfaceConfigurationImpl surfaceConfigs) {
+
+            Log.d(TAG, "initSession cameraId=" + cameraId);
+
+            mPreviewOutputSurfaceConfig = surfaceConfigs.getPreviewOutputSurface();
+            mCaptureOutputSurfaceConfig = surfaceConfigs.getImageCaptureOutputSurface();
+
+            Camera2SessionConfigImplBuilder builder =
+                    new Camera2SessionConfigImplBuilder()
+                    .setSessionTemplateId(CameraDevice.TEMPLATE_PREVIEW);
+
+            // Preview
+            if (mPreviewOutputSurfaceConfig.getSurface() != null) {
+                Camera2OutputConfigImplBuilder previewOutputConfigBuilder;
+
+                previewOutputConfigBuilder =
+                        Camera2OutputConfigImplBuilder.newSurfaceConfig(
+                            mPreviewOutputSurfaceConfig.getSurface());
+
+                mPreviewOutputConfig = previewOutputConfigBuilder.build();
+
+                builder.addOutputConfig(mPreviewOutputConfig);
+            }
+
+            // Image Capture
+            if (mCaptureOutputSurfaceConfig.getSurface() != null) {
+                Camera2OutputConfigImplBuilder captureOutputConfigBuilder;
+
+                captureOutputConfigBuilder =
+                        Camera2OutputConfigImplBuilder.newImageReaderConfig(
+                                mCaptureOutputSurfaceConfig.getSize(),
+                                ImageFormat.YUV_420_888,
+                                BASIC_CAPTURE_PROCESS_MAX_IMAGES);
+
+                mCaptureOutputConfig = captureOutputConfigBuilder.build();
+
+                builder.addOutputConfig(mCaptureOutputConfig);
+            }
+
+            addSessionParameter(builder);
+
+            return builder.build();
+        }
+
+        @Override
+        public Camera2SessionConfigImpl initSession(@NonNull String cameraId,
+                @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+                @NonNull Context context,
+                @NonNull OutputSurfaceImpl previewSurfaceConfig,
+                @NonNull OutputSurfaceImpl imageCaptureSurfaceConfig,
+                @Nullable OutputSurfaceImpl imageAnalysisSurfaceConfig) {
+
+            // Since this sample impl uses version 1.4, the other initSession method will be
+            // called. This is just a sample for earlier versions if wanting to redirect this call.
+            OutputSurfaceConfigurationImplImpl surfaceConfigs =
+                    new OutputSurfaceConfigurationImplImpl(previewSurfaceConfig,
+                    imageCaptureSurfaceConfig, imageAnalysisSurfaceConfig,
+                    null /*postviewSurfaceConfig*/);
+
+            return initSession(cameraId, cameraCharacteristicsMap, context, surfaceConfigs);
+        }
+
+        protected void addSessionParameter(Camera2SessionConfigImplBuilder builder) {
+            // default empty implementation
+        }
+
+        @Override
+        public void deInitSession() {
+            synchronized (mLockCaptureSurfaceImageWriter) {
+                if (mCaptureSurfaceImageWriter != null) {
+                    mCaptureSurfaceImageWriter.close();
+                    mCaptureSurfaceImageWriter = null;
+                }
+            }
+        }
+
+        @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);
+                    }
+                }
+            }
+        }
+
+        protected void applyParameters(RequestBuilder builder) {
+            synchronized (mLock) {
+                for (CaptureRequest.Key<?> key : mParameters.keySet()) {
+                    Object value = mParameters.get(key);
+                    builder.setParameters(key, value);
+                }
+            }
+        }
+
+        protected void addTriggerRequestKeys(RequestBuilder builder,
+                Map<CaptureRequest.Key<?>, Object> triggers) {
+            HashSet<CaptureRequest.Key> supportedCaptureRequestKeys =
+                    new HashSet<>(getAvailableCaptureRequestKeys());
+
+            for (CaptureRequest.Key<?> key : triggers.keySet()) {
+                if (supportedCaptureRequestKeys.contains(key)) {
+                    Object value = triggers.get(key);
+                    builder.setParameters(key, value);
+                }
+            }
+        }
+
+        @Override
+        public int startTrigger(Map<CaptureRequest.Key<?>, Object> triggers,
+                CaptureCallback captureCallback) {
+            RequestBuilder builder = new RequestBuilder(mPreviewOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_PREVIEW, 0);
+            addTriggerRequestKeys(builder, triggers);
+
+            final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+                @Override
+                public void onCaptureStarted(RequestProcessorImpl.Request request, long frameNumber,
+                        long timestamp) {
+                    captureCallback.onCaptureStarted(seqId, timestamp);
+                }
+
+                @Override
+                public void onCaptureProgressed(RequestProcessorImpl.Request request,
+                        CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(RequestProcessorImpl.Request request,
+                        TotalCaptureResult totalCaptureResult) {
+                    addCaptureResultKeys(seqId, totalCaptureResult, captureCallback);
+
+                    captureCallback.onCaptureProcessStarted(seqId);
+                }
+
+                @Override
+                public void onCaptureFailed(RequestProcessorImpl.Request request,
+                        CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(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;
+        }
+
+        @Override
+        public void onCaptureSessionStart(@NonNull RequestProcessorImpl requestProcessor) {
+            mRequestProcessor = requestProcessor;
+
+            if (mCaptureOutputSurfaceConfig.getSurface() != null) {
+                synchronized (mLockCaptureSurfaceImageWriter) {
+                    if (JpegEncoder.imageFormatToPublic(mCaptureOutputSurfaceConfig
+                            .getImageFormat()) == ImageFormat.JPEG) {
+                        mCaptureSurfaceImageWriter = new ImageWriter
+                                .Builder(mCaptureOutputSurfaceConfig.getSurface())
+                                .setImageFormat(ImageFormat.JPEG)
+                                .setMaxImages(MAX_NUM_IMAGES)
+                                // For JPEG format, width x height should be set to (w*h) x 1
+                                // since the JPEG image is returned as a 1D byte array
+                                .setWidthAndHeight(mCaptureOutputSurfaceConfig.getSize().getWidth()
+                                        * mCaptureOutputSurfaceConfig.getSize().getHeight(), 1)
+                                .build();
+                    } else {
+                        mCaptureSurfaceImageWriter = new ImageWriter
+                                .Builder(mCaptureOutputSurfaceConfig.getSurface())
+                                .setImageFormat(mCaptureOutputSurfaceConfig.getImageFormat())
+                                .setMaxImages(MAX_NUM_IMAGES)
+                                .build();
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onCaptureSessionEnd() {
+            synchronized (this) {
+                mImageCaptureCaptureResultImageMatcher.clear();
+            }
+
+            mRequestProcessor = null;
+        }
+
+        @Override
+        public int startRepeating(@NonNull CaptureCallback captureCallback) {
+            RequestBuilder builder = new RequestBuilder(mPreviewOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_PREVIEW, 0);
+            applyParameters(builder);
+            final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+                @Override
+                public void onCaptureStarted(RequestProcessorImpl.Request request, long frameNumber,
+                        long timestamp) {
+                    captureCallback.onCaptureStarted(seqId, timestamp);
+                }
+
+                @Override
+                public void onCaptureProgressed(RequestProcessorImpl.Request request,
+                        CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(RequestProcessorImpl.Request request,
+                        TotalCaptureResult totalCaptureResult) {
+                    addCaptureResultKeys(seqId, totalCaptureResult, captureCallback);
+
+                    captureCallback.onCaptureProcessStarted(seqId);
+                }
+
+                @Override
+                public void onCaptureFailed(RequestProcessorImpl.Request request,
+                        CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(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;
+        }
+
+        protected void addCaptureResultKeys(
+            @NonNull int seqId,
+            @NonNull TotalCaptureResult result,
+            @NonNull CaptureCallback captureCallback) {
+            HashMap<CaptureResult.Key, Object> captureResults = new HashMap<>();
+
+            Long shutterTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+
+            if (shutterTimestamp != null) {
+
+                List<CaptureResult.Key> captureResultKeys = getAvailableCaptureResultKeys();
+                for (CaptureResult.Key key : captureResultKeys) {
+                    if (result.get(key) != null) {
+                        captureResults.put(key, result.get(key));
+                    }
+                }
+
+                captureCallback.onCaptureCompleted(shutterTimestamp, seqId,
+                        captureResults);
+            }
+        }
+
+        protected void addCaptureRequestParameters(List<RequestProcessorImpl.Request> requestList) {
+            RequestBuilder build = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, DEFAULT_CAPTURE_ID);
+            applyParameters(build);
+
+            requestList.add(build.build());
+        }
+
+        @Override
+        public int startCaptureWithPostview(@NonNull CaptureCallback captureCallback) {
+            return startCapture(captureCallback);
+        }
+
+        @Override
+        public int startCapture(@NonNull CaptureCallback captureCallback) {
+            List<RequestProcessorImpl.Request> requestList = new ArrayList<>();
+            addCaptureRequestParameters(requestList);
+            final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+
+                @Override
+                public void onCaptureStarted(RequestProcessorImpl.Request request,
+                        long frameNumber, long timestamp) {
+                    captureCallback.onCaptureStarted(seqId, timestamp);
+                }
+
+                @Override
+                public void onCaptureProgressed(RequestProcessorImpl.Request request,
+                        CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(RequestProcessorImpl.Request request,
+                        TotalCaptureResult totalCaptureResult) {
+                    RequestBuilder.RequestProcessorRequest requestProcessorRequest =
+                            (RequestBuilder.RequestProcessorRequest) request;
+
+                    addCaptureResultKeys(seqId, totalCaptureResult, captureCallback);
+
+                    mImageCaptureCaptureResultImageMatcher.setCameraCaptureCallback(
+                            totalCaptureResult,
+                            requestProcessorRequest.getCaptureStageId());
+                }
+
+                @Override
+                public void onCaptureFailed(RequestProcessorImpl.Request request,
+                        CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(RequestProcessorImpl.Request request,
+                        long frameNumber, int outputStreamId) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+                    captureCallback.onCaptureSequenceCompleted(seqId);
+                    captureCallback.onCaptureProcessProgressed(100);
+                }
+
+                @Override
+                public void onCaptureSequenceAborted(int sequenceId) {
+                    captureCallback.onCaptureSequenceAborted(seqId);
+                }
+            };
+
+            Log.d(TAG, "startCapture");
+
+            mRequestProcessor.submit(requestList, callback);
+
+            if (mCaptureOutputSurfaceConfig.getSurface() != null) {
+                mRequestProcessor.setImageProcessor(mCaptureOutputConfig.getId(),
+                        new ImageProcessorImpl() {
+                                @Override
+                                public void onNextImageAvailable(int outputStreamId,
+                                        long timestampNs,
+                                        @NonNull ImageReferenceImpl imgReferenceImpl,
+                                        @Nullable String physicalCameraId) {
+                                    mImageCaptureCaptureResultImageMatcher
+                                            .setInputImage(imgReferenceImpl);
+                                }
+                });
+
+                mImageCaptureCaptureResultImageMatcher.setImageReferenceListener(
+                        new CaptureResultImageMatcher.ImageReferenceListener() {
+                                    @Override
+                                    public void onImageReferenceIncoming(
+                                            @NonNull ImageReferenceImpl imageReferenceImpl,
+                                            @NonNull TotalCaptureResult totalCaptureResult,
+                                            int captureId) {
+                                        captureCallback.onCaptureProcessStarted(seqId);
+                                        processImageCapture(imageReferenceImpl, totalCaptureResult,
+                                                captureId);
+                                    }
+                });
+            }
+
+            return seqId;
+        }
+
+        protected void processImageCapture(@NonNull ImageReferenceImpl imageReferenceImpl,
+                @NonNull TotalCaptureResult totalCaptureResult,
+                int captureId) {
+
+            mCaptureResults.put(captureId, new Pair<>(imageReferenceImpl, totalCaptureResult));
+
+            if (mCaptureResults.keySet().containsAll(mCaptureIdList)) {
+                List<Pair<ImageReferenceImpl, TotalCaptureResult>> imageDataPairs =
+                        new ArrayList<>(mCaptureResults.values());
+
+                Image resultImage = null;
+                int captureSurfaceWriterImageFormat = ImageFormat.UNKNOWN;
+                synchronized (mLockCaptureSurfaceImageWriter) {
+                    resultImage = mCaptureSurfaceImageWriter.dequeueInputImage();
+                    captureSurfaceWriterImageFormat = mCaptureSurfaceImageWriter.getFormat();
+                }
+
+                if (captureSurfaceWriterImageFormat == ImageFormat.JPEG) {
+                    // Simple processing sample that encodes image from YUV to JPEG
+                    Image yuvImage = imageDataPairs.get(DEFAULT_CAPTURE_ID).first.get();
+
+                    Integer jpegOrientation = JPEG_DEFAULT_ROTATION;
+
+                    synchronized (mLock) {
+                        if (mParameters.get(CaptureRequest.JPEG_ORIENTATION) != null) {
+                            jpegOrientation =
+                                    (Integer) mParameters.get(CaptureRequest.JPEG_ORIENTATION);
+                        }
+                    }
+
+                    JpegEncoder.encodeToJpeg(yuvImage, resultImage, jpegOrientation,
+                            JPEG_DEFAULT_QUALITY);
+
+                    resultImage.setTimestamp(yuvImage.getTimestamp());
+
+                } else {
+                    // Simple processing sample that transfers bytes and returns image as is
+                    ByteBuffer yByteBuffer = resultImage.getPlanes()[0].getBuffer();
+                    ByteBuffer uByteBuffer = resultImage.getPlanes()[2].getBuffer();
+                    ByteBuffer vByteBuffer = resultImage.getPlanes()[1].getBuffer();
+
+                    yByteBuffer.put(imageDataPairs.get(
+                            DEFAULT_CAPTURE_ID).first.get().getPlanes()[0].getBuffer());
+                    uByteBuffer.put(imageDataPairs.get(
+                            DEFAULT_CAPTURE_ID).first.get().getPlanes()[2].getBuffer());
+                    vByteBuffer.put(imageDataPairs.get(
+                            DEFAULT_CAPTURE_ID).first.get().getPlanes()[1].getBuffer());
+
+                    resultImage.setTimestamp(imageDataPairs.get(
+                                DEFAULT_CAPTURE_ID).first.get().getTimestamp());
+                }
+
+                synchronized (mLockCaptureSurfaceImageWriter) {
+                    mCaptureSurfaceImageWriter.queueInputImage(resultImage);
+                }
+
+                for (Pair<ImageReferenceImpl, TotalCaptureResult> val : mCaptureResults.values()) {
+                    val.first.decrement();
+                }
+            } else {
+                Log.w(TAG, "Unable to process, waiting for all images");
+            }
+        }
+
+        @Override
+        public void stopRepeating() {
+            mRequestProcessor.stopRepeating();
+        }
+
+        @Override
+        public void abortCapture(int captureSequenceId) {
+
+        }
+
+        @Override
+        public Pair<Long, Long> getRealtimeCaptureLatency() {
+            return null;
+        }
+    }
+
+    public static class OutputSurfaceConfigurationImplImpl implements OutputSurfaceConfigurationImpl {
+        private OutputSurfaceImpl mOutputPreviewSurfaceImpl;
+        private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
+        private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
+        private OutputSurfaceImpl mOutputPostviewSurfaceImpl;
+
+        public OutputSurfaceConfigurationImplImpl(OutputSurfaceImpl previewSurfaceConfig,
+                OutputSurfaceImpl imageCaptureSurfaceConfig,
+                OutputSurfaceImpl imageAnalysisSurfaceConfig,
+                OutputSurfaceImpl postviewSurfaceConfig) {
+            mOutputPreviewSurfaceImpl = previewSurfaceConfig;
+            mOutputImageCaptureSurfaceImpl = imageCaptureSurfaceConfig;
+            mOutputImageAnalysisSurfaceImpl = imageAnalysisSurfaceConfig;
+            mOutputPostviewSurfaceImpl = postviewSurfaceConfig;
+        }
+
+        @Override
+        public OutputSurfaceImpl getPreviewOutputSurface() {
+            return mOutputPreviewSurfaceImpl;
+        }
+
+        @Override
+        public OutputSurfaceImpl getImageCaptureOutputSurface() {
+            return mOutputImageCaptureSurfaceImpl;
+        }
+
+        @Override
+        public OutputSurfaceImpl getImageAnalysisOutputSurface() {
+            return mOutputImageAnalysisSurfaceImpl;
+        }
+
+        @Override
+        public OutputSurfaceImpl getPostviewOutputSurface() {
+            return mOutputPostviewSurfaceImpl;
+        }
+    }
+
+    @Override
+    public abstract SessionProcessorImpl createSessionProcessor();
+
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        final CaptureRequest.Key [] CAPTURE_REQUEST_SET = {CaptureRequest.JPEG_QUALITY,
+                CaptureRequest.JPEG_ORIENTATION};
+        return Arrays.asList(CAPTURE_REQUEST_SET);
+    }
+
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        final CaptureResult.Key [] CAPTURE_RESULT_SET = {CaptureResult.JPEG_QUALITY,
+                CaptureResult.JPEG_ORIENTATION};
+        return Arrays.asList(CAPTURE_RESULT_SET);
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return true;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
new file mode 100644
index 0000000..d8ff59f
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 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 androidx.camera.extensions.impl.advanced.BaseAdvancedExtenderImpl.BaseAdvancedSessionProcessor;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.os.Build;
+import android.util.Log;
+
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+@SuppressLint("UnknownNullness")
+public class BeautyAdvancedExtenderImpl extends BaseAdvancedExtenderImpl {
+
+    protected static final int AWB_MODE_TWILIGHT = CaptureRequest.CONTROL_AWB_MODE_TWILIGHT;
+
+    public BeautyAdvancedExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap) {
+        // Requires API 23 for ImageWriter
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        CameraCharacteristics cameraCharacteristics = characteristicsMap.get(cameraId);
+
+        if (cameraCharacteristics == null) {
+            return false;
+        }
+
+        return CameraCharacteristicAvailability.isWBModeAvailable(cameraCharacteristics,
+                AWB_MODE_TWILIGHT);
+    }
+
+    public class BeautyAdvancedSessionProcessor extends BaseAdvancedSessionProcessor {
+
+        public BeautyAdvancedSessionProcessor() {
+            appendTag("::Beauty");
+        }
+
+        @Override
+        protected void addSessionParameter(Camera2SessionConfigImplBuilder builder) {
+            builder.addSessionParameter(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_TWILIGHT);
+        }
+
+        @Override
+        protected void addCaptureRequestParameters(List<RequestProcessorImpl.Request> requestList) {
+            RequestBuilder build = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, DEFAULT_CAPTURE_ID);
+            build.setParameters(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_TWILIGHT);
+            applyParameters(build);
+
+            requestList.add(build.build());
+        }
+    }
+
+    @Override
+    public SessionProcessorImpl createSessionProcessor() {
+        return new BeautyAdvancedSessionProcessor();
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
new file mode 100644
index 0000000..0156991
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2021 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 androidx.camera.extensions.impl.advanced.BaseAdvancedExtenderImpl.BaseAdvancedSessionProcessor;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.os.Build;
+import android.util.Log;
+
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressLint("UnknownNullness")
+public class BokehAdvancedExtenderImpl extends BaseAdvancedExtenderImpl {
+
+    protected static final int AWB_MODE_SHADE = CaptureRequest.CONTROL_AWB_MODE_SHADE;
+
+    public BokehAdvancedExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap) {
+        // Requires API 23 for ImageWriter
+        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        CameraCharacteristics cameraCharacteristics = characteristicsMap.get(cameraId);
+
+        if (cameraCharacteristics == null) {
+            return false;
+        }
+
+        return CameraCharacteristicAvailability.isWBModeAvailable(cameraCharacteristics,
+                AWB_MODE_SHADE) &&
+                        CameraCharacteristicAvailability.hasFlashUnit(cameraCharacteristics);
+    }
+
+    public class BokehAdvancedSessionProcessor extends BaseAdvancedSessionProcessor {
+
+        public BokehAdvancedSessionProcessor() {
+            appendTag("::Bokeh");
+        }
+
+        @Override
+        protected void addSessionParameter(Camera2SessionConfigImplBuilder builder) {
+            builder.addSessionParameter(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_SHADE);
+        }
+
+        @Override
+        protected void addCaptureRequestParameters(List<RequestProcessorImpl.Request> requestList) {
+            RequestBuilder build = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, DEFAULT_CAPTURE_ID);
+            build.setParameters(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_SHADE);
+            applyParameters(build);
+
+            requestList.add(build.build());
+        }
+    }
+
+    @Override
+    public SessionProcessorImpl createSessionProcessor() {
+        return new BokehAdvancedSessionProcessor();
+    }
+
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        final CaptureRequest.Key [] CAPTURE_REQUEST_SET = {CaptureRequest.CONTROL_AE_MODE,
+            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_LOCK,
+            CaptureRequest.FLASH_MODE, CaptureRequest.JPEG_QUALITY,
+            CaptureRequest.JPEG_ORIENTATION};
+        return Arrays.asList(CAPTURE_REQUEST_SET);
+    }
+
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        final CaptureResult.Key [] CAPTURE_RESULT_SET = {CaptureResult.CONTROL_AE_MODE,
+            CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureResult.CONTROL_AE_LOCK,
+            CaptureResult.CONTROL_AE_STATE, CaptureResult.FLASH_MODE,
+            CaptureResult.FLASH_STATE, CaptureResult.JPEG_QUALITY, CaptureResult.JPEG_ORIENTATION};
+        return Arrays.asList(CAPTURE_RESULT_SET);
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
new file mode 100644
index 0000000..68de01b
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+
+import java.util.List;
+
+/**
+ * A config representing a {@link android.hardware.camera2.params.OutputConfiguration} where
+ * Surface will be created by the information in this config.
+ */
+@SuppressLint("UnknownNullness")
+public interface Camera2OutputConfigImpl {
+    /**
+     * Gets thd id of this output config. The id can be used to identify the stream in vendor
+     * implementations.
+     */
+    int getId();
+
+    /**
+     * Gets the surface group id. Vendor can use the surface group id to share memory between
+     * Surfaces.
+     */
+    int getSurfaceGroupId();
+
+    /**
+     * Gets the physical camera id. Returns null if not specified.
+     */
+    String getPhysicalCameraId();
+
+    /**
+     * If non-null, enable surface sharing and add the surface constructed by the return
+     * Camera2OutputConfig.
+     */
+    List<Camera2OutputConfigImpl> getSurfaceSharingOutputConfigs();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java
new file mode 100644
index 0000000..868be83
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2022 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.annotation.SuppressLint;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.util.Size;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A builder implementation to help OEM build the {@link Camera2OutputConfigImpl} instance.
+ */
+@SuppressLint("UnknownNullness")
+public class Camera2OutputConfigImplBuilder {
+    static AtomicInteger sLastId = new AtomicInteger(0);
+    private OutputConfigImplImpl mOutputConfig;
+    private int mSurfaceGroupId = OutputConfiguration.SURFACE_GROUP_ID_NONE;
+    private String mPhysicalCameraId;
+    private List<Camera2OutputConfigImpl> mSurfaceSharingConfigs;
+
+    private Camera2OutputConfigImplBuilder(OutputConfigImplImpl outputConfig) {
+        mOutputConfig = outputConfig;
+    }
+
+    private int getNextId() {
+        return sLastId.getAndIncrement();
+    }
+
+    /**
+     * Creates a {@link Camera2OutputConfigImpl} that represents a {@link android.media.ImageReader}
+     * with the given parameters.
+     */
+    public static Camera2OutputConfigImplBuilder newImageReaderConfig(
+            Size size, int imageFormat, int maxImages) {
+        return new Camera2OutputConfigImplBuilder(
+                new ImageReaderOutputConfigImplImpl(size, imageFormat, maxImages));
+    }
+
+    /**
+     * Creates a {@link Camera2OutputConfigImpl} that represents a MultiResolutionImageReader with
+     * the given parameters.
+     */
+    public static Camera2OutputConfigImplBuilder newMultiResolutionImageReaderConfig(
+            int imageFormat, int maxImages) {
+        return new Camera2OutputConfigImplBuilder(
+                new MultiResolutionImageReaderOutputConfigImplImpl(imageFormat, maxImages));
+    }
+
+    /**
+     * Creates a {@link Camera2OutputConfigImpl} that contains the Surface directly.
+     */
+    public static Camera2OutputConfigImplBuilder newSurfaceConfig(Surface surface) {
+        return new Camera2OutputConfigImplBuilder(new SurfaceOutputConfigImplImpl(surface));
+    }
+
+    /**
+     * Adds a {@link Camera2SessionConfigImpl} to be shared with current config.
+     */
+    public Camera2OutputConfigImplBuilder addSurfaceSharingOutputConfig(
+            Camera2OutputConfigImpl camera2OutputConfig) {
+        if (mSurfaceSharingConfigs == null) {
+            mSurfaceSharingConfigs = new ArrayList<>();
+        }
+
+        mSurfaceSharingConfigs.add(camera2OutputConfig);
+        return this;
+    }
+
+    /**
+     * Sets a physical camera id.
+     */
+    public Camera2OutputConfigImplBuilder setPhysicalCameraId(String physicalCameraId) {
+        mPhysicalCameraId = physicalCameraId;
+        return this;
+    }
+
+    /**
+     * Sets surface group id.
+     */
+    public Camera2OutputConfigImplBuilder setSurfaceGroupId(int surfaceGroupId) {
+        mSurfaceGroupId = surfaceGroupId;
+        return this;
+    }
+
+    /**
+     * Build a {@link Camera2OutputConfigImpl} instance.
+     */
+    public Camera2OutputConfigImpl build() {
+        mOutputConfig.setId(getNextId());
+        mOutputConfig.setPhysicalCameraId(mPhysicalCameraId);
+        mOutputConfig.setSurfaceGroup(mSurfaceGroupId);
+        mOutputConfig.setSurfaceSharingConfigs(mSurfaceSharingConfigs);
+        return mOutputConfig;
+    }
+
+    private static class OutputConfigImplImpl implements Camera2OutputConfigImpl {
+        private int mId;
+        private int mSurfaceGroup;
+        private String mPhysicalCameraId;
+        private List<Camera2OutputConfigImpl> mSurfaceSharingConfigs;
+
+        OutputConfigImplImpl() {
+            mId = -1;
+            mSurfaceGroup = 0;
+            mPhysicalCameraId = null;
+            mSurfaceSharingConfigs = null;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+
+        @Override
+        public int getSurfaceGroupId() {
+            return mSurfaceGroup;
+        }
+
+        @Override
+        public String getPhysicalCameraId() {
+            return mPhysicalCameraId;
+        }
+
+        @Override
+        public List<Camera2OutputConfigImpl> getSurfaceSharingOutputConfigs() {
+            return mSurfaceSharingConfigs;
+        }
+
+        public void setId(int id) {
+            mId = id;
+        }
+
+        public void setSurfaceGroup(int surfaceGroup) {
+            mSurfaceGroup = surfaceGroup;
+        }
+
+        public void setPhysicalCameraId(String physicalCameraId) {
+            mPhysicalCameraId = physicalCameraId;
+        }
+
+        public void setSurfaceSharingConfigs(
+                List<Camera2OutputConfigImpl> surfaceSharingConfigs) {
+            mSurfaceSharingConfigs = surfaceSharingConfigs;
+        }
+    }
+
+    private static class SurfaceOutputConfigImplImpl extends OutputConfigImplImpl
+            implements SurfaceOutputConfigImpl {
+        private Surface mSurface;
+
+        SurfaceOutputConfigImplImpl(Surface surface) {
+            mSurface = surface;
+        }
+
+        @Override
+        public Surface getSurface() {
+            return mSurface;
+        }
+    }
+
+    private static class ImageReaderOutputConfigImplImpl extends OutputConfigImplImpl
+            implements ImageReaderOutputConfigImpl {
+        private Size mSize;
+        private int mImageFormat;
+        private int mMaxImages;
+
+        ImageReaderOutputConfigImplImpl(Size size, int imageFormat, int maxImages) {
+            mSize = size;
+            mImageFormat = imageFormat;
+            mMaxImages = maxImages;
+        }
+
+        @Override
+        public Size getSize() {
+            return mSize;
+        }
+
+        @Override
+        public int getImageFormat() {
+            return mImageFormat;
+        }
+
+        @Override
+        public int getMaxImages() {
+            return mMaxImages;
+        }
+    }
+
+    private static class MultiResolutionImageReaderOutputConfigImplImpl extends OutputConfigImplImpl
+            implements MultiResolutionImageReaderOutputConfigImpl {
+        private int mImageFormat;
+        private int mMaxImages;
+
+        MultiResolutionImageReaderOutputConfigImplImpl(int imageFormat, int maxImages) {
+            mImageFormat = imageFormat;
+            mMaxImages = maxImages;
+        }
+
+        @Override
+        public int getImageFormat() {
+            return mImageFormat;
+        }
+
+        @Override
+        public int getMaxImages() {
+            return mMaxImages;
+        }
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
new file mode 100644
index 0000000..6fb45bc
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 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.annotation.SuppressLint;
+import android.hardware.camera2.CaptureRequest;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A config representing a {@link android.hardware.camera2.params.SessionConfiguration}
+ */
+@SuppressLint("UnknownNullness")
+public interface Camera2SessionConfigImpl {
+    /**
+     * Returns all the {@link Camera2OutputConfigImpl}s that will be used to create
+     * {@link android.hardware.camera2.params.OutputConfiguration}.
+     */
+    List<Camera2OutputConfigImpl> getOutputConfigs();
+
+    /**
+     * Gets all the parameters to create the session parameters with.
+     */
+    Map<CaptureRequest.Key<?>, Object> getSessionParameters();
+
+    /**
+     * Gets the template id used for creating {@link CaptureRequest}s to be passed in
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters}.
+     */
+    int getSessionTemplateId();
+
+    /**
+     * Retrieves the session type to be used when initializing the
+     * {@link android.hardware.camera2.CameraCaptureSession}.
+     *
+     * @since 1.4
+     * @return Camera capture session type. Regular and vendor specific types are supported but
+     * not high speed values. The extension can return -1 in which case the camera capture session
+     * will be configured to use the default regular type.
+     */
+    int getSessionType();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
new file mode 100644
index 0000000..dc1fecc
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder implementation to help OEM build the {@link Camera2SessionConfigImpl} instance.
+ */
+@SuppressLint("UnknownNullness")
+public class Camera2SessionConfigImplBuilder {
+    private int mSessionTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+    private int mSessionType = SessionConfiguration.SESSION_REGULAR;
+    Map<CaptureRequest.Key<?>, Object> mSessionParameters = new HashMap<>();
+    List<Camera2OutputConfigImpl> mCamera2OutputConfigs = new ArrayList<>();
+
+    public Camera2SessionConfigImplBuilder() {
+    }
+
+    /**
+     * Adds a output config.
+     */
+    public Camera2SessionConfigImplBuilder addOutputConfig(
+            Camera2OutputConfigImpl outputConfig) {
+        mCamera2OutputConfigs.add(outputConfig);
+        return this;
+    }
+
+    /**
+     * Sets session parameters.
+     */
+    public <T> Camera2SessionConfigImplBuilder addSessionParameter(
+            CaptureRequest.Key<T> key, T value) {
+        mSessionParameters.put(key, value);
+        return this;
+    }
+
+    /**
+     * Sets the template id for session parameters request.
+     */
+    public Camera2SessionConfigImplBuilder setSessionTemplateId(int templateId) {
+        mSessionTemplateId = templateId;
+        return this;
+    }
+
+    /**
+     * Gets the session template id.
+     */
+    public int getSessionTemplateId() {
+        return mSessionTemplateId;
+    }
+
+    /**
+     * Gets the session parameters.
+     */
+    public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
+        return mSessionParameters;
+    }
+
+    /**
+     * Gets all the output configs.
+     */
+    public List<Camera2OutputConfigImpl> getCamera2OutputConfigs() {
+        return mCamera2OutputConfigs;
+    }
+
+    /**
+     * Gets the camera capture session type.
+     */
+    public int getSessionType() {
+        return mSessionType;
+    }
+
+    /**
+     * Builds a {@link Camera2SessionConfigImpl} instance.
+     */
+    public Camera2SessionConfigImpl build() {
+        return new Camera2SessionConfigImplImpl(this);
+    }
+
+    private static class Camera2SessionConfigImplImpl implements
+            Camera2SessionConfigImpl {
+        int mSessionTemplateId;
+        int mSessionType;
+        Map<CaptureRequest.Key<?>, Object> mSessionParameters;
+        List<Camera2OutputConfigImpl> mCamera2OutputConfigs;
+
+        Camera2SessionConfigImplImpl(Camera2SessionConfigImplBuilder builder) {
+            mSessionTemplateId = builder.getSessionTemplateId();
+            mSessionParameters = builder.getSessionParameters();
+            mCamera2OutputConfigs = builder.getCamera2OutputConfigs();
+            mSessionType = builder.getSessionType();
+        }
+
+        @Override
+        public List<Camera2OutputConfigImpl> getOutputConfigs() {
+            return mCamera2OutputConfigs;
+        }
+
+        @Override
+        public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
+            return mSessionParameters;
+        }
+
+        @Override
+        public int getSessionTemplateId() {
+            return mSessionTemplateId;
+        }
+
+        @Override
+        public int getSessionType() {
+            return mSessionType;
+        }
+    }
+}
+
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/CameraCharacteristicAvailability.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/CameraCharacteristicAvailability.java
new file mode 100644
index 0000000..6a77e6c
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/CameraCharacteristicAvailability.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2020 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.CameraCharacteristics;
+import android.util.Log;
+import android.util.Range;
+
+import java.util.Arrays;
+
+/**
+ * A utility class to check the availabilities of camera characteristics.
+ */
+final class CameraCharacteristicAvailability {
+    private static final String TAG = "CharacteristicAbility";
+
+    private CameraCharacteristicAvailability() {
+    }
+
+    /**
+     * Check if the given device supports flash.
+     *
+     * @param cameraCharacteristics the camera characteristics.
+     * @return {@code true} if the device supports flash
+     * {@code false} otherwise.
+     */
+    static boolean hasFlashUnit(CameraCharacteristics cameraCharacteristics) {
+        Boolean flashInfo = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+
+        return (flashInfo != null) ? flashInfo : false;
+    }
+
+    /**
+     * Check if the given device supports zoom ratio.
+     *
+     * @param cameraCharacteristics the camera characteristics.
+     * @return {@code true} if the device supports zoom ratio
+     * {@code false} otherwise.
+     */
+    static boolean supportsZoomRatio(CameraCharacteristics cameraCharacteristics) {
+        Range<Float> zoomRatioRange = cameraCharacteristics.get(
+                CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
+
+        return (zoomRatioRange != null) && (zoomRatioRange.getUpper() > 1.f);
+    }
+
+    /**
+     * Check if the given device is fixed focus or not.
+     *
+     * @param cameraCharacteristics the camera characteristics.
+     * @return {@code true} if the device is not fixed focus
+     * {@code false} otherwise.
+     */
+    static boolean hasFocuser(CameraCharacteristics cameraCharacteristics) {
+        Float minFocusDistance = cameraCharacteristics.get(
+                CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+        if (minFocusDistance == null) {
+            Log.d(TAG, "No LENS_INFO_MINIMUM_FOCUS_DISTANCE info");
+            return false;
+        }
+
+        if (minFocusDistance > 0.f) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if the given white balance mode id is available in the camera characteristics.
+     *
+     * @param cameraCharacteristics the camera characteristics.
+     * @param mode white balance mode id.
+     * @return {@code true} if the given white balance mode id is available in the camera
+     *                      characteristics.
+     * {@code false} otherwise.
+     */
+    static boolean isWBModeAvailable(CameraCharacteristics cameraCharacteristics,
+            int mode) {
+        int[] availableModes = cameraCharacteristics.get(
+                CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);
+        if (availableModes == null) {
+            Log.d(TAG, "No CONTROL_AWB_AVAILABLE_MODES info");
+            return false;
+        }
+
+        for (int availableMode : availableModes) {
+            if (availableMode == mode) {
+                return true;
+            }
+        }
+        Log.d(TAG, "wb mode: " + mode + " is not in available list "
+                + Arrays.toString(availableModes));
+        return false;
+    }
+
+    /**
+     * Check if the given effect id is available in the camera characteristics.
+     *
+     * @param cameraCharacteristics the camera characteristics.
+     * @param effect the effect id.
+     * @return {@code true} if the given effect id is available in the camera characteristics.
+     * {@code false} otherwise.
+     */
+    static boolean isEffectAvailable(CameraCharacteristics cameraCharacteristics,
+            int effect) {
+        int[] availableEffects = cameraCharacteristics.get(
+                CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS);
+        if (availableEffects == null) {
+            Log.d(TAG, "No CONTROL_AVAILABLE_EFFECTS info");
+            return false;
+        }
+
+        for (int availableEffect : availableEffects) {
+            if (availableEffect == effect) {
+                return true;
+            }
+        }
+        Log.d(TAG, "effect: " + effect + " is not in available list "
+                + Arrays.toString(availableEffects));
+        return false;
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/CaptureResultImageMatcher.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/CaptureResultImageMatcher.java
new file mode 100644
index 0000000..f93271b
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/CaptureResultImageMatcher.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2022 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.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.util.LongSparseArray;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CaptureResultImageMatcher {
+    private static final String TAG = "CaptureResultImageReader";
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mClosed = false;
+
+    /** ImageInfos haven't been matched with Image. */
+    @GuardedBy("mLock")
+    private final LongSparseArray<TotalCaptureResult> mPendingImageInfos = new LongSparseArray<>();
+
+    Map<TotalCaptureResult, Integer> mCaptureStageIdMap = new HashMap<>();
+
+
+    /** Images haven't been matched with ImageInfo. */
+    @GuardedBy("mLock")
+    private final LongSparseArray<ImageReferenceImpl> mPendingImages = new LongSparseArray<>();
+
+    ImageReferenceListener mImageReferenceListener;
+
+    public CaptureResultImageMatcher() {
+
+    }
+
+    public void clear() {
+        synchronized (mLock) {
+            mPendingImageInfos.clear();
+            for (int i = 0; i < mPendingImages.size(); i++) {
+                long key = mPendingImages.keyAt(i);
+                mPendingImages.get(key).decrement();
+            }
+            mPendingImages.clear();
+            mCaptureStageIdMap.clear();
+            mClosed = false;
+        }
+    }
+
+    public void setImageReferenceListener(
+            @NonNull ImageReferenceListener imageReferenceImplListener) {
+        synchronized (mLock) {
+            mImageReferenceListener = imageReferenceImplListener;
+        }
+    }
+
+    public void setInputImage(@NonNull ImageReferenceImpl imageReferenceImpl) {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+
+            Image image = imageReferenceImpl.get();
+            // Add the incoming Image to pending list and do the matching logic.
+            mPendingImages.put(image.getTimestamp(), imageReferenceImpl);
+            matchImages();
+        }
+    }
+
+    public void setCameraCaptureCallback(@NonNull TotalCaptureResult captureResult) {
+        setCameraCaptureCallback(captureResult, 0);
+    }
+
+    public void setCameraCaptureCallback(@NonNull TotalCaptureResult captureResult,
+            int captureStageId) {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+
+            long timestamp = getTimeStampFromCaptureResult(captureResult);
+
+            // Add the incoming CameraCaptureResult to pending list and do the matching logic.
+            mPendingImageInfos.put(timestamp, captureResult);
+            mCaptureStageIdMap.put(captureResult, captureStageId);
+            matchImages();
+        }
+    }
+
+
+    private long getTimeStampFromCaptureResult(TotalCaptureResult captureResult) {
+        Long timestamp = captureResult.get(CaptureResult.SENSOR_TIMESTAMP);
+        long timestampValue = -1;
+        if (timestamp != null) {
+            timestampValue = timestamp;
+        }
+
+        return timestampValue;
+    }
+
+
+    private void notifyImage(ImageReferenceImpl imageReferenceImpl,
+            TotalCaptureResult totalCaptureResult) {
+        synchronized (mLock) {
+            if (mImageReferenceListener != null) {
+                mImageReferenceListener.onImageReferenceIncoming(imageReferenceImpl,
+                        totalCaptureResult, mCaptureStageIdMap.get(totalCaptureResult));
+            } else {
+                imageReferenceImpl.decrement();
+            }
+        }
+    }
+
+    // Remove the stale {@link ImageProxy} and {@link ImageInfo} from the pending queue if there are
+    // any missing which can happen if the camera is momentarily shut off.
+    // The ImageProxy and ImageInfo timestamps are assumed to be monotonically increasing. This
+    // means any ImageProxy or ImageInfo which has a timestamp older (smaller in value) than the
+    // oldest timestamp in the other queue will never get matched, so they should be removed.
+    //
+    // This should only be called at the end of matchImages(). The assumption is that there are no
+    // matching timestamps.
+    private void removeStaleData() {
+        synchronized (mLock) {
+            // No stale data to remove
+            if (mPendingImages.size() == 0 || mPendingImageInfos.size() == 0) {
+                return;
+            }
+
+            Long minImageProxyTimestamp = mPendingImages.keyAt(0);
+            Long minImageInfoTimestamp = mPendingImageInfos.keyAt(0);
+
+            // If timestamps are equal then matchImages did not correctly match up the ImageInfo
+            // and ImageProxy
+            if (minImageInfoTimestamp.equals(minImageProxyTimestamp)) {
+                throw new IllegalArgumentException();
+            }
+
+            if (minImageInfoTimestamp > minImageProxyTimestamp) {
+                for (int i = mPendingImages.size() - 1; i >= 0; i--) {
+                    if (mPendingImages.keyAt(i) < minImageInfoTimestamp) {
+                        ImageReferenceImpl imageReferenceImpl = mPendingImages.valueAt(i);
+                        imageReferenceImpl.decrement();
+                        mPendingImages.removeAt(i);
+                    }
+                }
+            } else {
+                for (int i = mPendingImageInfos.size() - 1; i >= 0; i--) {
+                    if (mPendingImageInfos.keyAt(i) < minImageProxyTimestamp) {
+                        mPendingImageInfos.removeAt(i);
+                    }
+                }
+            }
+
+        }
+    }
+
+    // Match incoming Image from the ImageReader with the corresponding ImageInfo.
+    private void matchImages() {
+        synchronized (mLock) {
+            // Iterate in reverse order so that ImageInfo can be removed in place
+            for (int i = mPendingImageInfos.size() - 1; i >= 0; i--) {
+                TotalCaptureResult captureResult = mPendingImageInfos.valueAt(i);
+                long timestamp = getTimeStampFromCaptureResult(captureResult);
+
+                ImageReferenceImpl imageReferenceImpl = mPendingImages.get(timestamp);
+
+                if (imageReferenceImpl != null) {
+                    mPendingImages.remove(timestamp);
+                    mPendingImageInfos.removeAt(i);
+                    // Got a match. Add the ImageProxy to matched list and invoke
+                    // onImageAvailableListener.
+                    notifyImage(imageReferenceImpl, captureResult);
+                }
+            }
+
+            removeStaleData();
+        }
+    }
+
+    public interface ImageReferenceListener {
+        void onImageReferenceIncoming(@NonNull ImageReferenceImpl imageReferenceImpl,
+                @NonNull TotalCaptureResult totalCaptureResult, int captureStageId);
+    }
+
+}
\ No newline at end of file
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
new file mode 100644
index 0000000..d8b9928
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2022 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 androidx.camera.extensions.impl.advanced.JpegEncoder;
+import androidx.camera.extensions.impl.advanced.BaseAdvancedExtenderImpl.BaseAdvancedSessionProcessor;
+
+import static  androidx.camera.extensions.impl.advanced.JpegEncoder.JPEG_DEFAULT_QUALITY;
+import static androidx.camera.extensions.impl.advanced.JpegEncoder.JPEG_DEFAULT_ROTATION;
+
+import android.annotation.SuppressLint;
+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.MeteringRectangle;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageWriter;
+import android.util.Log;
+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 java.io.Closeable;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressLint("UnknownNullness")
+public class HdrAdvancedExtenderImpl extends BaseAdvancedExtenderImpl {
+
+    public HdrAdvancedExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap) {
+        CameraCharacteristics cameraCharacteristics = characteristicsMap.get(cameraId);
+
+        if (cameraCharacteristics == null) {
+            return false;
+        }
+
+        boolean zoomRatioSupported =
+            CameraCharacteristicAvailability.supportsZoomRatio(cameraCharacteristics);
+        boolean hasFocuser =
+            CameraCharacteristicAvailability.hasFocuser(cameraCharacteristics);
+
+        // Requires API 23 for ImageWriter
+        return (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) &&
+                zoomRatioSupported && hasFocuser;
+    }
+
+    public class HDRAdvancedSessionProcessor extends BaseAdvancedSessionProcessor {
+        protected static final int UNDER_EXPOSED_CAPTURE_ID = 0;
+        protected static final int NORMAL_EXPOSED_CAPTURE_ID = 1;
+        protected static final int OVER_EXPOSED_CAPTURE_ID = 2;
+
+        List<Integer> mCaptureIdsList = List.of(UNDER_EXPOSED_CAPTURE_ID,
+                NORMAL_EXPOSED_CAPTURE_ID, OVER_EXPOSED_CAPTURE_ID);
+
+        public HDRAdvancedSessionProcessor() {
+            appendTag("::HDR");
+        }
+
+        @Override
+        protected void addCaptureRequestParameters(List<RequestProcessorImpl.Request> requestList) {
+            // Under exposed capture
+            RequestBuilder builderUnder = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, UNDER_EXPOSED_CAPTURE_ID);
+            // Turn off AE so that ISO sensitivity can be controlled
+            builderUnder.setParameters(CaptureRequest.CONTROL_AE_MODE,
+                    CaptureRequest.CONTROL_AE_MODE_OFF);
+            builderUnder.setParameters(CaptureRequest.SENSOR_EXPOSURE_TIME,
+                    TimeUnit.MILLISECONDS.toNanos(8));
+            applyParameters(builderUnder);
+
+            // Normal exposed capture
+            RequestBuilder builderNormal = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, NORMAL_EXPOSED_CAPTURE_ID);
+            builderNormal.setParameters(CaptureRequest.SENSOR_EXPOSURE_TIME,
+                    TimeUnit.MILLISECONDS.toNanos(16));
+            applyParameters(builderNormal);
+
+            // Over exposed capture
+            RequestBuilder builderOver = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, OVER_EXPOSED_CAPTURE_ID);
+            builderOver.setParameters(CaptureRequest.SENSOR_EXPOSURE_TIME,
+                    TimeUnit.MILLISECONDS.toNanos(32));
+            applyParameters(builderOver);
+
+            requestList.add(builderUnder.build());
+            requestList.add(builderNormal.build());
+            requestList.add(builderOver.build());
+        }
+
+        @Override
+        public int startCapture(@NonNull CaptureCallback captureCallback) {
+            List<RequestProcessorImpl.Request> requestList = new ArrayList<>();
+            addCaptureRequestParameters(requestList);
+            final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+                boolean mCaptureStarted = false;
+
+                @Override
+                public void onCaptureStarted(RequestProcessorImpl.Request request,
+                        long frameNumber, long timestamp) {
+                    if (!mCaptureStarted) {
+                        mCaptureStarted = true;
+                        captureCallback.onCaptureStarted(seqId, timestamp);
+                    }
+                }
+
+                @Override
+                public void onCaptureProgressed(RequestProcessorImpl.Request request,
+                        CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(RequestProcessorImpl.Request request,
+                        TotalCaptureResult totalCaptureResult) {
+                    RequestBuilder.RequestProcessorRequest requestProcessorRequest =
+                            (RequestBuilder.RequestProcessorRequest) request;
+
+                    mImageCaptureCaptureResultImageMatcher.setCameraCaptureCallback(
+                            totalCaptureResult,
+                            requestProcessorRequest.getCaptureStageId());
+                }
+
+                @Override
+                public void onCaptureFailed(RequestProcessorImpl.Request request,
+                        CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(RequestProcessorImpl.Request request,
+                        long frameNumber, int outputStreamId) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+                    captureCallback.onCaptureSequenceCompleted(seqId);
+                    captureCallback.onCaptureProcessProgressed(100);
+                }
+
+                @Override
+                public void onCaptureSequenceAborted(int sequenceId) {
+                    captureCallback.onCaptureSequenceAborted(seqId);
+                }
+            };
+
+            Log.d(TAG, "startCapture");
+
+            mRequestProcessor.submit(requestList, callback);
+
+            if (mCaptureOutputSurfaceConfig.getSurface() != null) {
+                mRequestProcessor.setImageProcessor(mCaptureOutputConfig.getId(),
+                        new ImageProcessorImpl() {
+                            boolean mCaptureStarted = false;
+                                @Override
+                                public void onNextImageAvailable(int outputStreamId,
+                                        long timestampNs,
+                                        @NonNull ImageReferenceImpl imgReferenceImpl,
+                                        @Nullable String physicalCameraId) {
+                                    mImageCaptureCaptureResultImageMatcher
+                                            .setInputImage(imgReferenceImpl);
+
+                                    if (!mCaptureStarted) {
+                                        mCaptureStarted = true;
+                                        captureCallback.onCaptureProcessStarted(seqId);
+                                    }
+                                }
+                });
+
+                mImageCaptureCaptureResultImageMatcher.setImageReferenceListener(
+                        new CaptureResultImageMatcher.ImageReferenceListener() {
+                                    @Override
+                                    public void onImageReferenceIncoming(
+                                            @NonNull ImageReferenceImpl imageReferenceImpl,
+                                            @NonNull TotalCaptureResult totalCaptureResult,
+                                            int captureId) {
+                                        processImageCapture(imageReferenceImpl, totalCaptureResult,
+                                                captureId, seqId, captureCallback);
+                                    }
+                });
+            }
+
+            return seqId;
+        }
+
+        private void processImageCapture(@NonNull ImageReferenceImpl imageReferenceImpl,
+            @NonNull TotalCaptureResult totalCaptureResult,
+            int captureId,
+            int seqId,
+            @NonNull CaptureCallback captureCallback) {
+
+            mCaptureResults.put(captureId, new Pair<>(imageReferenceImpl, totalCaptureResult));
+
+            if (mCaptureResults.keySet().containsAll(mCaptureIdsList)) {
+                List<Pair<ImageReferenceImpl, TotalCaptureResult>> imageDataPairs =
+                        new ArrayList<>(mCaptureResults.values());
+
+                Image resultImage = null;
+                int captureSurfaceWriterImageFormat = ImageFormat.UNKNOWN;
+                synchronized (mLockCaptureSurfaceImageWriter) {
+                    resultImage = mCaptureSurfaceImageWriter.dequeueInputImage();
+                    captureSurfaceWriterImageFormat = mCaptureSurfaceImageWriter.getFormat();
+                }
+
+                if (captureSurfaceWriterImageFormat == ImageFormat.JPEG) {
+                    Image yuvImage = imageDataPairs.get(NORMAL_EXPOSED_CAPTURE_ID).first.get();
+
+                    Integer jpegOrientation = JPEG_DEFAULT_ROTATION;
+
+                    synchronized (mLock) {
+                        if (mParameters.get(CaptureRequest.JPEG_ORIENTATION) != null) {
+                            jpegOrientation =
+                                    (Integer) mParameters.get(CaptureRequest.JPEG_ORIENTATION);
+                        }
+                    }
+
+                    JpegEncoder.encodeToJpeg(yuvImage, resultImage, jpegOrientation,
+                            JPEG_DEFAULT_QUALITY);
+
+                    addCaptureResultKeys(seqId, imageDataPairs.get(UNDER_EXPOSED_CAPTURE_ID)
+                            .second, captureCallback);
+                    resultImage.setTimestamp(imageDataPairs.get(UNDER_EXPOSED_CAPTURE_ID)
+                            .first.get().getTimestamp());
+
+                } else {
+                    ByteBuffer yByteBuffer = resultImage.getPlanes()[0].getBuffer();
+                    ByteBuffer uByteBuffer = resultImage.getPlanes()[2].getBuffer();
+                    ByteBuffer vByteBuffer = resultImage.getPlanes()[1].getBuffer();
+
+                    yByteBuffer.put(imageDataPairs.get(
+                        NORMAL_EXPOSED_CAPTURE_ID).first.get().getPlanes()[0].getBuffer());
+                    uByteBuffer.put(imageDataPairs.get(
+                        NORMAL_EXPOSED_CAPTURE_ID).first.get().getPlanes()[2].getBuffer());
+                    vByteBuffer.put(imageDataPairs.get(
+                        NORMAL_EXPOSED_CAPTURE_ID).first.get().getPlanes()[1].getBuffer());
+
+                    addCaptureResultKeys(seqId, imageDataPairs.get(UNDER_EXPOSED_CAPTURE_ID)
+                            .second, captureCallback);
+                    resultImage.setTimestamp(imageDataPairs.get(
+                        UNDER_EXPOSED_CAPTURE_ID).first.get().getTimestamp());
+                }
+
+                synchronized (mLockCaptureSurfaceImageWriter) {
+                    mCaptureSurfaceImageWriter.queueInputImage(resultImage);
+                }
+
+                for (Pair<ImageReferenceImpl, TotalCaptureResult> val : mCaptureResults.values()) {
+                    val.first.decrement();
+                }
+
+                mCaptureResults.clear();
+            } else {
+                Log.w(TAG, "Unable to process, waiting for all images");
+            }
+        }
+    }
+
+    @Override
+    public SessionProcessorImpl createSessionProcessor() {
+        return new HDRAdvancedSessionProcessor();
+    }
+
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        final CaptureRequest.Key [] CAPTURE_REQUEST_SET = {CaptureRequest.CONTROL_ZOOM_RATIO,
+            CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_REGIONS,
+            CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.JPEG_QUALITY,
+            CaptureRequest.JPEG_ORIENTATION};
+        return Arrays.asList(CAPTURE_REQUEST_SET);
+    }
+
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        final CaptureResult.Key [] CAPTURE_RESULT_SET = {CaptureResult.CONTROL_ZOOM_RATIO,
+            CaptureResult.CONTROL_AF_MODE, CaptureResult.CONTROL_AF_REGIONS,
+            CaptureResult.CONTROL_AF_TRIGGER, CaptureResult.CONTROL_AF_STATE,
+            CaptureResult.JPEG_QUALITY, CaptureResult.JPEG_ORIENTATION};
+        return Arrays.asList(CAPTURE_RESULT_SET);
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
new file mode 100644
index 0000000..037e947
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+
+/**
+ * A interface to receive and process the upcoming next available Image.
+ *
+ * <p>Implemented by OEM.
+ */
+@SuppressLint("UnknownNullness")
+public interface ImageProcessorImpl {
+    /**
+     * The reference count will not be decremented when this method returns. Extensions must
+     * decrement it when the image is no longer needed.
+     *
+     * <p>If OEM is not closing(decrement) the image fast enough, the imageReference passed
+     * in this method might contain null image meaning that the Image was closed to prevent
+     * preview from stalling.
+     *
+     * @param outputConfigId the id of {@link Camera2OutputConfigImpl} which identifies
+     *                       corresponding Surface
+     * @param timestampNs    the timestamp in nanoseconds associated with this image
+     * @param imageReference A reference to the {@link android.media.Image} which might contain
+     *                       null if OEM close(decrement) the image too slowly
+     * @param physicalCameraId used to distinguish which physical camera id the image comes from
+     *                         when the output configuration is
+     *                         MultiResolutionImageReaderOutputConfigImpl. It is also set if
+     *                         physicalCameraId is set in other Camera2OutputConfigImpl types.
+     *
+     */
+    void onNextImageAvailable(
+            int outputConfigId,
+            long timestampNs,
+            ImageReferenceImpl imageReference,
+            String physicalCameraId
+            );
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
new file mode 100644
index 0000000..ca4dcaf
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.util.Size;
+
+/**
+ * Surface will be created by constructing a ImageReader.
+ */
+@SuppressLint("UnknownNullness")
+public interface ImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
+    /**
+     * Returns the size of the surface.
+     */
+    Size getSize();
+
+    /**
+     * Gets the image format of the surface.
+     */
+    int getImageFormat();
+
+    /**
+     * Gets the capacity for TYPE_IMAGEREADER.
+     */
+    int getMaxImages();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
new file mode 100644
index 0000000..95f2c3b
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.media.Image;
+
+/**
+ * A Image reference container that enables the Image sharing between Camera2/CameraX and OEM
+ * using reference counting. The wrapped Image will be closed once the reference count
+ * reaches 0.
+ *
+ * <p>Implemented by Camera2/CameraX.
+ */
+@SuppressLint("UnknownNullness")
+public interface ImageReferenceImpl {
+
+    /**
+     * Increment the reference count. Returns true if the value was incremented.
+     * (returns false if the reference count has already reached zero.)
+     */
+    boolean increment();
+
+    /**
+     * Decrement the reference count. Image will be closed if reference count reaches 0.
+     * Returns true if the value was decremented (returns false if the reference count has
+     * already reached zero)
+     */
+    boolean decrement();
+
+    /**
+     * Return the Android image. This object MUST not be closed directly.
+     * Returns null when the reference count is zero.
+     */
+    Image get();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/JpegEncoder.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/JpegEncoder.java
new file mode 100644
index 0000000..f3eb7ea
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/JpegEncoder.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 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.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageWriter;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+// Jpeg compress input YUV and queue back in the client target surface.
+public class JpegEncoder {
+
+    public final static int JPEG_DEFAULT_QUALITY = 100;
+    public final static int JPEG_DEFAULT_ROTATION = 0;
+    public static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
+
+    /**
+     * Compresses a YCbCr image to jpeg, applying a crop and rotation.
+     * <p>
+     * The input is defined as a set of 3 planes of 8-bit samples, one plane for
+     * each channel of Y, Cb, Cr.<br>
+     * The Y plane is assumed to have the same width and height of the entire
+     * image.<br>
+     * The Cb and Cr planes are assumed to be downsampled by a factor of 2, to
+     * have dimensions (floor(width / 2), floor(height / 2)).<br>
+     * Each plane is specified by a direct java.nio.ByteBuffer, a pixel-stride,
+     * and a row-stride. So, the sample at coordinate (x, y) can be retrieved
+     * from byteBuffer[x * pixel_stride + y * row_stride].
+     * <p>
+     * The pre-compression transformation is applied as follows:
+     * <ol>
+     * <li>The image is cropped to the rectangle from (cropLeft, cropTop) to
+     * (cropRight - 1, cropBottom - 1). So, a cropping-rectangle of (0, 0) -
+     * (width, height) is a no-op.</li>
+     * <li>The rotation is applied counter-clockwise relative to the coordinate
+     * space of the image, so a CCW rotation will appear CW when the image is
+     * rendered in scanline order. Only rotations which are multiples of
+     * 90-degrees are suppored, so the parameter 'rot90' specifies which
+     * multiple of 90 to rotate the image.</li>
+     * </ol>
+     *
+     * @param width          the width of the image to compress
+     * @param height         the height of the image to compress
+     * @param yBuf           the buffer containing the Y component of the image
+     * @param yPStride       the stride between adjacent pixels in the same row in
+     *                       yBuf
+     * @param yRStride       the stride between adjacent rows in yBuf
+     * @param cbBuf          the buffer containing the Cb component of the image
+     * @param cbPStride      the stride between adjacent pixels in the same row in
+     *                       cbBuf
+     * @param cbRStride      the stride between adjacent rows in cbBuf
+     * @param crBuf          the buffer containing the Cr component of the image
+     * @param crPStride      the stride between adjacent pixels in the same row in
+     *                       crBuf
+     * @param crRStride      the stride between adjacent rows in crBuf
+     * @param outBuf         a direct java.nio.ByteBuffer to hold the compressed jpeg.
+     *                       This must have enough capacity to store the result, or an
+     *                       error code will be returned.
+     * @param outBufCapacity the capacity of outBuf
+     * @param quality        the jpeg-quality (1-100) to use
+     * @param cropLeft       left-edge of the bounds of the image to crop to before
+     *                       rotation
+     * @param cropTop        top-edge of the bounds of the image to crop to before
+     *                       rotation
+     * @param cropRight      right-edge of the bounds of the image to crop to before
+     *                       rotation
+     * @param cropBottom     bottom-edge of the bounds of the image to crop to
+     *                       before rotation
+     * @param rot90          the multiple of 90 to rotate the image CCW (after cropping)
+     */
+    public static native int compressJpegFromYUV420pNative(
+            int width, int height,
+            ByteBuffer yBuf, int yPStride, int yRStride,
+            ByteBuffer cbBuf, int cbPStride, int cbRStride,
+            ByteBuffer crBuf, int crPStride, int crRStride,
+            ByteBuffer outBuf, int outBufCapacity,
+            int quality,
+            int cropLeft, int cropTop, int cropRight, int cropBottom,
+            int rot90);
+
+    public static void encodeToJpeg(Image yuvImage, Image jpegImage,
+            int jpegOrientation, int jpegQuality) {
+
+        jpegOrientation =  (360 - (jpegOrientation % 360)) / 90;
+        ByteBuffer jpegBuffer = jpegImage.getPlanes()[0].getBuffer();
+
+        jpegBuffer.clear();
+
+        int jpegCapacity = jpegImage.getWidth();
+
+        Plane lumaPlane = yuvImage.getPlanes()[0];
+
+        Plane crPlane = yuvImage.getPlanes()[1];
+        Plane cbPlane = yuvImage.getPlanes()[2];
+
+        JpegEncoder.compressJpegFromYUV420pNative(
+            yuvImage.getWidth(), yuvImage.getHeight(),
+            lumaPlane.getBuffer(), lumaPlane.getPixelStride(), lumaPlane.getRowStride(),
+            crPlane.getBuffer(), crPlane.getPixelStride(), crPlane.getRowStride(),
+            cbPlane.getBuffer(), cbPlane.getPixelStride(), cbPlane.getRowStride(),
+            jpegBuffer, jpegCapacity, jpegQuality,
+            0, 0, yuvImage.getWidth(), yuvImage.getHeight(),
+            jpegOrientation);
+    }
+
+    public static int imageFormatToPublic(int format) {
+        switch (format) {
+            case HAL_PIXEL_FORMAT_BLOB:
+                return ImageFormat.JPEG;
+            case ImageFormat.JPEG:
+                throw new IllegalArgumentException(
+                        "ImageFormat.JPEG is an unknown internal format");
+            default:
+                return format;
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
new file mode 100644
index 0000000..c3ad61b
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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;
+
+/**
+ * Surface will be created by constructing a MultiResolutionImageReader.
+ */
+public interface MultiResolutionImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
+    /**
+     * Gets the image format of the surface.
+     */
+    int getImageFormat();
+
+    /**
+     * Gets the max images of the ImageReader.
+     */
+    int getMaxImages();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
new file mode 100644
index 0000000..8294063
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2021 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 androidx.camera.extensions.impl.advanced.BaseAdvancedExtenderImpl.BaseAdvancedSessionProcessor;
+
+import android.annotation.SuppressLint;
+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.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageWriter;
+import android.os.Build;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+@SuppressLint("UnknownNullness")
+public class NightAdvancedExtenderImpl extends BaseAdvancedExtenderImpl {
+
+    protected static final int AWB_MODE_INCANDESCENT = CaptureRequest.CONTROL_AWB_MODE_INCANDESCENT;
+
+    public NightAdvancedExtenderImpl() {
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap) {
+        // Requires API 23 for ImageWriter
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        CameraCharacteristics cameraCharacteristics = characteristicsMap.get(cameraId);
+
+        if (cameraCharacteristics == null) {
+            return false;
+        }
+
+        return CameraCharacteristicAvailability.isWBModeAvailable(cameraCharacteristics,
+                AWB_MODE_INCANDESCENT);
+    }
+
+    public class NightAdvancedSessionProcessor extends BaseAdvancedSessionProcessor {
+
+        public NightAdvancedSessionProcessor() {
+            appendTag("::Night");
+        }
+
+        protected final Object mLockPreviewSurfaceImageWriter = new Object();
+        @GuardedBy("mLockPreviewSurfaceImageWriter")
+        private ImageWriter mPreviewSurfaceImageWriter;
+
+        CaptureResultImageMatcher mCaptureResultImageMatcher =
+                new CaptureResultImageMatcher();
+
+        @Override
+        @NonNull
+        public Camera2SessionConfigImpl initSession(@NonNull String cameraId,
+                @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+                @NonNull Context context,
+                @NonNull OutputSurfaceConfigurationImpl surfaceConfigs) {
+
+            Log.d(TAG, "initSession cameraId=" + cameraId);
+
+            mPreviewOutputSurfaceConfig = surfaceConfigs.getPreviewOutputSurface();
+            mCaptureOutputSurfaceConfig = surfaceConfigs.getImageCaptureOutputSurface();
+
+            Camera2SessionConfigImplBuilder builder =
+                    new Camera2SessionConfigImplBuilder()
+                    .setSessionTemplateId(CameraDevice.TEMPLATE_PREVIEW);
+
+            // Preview
+            if (mPreviewOutputSurfaceConfig.getSurface() != null) {
+                Camera2OutputConfigImplBuilder previewOutputConfigBuilder;
+
+                previewOutputConfigBuilder =
+                        Camera2OutputConfigImplBuilder.newImageReaderConfig(
+                                mPreviewOutputSurfaceConfig.getSize(),
+                                ImageFormat.YUV_420_888,
+                                BASIC_CAPTURE_PROCESS_MAX_IMAGES);
+
+                mPreviewOutputConfig = previewOutputConfigBuilder.build();
+
+                builder.addOutputConfig(mPreviewOutputConfig);
+            }
+
+            // Image Capture
+            if (mCaptureOutputSurfaceConfig.getSurface() != null) {
+                Camera2OutputConfigImplBuilder captureOutputConfigBuilder;
+
+                captureOutputConfigBuilder =
+                        Camera2OutputConfigImplBuilder.newImageReaderConfig(
+                                mCaptureOutputSurfaceConfig.getSize(),
+                                ImageFormat.YUV_420_888,
+                                BASIC_CAPTURE_PROCESS_MAX_IMAGES);
+
+                mCaptureOutputConfig = captureOutputConfigBuilder.build();
+
+                builder.addOutputConfig(mCaptureOutputConfig);
+            }
+
+            addSessionParameter(builder);
+
+            return builder.build();
+        }
+
+        @Override
+        protected void addSessionParameter(Camera2SessionConfigImplBuilder builder) {
+            builder.addSessionParameter(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_INCANDESCENT);
+        }
+
+        @Override
+        public void deInitSession() {
+            super.deInitSession();
+
+            synchronized (mLockPreviewSurfaceImageWriter) {
+                if (mPreviewSurfaceImageWriter != null) {
+                    mPreviewSurfaceImageWriter.close();
+                    mPreviewSurfaceImageWriter = null;
+                }
+            }
+        }
+
+        @Override
+        public void onCaptureSessionStart(@NonNull RequestProcessorImpl requestProcessor) {
+            super.onCaptureSessionStart(requestProcessor);
+
+            if (mPreviewOutputSurfaceConfig.getSurface() != null) {
+                synchronized (mLockPreviewSurfaceImageWriter) {
+                    mPreviewSurfaceImageWriter = new ImageWriter
+                            .Builder(mPreviewOutputSurfaceConfig.getSurface())
+                            .setMaxImages(MAX_NUM_IMAGES)
+                            .build();
+                }
+            }
+
+            if (mPreviewOutputSurfaceConfig.getSurface() != null) {
+                requestProcessor.setImageProcessor(mPreviewOutputConfig.getId(),
+                        new ImageProcessorImpl() {
+                            @Override
+                            public void onNextImageAvailable(int outputStreamId, long timestampNs,
+                                    @NonNull ImageReferenceImpl imageReferenceImpl,
+                                    @Nullable String physicalCameraId) {
+                                mCaptureResultImageMatcher.setInputImage(imageReferenceImpl);
+                            }
+                        });
+
+                mCaptureResultImageMatcher.setImageReferenceListener(
+                    new CaptureResultImageMatcher.ImageReferenceListener() {
+                                @Override
+                                public void onImageReferenceIncoming(
+                                        @NonNull ImageReferenceImpl imageReferenceImpl,
+                                        @NonNull TotalCaptureResult totalCaptureResult,
+                                        int captureId) {
+                                    processCapture(imageReferenceImpl);
+                                }
+                });
+            }
+        }
+
+        private void processCapture(@NonNull ImageReferenceImpl imageReferenceImpl) {
+            synchronized (mLockPreviewSurfaceImageWriter) {
+                mPreviewSurfaceImageWriter.queueInputImage(imageReferenceImpl.get());
+            }
+
+            imageReferenceImpl.decrement();
+        }
+
+        @Override
+        public void onCaptureSessionEnd() {
+            super.onCaptureSessionEnd();
+
+            synchronized (this) {
+                mCaptureResultImageMatcher.clear();
+            }
+        }
+
+        @Override
+        public int startRepeating(@NonNull CaptureCallback captureCallback) {
+            RequestBuilder builder = new RequestBuilder(mPreviewOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_PREVIEW, 0);
+            applyParameters(builder);
+            final int seqId = mNextCaptureSequenceId.getAndIncrement();
+
+            RequestProcessorImpl.Callback callback = new RequestProcessorImpl.Callback() {
+                @Override
+                public void onCaptureStarted(RequestProcessorImpl.Request request, long frameNumber,
+                        long timestamp) {
+                    captureCallback.onCaptureStarted(seqId, timestamp);
+                }
+
+                @Override
+                public void onCaptureProgressed(RequestProcessorImpl.Request request,
+                        CaptureResult partialResult) {
+
+                }
+
+                @Override
+                public void onCaptureCompleted(RequestProcessorImpl.Request request,
+                        TotalCaptureResult totalCaptureResult) {
+
+                    addCaptureResultKeys(seqId, totalCaptureResult, captureCallback);
+
+                    mCaptureResultImageMatcher.setCameraCaptureCallback(
+                                totalCaptureResult);
+
+                    captureCallback.onCaptureProcessStarted(seqId);
+                }
+
+                @Override
+                public void onCaptureFailed(RequestProcessorImpl.Request request,
+                        CaptureFailure captureFailure) {
+                    captureCallback.onCaptureFailed(seqId);
+                }
+
+                @Override
+                public void onCaptureBufferLost(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
+        protected void addCaptureRequestParameters(List<RequestProcessorImpl.Request> requestList) {
+            RequestBuilder build = new RequestBuilder(mCaptureOutputConfig.getId(),
+                    CameraDevice.TEMPLATE_STILL_CAPTURE, DEFAULT_CAPTURE_ID);
+            build.setParameters(CaptureRequest.CONTROL_AWB_MODE, AWB_MODE_INCANDESCENT);
+            applyParameters(build);
+
+            requestList.add(build.build());
+        }
+    }
+
+    @Override
+    public SessionProcessorImpl createSessionProcessor() {
+        return new NightAdvancedSessionProcessor();
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java
new file mode 100644
index 0000000..ca3832e
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 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.annotation.SuppressLint;
+
+/**
+ * For specifying the output surface configurations for the extension.
+ *
+ * since 1.4
+ */
+@SuppressLint("UnknownNullness")
+public interface OutputSurfaceConfigurationImpl {
+    public OutputSurfaceImpl getPreviewOutputSurface();
+
+    public OutputSurfaceImpl getImageCaptureOutputSurface();
+
+    public OutputSurfaceImpl getImageAnalysisOutputSurface();
+
+    public OutputSurfaceImpl getPostviewOutputSurface();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
new file mode 100644
index 0000000..f692029
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.util.Size;
+import android.view.Surface;
+
+/**
+ * For specifying output surface of the extension.
+ */
+@SuppressLint("UnknownNullness")
+public interface OutputSurfaceImpl {
+    /**
+     * Gets the surface.
+     */
+    Surface getSurface();
+
+    /**
+     * Gets the size.
+     */
+    Size getSize();
+
+    /**
+     * Gets the image format.
+     */
+    int getImageFormat();
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/RequestBuilder.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/RequestBuilder.java
new file mode 100644
index 0000000..ccb4ec8
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/RequestBuilder.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 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;
+
+public class RequestBuilder {
+    List<Integer> mTargetOutputConfigIds = new ArrayList<>();
+    Map<CaptureRequest.Key<?>, Object> mParameters = new HashMap<>();
+    int mTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+    int mCaptureStageId;
+
+    public RequestBuilder() {
+    }
+
+    public RequestBuilder(int targetOutputConfigId, int templateId, int captureStageId) {
+        addTargetOutputConfigIds(targetOutputConfigId);
+        setTemplateId(templateId);
+        setCaptureStageId(captureStageId);
+    }
+
+    @NonNull
+    public RequestBuilder addTargetOutputConfigIds(int targetOutputConfigId) {
+        mTargetOutputConfigIds.add(targetOutputConfigId);
+        return this;
+    }
+
+    @NonNull
+    public RequestBuilder setParameters(@NonNull CaptureRequest.Key<?> key,
+            @NonNull Object value) {
+        mParameters.put(key, value);
+        return this;
+    }
+
+    @NonNull
+    public RequestBuilder setTemplateId(int templateId) {
+        mTemplateId = templateId;
+        return this;
+    }
+
+    @NonNull
+    public RequestBuilder setCaptureStageId(int captureStageId) {
+        mCaptureStageId = captureStageId;
+        return this;
+    }
+
+    @NonNull
+    public RequestProcessorImpl.Request build() {
+        return new RequestProcessorRequest(
+                mTargetOutputConfigIds, mParameters, mTemplateId, mCaptureStageId);
+    }
+
+    static class RequestProcessorRequest implements RequestProcessorImpl.Request {
+        final List<Integer> mTargetOutputConfigIds;
+        final Map<CaptureRequest.Key<?>, Object> mParameters;
+        final int mTemplateId;
+        final int mCaptureStageId;
+
+        RequestProcessorRequest(List<Integer> targetOutputConfigIds,
+                Map<CaptureRequest.Key<?>, Object> parameters,
+                int templateId,
+                int captureStageId) {
+            mTargetOutputConfigIds = targetOutputConfigIds;
+            mParameters = parameters;
+            mTemplateId = templateId;
+            mCaptureStageId = captureStageId;
+        }
+
+        @Override
+        public List<Integer> getTargetOutputConfigIds() {
+            return mTargetOutputConfigIds;
+        }
+
+        @Override
+        public Map<CaptureRequest.Key<?>, Object> getParameters() {
+            return mParameters;
+        }
+
+        @Override
+        public Integer getTemplateId() {
+            return mTemplateId;
+        }
+
+        public int getCaptureStageId() {
+            return mCaptureStageId;
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
new file mode 100644
index 0000000..5185333
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An Interface to execute Camera2 capture requests.
+ */
+@SuppressLint("UnknownNullness")
+public interface RequestProcessorImpl {
+    /**
+     * Sets a {@link ImageProcessorImpl} to receive {@link ImageReferenceImpl} to process.
+     */
+    void setImageProcessor(int outputconfigId, ImageProcessorImpl imageProcessor);
+
+    /**
+     * Submits a request.
+     * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
+     *         or receives an invalid argument.
+     */
+    int submit(Request request, Callback callback);
+
+    /**
+     * Submits a list of requests.
+     * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
+     *         or receives an invalid argument.
+     */
+    int submit(List<Request> requests, Callback callback);
+
+    /**
+     * Set repeating requests.
+     * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
+     *         or receives an invalid argument.
+     */
+    int setRepeating(Request request, Callback callback);
+
+
+    /**
+     * Abort captures.
+     */
+    void abortCaptures();
+
+    /**
+     * Stop Repeating.
+     */
+    void stopRepeating();
+
+    /**
+     * A interface representing a capture request configuration used for submitting requests in
+     * {@link RequestProcessorImpl}.
+     */
+    interface Request {
+        /**
+         * Gets the target ids of {@link Camera2OutputConfigImpl} which identifies corresponding
+         * Surface to be the targeted for the request.
+         */
+        List<Integer> getTargetOutputConfigIds();
+
+        /**
+         * Gets all the parameters.
+         */
+        Map<CaptureRequest.Key<?>, Object> getParameters();
+
+        /**
+         * Gets the template id.
+         */
+        Integer getTemplateId();
+    }
+
+    /**
+     * Callback to be invoked during the capture.
+     */
+    interface Callback {
+        void onCaptureStarted(
+                Request request,
+                long frameNumber,
+                long timestamp);
+
+        void onCaptureProgressed(
+                Request request,
+                CaptureResult partialResult);
+
+        void onCaptureCompleted(
+                Request request,
+                TotalCaptureResult totalCaptureResult);
+
+        void onCaptureFailed(
+                Request request,
+                CaptureFailure captureFailure);
+
+        void onCaptureBufferLost(
+                Request request,
+                long frameNumber,
+                int outputStreamId);
+
+        void onCaptureSequenceCompleted(int sequenceId, long frameNumber);
+
+        void onCaptureSequenceAborted(int sequenceId);
+
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
new file mode 100644
index 0000000..8dbfadc
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.view.Surface;
+
+import java.util.Map;
+
+/**
+ * Interface for creating Camera2 CameraCaptureSessions with extension enabled based on
+ * advanced vendor implementation.
+ *
+ * <p><pre>
+ * The flow of a extension session is shown below:
+ * (1) {@link #initSession}: CameraX prepares streams configuration for creating
+ *     CameraCaptureSession. Output surfaces for Preview, ImageCapture and ImageAnalysis are passed
+ *     in and vendor is responsible for outputting the results to these surfaces.
+ *
+ * (2) {@link #onCaptureSessionStart}: It is called after CameraCaptureSession is configured.
+ *     A {@link RequestProcessorImpl} is passed for vendor to send repeating requests and
+ *     single requests.
+ *
+ * (3) {@link #startRepeating}:  CameraX will call this method to start the repeating request
+ *     after CameraCaptureSession is called. Vendor should start the repeating request by
+ *     {@link RequestProcessorImpl}. Vendor can also update the repeating request if needed later.
+ *
+ * (4) {@link #setParameters(Map)}: The passed parameters will be attached to the repeating request
+ *     and single requests but vendor can choose to apply some of them only.
+ *
+ * (5) {@link #startCapture(CaptureCallback)}: It is called when apps want to
+ *     start a multi-frame image capture.  {@link CaptureCallback} will be called
+ *     to report the status and the output image will be written to the capture output surface
+ *     specified in {@link #initSession}.
+ *
+ * (5) {@link #onCaptureSessionEnd}: It is called right BEFORE CameraCaptureSession.close() is
+ *     called.
+ *
+ * (6) {@link #deInitSession}: called when CameraCaptureSession is closed.
+ * </pre>
+ */
+@SuppressLint("UnknownNullness")
+public interface SessionProcessorImpl {
+    /**
+     * Initializes the session for the extension. This is where the OEMs allocate resources for
+     * preparing a CameraCaptureSession. After initSession() is called, the camera ID,
+     * cameraCharacteristics and context will not change until deInitSession() has been called.
+     *
+     * <p>CameraX / Camera2 specifies the output surface configurations for preview using
+     * {@link OutputSurfaceConfigurationImpl#getPreviewOutputSurface}, image capture using
+     * {@link OutputSurfaceConfigurationImpl#getImageCaptureOutputSurface}, and image analysis
+     * [optional] using {@link OutputSurfaceConfigurationImpl#getImageAnalysisOutputSurface}.
+     * And OEM returns a {@link Camera2SessionConfigImpl} which consists of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters. The {@link Camera2SessionConfigImpl}
+     * will be used to configure the CameraCaptureSession.
+     *
+     * <p>OEM is responsible for outputting correct camera images output to these output surfaces.
+     * OEM can have the following options to enable the output:
+     * <pre>
+     * (1) Add these output surfaces in CameraCaptureSession directly using
+     * {@link Camera2OutputConfigImplBuilder#newSurfaceConfig(Surface)} }. Processing is done in
+     * HAL.
+     *
+     * (2) Use surface sharing with other surface by calling
+     * {@link Camera2OutputConfigImplBuilder#addSurfaceSharingOutputConfig(Camera2OutputConfigImpl)}
+     * to add the output surface to the other {@link Camera2OutputConfigImpl}.
+     *
+     * (3) Process output from other surfaces (RAW, YUV..) and write the result to the output
+     * surface. The output surface won't be contained in the returned
+     * {@link Camera2SessionConfigImpl}.
+     * </pre>
+     *
+     * <p>{@link Camera2OutputConfigImplBuilder} and {@link Camera2SessionConfigImplBuilder}
+     * implementations are provided in the stub for OEM to construct the
+     * {@link Camera2OutputConfigImpl} and {@link Camera2SessionConfigImpl} instances.
+     *
+     * @param surfaceConfigs contains output surfaces for preview, image capture, and an
+     *                       optional output config for image analysis (YUV_420_888).
+     * @return a {@link Camera2SessionConfigImpl} consisting of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters which will decide the
+     * {@link android.hardware.camera2.params.SessionConfiguration} for configuring the
+     * CameraCaptureSession. Please note that the OutputConfiguration list may not be part of any
+     * supported or mandatory stream combination BUT OEM must ensure this list will always
+     * produce a valid camera capture session.
+     *
+     * @since 1.4
+     */
+    Camera2SessionConfigImpl initSession(
+            String cameraId,
+            Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            Context context,
+            OutputSurfaceConfigurationImpl surfaceConfigs);
+
+    /**
+     * Initializes the session for the extension. This is where the OEMs allocate resources for
+     * preparing a CameraCaptureSession. After initSession() is called, the camera ID,
+     * cameraCharacteristics and context will not change until deInitSession() has been called.
+     *
+     * <p>CameraX / Camera2 specifies the output surface configurations for preview, image capture
+     * and image analysis[optional]. And OEM returns a {@link Camera2SessionConfigImpl} which
+     * consists of a list of {@link Camera2OutputConfigImpl} and session parameters. The
+     * {@link Camera2SessionConfigImpl} will be used to configure the CameraCaptureSession.
+     *
+     * <p>OEM is responsible for outputting correct camera images output to these output surfaces.
+     * OEM can have the following options to enable the output:
+     * <pre>
+     * (1) Add these output surfaces in CameraCaptureSession directly using
+     * {@link Camera2OutputConfigImplBuilder#newSurfaceConfig(Surface)} }. Processing is done in
+     * HAL.
+     *
+     * (2) Use surface sharing with other surface by calling
+     * {@link Camera2OutputConfigImplBuilder#addSurfaceSharingOutputConfig(Camera2OutputConfigImpl)}
+     * to add the output surface to the other {@link Camera2OutputConfigImpl}.
+     *
+     * (3) Process output from other surfaces (RAW, YUV..) and write the result to the output
+     * surface. The output surface won't be contained in the returned
+     * {@link Camera2SessionConfigImpl}.
+     * </pre>
+     *
+     * <p>{@link Camera2OutputConfigImplBuilder} and {@link Camera2SessionConfigImplBuilder}
+     * implementations are provided in the stub for OEM to construct the
+     * {@link Camera2OutputConfigImpl} and {@link Camera2SessionConfigImpl} instances.
+     *
+     * @param previewSurfaceConfig       output surface for preview
+     * @param imageCaptureSurfaceConfig  output surface for image capture.
+     * @param imageAnalysisSurfaceConfig an optional output config for image analysis
+     *                                   (YUV_420_888).
+     * @return a {@link Camera2SessionConfigImpl} consisting of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters which will decide the
+     * {@link android.hardware.camera2.params.SessionConfiguration} for configuring the
+     * CameraCaptureSession. Please note that the OutputConfiguration list may not be part of any
+     * supported or mandatory stream combination BUT OEM must ensure this list will always
+     * produce a valid camera capture session.
+     */
+    Camera2SessionConfigImpl initSession(
+            String cameraId,
+            Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            Context context,
+            OutputSurfaceImpl previewSurfaceConfig,
+            OutputSurfaceImpl imageCaptureSurfaceConfig,
+            OutputSurfaceImpl imageAnalysisSurfaceConfig);
+
+    /**
+     * Notify to de-initialize the extension. This callback will be invoked after
+     * CameraCaptureSession is closed. After onDeInit() was called, it is expected that the
+     * camera ID, cameraCharacteristics will no longer hold and tear down any resources allocated
+     * for this extension. Aborts all pending captures.
+     */
+    void deInitSession();
+
+    /**
+     * CameraX / Camera2 would call these API’s to pass parameters from the app to the OEM. It’s
+     * expected that the OEM would (eventually) update the repeating request if the keys are
+     * supported. Setting a value to null explicitly un-sets the value.
+     */
+    void setParameters(Map<CaptureRequest.Key<?>, Object> parameters);
+
+    /**
+     * CameraX / Camera2 will call this interface in response to client requests involving
+     * the output preview surface. Typical examples include requests that include AF/AE triggers.
+     * Extensions can disregard any capture request keys that were not advertised in
+     * {@link AdvancedExtenderImpl#getAvailableCaptureRequestKeys}.
+     *
+     * @param triggers Capture request key value map.
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     *
+     * @throws IllegalArgumentException If there are no valid settings that can be applied
+     *
+     * @since 1.3
+     */
+    int startTrigger(Map<CaptureRequest.Key<?>, Object> triggers, CaptureCallback callback);
+
+    /**
+     * This will be invoked once after the {@link android.hardware.camera2.CameraCaptureSession}
+     * has been created. {@link RequestProcessorImpl} is passed for OEM to submit single
+     * requests or set repeating requests. This ExtensionRequestProcessor will be valid to use
+     * until onCaptureSessionEnd is called.
+     */
+    void onCaptureSessionStart(RequestProcessorImpl requestProcessor);
+
+    /**
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+     * closed. {@link RequestProcessorImpl} passed in onCaptureSessionStart will no longer
+     * accept any requests after onCaptureSessionEnd() returns.
+     */
+    void onCaptureSessionEnd();
+
+    /**
+     * Starts the repeating request after CameraCaptureSession is called. Vendor should start the
+     * repeating request by {@link RequestProcessorImpl}. Vendor can also update the
+     * repeating request when needed later.
+     *
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     */
+    int startRepeating(CaptureCallback callback);
+
+    /**
+     * Stop the repeating request. To prevent OEM from not calling stopRepeating, CameraX will
+     * first stop the repeating request of current CameraCaptureSession and call this API to signal
+     * OEM that the repeating request was stopped and going forward calling
+     * {@link RequestProcessorImpl#setRepeating} will simply do nothing.
+     */
+    void stopRepeating();
+
+    /**
+     * Start a multi-frame capture with a postview. {@link #startCapture(CaptureCallback)}
+     * will be used for captures without a postview request.
+     *
+     * Postview will be available before the capture. Upon postview completion,
+     * {@code OnImageAvailableListener#onImageAvailable} will be called on the ImageReader
+     * that creates the postview output surface. When the capture is completed,
+     * {@link CaptureCallback#onCaptureSequenceCompleted} is called and
+     * {@code OnImageAvailableListener#onImageAvailable} will also be called on the ImageReader
+     * that creates the image capture output surface.
+     *
+     * <p>Only one capture can perform at a time. Starting a capture when another capture is
+     * running will cause onCaptureFailed to be called immediately.
+     *
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     * @since 1.4
+     */
+    int startCaptureWithPostview(CaptureCallback callback);
+
+    /**
+     * Start a multi-frame capture.
+     *
+     * When the capture is completed, {@link CaptureCallback#onCaptureSequenceCompleted}
+     * is called and {@code OnImageAvailableListener#onImageAvailable}
+     * will also be called on the ImageReader that creates the image capture output surface.
+     *
+     * <p>Only one capture can perform at a time. Starting a capture when another capture is running
+     * will cause onCaptureFailed to be called immediately.
+     *
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     */
+    int startCapture(CaptureCallback callback);
+
+    /**
+     * Abort all capture tasks.
+     */
+    void abortCapture(int captureSequenceId);
+
+    /**
+     * Returns the dynamically calculated capture latency pair in milliseconds.
+     *
+     * <p>In contrast to {@link AdvancedExtenderImpl#getEstimatedCaptureLatencyRange} this method is
+     * guaranteed to be called after {@link #onCaptureSessionStart}.
+     * The measurement is expected to take in to account dynamic parameters such as the current
+     * scene, the state of 3A algorithms, the state of internal HW modules and return a more
+     * accurate assessment of the still capture latency.</p>
+     *
+     * @return pair that includes the estimated input frame/frames camera capture latency as the
+     * first field. This is the time between {@link #onCaptureStarted} and
+     * {@link #onCaptureProcessStarted}. The second field value includes the estimated
+     * post-processing latency. This is the time between {@link #onCaptureProcessStarted} until
+     * the processed frame returns back to the client registered surface.
+     * Both first and second values will be in milliseconds. The total still capture latency will be
+     * the sum of both the first and second values of the pair.
+     * The pair is expected to be null if the dynamic latency estimation is not supported.
+     * If clients have not configured a still capture output, then this method can also return a
+     * null pair.
+     * @since 1.4
+     */
+    Pair<Long, Long> getRealtimeCaptureLatency();
+
+    /**
+     * Callback for notifying the status of {@link #startCapture(CaptureCallback)} and
+     * {@link #startRepeating(CaptureCallback)}.
+     */
+    interface CaptureCallback {
+        /**
+         * This method is called when the camera device has started capturing the initial input
+         * image.
+         *
+         * For a multi-frame capture, the method is called when the
+         * CameraCaptureSession.CaptureCallback onCaptureStarted of first frame is called and its
+         * timestamp is directly forwarded to timestamp parameter of
+         * this method.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         * @param timestamp         the timestamp at start of capture for repeating
+         *                          request or the timestamp at start of capture of the
+         *                          first frame in a multi-frame capture, in nanoseconds.
+         */
+        void onCaptureStarted(int captureSequenceId, long timestamp);
+
+        /**
+         * This method is called when an image (or images in case of multi-frame
+         * capture) is captured and device-specific extension processing is triggered.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureProcessStarted(int captureSequenceId);
+
+        /**
+         * This method is called instead of
+         * {@link #onCaptureProcessStarted} when the camera device failed
+         * to produce the required input for the device-specific extension. The
+         * cause could be a failed camera capture request, a failed
+         * capture result or dropped camera frame.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureFailed(int captureSequenceId);
+
+        /**
+         * This method is called independently of the others in the CaptureCallback, when a capture
+         * sequence finishes.
+         *
+         * <p>In total, there will be at least one
+         * {@link #onCaptureProcessStarted}/{@link #onCaptureFailed}
+         * invocation before this callback is triggered. If the capture
+         * sequence is aborted before any requests have begun processing,
+         * {@link #onCaptureSequenceAborted} is invoked instead.</p>
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureSequenceCompleted(int captureSequenceId);
+
+        /**
+         * This method is called when a capture sequence aborts.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureSequenceAborted(int captureSequenceId);
+
+        /**
+         * Capture result callback that needs to be called when the process capture results are
+         * ready as part of frame post-processing.
+         *
+         * This callback will fire after {@link #onCaptureStarted}, {@link #onCaptureProcessStarted}
+         * and before {@link #onCaptureSequenceCompleted}. The callback is not expected to fire
+         * in case of capture failure  {@link #onCaptureFailed} or capture abort
+         * {@link #onCaptureSequenceAborted}.
+         *
+         * @param timestamp            The timestamp at start of capture. The same timestamp value
+         *                             passed to {@link #onCaptureStarted}.
+         * @param captureSequenceId    the capture id of the request that generated the capture
+         *                             results. This is the return value of either
+         *                             {@link #startRepeating} or {@link #startCapture}.
+         * @param result               Map containing the supported capture results. Do note
+         *                             that if results 'android.jpeg.quality' and
+         *                             'android.jpeg.orientation' are present in the process
+         *                             capture input results, then the values must also be passed
+         *                             as part of this callback. Both Camera2 and CameraX guarantee
+         *                             that those two settings and results are always supported and
+         *                             applied by the corresponding framework.
+         */
+        void onCaptureCompleted(long timestamp, int captureSequenceId,
+                Map<CaptureResult.Key, Object> result);
+
+        /**
+         * Capture progress callback that needs to be called when the process capture is
+         * ongoing and includes the estimated progress of the processing.
+         *
+         * <p>Extensions must ensure that they always call this callback with monotonically
+         * increasing values.</p>
+         *
+         * <p>Extensions are allowed to trigger this callback multiple times but at the minimum the
+         * callback is expected to be called once when processing is done with value 100.</p>
+         *
+         * @param progress             Value between 0 and 100.
+         * @since 1.4
+         */
+        void onCaptureProcessProgressed(int progress);
+    }
+}
diff --git a/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
new file mode 100644
index 0000000..7b8d83c
--- /dev/null
+++ b/camera2/extensions/advancedSample/src/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.view.Surface;
+
+/**
+ * Use Surface directly to create the OutputConfiguration.
+ */
+@SuppressLint("UnknownNullness")
+public interface SurfaceOutputConfigImpl extends Camera2OutputConfigImpl {
+    /**
+     * Get the {@link Surface}. It'll return valid surface only when type is TYPE_SURFACE.
+     */
+    Surface getSurface();
+}
diff --git a/camera2/extensions/jni/Android.bp b/camera2/extensions/jni/Android.bp
new file mode 100644
index 0000000..b30253e
--- /dev/null
+++ b/camera2/extensions/jni/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libencoderjpeg_jni",
+    srcs: [
+        "JpegEncoder.cpp"
+    ],
+    include_dirs: ["external/libjpeg-turbo"],
+
+    header_libs: [
+        "jni_headers",
+    ],
+
+   shared_libs: [
+        "libnativehelper",
+        "liblog",
+        "libjpeg",
+    ],
+    static_libs: [
+        "android.hardware.camera.device@3.2",
+    ],
+
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+
+    system_ext_specific: true,
+}
diff --git a/camera2/extensions/jni/JpegEncoder.cpp b/camera2/extensions/jni/JpegEncoder.cpp
new file mode 100644
index 0000000..dac6d00
--- /dev/null
+++ b/camera2/extensions/jni/JpegEncoder.cpp
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <array>
+#include <cstring>
+#include <cstdio>
+#include <inttypes.h>
+#include <memory.h>
+#include <vector>
+#include <iostream>
+#include <utils/Log.h>
+#include <setjmp.h>
+
+#include <android/hardware/camera/device/3.2/types.h>
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+
+extern "C" {
+#include "jpeglib.h"
+}
+
+using namespace std;
+using namespace android;
+
+using android::hardware::camera::device::V3_2::CameraBlob;
+using android::hardware::camera::device::V3_2::CameraBlobId;
+
+class Transform;
+struct Plane;
+
+inline int sgn(int val) { return (0 < val) - (val < 0); }
+
+inline int min(int a, int b) { return a < b ? a : b; }
+
+inline int max(int a, int b) { return a > b ? a : b; }
+
+/**
+ * Represents a combined cropping and rotation transformation.
+ *
+ * The transformation maps the coordinates (mOrigX, mOrigY) and (mOneX, mOneY)
+ * in the input image to the origin and (mOutputWidth, mOutputHeight)
+ * respectively.
+ */
+class Transform {
+    public:
+        Transform(int origX, int origY, int oneX, int oneY);
+
+        static Transform forCropFollowedByRotation(int cropLeft, int cropTop,
+                int cropRight, int cropBottom, int rot90);
+
+        inline int getOutputWidth() const { return mOutputWidth; }
+
+        inline int getOutputHeight() const { return mOutputHeight; }
+
+        bool operator==(const Transform& other) const;
+
+        /**
+         * Transforms the input coordinates.  Coordinates outside the cropped region
+         * are clamped to valid values.
+         */
+        void map(int x, int y, int* outX, int* outY) const;
+
+    private:
+        int mOutputWidth;
+        int mOutputHeight;
+
+        // The coordinates of the point to map the origin to.
+        const int mOrigX, mOrigY;
+        // The coordinates of the point to map the point (getOutputWidth(),
+        // getOutputHeight()) to.
+        const int mOneX, mOneY;
+
+        // A matrix for the rotational component.
+        int mMat00, mMat01;
+        int mMat10, mMat11;
+};
+
+/**
+ * Represents a model for accessing pixel data for a single plane of an image.
+ * Note that the actual data is not owned by this class, and the underlying
+ * data does not need to be stored in separate planes.
+ */
+struct Plane {
+    // The dimensions of this plane of the image
+    int width;
+    int height;
+
+    // A pointer to raw pixel data
+    const unsigned char* data;
+    // The difference in address between consecutive pixels in the same row
+    int pixelStride;
+    // The difference in address between the start of consecutive rows
+    int rowStride;
+};
+
+/**
+ * Provides an interface for simultaneously reading a certain number of rows of
+ * an image plane as contiguous arrays, suitable for use with libjpeg.
+ */
+template <unsigned int ROWS>
+class RowIterator {
+    public:
+        /**
+         * Creates a new RowIterator which will crop and rotate with the given
+         * transform.
+         *
+         * @param plane the plane to iterate over
+         * @param transform the transformation to map output values into the
+         * coordinate space of the plane
+         * @param rowLength the length of the rows returned via LoadAt().  If this is
+         * longer than the width of the output (after applying the transform), then
+         * the right-most value is repeated.
+         */
+        inline RowIterator(Plane plane, Transform transform, int rowLength);
+
+        /**
+         * Returns an array of pointers into consecutive rows of contiguous image
+         * data starting at y.  That is, samples within each row are contiguous.
+         * However, the individual arrays pointed-to may be separate.
+         * When the end of the image is reached, the last row of the image is
+         * repeated.
+         * The returned pointers are valid until the next call to loadAt().
+         */
+        inline const std::array<unsigned char*, ROWS> loadAt(int baseY);
+
+    private:
+        Plane mPlane;
+        Transform mTransform;
+        // The length of a row, with padding to the next multiple of 64.
+        int mPaddedRowLength;
+        std::vector<unsigned char> mBuffer;
+};
+
+template <unsigned int ROWS>
+RowIterator<ROWS>::RowIterator(Plane plane, Transform transform,
+                                         int rowLength)
+        : mPlane(plane), mTransform(transform) {
+    mPaddedRowLength = rowLength;
+    mBuffer = std::vector<unsigned char>(rowLength * ROWS);
+}
+
+template <unsigned int ROWS>
+const std::array<unsigned char*, ROWS> RowIterator<ROWS>::loadAt(int baseY) {
+    std::array<unsigned char*, ROWS> bufPtrs;
+    for (unsigned int i = 0; i < ROWS; i++) {
+        bufPtrs[i] = &mBuffer[mPaddedRowLength * i];
+    }
+
+    if (mPlane.width == 0 || mPlane.height == 0) {
+        return bufPtrs;
+    }
+
+    for (unsigned int i = 0; i < ROWS; i++) {
+        int y = i + baseY;
+        y = min(y, mTransform.getOutputHeight() - 1);
+
+        int output_width = mPaddedRowLength;
+        output_width = min(output_width, mTransform.getOutputWidth());
+        output_width = min(output_width, mPlane.width);
+
+        // Each row in the output image will be copied into buf_ by gathering pixels
+        // along an axis-aligned line in the plane.
+        // The line is defined by (startX, startY) -> (endX, endY), computed via the
+        // current Transform.
+        int startX;
+        int startY;
+        mTransform.map(0, y, &startX, &startY);
+
+        int endX;
+        int endY;
+        mTransform.map(output_width - 1, y, &endX, &endY);
+
+        // Clamp (startX, startY) and (endX, endY) to the valid bounds of the plane.
+        startX = min(startX, mPlane.width - 1);
+        startY = min(startY, mPlane.height - 1);
+        endX = min(endX, mPlane.width - 1);
+        endY = min(endY, mPlane.height - 1);
+        startX = max(startX, 0);
+        startY = max(startY, 0);
+        endX = max(endX, 0);
+        endY = max(endY, 0);
+
+        // To reduce work inside the copy-loop, precompute the start, end, and
+        // stride relating the values to be gathered from mPlane into buf
+        // for this particular scan-line.
+        int dx = sgn(endX - startX);
+        int dy = sgn(endY - startY);
+        if (!(dx == 0 || dy == 0)) {
+            ALOGE("%s: Unexpected bounds: %dx%d %dx%d!", __FUNCTION__, startX, endX, startY, endY);
+            return bufPtrs;
+        }
+
+        // The index into mPlane.data of (startX, startY)
+        int plane_start = startX * mPlane.pixelStride + startY * mPlane.rowStride;
+        // The index into mPlane.data of (endX, endY)
+        int plane_end = endX * mPlane.pixelStride + endY * mPlane.rowStride;
+        // The stride, in terms of indices in plane_data, required to enumerate the
+        // samples between the start and end points.
+        int stride = dx * mPlane.pixelStride + dy * mPlane.rowStride;
+        // In the degenerate-case of a 1x1 plane, startX and endX are equal, so
+        // stride would be 0, resulting in an infinite-loop.  To avoid this case,
+        // use a stride of at-least 1.
+        if (stride == 0) {
+            stride = 1;
+        }
+
+        int outX = 0;
+        for (int idx = plane_start; idx >= min(plane_start, plane_end) &&
+                idx <= max(plane_start, plane_end); idx += stride) {
+            bufPtrs[i][outX] = mPlane.data[idx];
+            outX++;
+        }
+
+        // Fill the remaining right-edge of the buffer by extending the last
+        // value.
+        unsigned char right_padding_value = bufPtrs[i][outX - 1];
+        for (; outX < mPaddedRowLength; outX++) {
+            bufPtrs[i][outX] = right_padding_value;
+        }
+    }
+
+    return bufPtrs;
+}
+
+template <typename T>
+void safeDelete(T& t) {
+    delete t;
+    t = nullptr;
+}
+
+template <typename T>
+void safeDeleteArray(T& t) {
+    delete[] t;
+    t = nullptr;
+}
+
+Transform::Transform(int origX, int origY, int oneX, int oneY)
+    : mOrigX(origX), mOrigY(origY), mOneX(oneX), mOneY(oneY) {
+    if (origX == oneX || origY == oneY) {
+        // Handle the degenerate case of cropping to a 0x0 rectangle.
+        mMat00 = 0;
+        mMat01 = 0;
+        mMat10 = 0;
+        mMat11 = 0;
+        return;
+    }
+
+    if (oneX > origX && oneY > origY) {
+        // 0-degree rotation
+        mMat00 = 1;
+        mMat01 = 0;
+        mMat10 = 0;
+        mMat11 = 1;
+        mOutputWidth = abs(oneX - origX);
+        mOutputHeight = abs(oneY - origY);
+    } else if (oneX < origX && oneY > origY) {
+        // 90-degree CCW rotation
+        mMat00 = 0;
+        mMat01 = -1;
+        mMat10 = 1;
+        mMat11 = 0;
+        mOutputWidth = abs(oneY - origY);
+        mOutputHeight = abs(oneX - origX);
+    } else if (oneX > origX && oneY < origY) {
+        // 270-degree CCW rotation
+        mMat00 = 0;
+        mMat01 = 1;
+        mMat10 = -1;
+        mMat11 = 0;
+        mOutputWidth = abs(oneY - origY);
+        mOutputHeight = abs(oneX - origX);;
+    } else if (oneX < origX && oneY < origY) {
+        // 180-degree CCW rotation
+        mMat00 = -1;
+        mMat01 = 0;
+        mMat10 = 0;
+        mMat11 = -1;
+        mOutputWidth = abs(oneX - origX);
+        mOutputHeight = abs(oneY - origY);
+    }
+}
+
+Transform Transform::forCropFollowedByRotation(int cropLeft, int cropTop, int cropRight,
+        int cropBottom, int rot90) {
+    // The input crop-region excludes cropRight and cropBottom, so transform the
+    // crop rect such that it defines the entire valid region of pixels
+    // inclusively.
+    cropRight -= 1;
+    cropBottom -= 1;
+
+    int cropXLow = min(cropLeft, cropRight);
+    int cropYLow = min(cropTop, cropBottom);
+    int cropXHigh = max(cropLeft, cropRight);
+    int cropYHigh = max(cropTop, cropBottom);
+    rot90 %= 4;
+    if (rot90 == 0) {
+        return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1);
+    } else if (rot90 == 1) {
+        return Transform(cropXHigh, cropYLow, cropXLow - 1, cropYHigh + 1);
+    } else if (rot90 == 2) {
+        return Transform(cropXHigh, cropYHigh, cropXLow - 1, cropYLow - 1);
+    } else if (rot90 == 3) {
+        return Transform(cropXLow, cropYHigh, cropXHigh + 1, cropYLow - 1);
+    }
+    // Impossible case.
+    return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1);
+}
+
+bool Transform::operator==(const Transform& other) const {
+    return other.mOrigX == mOrigX &&  //
+           other.mOrigY == mOrigY &&  //
+           other.mOneX == mOneX &&    //
+           other.mOneY == mOneY;
+}
+
+/**
+ * Transforms the input coordinates.  Coordinates outside the cropped region
+ * are clamped to valid values.
+ */
+void Transform::map(int x, int y, int* outX, int* outY) const {
+    x = max(x, 0);
+    y = max(y, 0);
+    x = min(x, getOutputWidth() - 1);
+    y = min(y, getOutputHeight() - 1);
+    *outX = x * mMat00 + y * mMat01 + mOrigX;
+    *outY = x * mMat10 + y * mMat11 + mOrigY;
+}
+
+int compress(int img_width, int img_height, RowIterator<16>& y_row_generator,
+        RowIterator<8>& cb_row_generator, RowIterator<8>& cr_row_generator,
+        unsigned char* out_buf, size_t out_buf_capacity, std::function<void(size_t)> flush,
+        int quality) {
+    // libjpeg requires the use of setjmp/longjmp to recover from errors.  Since
+    // this doesn't play well with RAII, we must use pointers and manually call
+    // delete. See POSIX documentation for longjmp() for details on why the
+    // volatile keyword is necessary.
+    volatile jpeg_compress_struct cinfov;
+
+    jpeg_compress_struct& cinfo =
+            *const_cast<struct jpeg_compress_struct*>(&cinfov);
+
+    JSAMPROW* volatile yArr = nullptr;
+    JSAMPROW* volatile cbArr = nullptr;
+    JSAMPROW* volatile crArr = nullptr;
+
+    JSAMPARRAY imgArr[3];
+
+    // Error handling
+
+    struct my_error_mgr {
+        struct jpeg_error_mgr pub;
+        jmp_buf setjmp_buffer;
+    } err;
+
+    cinfo.err = jpeg_std_error(&err.pub);
+
+    // Default error_exit will call exit(), so override
+    // to return control via setjmp/longjmp.
+    err.pub.error_exit = [](j_common_ptr cinfo) {
+        my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err);
+
+        (*cinfo->err->output_message)(cinfo);
+
+        // Return control to the setjmp point (see call to setjmp()).
+        longjmp(myerr->setjmp_buffer, 1);
+    };
+
+    cinfo.err = (struct jpeg_error_mgr*)&err;
+
+    // Set the setjmp point to return to in case of error.
+    if (setjmp(err.setjmp_buffer)) {
+        // If libjpeg hits an error, control will jump to this point (see call to
+        // longjmp()).
+        jpeg_destroy_compress(&cinfo);
+
+        safeDeleteArray(yArr);
+        safeDeleteArray(cbArr);
+        safeDeleteArray(crArr);
+
+        return -1;
+    }
+
+    // Create jpeg compression context
+    jpeg_create_compress(&cinfo);
+
+    // Stores data needed by our c-style callbacks into libjpeg
+    struct ClientData {
+        unsigned char* out_buf;
+        size_t out_buf_capacity;
+        std::function<void(size_t)> flush;
+        int totalOutputBytes;
+    } clientData{out_buf, out_buf_capacity, flush, 0};
+
+    cinfo.client_data = &clientData;
+
+    // Initialize destination manager
+    jpeg_destination_mgr dest;
+
+    dest.init_destination = [](j_compress_ptr cinfo) {
+        ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
+
+        cinfo->dest->next_output_byte = cdata.out_buf;
+        cinfo->dest->free_in_buffer = cdata.out_buf_capacity;
+    };
+
+    dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
+        ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
+
+        size_t numBytesInBuffer = cdata.out_buf_capacity;
+        cdata.flush(numBytesInBuffer);
+        cdata.totalOutputBytes += numBytesInBuffer;
+
+        // Reset the buffer
+        cinfo->dest->next_output_byte = cdata.out_buf;
+        cinfo->dest->free_in_buffer = cdata.out_buf_capacity;
+
+        return true;
+    };
+
+    dest.term_destination = [](j_compress_ptr cinfo __unused) {
+        // do nothing to terminate the output buffer
+    };
+
+    cinfo.dest = &dest;
+
+    // Set jpeg parameters
+    cinfo.image_width = img_width;
+    cinfo.image_height = img_height;
+    cinfo.input_components = 3;
+
+    // Set defaults based on the above values
+    jpeg_set_defaults(&cinfo);
+
+    jpeg_set_quality(&cinfo, quality, true);
+
+    cinfo.dct_method = JDCT_IFAST;
+
+    cinfo.raw_data_in = true;
+
+    jpeg_set_colorspace(&cinfo, JCS_YCbCr);
+
+    cinfo.comp_info[0].h_samp_factor = 2;
+    cinfo.comp_info[0].v_samp_factor = 2;
+    cinfo.comp_info[1].h_samp_factor = 1;
+    cinfo.comp_info[1].v_samp_factor = 1;
+    cinfo.comp_info[2].h_samp_factor = 1;
+    cinfo.comp_info[2].v_samp_factor = 1;
+
+    jpeg_start_compress(&cinfo, true);
+
+    yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE];
+    cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE];
+    crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE];
+
+    imgArr[0] = const_cast<JSAMPARRAY>(yArr);
+    imgArr[1] = const_cast<JSAMPARRAY>(cbArr);
+    imgArr[2] = const_cast<JSAMPARRAY>(crArr);
+
+    for (int y = 0; y < img_height; y += DCTSIZE * 2) {
+        std::array<unsigned char*, 16> yData = y_row_generator.loadAt(y);
+        std::array<unsigned char*, 8> cbData = cb_row_generator.loadAt(y / 2);
+        std::array<unsigned char*, 8> crData = cr_row_generator.loadAt(y / 2);
+
+        for (int row = 0; row < DCTSIZE * 2; row++) {
+            yArr[row] = yData[row];
+        }
+        for (int row = 0; row < DCTSIZE; row++) {
+            cbArr[row] = cbData[row];
+            crArr[row] = crData[row];
+        }
+
+        jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2);
+    }
+
+    jpeg_finish_compress(&cinfo);
+
+    int numBytesInBuffer = cinfo.dest->next_output_byte - out_buf;
+
+    flush(numBytesInBuffer);
+
+    clientData.totalOutputBytes += numBytesInBuffer;
+
+    safeDeleteArray(yArr);
+    safeDeleteArray(cbArr);
+    safeDeleteArray(crArr);
+
+    jpeg_destroy_compress(&cinfo);
+
+    return clientData.totalOutputBytes;
+}
+
+int compress(
+        /** Input image dimensions */
+        int width, int height,
+        /** Y Plane */
+        unsigned char* yBuf, int yPStride, int yRStride,
+        /** Cb Plane */
+        unsigned char* cbBuf, int cbPStride, int cbRStride,
+        /** Cr Plane */
+        unsigned char* crBuf, int crPStride, int crRStride,
+        /** Output */
+        unsigned char* outBuf, size_t outBufCapacity,
+        /** Jpeg compression parameters */
+        int quality,
+        /** Crop */
+        int cropLeft, int cropTop, int cropRight, int cropBottom,
+        /** Rotation (multiple of 90).  For example, rot90 = 1 implies a 90 degree
+         * rotation. */
+        int rot90) {
+    int finalWidth;
+    int finalHeight;
+    finalWidth = cropRight - cropLeft;
+    finalHeight = cropBottom - cropTop;
+
+    rot90 %= 4;
+    // for 90 and 270-degree rotations, flip the final width and height
+    if (rot90 == 1) {
+        finalWidth = cropBottom - cropTop;
+        finalHeight = cropRight - cropLeft;
+    } else if (rot90 == 3) {
+        finalWidth = cropBottom - cropTop;
+        finalHeight = cropRight - cropLeft;
+    }
+
+    const Plane yP = {width, height, yBuf, yPStride, yRStride};
+    const Plane cbP = {width / 2, height / 2, cbBuf, cbPStride, cbRStride};
+    const Plane crP = {width / 2, height / 2, crBuf, crPStride, crRStride};
+
+    auto flush = [](size_t numBytes __unused) {
+        // do nothing
+    };
+
+    // Round up to the nearest multiple of 64.
+    int y_row_length = (finalWidth + 16 + 63) & ~63;
+    int cb_row_length = (finalWidth / 2 + 16 + 63) & ~63;
+    int cr_row_length = (finalWidth / 2 + 16 + 63) & ~63;
+
+    Transform yTrans = Transform::forCropFollowedByRotation(
+            cropLeft, cropTop, cropRight, cropBottom, rot90);
+
+    Transform chromaTrans = Transform::forCropFollowedByRotation(
+            cropLeft / 2, cropTop / 2, cropRight / 2, cropBottom / 2, rot90);
+
+    RowIterator<16> yIter(yP, yTrans, y_row_length);
+    RowIterator<8> cbIter(cbP, chromaTrans, cb_row_length);
+    RowIterator<8> crIter(crP, chromaTrans, cr_row_length);
+
+    return compress(finalWidth, finalHeight, yIter, cbIter, crIter, outBuf, outBufCapacity, flush,
+            quality);
+}
+
+extern "C" {
+
+static jint JpegEncoder_compressJpegFromYUV420p(
+        JNIEnv* env, jclass clazz __unused,
+        /** Input image dimensions */
+        jint width, jint height,
+        /** Y Plane */
+        jobject yBuf, jint yPStride, jint yRStride,
+        /** Cb Plane */
+        jobject cbBuf, jint cbPStride, jint cbRStride,
+        /** Cr Plane */
+        jobject crBuf, jint crPStride, jint crRStride,
+        /** Output */
+        jobject outBuf, jint outBufCapacity,
+        /** Jpeg compression parameters */
+        jint quality,
+        /** Crop */
+        jint cropLeft, jint cropTop, jint cropRight, jint cropBottom,
+        /** Rotation (multiple of 90).  For example, rot90 = 1 implies a 90 degree
+         * rotation. */
+        jint rot90) {
+    // ALOGE("Insert log inside JpegEncoder_compressJpegFromYUV420p");
+    jbyte* y = (jbyte*)env->GetDirectBufferAddress(yBuf);
+    jbyte* cb = (jbyte*)env->GetDirectBufferAddress(cbBuf);
+    jbyte* cr = (jbyte*)env->GetDirectBufferAddress(crBuf);
+    jbyte* out = (jbyte*)env->GetDirectBufferAddress(outBuf);
+
+    size_t actualJpegSize = compress(width, height,
+            (unsigned char*)y, yPStride, yRStride,
+            (unsigned char*)cb, cbPStride, cbRStride,
+            (unsigned char*)cr, crPStride, crRStride,
+            (unsigned char*)out, (size_t)outBufCapacity,
+            quality, cropLeft, cropTop, cropRight, cropBottom, rot90);
+
+    size_t finalJpegSize = actualJpegSize + sizeof(CameraBlob);
+    if (finalJpegSize > outBufCapacity) {
+        // ALOGE("%s: Final jpeg buffer %zu not large enough for the jpeg blob header with "\
+        //         "capacity %d", __FUNCTION__, finalJpegSize, outBufCapacity);
+        return actualJpegSize;
+    }
+
+    int8_t* header = static_cast<int8_t *> (out) +
+            (outBufCapacity - sizeof(CameraBlob));
+    CameraBlob *blob = reinterpret_cast<CameraBlob *> (header);
+    blob->blobId = CameraBlobId::JPEG;
+    blob->blobSize = actualJpegSize;
+
+    return actualJpegSize;
+}
+
+} // extern "C"
+
+static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
+        const JNINativeMethod* gMethods, int numMethods) {
+    int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+    return res;
+}
+
+static const JNINativeMethod gJpegEncoderMethods[] = {
+    {"compressJpegFromYUV420pNative",
+    "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIIIIII)I",
+    (void*)JpegEncoder_compressJpegFromYUV420p}};
+
+int register_android_hardware_camera2_impl_JpegEncoder(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "androidx/camera/extensions/impl/advanced/JpegEncoder",
+            gJpegEncoderMethods, NELEM(gJpegEncoderMethods));
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+    JNIEnv* env;
+
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        return -1;
+    }
+
+    if (register_android_hardware_camera2_impl_JpegEncoder(env) < 0) {
+        ALOGE("ERROR: JpegEncoder native registration failed");
+        return -1;
+    }
+
+    return JNI_VERSION_1_6;
+}
\ No newline at end of file
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index 5f6cb2b..30a79a6 100755
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -20,6 +20,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -116,6 +117,26 @@
                     }
 
                     @Override
+                    public void onPostviewOutputSurface(Surface surface) {
+
+                    }
+
+                    @Override
+                    public void processWithPostview(
+                            Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                            ProcessResultImpl resultCallback, Executor executor) {
+                        if (!isPostviewAvailable()) {
+                            throw new RuntimeException("The extension doesn't support postview");
+                        }
+
+                        if (resultCallback != null) {
+                            process(results, resultCallback, executor);
+                        } else {
+                            process(results);
+                        }
+                    }
+
+                    @Override
                     public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
                             ProcessResultImpl resultCallback, Executor executor) {
                         throw new RuntimeException("The extension doesn't support capture " +
@@ -164,6 +185,11 @@
                     }
 
                     @Override
+                    public void onResolutionUpdate(Size size, Size postviewSize) {
+
+                    }
+
+                    @Override
                     public void onImageFormatUpdate(int imageFormat) {
 
                     }
@@ -244,6 +270,11 @@
     }
 
     @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        return new ArrayList<>();
+    }
+
+    @Override
     public Range<Long> getEstimatedCaptureLatencyRange(Size captureOutputSize) {
         return null;
     }
@@ -257,4 +288,24 @@
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
         return new ArrayList<>();
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
index 5018df8..aa95a4a 100755
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.util.Pair;
 import android.util.Size;
 
@@ -162,4 +163,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index 5c9b2d3..c9b2420 100755
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.ImageWriter;
@@ -118,6 +119,26 @@
                     }
 
                     @Override
+                    public void onPostviewOutputSurface(Surface surface) {
+
+                    }
+
+                    @Override
+                    public void processWithPostview(
+                            Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                            ProcessResultImpl resultCallback, Executor executor) {
+                        if (!isPostviewAvailable()) {
+                            throw new RuntimeException("The extension doesn't support postview");
+                        }
+
+                        if (resultCallback != null) {
+                            process(results, resultCallback, executor);
+                        } else {
+                            process(results);
+                        }
+                    }
+
+                    @Override
                     public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
                             ProcessResultImpl resultCallback, Executor executor) {
                         throw new RuntimeException("The extension doesn't support capture " +
@@ -166,6 +187,11 @@
                     }
 
                     @Override
+                    public void onResolutionUpdate(Size size, Size postviewSize) {
+
+                    }
+
+                    @Override
                     public void onImageFormatUpdate(int imageFormat) {
 
                     }
@@ -267,6 +293,11 @@
     }
 
     @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        return new ArrayList<>();
+    }
+
+    @Override
     public Range<Long> getEstimatedCaptureLatencyRange(Size captureOutputSize) {
         return null;
     }
@@ -280,4 +311,24 @@
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
         return new ArrayList<>();
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
index fcc78d5..2ef357c 100755
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
@@ -19,6 +19,7 @@
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.util.Pair;
 import android.util.Size;
@@ -183,4 +184,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index 5c3882b..a72deef 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -16,10 +16,13 @@
 package androidx.camera.extensions.impl;
 
 import android.content.Context;
+import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -51,6 +54,8 @@
     private static final int SESSION_STAGE_ID = 101;
     private static final int MODE = CaptureRequest.CONTROL_AWB_MODE_SHADE;
 
+    private CameraCharacteristics mCameraCharacteristics;
+
     /**
      * @hide
      */
@@ -62,6 +67,7 @@
      */
     @Override
     public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        mCameraCharacteristics = cameraCharacteristics;
     }
 
     /**
@@ -104,6 +110,7 @@
         CaptureProcessorImpl captureProcessor =
                 new CaptureProcessorImpl() {
                     private ImageWriter mImageWriter;
+                    private ImageWriter mImageWriterPostview;
 
                     @Override
                     public void onOutputSurface(Surface surface, int imageFormat) {
@@ -113,6 +120,61 @@
                     }
 
                     @Override
+                    public void onPostviewOutputSurface(Surface surface) {
+                        if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) &&
+                                mImageWriterPostview == null) {
+                            mImageWriterPostview = ImageWriter.newInstance(surface, 1);
+                        }
+                    }
+
+                    @Override
+                    public void processWithPostview(
+                            Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                            ProcessResultImpl resultCallback, Executor executor) {
+
+                        Pair<Image, TotalCaptureResult> result = results.get(DEFAULT_STAGE_ID);
+                        if (result == null) {
+                            Log.w(TAG,
+                                    "Unable to process since images does not contain all " +
+                                    "stages.");
+                            return;
+                        } else {
+                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                                Image image = mImageWriterPostview.dequeueInputImage();
+
+                                // Postview processing here
+                                ByteBuffer yByteBuffer = image.getPlanes()[0].getBuffer();
+                                ByteBuffer uByteBuffer = image.getPlanes()[2].getBuffer();
+                                ByteBuffer vByteBuffer = image.getPlanes()[1].getBuffer();
+
+                                // For sample, allocate empty buffer to match postview size
+                                yByteBuffer.put(ByteBuffer.allocate(image.getPlanes()[0]
+                                        .getBuffer().capacity()));
+                                uByteBuffer.put(ByteBuffer.allocate(image.getPlanes()[2]
+                                        .getBuffer().capacity()));
+                                vByteBuffer.put(ByteBuffer.allocate(image.getPlanes()[1]
+                                        .getBuffer().capacity()));
+                                Long sensorTimestamp =
+                                        result.second.get(CaptureResult.SENSOR_TIMESTAMP);
+                                if (sensorTimestamp != null) {
+                                    image.setTimestamp(sensorTimestamp);
+                                } else {
+                                    Log.e(TAG, "Sensor timestamp absent using default!");
+                                }
+
+                                mImageWriterPostview.queueInputImage(image);
+                            }
+                        }
+
+                        // Process still capture
+                        if (resultCallback != null) {
+                            process(results, resultCallback, executor);
+                        } else {
+                            process(results);
+                        }
+                    }
+
+                    @Override
                     public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
                             ProcessResultImpl resultCallback, Executor executor) {
                         Pair<Image, TotalCaptureResult> result = results.get(DEFAULT_STAGE_ID);
@@ -241,6 +303,11 @@
                     }
 
                     @Override
+                    public void onResolutionUpdate(Size size, Size postviewSize) {
+
+                    }
+
+                    @Override
                     public void onImageFormatUpdate(int imageFormat) {
 
                     }
@@ -317,7 +384,59 @@
      */
     @Override
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
-        return null;
+        List<Pair<Integer, Size[]>> formatResolutionsPairList = new ArrayList<>();
+
+        StreamConfigurationMap map =
+                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        if (map != null) {
+            // The sample implementation only retrieves originally supported resolutions from
+            // CameraCharacteristics for JPEG and YUV_420_888 formats to return.
+            Size[] outputSizes = map.getOutputSizes(ImageFormat.JPEG);
+
+            if (outputSizes != null) {
+                formatResolutionsPairList.add(Pair.create(ImageFormat.JPEG, outputSizes));
+            }
+
+            outputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
+
+            if (outputSizes != null) {
+                formatResolutionsPairList.add(Pair.create(ImageFormat.YUV_420_888, outputSizes));
+            }
+        }
+
+        return formatResolutionsPairList;
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        // Sample for supported postview sizes, returns subset of supported resolutions for
+        // still capture that are less than its size and match the aspect ratio
+        List<Pair<Integer, Size[]>> res = new ArrayList<>();
+        List<Pair<Integer, Size[]>> captureSupportedResolutions = getSupportedResolutions();
+        float targetAr = ((float) captureSize.getWidth()) / captureSize.getHeight();
+
+        for (Pair<Integer, Size[]> elem : captureSupportedResolutions) {
+            Integer currFormat = elem.first;
+            Size[] currFormatSizes = elem.second;
+            List<Size> postviewSizes = new ArrayList<>();
+
+            for (Size s : currFormatSizes) {
+                if ((s.equals(captureSize)) || (s.getWidth() > captureSize.getWidth())
+                        || (s.getHeight() > captureSize.getHeight())) continue;
+                float currentAr = ((float) s.getWidth()) / s.getHeight();
+                if (Math.abs(targetAr - currentAr) < 0.01) {
+                    postviewSizes.add(s);
+                }
+            }
+
+            if (!postviewSizes.isEmpty()) {
+                res.add(new Pair<Integer, Size[]>(currFormat,
+                        postviewSizes.toArray(new Size[postviewSizes.size()])));
+            }
+        }
+
+        return res;
     }
 
     @Override
@@ -341,4 +460,24 @@
             CaptureResult.FLASH_STATE};
         return Arrays.asList(CAPTURE_RESULT_SET);
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return true;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
index 45c7f47..ace54c8 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -19,6 +19,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.util.Pair;
 import android.util.Size;
 import android.view.Surface;
@@ -202,4 +203,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
index efd0afa..f4719b8 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
@@ -16,46 +16,60 @@
 
 package androidx.camera.extensions.impl;
 
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
 import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 import android.util.Pair;
 import android.util.Size;
 import android.view.Surface;
 
-import java.util.concurrent.Executor;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * The interface for processing a set of {@link Image}s that have captured.
  *
  * @since 1.0
- * @hide
  */
-public interface CaptureProcessorImpl {
-    /**
-     * This gets called to update where the CaptureProcessor should write the output of {@link
-     * #process(Map)}.
-     *
-     * @param surface The {@link Surface} that the CaptureProcessor should write data into.
-     * @param imageFormat The format of that the surface expects.
-     * @hide
-     */
-    void onOutputSurface(Surface surface, int imageFormat);
-
+@SuppressLint("UnknownNullness")
+public interface CaptureProcessorImpl extends ProcessorImpl {
     /**
      * Process a set images captured that were requested.
      *
      * <p> The result of the processing step should be written to the {@link Surface} that was
      * received by {@link #onOutputSurface(Surface, int)}.
      *
-     * @param results The map of images and metadata to process. The {@link Image} that are
-     *                contained within the map will become invalid after this method completes,
-     *                so no references to them should be kept.
-     * @hide
+     * @param results The map of {@link ImageFormat#YUV_420_888} format images and metadata to
+     *                process. The {@link Image} that are contained within the map will become
+     *                invalid after this method completes, so no references to them should be kept.
      */
     void process(Map<Integer, Pair<Image, TotalCaptureResult>> results);
 
     /**
+     * Informs the CaptureProcessorImpl where it should write the postview output to.
+     * This will only be invoked once if a valid postview surface was set.
+     *
+     * @param surface A valid {@link ImageFormat#YUV_420_888} {@link Surface}
+     *                that the CaptureProcessorImpl should write data into.
+     * @since 1.4
+     */
+    void onPostviewOutputSurface(Surface surface);
+
+    /**
+     * Invoked when the Camera Framework changes the configured output resolution for
+     * still capture and postview.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
+     * input for still capture and postview to be at the specified resolutions.
+     *
+     * @param size for the surface for still capture.
+     * @param postviewSize for the surface for postview.
+     * @since 1.4
+     */
+    void onResolutionUpdate(Size size, Size postviewSize);
+
+    /**
      * Process a set images captured that were requested.
      *
      * <p> The result of the processing step should be written to the {@link Surface} that was
@@ -66,7 +80,7 @@
      *                       become invalid after this method completes, so no references to them
      *                       should be kept.
      * @param resultCallback Capture result callback to be called once the capture result
-     *                       values are ready.
+     *                       values of the processed image are ready.
      * @param executor       The executor to run the callback on. If null then the callback will
      *                       run on any arbitrary executor.
      * @since 1.3
@@ -75,22 +89,28 @@
             ProcessResultImpl resultCallback, Executor executor);
 
     /**
-     * This callback will be invoked when CameraX changes the configured input resolution. After
-     * this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as input
-     * to be at the specified resolution.
+     * Process a set images captured that were requested for both postview and
+     * still capture.
      *
-     * @param size for the surface.
-     * @hide
-     */
-    void onResolutionUpdate(Size size);
-
-    /**
-     * This callback will be invoked when CameraX changes the configured input image format.
-     * After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
-     * input to have the specified image format.
+     * <p> This processing method will be called if a postview was requested, therefore the
+     * processed postview should be written to the
+     * {@link Surface} received by {@link #onPostviewOutputSurface(Surface, int)}.
+     * The final result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}. Since postview should be available
+     * before the capture, it should be processed and written to the surface before
+     * the final capture is processed.
      *
-     * @param imageFormat for the surface.
-     * @hide
+     * @param results             The map of {@link ImageFormat#YUV_420_888} format images and
+     *                            metadata to process. The {@link Image} that are contained within
+     *                            the map will become invalid after this method completes, so no
+     *                            references to them should be kept.
+     * @param resultCallback      Capture result callback to be called once the capture result
+     *                            values of the processed image are ready.
+     * @param executor            The executor to run the callback on. If null then the callback
+     *                            will run on any arbitrary executor.
+     * @throws RuntimeException   if postview feature is not supported
+     * @since 1.4
      */
-    void onImageFormatUpdate(int imageFormat);
+    void processWithPostview(Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            ProcessResultImpl resultCallback, Executor executor);
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java
index f926cff..23570c4 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java
@@ -84,4 +84,21 @@
      * @hide
      */
     CaptureStageImpl onDisableSession();
+
+    /**
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+     * initialized and must return a valid camera session type
+     * {@link android.hardware.camera2.params.SessionConfiguration#getSessionType}
+     * to be used to configure camera capture session. Both the preview and the image capture
+     * extender must return the same session type value for a specific extension type. If there
+     * is inconsistency between the session type values from preview and image extenders, then
+     * the session configuration will fail.
+     *
+     *
+     * @since 1.4
+     * @return Camera capture session type. Regular and vendor specific types are supported but
+     * not high speed values. The extension can return -1 in which case the camera capture session
+     * will be configured to use the default regular type.
+     */
+    int onSessionType();
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
index af147ed..75739ce 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
@@ -29,7 +29,7 @@
  */
 public class ExtensionVersionImpl {
     private static final String TAG = "ExtenderVersionImpl";
-    private static final String VERSION = "1.3.0";
+    private static final String VERSION = "1.4.0";
 
     /**
      * @hide
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index 57b7fe6..2523263 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -17,10 +17,12 @@
 
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -130,6 +132,26 @@
                     }
 
                     @Override
+                    public void onPostviewOutputSurface(Surface surface) {
+
+                    }
+
+                    @Override
+                    public void processWithPostview(
+                            Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                            ProcessResultImpl resultCallback, Executor executor) {
+                        if (!isPostviewAvailable()) {
+                            throw new RuntimeException("The extension doesn't support postview");
+                        }
+
+                        if (resultCallback != null) {
+                            process(results, resultCallback, executor);
+                        } else {
+                            process(results);
+                        }
+                    }
+
+                    @Override
                     public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
                             ProcessResultImpl resultCallback, Executor executor) {
                         Pair<Image, TotalCaptureResult> result = results.get(NORMAL_STAGE_ID);
@@ -184,12 +206,25 @@
                                             jpegOrientation));
                                 }
 
+                                Integer strength = result.second.get(
+                                        CaptureResult.EXTENSION_STRENGTH);
+                                if (strength != null) {
+                                    captureResults.add(new Pair<>(CaptureResult.EXTENSION_STRENGTH,
+                                            strength));
+                                }
+
+                                captureResults.add(new Pair<>(CaptureResult.EXTENSION_CURRENT_TYPE,
+                                            CameraExtensionCharacteristics.EXTENSION_HDR));
+
                                 if (executor != null) {
                                     executor.execute(() -> resultCallback.onCaptureCompleted(
                                             shutterTimestamp, captureResults));
+                                    executor.execute(() ->
+                                            resultCallback.onCaptureProcessProgressed(100));
                                 } else {
                                     resultCallback.onCaptureCompleted(shutterTimestamp,
                                             captureResults);
+                                    resultCallback.onCaptureProcessProgressed(100);
                                 }
                             }
                         }
@@ -264,6 +299,11 @@
                     }
 
                     @Override
+                    public void onResolutionUpdate(Size size, Size postviewSize) {
+
+                    }
+
+                    @Override
                     public void onImageFormatUpdate(int imageFormat) {
 
                     }
@@ -332,6 +372,11 @@
     }
 
     @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        return new ArrayList<>();
+    }
+
+    @Override
     public Range<Long> getEstimatedCaptureLatencyRange(Size captureOutputSize) {
         return null;
     }
@@ -340,7 +385,7 @@
     public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
         final CaptureRequest.Key [] CAPTURE_REQUEST_SET = {CaptureRequest.CONTROL_ZOOM_RATIO,
             CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_REGIONS,
-            CaptureRequest.CONTROL_AF_TRIGGER};
+            CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.EXTENSION_STRENGTH};
         return Arrays.asList(CAPTURE_REQUEST_SET);
     }
 
@@ -348,7 +393,28 @@
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
         final CaptureResult.Key [] CAPTURE_RESULT_SET = {CaptureResult.CONTROL_ZOOM_RATIO,
             CaptureResult.CONTROL_AF_MODE, CaptureResult.CONTROL_AF_REGIONS,
-            CaptureResult.CONTROL_AF_TRIGGER, CaptureResult.CONTROL_AF_STATE};
+            CaptureResult.CONTROL_AF_TRIGGER, CaptureResult.CONTROL_AF_STATE,
+            CaptureResult.EXTENSION_CURRENT_TYPE, CaptureResult.EXTENSION_STRENGTH};
         return Arrays.asList(CAPTURE_RESULT_SET);
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return true;
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
index 78b0a9d..7777bfa 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -18,15 +18,18 @@
 
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.ImageWriter;
 import android.media.Image;
 import android.util.Pair;
 import android.util.Size;
 import android.view.Surface;
 
+import java.io.Closeable;
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
 import java.util.List;
@@ -43,8 +46,6 @@
 public final class HdrPreviewExtenderImpl implements PreviewExtenderImpl {
     private static final int DEFAULT_STAGE_ID = 0;
 
-    ImageWriter mWriter;
-
     /**
      * @hide
      */
@@ -109,17 +110,32 @@
         return null;
     }
 
-    private PreviewImageProcessorImpl mProcessor = new PreviewImageProcessorImpl() {
+    private HdrPreviewProcessor mProcessor = new HdrPreviewProcessor();
+
+    private static class HdrPreviewProcessor implements PreviewImageProcessorImpl, Closeable {
         Surface mSurface;
         int mFormat = -1;
+        final Object mLock = new Object(); // Synchronize access to 'mWriter'
+        ImageWriter mWriter;
 
-        private void setWindowSurface() {
-            if (mSurface != null && mFormat >= 0) {
+        public void close() {
+            synchronized(mLock) {
                 if (mWriter != null) {
                     mWriter.close();
+                    mWriter = null;
                 }
+            }
+        }
 
-                mWriter = ImageWriter.newInstance(mSurface, 2, mFormat);
+        private void setWindowSurface() {
+            synchronized(mLock) {
+                if (mSurface != null && mFormat >= 0) {
+                    if (mWriter != null) {
+                        mWriter.close();
+                    }
+
+                    mWriter = ImageWriter.newInstance(mSurface, 2, mFormat);
+                }
             }
         }
 
@@ -132,7 +148,11 @@
 
         @Override
         public void process(Image image, TotalCaptureResult result) {
-            mWriter.queueInputImage(image);
+            synchronized(mLock) {
+                if (mWriter != null) {
+                    mWriter.queueInputImage(image);
+                }
+            }
         }
 
         @Override
@@ -175,6 +195,14 @@
                                 jpegOrientation));
                     }
 
+                    Integer strength = result.get(CaptureResult.EXTENSION_STRENGTH);
+                    if (strength != null) {
+                        captureResults.add(new Pair<>(CaptureResult.EXTENSION_STRENGTH, strength));
+                    }
+
+                    captureResults.add(new Pair<>(CaptureResult.EXTENSION_CURRENT_TYPE,
+                                CameraExtensionCharacteristics.EXTENSION_HDR));
+
                     if (executor != null) {
                         executor.execute(() -> resultCallback.onCaptureCompleted(shutterTimestamp,
                                 captureResults));
@@ -209,10 +237,7 @@
      */
     @Override
     public void onDeInit() {
-        if (mWriter != null) {
-            mWriter.close();
-            mWriter = null;
-        }
+        mProcessor.close();
     }
 
     /**
@@ -238,4 +263,9 @@
     public CaptureStageImpl onDisableSession() {
         return null;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index c27a5db..37e7baf 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -91,6 +91,21 @@
     List<Pair<Integer, Size[]>> getSupportedResolutions();
 
     /**
+     * Returns the customized supported postview resolutions for a still capture using
+     * its size.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     *
+     * <p>The returned resolutions should be subset of the supported sizes retrieved from
+     * {@link android.hardware.camera2.params.StreamConfigurationMap} for the camera device.
+     *
+     * @return the customized supported resolutions, or null to support all sizes retrieved from
+     *         {@link android.hardware.camera2.params.StreamConfigurationMap}.
+     * @since 1.4
+     */
+    List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize);
+
+    /**
      * Returns the estimated capture latency range in milliseconds for the target capture
      * resolution.
      *
@@ -138,5 +153,44 @@
      * @since 1.3
      */
     List<CaptureResult.Key> getAvailableCaptureResultKeys();
-}
 
+    /**
+     * Advertise support for {@link ProcessResultImpl#onCaptureProcessProgressed}.
+     *
+     * @return {@code true} in case the process progress callback is supported and is expected to
+     * be triggered, {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isCaptureProcessProgressAvailable();
+
+    /**
+     * Returns the dynamically calculated capture latency pair in milliseconds.
+     *
+     * <p>In contrast to {@link #getEstimatedCaptureLatencyRange} this method is guaranteed to be
+     * called after the camera capture session is initialized and camera preview is enabled.
+     * The measurement is expected to take in to account dynamic parameters such as the current
+     * scene, the state of 3A algorithms, the state of internal HW modules and return a more
+     * accurate assessment of the still capture latency.</p>
+     *
+     * @return pair that includes the estimated input frame/frames camera capture latency as the
+     * first field and the estimated post-processing latency {@link CaptureProcessorImpl#process}
+     * as the second pair field. Both first and second fields will be in milliseconds. The total
+     * still capture latency will be the sum of both the first and second values.
+     * The pair is expected to be null if the dynamic latency estimation is not supported.
+     * If clients have not configured a still capture output, then this method can also return a
+     * null pair.
+     * @since 1.4
+     */
+    Pair<Long, Long> getRealtimeCaptureLatency();
+
+    /**
+     * Indicates whether the extension supports the postview for still capture feature.
+     * If the extension is using HAL processing, false should be returned since the
+     * postview feature is not currently supported for this case.
+     *
+     * @return {@code true} in case postview for still capture is supported
+     * {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isPostviewAvailable();
+}
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index f0821ed..e3317d9 100755
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -20,6 +20,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -116,6 +117,26 @@
                     }
 
                     @Override
+                    public void onPostviewOutputSurface(Surface surface) {
+
+                    }
+
+                    @Override
+                    public void processWithPostview(
+                            Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                            ProcessResultImpl resultCallback, Executor executor) {
+                        if (!isPostviewAvailable()) {
+                            throw new RuntimeException("The extension doesn't support postview");
+                        }
+
+                        if (resultCallback != null) {
+                            process(results, resultCallback, executor);
+                        } else {
+                            process(results);
+                        }
+                    }
+
+                    @Override
                     public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
                             ProcessResultImpl resultCallback, Executor executor) {
                         throw new RuntimeException("The extension doesn't support capture " +
@@ -164,6 +185,11 @@
                     }
 
                     @Override
+                    public void onResolutionUpdate(Size size, Size postviewSize) {
+
+                    }
+
+                    @Override
                     public void onImageFormatUpdate(int imageFormat) {
 
                     }
@@ -244,6 +270,11 @@
     }
 
     @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        return new ArrayList<>();
+    }
+
+    @Override
     public Range<Long> getEstimatedCaptureLatencyRange(Size captureOutputSize) {
         return null;
     }
@@ -257,4 +288,24 @@
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
         return new ArrayList<>();
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
index e29abec..46be86a 100755
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.util.Pair;
 import android.util.Size;
 
@@ -162,4 +163,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java
index d4c2014..518942e 100644
--- a/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java
+++ b/camera2/extensions/sample/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java
@@ -24,7 +24,6 @@
 /**
  * Allows clients to receive information about the capture result values of processed frames.
  *
- * @since 1.3
  */
 public interface ProcessResultImpl {
     /**
@@ -38,6 +37,23 @@
      *                             must also be passed as part of this callback. Both Camera2 and
      *                             CameraX guarantee that those two settings and results are always
      *                             supported and applied by the corresponding framework.
+     * @since 1.3
      */
     void onCaptureCompleted(long shutterTimestamp, List<Pair<CaptureResult.Key, Object>> result);
+
+
+    /**
+     * Capture progress callback that needs to be called when the process capture is
+     * ongoing and includes the estimated progress of the processing.
+     *
+     * <p>Extensions must ensure that they always call this callback with monotonically increasing
+     * values.</p>
+     *
+     * <p>Extensions are allowed to trigger this callback multiple times but at the minimum the
+     * callback is expected to be called once when processing is done with value 100.</p>
+     *
+     * @param progress             Value between 0 and 100.
+     * @since 1.4
+     */
+    void onCaptureProcessProgressed(int progress);
 }
diff --git a/camera2/extensions/service_based_sample/README.txt b/camera2/extensions/service_based_sample/README.txt
new file mode 100644
index 0000000..a212a39
--- /dev/null
+++ b/camera2/extensions/service_based_sample/README.txt
@@ -0,0 +1,26 @@
+Service-Based reference implementation
+This reference implementation demonstrates how to implement the Extensions in a standalone service.
+
+It contains the following components:
+1) oem_library:
+   A Camera Extensions OEM library that implements the Extensions-Interface to enable both Camera2
+   and CameraX Extensions APIS. It is basically a pass-through that forwards all calls from
+   Extensions-Interface to the service. If it works well for you, you don't have to modify it.
+
+   It also contains the AIDL and wrapper classes for communicating with the service. AIDL and
+   wrapper classes are located in androidx.camera.extensions.impl.service package.
+
+   Both Advanced Extender and Basic Extender is supported, however, Advanced Extender is enabled
+   by default. If you want to use Basic Extender to implement, change
+   ExtensionsVersionImpl#isAdvancedExtenderImplemented to return false.
+
+2) extensions_service:
+   A sample implementation of extensions service is provided. You should add your real implementation
+   here. The sample service is built using Android.bp, but you can transform it into a gradle
+   project by adding the stub jar of oem_library (located in
+   out/target/common/obj/JAVA_LIBRARIES/service_based_camera_extensions_intermediates/) to the
+   dependencies.
+
+In this service-based architecture, all functionalities of the Extensions-Interface are supposed to
+be implemented in extensions_service except for ExtensionVersionImpl#checkApiVersion and
+#isAdvancedExtenderImplemented which require you to implement it in the oem_library.
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/extensions_service/Android.bp b/camera2/extensions/service_based_sample/extensions_service/Android.bp
new file mode 100644
index 0000000..383a3c0
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "ExtensionsService",
+    srcs: ["src/**/*.java"],
+    libs: ["service_based_camera_extensions"],
+    static_libs: [
+        "androidx.annotation_annotation",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    enforce_uses_libs: false,
+}
diff --git a/camera2/extensions/service_based_sample/extensions_service/AndroidManifest.xml b/camera2/extensions/service_based_sample/extensions_service/AndroidManifest.xml
new file mode 100644
index 0000000..abd4076
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.oemextensions">
+    <queries>
+        <intent>
+            <action android:name="androidx.camera.extensions.action.VENDOR_ACTION" />
+        </intent>
+    </queries>
+
+    <application
+        android:label="@string/app_name"
+        android:defaultToDeviceProtectedStorage="true"
+        android:directBootAware="true">
+        <uses-library android:name="androidx.camera.extensions.impl" android:required="true" />
+        <service
+            android:name="ExtensionsService"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="androidx.camera.extensions.action.VENDOR_ACTION" />
+            </intent-filter>
+        </service>
+    </application>
+
+</manifest>
diff --git a/camera2/extensions/service_based_sample/extensions_service/res/values/strings.xml b/camera2/extensions/service_based_sample/extensions_service/res/values/strings.xml
new file mode 100644
index 0000000..7a8f9d4
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Camera Extensions Service</string>
+
+</resources>
diff --git a/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/AdvancedExtenderImplStub.java b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/AdvancedExtenderImplStub.java
new file mode 100644
index 0000000..960f9a4
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/AdvancedExtenderImplStub.java
@@ -0,0 +1,268 @@
+/*
+ * 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 com.android.oemextensions;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.IAdvancedExtenderImpl;
+import androidx.camera.extensions.impl.service.ISessionProcessorImpl;
+import androidx.camera.extensions.impl.service.LatencyRange;
+import androidx.camera.extensions.impl.service.SizeList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+public class AdvancedExtenderImplStub extends IAdvancedExtenderImpl.Stub {
+    private static final String TAG = "AdvancedExtenderStub";
+    private Context mContext;
+    private int mExtensionType;
+    private String mCurrentCameraId;
+    private CameraCharacteristics mCameraCharacteristics;
+
+    /**
+     * Construct the AdvancedExtenderImplStub instance.
+     *
+     * @param context       a context.
+     * @param extensionType CameraExtensionCharacteristics#EXTENSION_AUTOMATIC for Auto,
+     *                      CameraExtensionCharacteristics#EXTENSION_NIGHT for Night,
+     *                      CameraExtensionCharacteristics#EXTENSION_HDR  for HDR,
+     *                      CameraExtensionCharacteristics#EXTENSION_BOKEH  for Bokeh,
+     *                      CameraExtensionCharacteristics#EXTENSION_FACE_RETOUCH for face retouch.
+     */
+    public AdvancedExtenderImplStub(@NonNull Context context, int extensionType) {
+        mContext = context;
+        mExtensionType = extensionType;
+    }
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId) throws RemoteException {
+        return true;
+    }
+
+    @Override
+    public void init(@NonNull String cameraId) throws RemoteException {
+        mCurrentCameraId = cameraId;
+        try {
+            CameraManager cameraManager = mContext.getSystemService(CameraManager.class);
+            mCameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
+        } catch (CameraAccessException e) {
+            throw new IllegalStateException("Cannot get CameraCharacteristics", e);
+        }
+    }
+
+    @Override
+    @NonNull
+    public LatencyRange getEstimatedCaptureLatencyRange(@NonNull String cameraId,
+            @Nullable androidx.camera.extensions.impl.service.Size outputSize,
+            int format) throws RemoteException {
+        Log.d(TAG, "getEstimatedCaptureLatencyRange format" + format);
+
+        LatencyRange latencyRange = new LatencyRange();
+        latencyRange.min = 100;
+        latencyRange.max = 1000;
+        return latencyRange;
+    }
+
+    private static SizeList getSupportedSizeByFormat(
+            CameraCharacteristics cameraCharacteristics, int imageFormat) {
+        StreamConfigurationMap streamConfigMap =
+                cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        Size[] sizes = streamConfigMap.getOutputSizes(imageFormat);
+        SizeList sizeList = new SizeList();
+        sizeList.sizes = new ArrayList<>();
+        for (Size size : sizes) {
+            androidx.camera.extensions.impl.service.Size sz =
+                    new androidx.camera.extensions.impl.service.Size();
+            sz.width = size.getWidth();
+            sz.height = size.getHeight();
+            sizeList.sizes.add(sz);
+        }
+        sizeList.format = imageFormat;
+        return sizeList;
+    }
+
+    @Override
+    @NonNull
+    public List<SizeList> getSupportedPreviewOutputResolutions(@NonNull String cameraId)
+            throws RemoteException {
+        return Arrays.asList(
+                getSupportedSizeByFormat(mCameraCharacteristics, ImageFormat.PRIVATE));
+    }
+
+    @Override
+    @NonNull
+    public List<SizeList> getSupportedCaptureOutputResolutions(@NonNull String cameraId)
+            throws RemoteException {
+        return Arrays.asList(
+                getSupportedSizeByFormat(mCameraCharacteristics,
+                        ImageFormat.JPEG),
+                getSupportedSizeByFormat(mCameraCharacteristics,
+                        ImageFormat.YUV_420_888));
+    }
+
+    @Override
+    @NonNull
+    public List<SizeList> getSupportedYuvAnalysisResolutions(@NonNull String cameraId)
+            throws RemoteException {
+        return Arrays.asList(
+                getSupportedSizeByFormat(mCameraCharacteristics, ImageFormat.YUV_420_888));
+    }
+
+    @Override
+    @NonNull
+    public ISessionProcessorImpl getSessionProcessor() throws RemoteException {
+        Log.d(TAG, "getSessionProcessor");
+        return new SimpleSessionProcessorStub(mCameraCharacteristics,
+                getSupportedCaptureRequestKeys().keySet(),
+                getSupportedCaptureResultKeys().keySet());
+    }
+
+    private Map<CaptureRequest.Key, Object> getSupportedCaptureRequestKeys() {
+        Map<CaptureRequest.Key, Object> map = new HashMap<>();
+        map.put(CaptureRequest.CONTROL_ZOOM_RATIO,
+                1.0f /* don't care, must not be null */);
+        map.put(CaptureRequest.SCALER_CROP_REGION,
+                new Rect() /* don't care, must not be null */);
+        map.put(CaptureRequest.CONTROL_AE_REGIONS,
+                new MeteringRectangle[0] /* don't care, must not be null */);
+        map.put(CaptureRequest.CONTROL_AWB_REGIONS,
+                new MeteringRectangle[0] /* don't care, must not be null */);
+        map.put(CaptureRequest.JPEG_QUALITY,
+                (byte)0 /* don't care, must not be null */);
+        map.put(CaptureRequest.JPEG_ORIENTATION,
+                0 /* don't care, must not be null */);
+        if (isAfAutoSupported()) {
+            map.put(CaptureRequest.CONTROL_AF_TRIGGER,
+                    0 /* don't care, must not be null */);
+            map.put(CaptureRequest.CONTROL_AF_MODE,
+                    0 /* don't care, must not be null */);
+            map.put(CaptureRequest.CONTROL_AF_REGIONS,
+                    new MeteringRectangle[0] /* don't care, must not be null */);
+        }
+
+
+        // Filters out unsupported keys
+        List<CaptureRequest.Key<?>> camera2SupportKeys=
+                mCameraCharacteristics.getAvailableCaptureRequestKeys();
+        for (CaptureRequest.Key key : new HashSet<>(map.keySet())) {
+            if (!camera2SupportKeys.contains(key)) {
+                map.remove(key);
+            }
+        }
+
+        return map;
+    }
+
+    private Map<CaptureResult.Key, Object> getSupportedCaptureResultKeys() {
+        Map<CaptureResult.Key, Object> map = new HashMap<>();
+        map.put(CaptureResult.CONTROL_ZOOM_RATIO,
+                1.0f /* don't care, must not be null */);
+        map.put(CaptureResult.SCALER_CROP_REGION,
+                new Rect() /* don't care, must not be null */);
+        map.put(CaptureResult.CONTROL_AE_REGIONS,
+                new MeteringRectangle[0] /* don't care, must not be null */);
+        map.put(CaptureResult.CONTROL_AWB_REGIONS,
+                new MeteringRectangle[0] /* don't care, must not be null */);
+        map.put(CaptureResult.JPEG_QUALITY,
+                (byte)0 /* don't care, must not be null */);
+        map.put(CaptureResult.JPEG_ORIENTATION,
+                0 /* don't care, must not be null */);
+        if (isAfAutoSupported()) {
+            map.put(CaptureResult.CONTROL_AF_REGIONS,
+                    new MeteringRectangle[0] /* don't care, must not be null */);
+            map.put(CaptureResult.CONTROL_AF_TRIGGER,
+                    0 /* don't care, must not be null */);
+            map.put(CaptureResult.CONTROL_AF_MODE,
+                    0 /* don't care, must not be null */);
+            map.put(CaptureResult.CONTROL_AF_STATE,
+                    0 /* don't care, must not be null */);
+        }
+
+        // Filters out unsupported keys
+        List<CaptureResult.Key<?>> camera2SupportKeys=
+                mCameraCharacteristics.getAvailableCaptureResultKeys();
+        for (CaptureResult.Key key : new HashSet<>(map.keySet())) {
+            if (!camera2SupportKeys.contains(key)) {
+                map.remove(key);
+            }
+        }
+        return map;
+    }
+
+
+    private boolean isAfAutoSupported() {
+        int[] afModes = mCameraCharacteristics
+                .get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
+        if (afModes == null) {
+            return false;
+        }
+
+        for (int afMode : afModes) {
+            if (afMode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public CameraMetadataWrapper getAvailableCaptureRequestKeys()
+            throws RemoteException {
+        CameraMetadataWrapper cameraMetadataWrapper =
+                new CameraMetadataWrapper(mCameraCharacteristics);
+        Map<CaptureRequest.Key, Object> keysmap = getSupportedCaptureRequestKeys();
+        for (CaptureRequest.Key key : keysmap.keySet()) {
+            cameraMetadataWrapper.set(key, keysmap.get(key));
+        }
+
+        return cameraMetadataWrapper;
+    }
+
+    @Override
+    public CameraMetadataWrapper getAvailableCaptureResultKeys()
+            throws RemoteException {
+        CameraMetadataWrapper cameraMetadataWrapper =
+                new CameraMetadataWrapper(mCameraCharacteristics);
+        Map<CaptureResult.Key, Object> keysmap = getSupportedCaptureResultKeys();
+        for (CaptureResult.Key key : keysmap.keySet()) {
+            cameraMetadataWrapper.set(key, keysmap.get(key));
+        }
+
+        return cameraMetadataWrapper;
+    }
+}
diff --git a/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/CameraOutputConfigBuilder.java b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/CameraOutputConfigBuilder.java
new file mode 100644
index 0000000..c339ed9
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/CameraOutputConfigBuilder.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 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 com.android.oemextensions;
+
+import android.annotation.NonNull;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.view.Surface;
+
+import androidx.camera.extensions.impl.service.CameraOutputConfig;
+import androidx.camera.extensions.impl.service.Size;
+
+import java.util.ArrayList;
+
+public class CameraOutputConfigBuilder {
+    private final CameraOutputConfig mConfig;
+
+    private CameraOutputConfigBuilder(CameraOutputConfig config) {
+        mConfig = config;
+        mConfig.surfaceGroupId = OutputConfiguration.SURFACE_GROUP_ID_NONE;
+    }
+
+    public static CameraOutputConfigBuilder createSurfaceOutput(
+            int outputId, @NonNull Surface surface) {
+        CameraOutputConfig config = new CameraOutputConfig();
+        config.type = CameraOutputConfig.TYPE_SURFACE;
+        config.outputId = outputId;
+        config.surface = surface;
+        return new CameraOutputConfigBuilder(config);
+    }
+
+    public static CameraOutputConfigBuilder createImageReaderOutput(
+            int outputId, int width, int height, int imageFormat, int maxImages) {
+        CameraOutputConfig config = new CameraOutputConfig();
+        config.type = CameraOutputConfig.TYPE_IMAGEREADER;
+        config.outputId = outputId;
+        config.size = new Size();
+        config.size.width = width;
+        config.size.height = height;
+        config.imageFormat = imageFormat;
+        config.capacity = maxImages;
+        return new CameraOutputConfigBuilder(config);
+    }
+
+    public CameraOutputConfigBuilder setPhysicalCameraId(String physicalCameraId) {
+        mConfig.physicalCameraId = physicalCameraId;
+        return this;
+    }
+
+    public CameraOutputConfigBuilder setSurfaceGroupId(int surfaceGroupId) {
+        mConfig.surfaceGroupId = surfaceGroupId;
+        return this;
+    }
+
+    public CameraOutputConfigBuilder addSharedOutputConfig(CameraOutputConfig cameraOutputConfig) {
+        if (mConfig.sharedSurfaceConfigs == null) {
+            mConfig.sharedSurfaceConfigs = new ArrayList<>();
+        }
+        mConfig.sharedSurfaceConfigs.add(cameraOutputConfig);
+        return this;
+    }
+
+    public CameraOutputConfig build() {
+        CameraOutputConfig result = new CameraOutputConfig();
+        result.outputId = mConfig.outputId;
+        result.type = mConfig.type;
+        result.surface = mConfig.surface;
+        result.physicalCameraId = mConfig.physicalCameraId;
+        result.surfaceGroupId = mConfig.surfaceGroupId;
+        result.capacity = mConfig.capacity;
+        result.imageFormat = mConfig.imageFormat;
+        if (mConfig.size != null) {
+            result.size = new Size();
+            result.size.width = mConfig.size.width;
+            result.size.height = mConfig.size.height;
+        }
+        if (mConfig.sharedSurfaceConfigs != null) {
+            result.sharedSurfaceConfigs = new ArrayList<>(mConfig.sharedSurfaceConfigs);
+        }
+        return result;
+    }
+}
diff --git a/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/ExtensionsService.java b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/ExtensionsService.java
new file mode 100644
index 0000000..3a27971
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/ExtensionsService.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.android.oemextensions;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.camera.extensions.impl.PreviewExtenderImpl;
+import androidx.camera.extensions.impl.service.IAdvancedExtenderImpl;
+import androidx.camera.extensions.impl.service.IExtensionsService;
+import androidx.camera.extensions.impl.service.IOnExtensionsDeinitializedCallback;
+import androidx.camera.extensions.impl.service.IOnExtensionsInitializedCallback;
+import androidx.camera.extensions.impl.service.IImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.service.IPreviewExtenderImpl;
+
+public class ExtensionsService extends Service {
+    private static final String TAG = "ExtensionsService";
+
+    public ExtensionsService() {
+    }
+
+    @Override
+    @NonNull
+    public IBinder onBind(Intent intent) {
+        return new ExtensionsServiceStub();
+    }
+
+    class ExtensionsServiceStub extends IExtensionsService.Stub {
+        @Override
+        public boolean isAdvancedExtenderImplemented() throws RemoteException {
+            return true;
+        }
+
+        @Override
+        public void initialize(String version, IOnExtensionsInitializedCallback callback)
+                throws RemoteException {
+            Log.d(TAG, "initialize");
+            callback.onSuccess();
+        }
+
+        @Override
+        public void deInitialize(IOnExtensionsDeinitializedCallback callback)
+                throws RemoteException {
+            Log.d(TAG, "deInitialize");
+            callback.onSuccess();
+        }
+
+        @Override
+        public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
+                throws RemoteException {
+            Log.d(TAG, "initializeAdvancedExtension");
+            return new AdvancedExtenderImplStub(ExtensionsService.this, extensionType);
+        }
+
+        @Override
+        public IPreviewExtenderImpl initializePreviewExtension(int extensionType) {
+            return new PreviewExtenderImplStub(ExtensionsService.this, extensionType);
+        }
+
+        @Override
+        public IImageCaptureExtenderImpl initializeImageCaptureExtension(int extensionType) {
+            return new ImageCaptureExtenderImplStub(ExtensionsService.this, extensionType);
+        }
+    }
+}
diff --git a/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/ImageCaptureExtenderImplStub.java b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/ImageCaptureExtenderImplStub.java
new file mode 100644
index 0000000..75cd99c
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/ImageCaptureExtenderImplStub.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 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 com.android.oemextensions;
+
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.media.ImageWriter;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.CaptureBundle;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+import androidx.camera.extensions.impl.service.ICaptureProcessorImpl;
+import androidx.camera.extensions.impl.service.IImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.service.IProcessResultImpl;
+import androidx.camera.extensions.impl.service.LatencyRange;
+import androidx.camera.extensions.impl.service.Size;
+import androidx.camera.extensions.impl.service.SizeList;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+public class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub {
+    private static final String TAG = "ImageCaptureExtenderImplStub";
+
+    private static final int DEFAULT_STAGE_ID = 0;
+    private static final int SESSION_STAGE_ID = 101;
+    private static final int MODE = CaptureRequest.CONTROL_AWB_MODE_SHADE;
+
+    private final Context mContext;
+    private final List<CaptureResult.Key> mResultKeyList = Arrays.asList(
+            CaptureResult.CONTROL_AE_MODE,
+            CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER,
+            CaptureResult.CONTROL_AE_LOCK,
+            CaptureResult.CONTROL_AE_STATE,
+            CaptureResult.FLASH_MODE,
+            CaptureResult.FLASH_STATE,
+            CaptureResult.JPEG_QUALITY,
+            CaptureResult.JPEG_ORIENTATION
+    );
+
+    private CameraCharacteristics mCameraCharacteristics;
+    ICaptureProcessorImpl mCaptureProcessor =
+            new ICaptureProcessorImpl.Stub() {
+                private ImageWriter mImageWriter;
+
+                @Override
+                public void onOutputSurface(Surface surface, int imageFormat) {
+                    mImageWriter = ImageWriter.newInstance(surface, 1);
+                }
+
+                @Override
+                public void process(List<CaptureBundle> captureList,
+                        IProcessResultImpl resultCallback) {
+                    CaptureBundle captureBundle = captureList.get(0);
+                    TotalCaptureResult captureResult =
+                            captureBundle.captureResult.toTotalCaptureResult();
+
+                    if (resultCallback != null) {
+                        CameraMetadataWrapper cameraMetadataWrapper =
+                                new CameraMetadataWrapper(mCameraCharacteristics);
+                        Long shutterTimestamp = captureResult.get(CaptureResult.SENSOR_TIMESTAMP);
+                        if (shutterTimestamp != null) {
+                            for (CaptureResult.Key key : mResultKeyList) {
+                                if (captureResult.get(key) != null) {
+                                    cameraMetadataWrapper.set(key, captureResult.get(key));
+                                }
+                            }
+                            try {
+                                resultCallback.onCaptureCompleted(shutterTimestamp,
+                                        cameraMetadataWrapper);
+                            } catch (RemoteException e) {
+
+                            }
+                        }
+                    }
+                    Image image = mImageWriter.dequeueInputImage();
+
+                    // Do processing here
+                    ByteBuffer yByteBuffer = image.getPlanes()[0].getBuffer();
+                    ByteBuffer uByteBuffer = image.getPlanes()[2].getBuffer();
+                    ByteBuffer vByteBuffer = image.getPlanes()[1].getBuffer();
+
+                    Image captureImage = captureBundle.captureImage.get();
+
+                    // Sample here just simply copy/paste the capture image result
+                    yByteBuffer.put(captureImage.getPlanes()[0].getBuffer());
+                    uByteBuffer.put(captureImage.getPlanes()[2].getBuffer());
+                    vByteBuffer.put(captureImage.getPlanes()[1].getBuffer());
+                    Long sensorTimestamp =
+                            captureResult.get(CaptureResult.SENSOR_TIMESTAMP);
+                    if (sensorTimestamp != null) {
+                        image.setTimestamp(sensorTimestamp);
+                    } else {
+                        Log.e(TAG, "Sensor timestamp absent using default!");
+                    }
+
+                    mImageWriter.queueInputImage(image);
+
+                    for (CaptureBundle bundle : captureList) {
+                        bundle.captureImage.decrement();
+                    }
+                }
+
+                @Override
+                public void onResolutionUpdate(androidx.camera.extensions.impl.service.Size size) {
+
+                }
+
+                @Override
+                public void onImageFormatUpdate(int imageFormat) {
+
+                }
+            };
+
+    public ImageCaptureExtenderImplStub(@NonNull Context context, int extensionType) {
+        mContext = context;
+
+    }
+
+    @Override
+    public void onInit(String cameraId) {
+
+    }
+
+    @Override
+    public void onDeInit() {
+
+    }
+
+    @Override
+    @Nullable
+    public CaptureStageImplWrapper onPresetSession() {
+        return null;
+    }
+
+    @Override
+    @Nullable
+    public CaptureStageImplWrapper onEnableSession() {
+        return null;
+    }
+
+    @Override
+    @Nullable
+    public CaptureStageImplWrapper onDisableSession() {
+        return null;
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId) {
+        return true;
+    }
+
+    @Override
+    public void init(String cameraId) {
+        try {
+            CameraManager cameraManager = mContext.getSystemService(CameraManager.class);
+            mCameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Cannot get CameraCharacteristics", e);
+        }
+
+    }
+
+    @Override
+    public ICaptureProcessorImpl getCaptureProcessor() {
+        return mCaptureProcessor;
+    }
+
+    @Override
+    public List<CaptureStageImplWrapper> getCaptureStages() {
+        CaptureStageImplWrapper captureStage = new CaptureStageImplWrapper();
+        captureStage.id = DEFAULT_STAGE_ID;
+        captureStage.parameters = new CameraMetadataWrapper(mCameraCharacteristics);
+        return Arrays.asList(captureStage);
+    }
+
+    @Override
+    public int getMaxCaptureStage() {
+        return 1;
+    }
+
+    @Override
+    public List<SizeList> getSupportedResolutions() {
+        return null;
+    }
+
+    @Override
+    @Nullable
+    public LatencyRange getEstimatedCaptureLatencyRange(Size outputSize) {
+        return null;
+    }
+
+    @Override
+    public CameraMetadataWrapper getAvailableCaptureRequestKeys() {
+        return null;
+    }
+
+    @Override
+    public CameraMetadataWrapper getAvailableCaptureResultKeys() {
+        return null;
+    }
+}
diff --git a/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/PreviewExtenderImplStub.java b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/PreviewExtenderImplStub.java
new file mode 100644
index 0000000..c9add1c
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/PreviewExtenderImplStub.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 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 com.android.oemextensions;
+
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.media.ImageWriter;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+import androidx.camera.extensions.impl.service.IPreviewExtenderImpl;
+import androidx.camera.extensions.impl.service.IPreviewImageProcessorImpl;
+import androidx.camera.extensions.impl.service.IProcessResultImpl;
+import androidx.camera.extensions.impl.service.IRequestUpdateProcessorImpl;
+import androidx.camera.extensions.impl.service.ImageWrapper;
+import androidx.camera.extensions.impl.service.SizeList;
+import androidx.camera.extensions.impl.service.TotalCaptureResultWrapper;
+
+import java.util.List;
+
+public class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub {
+    private static final String TAG = "PreviewExtenderImplStub";
+
+    private static final int DEFAULT_STAGE_ID = 0;
+    private static final int SESSION_STAGE_ID = 101;
+    private static final int MODE = CaptureRequest.CONTROL_AWB_MODE_SHADE;
+    private final int mProcessorType;
+    private final Context mContext;
+    private CaptureStageImplWrapper mCaptureStage;
+    private CameraCharacteristics mCameraCharacteristics;
+    private final SimplePreviewImageProcessor mPreviewImageProcessor;
+    private final SimpleRequestUpdateProcessor mRequestUpdateProcessor;
+
+    public PreviewExtenderImplStub(@NonNull Context context, int extensionType) {
+        mContext = context;
+        switch (extensionType) {
+            case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
+            case CameraExtensionCharacteristics.EXTENSION_BOKEH:
+                mRequestUpdateProcessor = new SimpleRequestUpdateProcessor();
+                mPreviewImageProcessor = null;
+                mProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY;
+                break;
+            case CameraExtensionCharacteristics.EXTENSION_HDR:
+            case CameraExtensionCharacteristics.EXTENSION_NIGHT:
+                mRequestUpdateProcessor = null;
+                mPreviewImageProcessor = new SimplePreviewImageProcessor();
+                mProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR;
+                break;
+            case CameraExtensionCharacteristics.EXTENSION_FACE_RETOUCH:
+            default:
+                mRequestUpdateProcessor = null;
+                mPreviewImageProcessor = null;
+                mProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_NONE;
+                break;
+        }
+    }
+
+    private class SimpleRequestUpdateProcessor extends IRequestUpdateProcessorImpl.Stub {
+        private int mFrameCount = 0;
+        private Integer mWBMode = CaptureRequest.CONTROL_AWB_MODE_AUTO;
+
+        @Override
+        public CaptureStageImplWrapper process(TotalCaptureResultWrapper result) {
+            mFrameCount++;
+            if (mFrameCount % 90 == 0) {
+                mCaptureStage = new CaptureStageImplWrapper();
+                mCaptureStage.id = DEFAULT_STAGE_ID;
+                switch (mWBMode) {
+                    case CaptureRequest.CONTROL_AWB_MODE_AUTO:
+                        mWBMode = MODE;
+                        break;
+                        case MODE:
+                            mWBMode = CaptureRequest.CONTROL_AWB_MODE_AUTO;
+                            break; default:
+                }
+                mCaptureStage.parameters = new CameraMetadataWrapper(
+                        mCameraCharacteristics);
+                mCaptureStage.parameters.set(CaptureRequest.CONTROL_AWB_MODE,
+                        mWBMode);
+                mFrameCount = 0;
+
+                return mCaptureStage;
+            }
+            return null;
+        }
+    };
+    private static class SimplePreviewImageProcessor extends IPreviewImageProcessorImpl.Stub {
+        private ImageWriter mWriter;
+
+        public void close() {
+            if (mWriter != null) {
+                mWriter.close();
+            }
+        }
+        @Override
+        public void onOutputSurface(Surface surface, int imageFormat) {
+            mWriter = ImageWriter.newInstance(surface, imageFormat);
+        }
+
+        @Override
+        public void onResolutionUpdate(
+                androidx.camera.extensions.impl.service.Size size) {
+        }
+
+        @Override
+        public void onImageFormatUpdate(int imageFormat) {
+        }
+
+        @Override
+        public void process(ImageWrapper image, TotalCaptureResultWrapper result,
+                IProcessResultImpl resultCallback) {
+            mWriter.queueInputImage(image.get());
+            image.decrement();
+        }
+    }
+
+    @Override
+    public void onInit(String cameraId) {
+
+    }
+
+    @Override
+    public void onDeInit() {
+        if (mPreviewImageProcessor != null) {
+            mPreviewImageProcessor.close();
+        }
+    }
+
+    @Override
+    @Nullable
+    public CaptureStageImplWrapper onPresetSession() {
+        CaptureStageImplWrapper captureStage = new CaptureStageImplWrapper();
+        captureStage.id = SESSION_STAGE_ID;
+        captureStage.parameters = new CameraMetadataWrapper(mCameraCharacteristics);
+        captureStage.parameters.set(CaptureRequest.CONTROL_AWB_MODE, MODE);
+        return captureStage;
+    }
+
+    @Override
+    @Nullable
+    public CaptureStageImplWrapper onEnableSession() {
+        CaptureStageImplWrapper captureStage = new CaptureStageImplWrapper();
+        captureStage.id = SESSION_STAGE_ID;
+        captureStage.parameters = new CameraMetadataWrapper(mCameraCharacteristics);
+        captureStage.parameters.set(CaptureRequest.CONTROL_AWB_MODE, MODE);
+        return captureStage;
+    }
+
+    @Override
+    @Nullable
+    public CaptureStageImplWrapper onDisableSession() {
+        CaptureStageImplWrapper captureStage = new CaptureStageImplWrapper();
+        captureStage.id = SESSION_STAGE_ID;
+        captureStage.parameters = new CameraMetadataWrapper(mCameraCharacteristics);
+        captureStage.parameters.set(CaptureRequest.CONTROL_AWB_MODE, MODE);
+        return captureStage;
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId) {
+        return true;
+    }
+
+    @Override
+    public void init(String cameraId) {
+        try {
+            CameraManager cameraManager = mContext.getSystemService(CameraManager.class);
+            mCameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Cannot get CameraCharacteristics", e);
+        }
+
+        mCaptureStage = new CaptureStageImplWrapper();
+        mCaptureStage.id = DEFAULT_STAGE_ID;
+        mCaptureStage.parameters = new CameraMetadataWrapper(mCameraCharacteristics);
+        mCaptureStage.parameters.set(CaptureRequest.CONTROL_AWB_MODE,
+                CaptureRequest.CONTROL_AWB_MODE_AUTO);
+    }
+
+    @Override
+    public CaptureStageImplWrapper getCaptureStage() {
+        return mCaptureStage;
+    }
+
+    @Override
+    public int getProcessorType() {
+        return mProcessorType;
+    }
+
+    @Override
+    @Nullable
+    public IPreviewImageProcessorImpl getPreviewImageProcessor() {
+        return mPreviewImageProcessor;
+    }
+
+    @Override
+    @Nullable
+    public IRequestUpdateProcessorImpl getRequestUpdateProcessor() {
+        return mRequestUpdateProcessor;
+    }
+
+    @Override
+    @Nullable
+    public List<SizeList> getSupportedResolutions() {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/RequestBuilder.java b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/RequestBuilder.java
new file mode 100644
index 0000000..52bb26b
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/RequestBuilder.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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 com.android.oemextensions;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.Request;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class RequestBuilder {
+    private static final AtomicInteger sNextRequestId = new AtomicInteger(0);
+    private final List<Integer> mTargetOutputConfigIds = new ArrayList<>();
+    private final CameraMetadataWrapper mParameters;
+    private int mTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+    private final int mRequestId = 0;
+
+    public RequestBuilder(CameraCharacteristics cameraCharacteristics) {
+        mParameters = new CameraMetadataWrapper(cameraCharacteristics);
+    }
+
+    public RequestBuilder addTargetOutputConfigId(int... outputConfigIds) {
+        for (int id : outputConfigIds) {
+            mTargetOutputConfigIds.add(id);
+        }
+        return this;
+    }
+
+    public <T> RequestBuilder setParameter(CaptureRequest.Key<T> key, T value) {
+        mParameters.set(key, value);
+        return this;
+    }
+
+    public RequestBuilder setTemplateId(int templateId) {
+        mTemplateId = templateId;
+        return this;
+    }
+
+    public Request build() {
+        Request request = new Request();
+        int[] idArray = new int[mTargetOutputConfigIds.size()];
+        for (int i = 0; i < idArray.length; i++) {
+            idArray[i] = mTargetOutputConfigIds.get(i);
+        }
+        request.targetOutputConfigIds = idArray;
+        request.requestId = mRequestId;
+        request.parameters = mParameters;
+        request.templateId = mTemplateId;
+        request.requestId = sNextRequestId.getAndIncrement();
+        return request;
+    }
+}
diff --git a/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/SimpleSessionProcessorStub.java b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/SimpleSessionProcessorStub.java
new file mode 100644
index 0000000..ed749ae
--- /dev/null
+++ b/camera2/extensions/service_based_sample/extensions_service/src/com/android/oemextensions/SimpleSessionProcessorStub.java
@@ -0,0 +1,383 @@
+/*
+ * 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 com.android.oemextensions;
+
+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.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.CameraOutputConfig;
+import androidx.camera.extensions.impl.service.CameraSessionConfig;
+import androidx.camera.extensions.impl.service.CaptureFailureWrapper;
+import androidx.camera.extensions.impl.service.CaptureResultWrapper;
+import androidx.camera.extensions.impl.service.ICaptureCallback;
+import androidx.camera.extensions.impl.service.IRequestCallback;
+import androidx.camera.extensions.impl.service.IRequestProcessorImpl;
+import androidx.camera.extensions.impl.service.ISessionProcessorImpl;
+import androidx.camera.extensions.impl.service.OutputSurface;
+import androidx.camera.extensions.impl.service.TotalCaptureResultWrapper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Demonstrates a very simple SessionProcessor which just pass all the output surface into the
+ * camera pipeline directly. No postprocessing is performed.
+ */
+public class SimpleSessionProcessorStub extends ISessionProcessorImpl.Stub {
+    private static final String TAG = "SimpleSessionProcessor";
+    private final CameraCharacteristics mCameraCharacteristics;
+    private final Set<CaptureRequest.Key> mSupportedRequestKeys;
+    private final Set<CaptureResult.Key> mSupportedResultKeys;
+
+    private IRequestProcessorImpl mIRequestProcessor;
+    private AtomicInteger mNextCaptureSequenceId = new AtomicInteger(1);
+    private Map<CaptureRequest.Key, Object> mParameters = new HashMap<>();
+    private boolean mHasAnalysisOutput = false;
+
+    private static final int PREVIEW_OUTPUT_ID = 1;
+    private static final int CAPTURE_OUTPUT_ID = 2;
+    private static final int ANALYSIS_OUTPUT_ID = 3;
+
+    public SimpleSessionProcessorStub(@NonNull CameraCharacteristics cameraCharacteristics,
+            @NonNull Set<CaptureRequest.Key> supportedRequestKeys,
+            @NonNull Set<CaptureResult.Key> supportedResultKeys) {
+        mCameraCharacteristics = cameraCharacteristics;
+        mSupportedRequestKeys = supportedRequestKeys;
+        mSupportedResultKeys = supportedResultKeys;
+    }
+
+    @Override
+    public CameraSessionConfig initSession(@NonNull String cameraId,
+            @NonNull OutputSurface outputSurfacePreview,
+            @NonNull OutputSurface outputSurfaceStillCapture,
+            @Nullable OutputSurface outputSurfaceAnalysis) throws RemoteException {
+        Log.d(TAG, "initSession cameraId=" + cameraId);
+        CameraSessionConfig cameraSessionConfig = new CameraSessionConfig();
+        cameraSessionConfig.sessionParameter = new CameraMetadataWrapper(mCameraCharacteristics);
+        // if needed, invoke cameraSessionConfig.sessionParameter.set(...) to set session parameters
+        cameraSessionConfig.sessionTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+        List<CameraOutputConfig> outputConfigList = new ArrayList<>();
+
+        // Preview
+        CameraOutputConfig previewOutputConfig =
+                CameraOutputConfigBuilder
+                        .createSurfaceOutput(PREVIEW_OUTPUT_ID, outputSurfacePreview.surface)
+                        .build();
+        outputConfigList.add(previewOutputConfig);
+
+        // Still capture
+        CameraOutputConfig captureOutputConfig =
+                CameraOutputConfigBuilder
+                        .createSurfaceOutput(CAPTURE_OUTPUT_ID, outputSurfaceStillCapture.surface)
+                        .build();
+        outputConfigList.add(captureOutputConfig);
+
+        // ImageAnalysis
+        if (outputSurfaceAnalysis != null) {
+            mHasAnalysisOutput = true;
+            CameraOutputConfig analysisOutputConfig =
+                    CameraOutputConfigBuilder
+                            .createSurfaceOutput(ANALYSIS_OUTPUT_ID, outputSurfaceAnalysis.surface)
+                            .build();
+            outputConfigList.add(analysisOutputConfig);
+        }
+
+        cameraSessionConfig.outputConfigs = outputConfigList;
+        return cameraSessionConfig;
+    }
+
+    @Override
+    public void deInitSession() throws RemoteException {
+        Log.d(TAG, "deInitSession");
+    }
+
+    @Override
+    public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor)
+            throws RemoteException {
+        Log.d(TAG, "onCaptureSessionStart");
+        mIRequestProcessor = requestProcessor;
+    }
+
+    @Override
+    public void onCaptureSessionEnd() throws RemoteException {
+        Log.d(TAG, "onCaptureSessionEnd");
+
+        mIRequestProcessor = null;
+    }
+
+    @Override
+    public void setParameters(CaptureRequest captureRequest) throws RemoteException {
+        Log.d(TAG, "setParameters");
+
+        for (CaptureRequest.Key<?> key : captureRequest.getKeys()) {
+            if (mSupportedRequestKeys.contains(key)) {
+                mParameters.put(key, captureRequest.get(key));
+            }
+        }
+    }
+
+    protected void notifyOnCaptureCompleted(
+            @NonNull int seqId,
+            @NonNull TotalCaptureResult result,
+            @NonNull ICaptureCallback captureCallback) {
+        Long shutterTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+        if (shutterTimestamp != null) {
+            CameraMetadataWrapper cameraMetadataWrapper =
+                    new CameraMetadataWrapper(mCameraCharacteristics);
+
+            for (CaptureResult.Key key : mSupportedResultKeys) {
+                if (result.get(key) != null) {
+                    cameraMetadataWrapper.set(key, result.get(key));
+                }
+            }
+
+            try {
+                captureCallback.onCaptureCompleted(shutterTimestamp, seqId,
+                        cameraMetadataWrapper);
+            } catch (RemoteException e) {
+                Log.e(TAG, "cannot notify onCaptureCompleted", e);
+            }
+        }
+    }
+
+    @Override
+    public int startTrigger(CaptureRequest captureRequest, ICaptureCallback captureCallback)
+            throws RemoteException {
+        Log.d(TAG, "startTrigger");
+
+        int captureSequenceId = mNextCaptureSequenceId.getAndIncrement();
+
+        RequestBuilder requestBuilder = new RequestBuilder(mCameraCharacteristics)
+                .addTargetOutputConfigId(PREVIEW_OUTPUT_ID)
+                .setTemplateId(CameraDevice.TEMPLATE_PREVIEW);
+
+        if (mHasAnalysisOutput) {
+            requestBuilder.addTargetOutputConfigId(ANALYSIS_OUTPUT_ID);
+        }
+        applyParameters(requestBuilder);
+
+        for (CaptureRequest.Key key : captureRequest.getKeys()) {
+            if (mSupportedRequestKeys.contains(key)) {
+                requestBuilder.setParameter(key, captureRequest.get(key));
+            } else {
+                Log.e(TAG, "startTrigger: key " + key + " not supported");
+            }
+        }
+
+        mIRequestProcessor.submit(requestBuilder.build(), new IRequestCallback.Stub() {
+            @Override
+            public void onCaptureStarted(int requestId, long frameNumber, long timestamp)
+                    throws RemoteException {
+                captureCallback.onCaptureStarted(captureSequenceId, timestamp);
+            }
+
+            @Override
+            public void onCaptureProgressed(int requestId, CaptureResultWrapper partialResult)
+                    throws RemoteException {
+                CaptureResult captureResult = partialResult.toCaptureResult();
+            }
+
+            @Override
+            public void onCaptureCompleted(int requestId,
+                    TotalCaptureResultWrapper totalCaptureResult) throws RemoteException {
+                TotalCaptureResult captureResult = totalCaptureResult.toTotalCaptureResult();
+                captureCallback.onCaptureProcessStarted(captureSequenceId);
+                notifyOnCaptureCompleted(captureSequenceId, captureResult, captureCallback);
+            }
+
+            @Override
+            public void onCaptureFailed(int requestId, CaptureFailureWrapper captureFailureWrapper)
+                    throws RemoteException {
+                CaptureFailure captureFailure = captureFailureWrapper.toCaptureFailure();
+                captureCallback.onCaptureFailed(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId)
+                    throws RemoteException {
+                captureCallback.onCaptureFailed(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureSequenceCompleted(int sequenceId, long frameNumber)
+                    throws RemoteException {
+                captureCallback.onCaptureSequenceCompleted(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureSequenceAborted(int sequenceId) throws RemoteException {
+                captureCallback.onCaptureSequenceAborted(captureSequenceId);
+            }
+        });
+        return captureSequenceId;
+    }
+
+    private void applyParameters(RequestBuilder requestBuilder) {
+        for (CaptureRequest.Key key : mParameters.keySet()) {
+            requestBuilder.setParameter(key, mParameters.get(key));
+        }
+    }
+
+    @Override
+    public int startRepeating(ICaptureCallback captureCallback) throws RemoteException {
+        Log.d(TAG, "startRepeating");
+
+        int captureSequenceId = mNextCaptureSequenceId.getAndIncrement();
+
+        RequestBuilder requestBuilder = new RequestBuilder(mCameraCharacteristics)
+                .addTargetOutputConfigId(PREVIEW_OUTPUT_ID)
+                .setTemplateId(CameraDevice.TEMPLATE_PREVIEW);
+        if (mHasAnalysisOutput) {
+            requestBuilder.addTargetOutputConfigId(ANALYSIS_OUTPUT_ID);
+        }
+        applyParameters(requestBuilder);
+
+        mIRequestProcessor.setRepeating(requestBuilder.build(), new IRequestCallback.Stub() {
+            @Override
+            public void onCaptureStarted(int requestId, long frameNumber, long timestamp)
+                    throws RemoteException {
+                captureCallback.onCaptureStarted(captureSequenceId, timestamp);
+
+            }
+
+            @Override
+            public void onCaptureProgressed(int requestId, CaptureResultWrapper partialResult)
+                    throws RemoteException {
+                CaptureResult captureResult = partialResult.toCaptureResult();
+            }
+
+            @Override
+            public void onCaptureCompleted(int requestId,
+                    TotalCaptureResultWrapper totalCaptureResult) throws RemoteException {
+                TotalCaptureResult captureResult = totalCaptureResult.toTotalCaptureResult();
+                captureCallback.onCaptureProcessStarted(captureSequenceId);
+                notifyOnCaptureCompleted(captureSequenceId, captureResult, captureCallback);
+            }
+
+            @Override
+            public void onCaptureFailed(int requestId, CaptureFailureWrapper captureFailureWrapper)
+                    throws RemoteException {
+                CaptureFailure captureFailure = captureFailureWrapper.toCaptureFailure();
+                captureCallback.onCaptureFailed(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId)
+                    throws RemoteException {
+                captureCallback.onCaptureFailed(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureSequenceCompleted(int sequenceId, long frameNumber)
+                    throws RemoteException {
+                captureCallback.onCaptureSequenceCompleted(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureSequenceAborted(int sequenceId) throws RemoteException {
+                captureCallback.onCaptureSequenceAborted(captureSequenceId);
+            }
+        });
+        return captureSequenceId;
+    }
+
+    @Override
+    public void stopRepeating() throws RemoteException {
+        Log.d(TAG, "stopRepeating");
+
+        mIRequestProcessor.stopRepeating();
+    }
+
+    @Override
+    public int startCapture(ICaptureCallback captureCallback) throws RemoteException {
+        Log.d(TAG, "startCapture");
+
+        int captureSequenceId = mNextCaptureSequenceId.getAndIncrement();
+
+        RequestBuilder requestBuilder = new RequestBuilder(mCameraCharacteristics)
+                .addTargetOutputConfigId(CAPTURE_OUTPUT_ID)
+                .setTemplateId(CameraDevice.TEMPLATE_STILL_CAPTURE);
+
+        applyParameters(requestBuilder);
+
+        mIRequestProcessor.submit(requestBuilder.build(), new IRequestCallback.Stub() {
+            @Override
+            public void onCaptureStarted(int requestId, long frameNumber, long timestamp)
+                    throws RemoteException {
+                captureCallback.onCaptureStarted(captureSequenceId, timestamp);
+            }
+
+            @Override
+            public void onCaptureProgressed(int requestId,
+                    CaptureResultWrapper captureResultWrapper)
+                    throws RemoteException {
+            }
+
+            @Override
+            public void onCaptureCompleted(int requestId,
+                    TotalCaptureResultWrapper totalCaptureResultWrapper) throws RemoteException {
+                TotalCaptureResult captureResult = totalCaptureResultWrapper.toTotalCaptureResult();
+                captureCallback.onCaptureProcessStarted(captureSequenceId);
+                notifyOnCaptureCompleted(captureSequenceId, captureResult, captureCallback);
+            }
+
+            @Override
+            public void onCaptureFailed(int requestId, CaptureFailureWrapper captureFailureWrapper)
+                    throws RemoteException {
+                captureCallback.onCaptureFailed(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId)
+                    throws RemoteException {
+                captureCallback.onCaptureFailed(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureSequenceCompleted(int sequenceId, long frameNumber)
+                    throws RemoteException {
+                captureCallback.onCaptureSequenceCompleted(captureSequenceId);
+            }
+
+            @Override
+            public void onCaptureSequenceAborted(int sequenceId) throws RemoteException {
+                captureCallback.onCaptureSequenceAborted(captureSequenceId);
+
+            }
+        });
+        return captureSequenceId;
+    }
+
+    @Override
+    public void abortCapture(int i) throws RemoteException {
+        Log.d(TAG, "abortCapture");
+        mIRequestProcessor.abortCaptures();
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/Android.bp b/camera2/extensions/service_based_sample/oem_library/Android.bp
new file mode 100644
index 0000000..adfb1b5
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "service_based_camera_extensions",
+    installable: true,
+    srcs: [
+        "src/java/**/*.java",
+        "src/java/**/*.aidl",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+    ],
+    aidl: {
+        local_include_dirs: ["src/java"],
+        include_dirs: [
+           "frameworks/native/aidl/gui",  // For SurfaceView AIDL
+           "frameworks/av/camera/aidl",   // For CaptureRequest AIDL
+        ]
+    },
+    system_ext_specific: true,
+    platform_apis: true,
+}
+
+prebuilt_etc {
+    name: "service_based_camera_extensions.xml",
+    src: "service_based_camera_extensions.xml",
+    sub_dir: "permissions",
+    system_ext_specific: true,
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/AndroidManifest.xml b/camera2/extensions/service_based_sample/oem_library/AndroidManifest.xml
new file mode 100644
index 0000000..54b5b10
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="androidx.camera.extensions.impl" >
+</manifest>
diff --git a/camera2/extensions/service_based_sample/oem_library/service_based_camera_extensions.xml b/camera2/extensions/service_based_sample/oem_library/service_based_camera_extensions.xml
new file mode 100644
index 0000000..6501b9b
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/service_based_camera_extensions.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<permissions>
+    <library name="androidx.camera.extensions.impl"
+             file="/system_ext/framework/service_based_camera_extensions.jar"/>
+</permissions>
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
new file mode 100755
index 0000000..55486a3
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardImageCaptureExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for auto image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class AutoImageCaptureExtenderImpl extends ForwardImageCaptureExtender {
+    public AutoImageCaptureExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_AUTOMATIC);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
new file mode 100755
index 0000000..94b8808
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardPreviewExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for auto preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class AutoPreviewExtenderImpl extends ForwardPreviewExtender {
+    public AutoPreviewExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_AUTOMATIC);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
new file mode 100755
index 0000000..ce9e9ea
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardImageCaptureExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for beauty image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BeautyImageCaptureExtenderImpl extends ForwardImageCaptureExtender {
+    public BeautyImageCaptureExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_FACE_RETOUCH);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
new file mode 100755
index 0000000..0a452c3
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardPreviewExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for beauty preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BeautyPreviewExtenderImpl extends ForwardPreviewExtender {
+    public BeautyPreviewExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_FACE_RETOUCH);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
new file mode 100644
index 0000000..f24e9f1
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardImageCaptureExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for bokeh image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BokehImageCaptureExtenderImpl extends ForwardImageCaptureExtender {
+    public BokehImageCaptureExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_BOKEH);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
new file mode 100644
index 0000000..e17d941
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardPreviewExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for bokeh preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class BokehPreviewExtenderImpl extends ForwardPreviewExtender {
+    public BokehPreviewExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_BOKEH);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
new file mode 100644
index 0000000..3eee146
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.util.Pair;
+import android.view.Surface;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * The interface for processing a set of {@link Image}s that have captured.
+ *
+ * @since 1.0
+ */
+@SuppressLint("UnknownNullness")
+public interface CaptureProcessorImpl extends ProcessorImpl {
+    /**
+     * Process a set images captured that were requested.
+     *
+     * <p> The result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}.
+     *
+     * @param results The map of {@link ImageFormat#YUV_420_888} format images and metadata to
+     *                process. The {@link Image} that are contained within the map will become
+     *                invalid after this method completes, so no references to them should be kept.
+     */
+    void process(Map<Integer, Pair<Image, TotalCaptureResult>> results);
+
+    /**
+     * Process a set images captured that were requested.
+     *
+     * <p> The result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}.
+     *
+     * @param results        The map of {@link ImageFormat#YUV_420_888} format images and metadata
+     *                       to process. The {@link Image} that are contained within the map will
+     *                       become invalid after this method completes, so no references to them
+     *                       should be kept.
+     * @param resultCallback Capture result callback to be called once the capture result
+     *                       values of the processed image are ready.
+     * @param executor       The executor to run the callback on. If null then the callback will
+     *                       run on any arbitrary executor.
+     * @since 1.3
+     */
+    void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            ProcessResultImpl resultCallback, Executor executor);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/CaptureStageImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/CaptureStageImpl.java
new file mode 100644
index 0000000..c4f4a47
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/CaptureStageImpl.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.hardware.camera2.CaptureRequest;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * The set of parameters that defines a single capture that will be sent to the camera.
+ *
+ * @since 1.0
+ */
+public interface CaptureStageImpl {
+    /** Returns the identifier for the {@link CaptureStageImpl}. */
+    int getId();
+
+    /**
+     * Returns the set of {@link CaptureRequest.Key} and the corresponding values that will be
+     * set for a single {@link CaptureRequest}.
+     */
+    @NonNull
+    List<Pair<CaptureRequest.Key, Object>> getParameters();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java
new file mode 100644
index 0000000..a74a880
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ExtenderStateListener.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Provides interfaces that the OEM needs to implement to handle the state change.
+ *
+ * @since 1.0
+ */
+public interface ExtenderStateListener {
+
+    /**
+     * Notify to initialize the extension. This will be called after bindToLifeCycle. This is
+     * where the use case is started and would be able to allocate resources here. After onInit() is
+     * called, the camera ID, cameraCharacteristics and context will not change until onDeInit()
+     * has been called.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     * @param context The {@link Context} used for CameraX.
+     */
+    void onInit(@NonNull String cameraId, @NonNull CameraCharacteristics cameraCharacteristics,
+            @NonNull Context context);
+
+    /**
+     * Notify to de-initialize the extension. This callback will be invoked after unbind.
+     * After onDeInit() was called, it is expected that the camera ID, cameraCharacteristics will
+     * no longer hold, this should be where to clear all resources allocated for this use case.
+     */
+    void onDeInit();
+
+    /**
+     * This will be invoked before creating a
+     * {@link CameraCaptureSession}. The {@link CaptureRequest}
+     * parameters returned via {@link CaptureStageImpl} will be passed to the camera device as
+     * part of the capture session initialization via
+     * {@link SessionConfiguration#setSessionParameters(CaptureRequest)} which only supported from
+     * API level 28. The valid parameter is a subset of the available capture request parameters.
+     *
+     * @return The request information to set the session wide camera parameters.
+     */
+    @Nullable
+    CaptureStageImpl onPresetSession();
+
+    /**
+     * This will be invoked once after the {@link CameraCaptureSession}
+     * has been created. The {@link CaptureRequest} parameters returned via
+     * {@link CaptureStageImpl} will be used to generate a single request to the current
+     * configured {@link CameraDevice}. The generated request will be submitted to camera before
+     * processing other single requests.
+     *
+     * @return The request information to create a single capture request to camera device.
+     */
+    @Nullable
+    CaptureStageImpl onEnableSession();
+
+    /**
+     * This will be invoked before the {@link CameraCaptureSession} is
+     * closed. The {@link CaptureRequest} parameters returned via {@link CaptureStageImpl} will
+     * be used to generate a single request to the currently configured {@link CameraDevice}. The
+     * generated request will be submitted to camera before the CameraCaptureSession is closed.
+     *
+     * @return The request information to customize the session.
+     */
+    @Nullable
+    CaptureStageImpl onDisableSession();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
new file mode 100644
index 0000000..cc357b1
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Stub implementation for the extension version check.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public class ExtensionVersionImpl {
+    public ExtensionVersionImpl() {
+    }
+
+    /**
+     * Provide the current CameraX extension library version to vendor library and vendor would
+     * need to return the supported version for this device. If the returned version is not
+     * supported by CameraX library, the Preview and ImageCapture would not be able to enable the
+     * specific effects provided by the vendor.
+     *
+     * <p>CameraX library provides the Semantic Versioning string in a form of
+     * MAJOR.MINOR.PATCH-description
+     * We will increment the
+     * MAJOR version when make incompatible API changes,
+     * MINOR version when add functionality in a backwards-compatible manner, and
+     * PATCH version when make backwards-compatible bug fixes. And the description can be ignored.
+     *
+     * <p>Vendor library should provide MAJOR.MINOR.PATCH to CameraX. The MAJOR and MINOR
+     * version is used to map to the version of CameraX that it supports, and CameraX extension
+     * would only available when MAJOR version is matched with CameraX current version. The PATCH
+     * version does not indicate compatibility. The patch version should be incremented whenever
+     * the vendor library makes bug fixes or updates to the algorithm.
+     *
+     * @param version the version of CameraX library formatted as MAJOR.MINOR.PATCH-description.
+     * @return the version that vendor supported in this device. The MAJOR.MINOR.PATCH format
+     * should be used.
+     */
+    @SuppressWarnings("unused")
+    @NonNull
+    public String checkApiVersion(@NonNull String version) {
+        // This sample implements Extensions-Interface 1.3.0.
+        return "1.3.0";
+    }
+
+    /**
+     * Specify whether or not CameraX should invoke the AdvancedExtenderImpl instead of
+     * PreviewExtenderImpl/ImageCaptureExtenderImpl.
+     *
+     * <p>Starting from version 1.2, a set of alternative interfaces called advanced extender for
+     * implementing extensions are provided to OEMs as another option. OEMs can continue using
+     * previous interfaces (PreviewExtenderImpl/ImageCaptureExtenderImpl, also called basic
+     * extender).
+     *
+     * <p>OEMs should return false here if only basic extender is implemented. When returning true,
+     * CameraX will invoke the AdvancedExtenderImpl implementation in advanced package for all
+     * types of extension modes.
+     *
+     * <p>ExtensionVersionImpl, InitializerImpl will still be called for both basic and advanced
+     * extender implementation paths.
+     *
+     * @return true if AdvancedExtenderImpl is implemented
+     * @since 1.2
+     */
+    public boolean isAdvancedExtenderImplemented() {
+        return true;
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
new file mode 100644
index 0000000..264a714
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardImageCaptureExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for HDR image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class HdrImageCaptureExtenderImpl extends ForwardImageCaptureExtender {
+    public HdrImageCaptureExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_HDR);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
new file mode 100644
index 0000000..8e7bbd8
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardPreviewExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for HDR preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class HdrPreviewExtenderImpl extends ForwardPreviewExtender {
+    public HdrPreviewExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_HDR);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
new file mode 100644
index 0000000..10b4513
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Provides abstract methods that the OEM needs to implement to enable extensions for image capture.
+ *
+ * @since 1.0
+ */
+public interface ImageCaptureExtenderImpl extends ExtenderStateListener {
+    /**
+     * Indicates whether the extension is supported on the device.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     * @return true if the extension is supported, otherwise false
+     */
+    boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * Initializes the extender to be used with the specified camera.
+     *
+     * <p>This should be called before any other method on the extender. The exception is {@link
+     * #isExtensionAvailable(String, CameraCharacteristics)}.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     */
+    void init(@NonNull String cameraId, @NonNull CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * The processing that will be done on a set of captures to create and image with the effect.
+     */
+    @Nullable
+    CaptureProcessorImpl getCaptureProcessor();
+
+    /** The set of captures that are needed to create an image with the effect. */
+    @Nullable
+    List<CaptureStageImpl> getCaptureStages();
+
+    /**
+     * Returns the maximum size of the list returned by {@link #getCaptureStages()}.
+     * @return the maximum count.
+     */
+    int getMaxCaptureStage();
+
+    /**
+     * Returns the customized supported resolutions.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     *
+     * <p>The returned resolutions should be subset of the supported sizes retrieved from
+     * {@link StreamConfigurationMap} for the camera device. If the
+     * returned list is not null, it will be used to find the best resolutions combination for
+     * the bound use cases.
+     *
+     * @return the customized supported resolutions, or null to support all sizes retrieved from
+     * {@link StreamConfigurationMap}.
+     * @since 1.1
+     */
+    @Nullable
+    List<Pair<Integer, Size[]>> getSupportedResolutions();
+
+    /**
+     * Returns the estimated capture latency range in milliseconds for the target capture
+     * resolution.
+     *
+     * <p>This includes the time spent processing the multi-frame capture request along with any
+     * additional time for encoding of the processed buffer in the framework if necessary.</p>
+     *
+     * @param captureOutputSize size of the capture output surface. If it is null or not in the
+     *                          supported output sizes, maximum capture output size is used for
+     *                          the estimation.
+     * @return the range of estimated minimal and maximal capture latency in milliseconds, or
+     * null if no capture latency info can be provided.
+     * @since 1.2
+     */
+    @Nullable
+    Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize);
+
+    /**
+     * Return a list of orthogonal capture request keys.
+     *
+     * <p>Any keys included in the list will be configurable by clients of the extension and will
+     * affect the extension functionality.</p>
+     *
+     * <p>Do note that the list of keys applies to {@link PreviewExtenderImpl} as well.</p>
+     *
+     * <p>Also note that the keys {@link CaptureRequest#JPEG_QUALITY} and
+     * {@link CaptureRequest#JPEG_ORIENTATION} are always supported regardless being added in the
+     * list or not. To support common camera operations like zoom, tap-to-focus, flash and
+     * exposure compensation, we recommend supporting the following keys if possible.
+     * <pre>
+     *  zoom:  {@link CaptureRequest#CONTROL_ZOOM_RATIO}
+     *         {@link CaptureRequest#SCALER_CROP_REGION}
+     *  tap-to-focus:
+     *         {@link CaptureRequest#CONTROL_AF_MODE}
+     *         {@link CaptureRequest#CONTROL_AF_TRIGGER}
+     *         {@link CaptureRequest#CONTROL_AF_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AE_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AWB_REGIONS}
+     *  flash:
+     *         {@link CaptureRequest#CONTROL_AE_MODE}
+     *         {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER}
+     *         {@link CaptureRequest#FLASH_MODE}
+     *  exposure compensation:
+     *         {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION}
+     * </pre>
+     * On basic extensions that implement 1.2 or prior version, the above keys are all supported
+     * explicitly. When migrating from 1.2 or prior to 1.3, please note that both CameraX and
+     * Camera2 will honor the returned list and support only the keys contained in it. For
+     * example, if OEM decides to return only {@link CaptureRequest#CONTROL_ZOOM_RATIO} and
+     * {@link CaptureRequest#SCALER_CROP_REGION} in the 1.3 implementation, it means only zoom is
+     * supported for the app while tap-to-focus , flash and exposure compensation are not allowed.
+     *
+     * @return List of supported orthogonal capture keys, or an empty list if no capture settings
+     * are not supported.
+     * @since 1.3
+     */
+    @NonNull
+    List<CaptureRequest.Key> getAvailableCaptureRequestKeys();
+
+    /**
+     * Return a list of supported capture result keys.
+     *
+     * <p>Any keys included in this list must be available as part of the registered
+     * {@link ProcessResultImpl} callback. In case frame processing is not supported,
+     * then the Camera2/CameraX framework will use the list to filter and notify camera clients
+     * using the respective camera results.</p>
+     *
+     * <p>At the very minimum, it is expected that the result key list is a superset of the
+     * capture request keys.</p>
+     *
+     * <p>Do note that the list of keys applies to {@link PreviewExtenderImpl} as well.</p>
+     *
+     * @return List of supported capture result keys, or an empty list if capture results are not
+     * supported.
+     * @since 1.3
+     */
+    @NonNull
+    List<CaptureResult.Key> getAvailableCaptureResultKeys();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/InitializerImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/InitializerImpl.java
new file mode 100644
index 0000000..5a9f0ff
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/InitializerImpl.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ServiceManager;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Used for initializing the extensions library.
+ *
+ * @since 1.1
+ */
+public class InitializerImpl {
+    private InitializerImpl() {
+    }
+
+    /** An unknown error has occurred. */
+    public static final int ERROR_UNKNOWN = 0;
+
+    /**
+     * Error reported if the application version of extensions is incompatible with the on device
+     * library version.
+     */
+    public static final int ERROR_INITIALIZE_VERSION_INCOMPATIBLE = 1;
+
+    /**
+     * Initializes the {@link Context}.
+     *
+     * <p>Before this call has been made no calls to the extensions library should be made except
+     * for {@link ExtensionVersionImpl#checkApiVersion(String)}.
+     *
+     * @param version  The version of the extension used by the application.
+     * @param context  The {@link Context} of the calling application.
+     * @param executor The executor to run the callback on. If null then the callback will run on
+     *                 any arbitrary executor.
+     */
+    public static void init(@NonNull String version, @NonNull Context context,
+            @NonNull OnExtensionsInitializedCallback callback, @Nullable Executor executor) {
+        ServiceManager.init(context, version, callback, executor);
+    }
+
+    /**
+     * Deinitializes the extensions to release resources.
+     *
+     * <p>After this call has been made no calls to the extensions library should be made except
+     * for {@link ExtensionVersionImpl#checkApiVersion(String)}.
+     *
+     * @param executor The executor to run the callback on. If null then the callback will run on
+     *                 any arbitrary executor.
+     */
+    public static void deinit(@NonNull OnExtensionsDeinitializedCallback callback,
+            @Nullable Executor executor) {
+        ServiceManager.getInstance().deinit(callback, executor);
+    }
+
+    /**
+     * Callback that gets called when the library has finished initializing and is ready for used.
+     */
+    public interface OnExtensionsInitializedCallback {
+        /** Called if the library successfully initializes. */
+        void onSuccess();
+
+        /**
+         * Called if the library is unable to successfully initialize.
+         *
+         * @param error The reason for failing to initialize.
+         */
+        void onFailure(int error);
+    }
+
+    /**
+     * Callback that gets called when the library has finished deinitialized.
+     *
+     * <p> Once this interface has been called then
+     * {@link #init(String, Context, OnExtensionsInitializedCallback, Executor)} can be called
+     * again regardless of whether or not the deinitialization has succeeded or failed.
+     */
+    public interface OnExtensionsDeinitializedCallback {
+        /**
+         * Called if the library successfully deinitializes.
+         */
+        void onSuccess();
+
+        /**
+         * Called if the library encountered some error during the deinitialization.
+         *
+         * <p>Even if the library fails to deinitialize it is now valid for
+         * {@link #init(String, Context, OnExtensionsInitializedCallback, Executor)} to be called
+         * again.
+         *
+         * @param error The reason for failing to deinitialize.
+         */
+        void onFailure(int error);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
new file mode 100755
index 0000000..36c554f
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardImageCaptureExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for night image capture use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class NightImageCaptureExtenderImpl extends ForwardImageCaptureExtender {
+    public NightImageCaptureExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_NIGHT);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
new file mode 100755
index 0000000..973e79c
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.serviceforward.ForwardPreviewExtender;
+
+import java.util.List;
+
+/**
+ * Stub implementation for night preview use case.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.0
+ */
+public final class NightPreviewExtenderImpl extends ForwardPreviewExtender {
+    public NightPreviewExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_NIGHT);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
new file mode 100644
index 0000000..b320813
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Provides abstract methods that the OEM needs to implement to enable extensions in the preview.
+ *
+ * @since 1.0
+ */
+public interface PreviewExtenderImpl extends ExtenderStateListener {
+    /** The different types of the preview processing. */
+    enum ProcessorType {
+        /** Processor which only updates the {@link CaptureStageImpl}. */
+        PROCESSOR_TYPE_REQUEST_UPDATE_ONLY,
+        /** Processor which updates the received {@link android.media.Image}. */
+        PROCESSOR_TYPE_IMAGE_PROCESSOR,
+        /** No processor, only a {@link CaptureStageImpl} is defined. */
+        PROCESSOR_TYPE_NONE
+    }
+
+    /**
+     * Indicates whether the extension is supported on the device.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     * @return true if the extension is supported, otherwise false
+     */
+    boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * Initializes the extender to be used with the specified camera.
+     *
+     * <p>This should be called before any other method on the extender. The exception is {@link
+     * #isExtensionAvailable(String, CameraCharacteristics)}.
+     *
+     * @param cameraId The camera2 id string of the camera.
+     * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+     */
+    void init(@NonNull String cameraId, @NonNull CameraCharacteristics cameraCharacteristics);
+
+    /**
+     * The set of parameters required to produce the effect on the preview stream.
+     *
+     * <p> This will be the initial set of parameters used for the preview
+     * {@link CaptureRequest}. If the {@link ProcessorType} is defined as
+     * {@link ProcessorType#PROCESSOR_TYPE_REQUEST_UPDATE_ONLY} then this will be updated when
+     * the {@link RequestUpdateProcessorImpl#process(TotalCaptureResult)} from {@link
+     * #getProcessor()} has been called, this should be updated to reflect the new {@link
+     * CaptureStageImpl}. If the processing step returns a {@code null}, meaning the required
+     * parameters has not changed, then calling this will return the previous non-null value.
+     */
+    @NonNull
+    CaptureStageImpl getCaptureStage();
+
+    /** The type of preview processing to use. */
+    @NonNull
+    ProcessorType getProcessorType();
+
+    /**
+     * Returns a processor which only updates the {@link CaptureStageImpl}.
+     *
+     * <p>The type of processor is dependent on the return of {@link #getProcessorType()}. The
+     * type of ProcessorImpl returned will be according to the following table.
+     *
+     * <table>
+     * <tr><th> ProcessorType </th> <th> ProcessorImpl </th> </tr>
+     * <tr><td> PROCESSOR_TYPE_REQUEST_UPDATE_ONLY </td> <td> RequestUpdateProcessorImpl </td> </tr>
+     * <tr><td> PROCESSOR_TYPE_IMAGE_PROCESSOR </td> <td> PreviewImageProcessorImpl </td> </tr>
+     * <tr><td> PROCESSOR_TYPE_NONE </td> <td> null </td> </tr>
+     * </table>
+     */
+    @Nullable
+    ProcessorImpl getProcessor();
+
+    /**
+     * Returns the customized supported resolutions.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     *
+     * <p>The returned resolutions should be subset of the supported sizes retrieved from
+     * {@link android.hardware.camera2.params.StreamConfigurationMap} for the camera device. If the
+     * returned list is not null, it will be used to find the best resolutions combination for
+     * the bound use cases.
+     *
+     * @return the customized supported resolutions.
+     * @since 1.1
+     */
+    @Nullable
+    List<Pair<Integer, Size[]>> getSupportedResolutions();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java
new file mode 100644
index 0000000..f203eba
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Processes a single {@link Image} and {@link TotalCaptureResult} to produce an output to a
+ * stream.
+ *
+ * @since 1.0
+ */
+@SuppressLint("UnknownNullness")
+public interface PreviewImageProcessorImpl extends ProcessorImpl {
+    /**
+     * Processes the requested image capture.
+     *
+     * <p> The result of the processing step should be written to the {@link android.view.Surface}
+     * that was received by {@link ProcessorImpl#onOutputSurface(android.view.Surface, int)}.
+     *
+     * @param image  The {@link ImageFormat#YUV_420_888} format image to process. This will be
+     *               invalid after the method completes so no reference to it should be kept.
+     * @param result The metadata associated with the image to process.
+     */
+    void process(Image image, TotalCaptureResult result);
+
+    /**
+     * Processes the requested image capture.
+     *
+     * <p> The result of the processing step should be written to the {@link android.view.Surface}
+     * that was received by {@link ProcessorImpl#onOutputSurface(android.view.Surface, int)}.
+     *
+     * @param image          The {@link ImageFormat#YUV_420_888} format image to process. This will
+     *                       be invalid after the method completes so no reference to it should be
+     *                       kept.
+     * @param result         The metadata associated with the image to process.
+     * @param resultCallback Capture result callback to be called once the capture result
+     *                       values of the processed image are ready.
+     * @param executor       The executor to run the callback on. If null then the callback will
+     *                       run on any arbitrary executor.
+     * @since 1.3
+     */
+    void process(Image image, TotalCaptureResult result, ProcessResultImpl resultCallback,
+            Executor executor);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java
new file mode 100644
index 0000000..d0e3605
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ProcessResultImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 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;
+
+import android.annotation.SuppressLint;
+import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
+
+import java.util.List;
+
+/**
+ * Allows clients to receive information about the capture result values of processed frames.
+ *
+ * @since 1.3
+ */
+@SuppressLint("UnknownNullness")
+public interface ProcessResultImpl {
+    /**
+     * Capture result callback that needs to be called when the process capture results are
+     * ready as part of frame post-processing.
+     *
+     * @param shutterTimestamp     The shutter time stamp of the processed frame.
+     * @param result               Key value pairs for all supported capture results. Do note that
+     *                             if results 'android.jpeg.quality' and 'android.jpeg.orientation'
+     *                             are present in the process capture input results, then the values
+     *                             must also be passed as part of this callback. Both Camera2 and
+     *                             CameraX guarantee that those two settings and results are always
+     *                             supported and applied by the corresponding framework.
+     */
+    void onCaptureCompleted(long shutterTimestamp, List<Pair<CaptureResult.Key, Object>> result);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ProcessorImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ProcessorImpl.java
new file mode 100644
index 0000000..e5ca19e
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/ProcessorImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Processes an input image stream and produces an output image stream.
+ *
+ * @since 1.0
+ */
+public interface ProcessorImpl {
+    /**
+     * Updates where the ProcessorImpl should write the output to.
+     *
+     * @param surface     The {@link Surface} that the ProcessorImpl should write data into.
+     * @param imageFormat The format of that the surface expects.
+     */
+    void onOutputSurface(@NonNull Surface surface, int imageFormat);
+
+    /**
+     * Invoked when CameraX changes the configured output resolution.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any
+     * {@link android.media.Image} received as input to be at the specified resolution.
+     *
+     * @param size for the surface.
+     */
+    void onResolutionUpdate(@NonNull Size size);
+
+    /**
+     * Invoked when CameraX changes the configured input image format.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any
+     * {@link android.media.Image} received as input to have the specified image format.
+     *
+     * @param imageFormat for the surface.
+     */
+    void onImageFormatUpdate(int imageFormat);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
new file mode 100644
index 0000000..ac3bfb3
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.extensions.impl;
+
+import android.hardware.camera2.TotalCaptureResult;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Processes a {@link TotalCaptureResult} to update a CaptureStage.
+ *
+ * @since 1.0
+ */
+public interface RequestUpdateProcessorImpl extends ProcessorImpl {
+    /**
+     * Process the {@link TotalCaptureResult} to update the {@link CaptureStageImpl}
+     *
+     * @param result The metadata associated with the image. Can be null if the image and meta have
+     *               not been synced.
+     * @return The updated parameters used for the repeating requests. If this is {@code null} then
+     * the previous parameters will be used.
+     */
+    @Nullable
+    CaptureStageImpl process(@Nullable TotalCaptureResult result);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
new file mode 100644
index 0000000..465bfe8
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.camera.extensions.impl.ExtensionVersionImpl;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Advanced OEM contract for implementing Extensions. ImageCapture/Preview Extensions are both
+ * implemented on this interface.
+ *
+ * <p>This advanced OEM contract empowers OEM to gain access to more Camera2 capability. This
+ * includes: (1) Add custom surfaces with specific formats like YUV, RAW, RAW_DEPTH. (2) Access to
+ * the capture request callbacks as well as all the images retrieved of various image formats. (3)
+ * Able to triggers single or repeating request with the capabilities to specify target surfaces,
+ * template id and parameters.
+ *
+ * <p>OEM needs to implement it with class name HdrAdvancedExtenderImpl for HDR,
+ * NightAdvancedExtenderImpl for night mode, BeautyAdvancedExtenderImpl for beauty mode,
+ * BokehAdvancedExtenderImpl for bokeh mode and AutoAdvancedExtenderImpl for auto mode.
+ *
+ * <p>OEMs are required to return true in
+ * {@link ExtensionVersionImpl#isAdvancedExtenderImplemented()} in order to request CameraX to
+ * use advanced extender over basic extender. OEM is okay to implement advanced
+ * extender only Or basic extender only. However the caveat of advanced-only implementation is,
+ * extensions will be unavailable on the apps using interfaces prior to 1.2.
+ *
+ * @since 1.2
+ */
+@SuppressLint("UnknownNullness")
+public interface AdvancedExtenderImpl {
+
+    /**
+     * Indicates whether the extension is supported on the device.
+     *
+     * @param cameraId           The camera2 id string of the camera.
+     * @param characteristicsMap A map consisting of the camera ids and the
+     *                           {@link CameraCharacteristics}s. For every camera, the map
+     *                           contains at least the CameraCharacteristics for the camera id.
+     *                           If the camera is logical camera, it will also contain associated
+     *                           physical camera ids and their CameraCharacteristics.
+     * @return true if the extension is supported, otherwise false
+     */
+    boolean isExtensionAvailable(String cameraId,
+            Map<String, CameraCharacteristics> characteristicsMap);
+
+    /**
+     * Initializes the extender to be used with the specified camera.
+     *
+     * <p>This should be called before any other method on the extender. The exception is {@link
+     * #isExtensionAvailable}.
+     *
+     * @param cameraId           The camera2 id string of the camera.
+     * @param characteristicsMap A map consisting of the camera ids and the
+     *                           {@link CameraCharacteristics}s. For every camera, the map
+     *                           contains at least the CameraCharacteristics for the camera id.
+     *                           If the camera is logical camera, it will also contain associated
+     *                           physical camera ids and their CameraCharacteristics.
+     */
+    void init(String cameraId, Map<String, CameraCharacteristics> characteristicsMap);
+
+    /**
+     * Returns the estimated capture latency range in milliseconds for the
+     * target capture resolution during the calls to
+     * {@link SessionProcessorImpl#startCapture}. This
+     * includes the time spent processing the multi-frame capture request along with any additional
+     * time for encoding of the processed buffer in the framework if necessary.
+     *
+     * @param cameraId          the camera id
+     * @param captureOutputSize size of the capture output surface. If it is null or not in the
+     *                          supported output sizes, maximum capture output size is used for
+     *                          the estimation.
+     * @param imageFormat the image format of the capture output surface.
+     * @return the range of estimated minimal and maximal capture latency in milliseconds.
+     * Returns null if no capture latency info can be provided.
+     */
+    Range<Long> getEstimatedCaptureLatencyRange(String cameraId,
+            Size captureOutputSize, int imageFormat);
+
+    /**
+     * Returns supported output format/size map for preview. The format could be PRIVATE or
+     * YUV_420_888. OEM must support PRIVATE format at least. CameraX will only use resolutions
+     * for preview from the list.
+     *
+     * <p>The preview surface format in the CameraCaptureSession may not be identical to the
+     * supported preview output format returned here. Like in the basic extender interface, the
+     * preview PRIVATE surface could be added to the CameraCaptureSession and OEM processes it in
+     * the HAL. Alternatively OEM can configure a intermediate YUV surface of the same size and
+     * writes the output to the preview output surface.
+     */
+    Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(String cameraId);
+
+    /**
+     * Returns supported output format/size map for image capture. OEM is required to support
+     * both JPEG and YUV_420_888 format output.
+     *
+     * <p>Like in the basic extender interface, the surface created with this supported
+     * format/size could be either added in CameraCaptureSession with HAL processing OR it
+     * configures intermediate surfaces(YUV/RAW..) and writes the output to the output surface.
+     */
+    Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(String cameraId);
+
+    /**
+     * Returns supported output sizes for Image Analysis (YUV_420_888 format).
+     *
+     * <p>OEM can optionally support a YUV surface for ImageAnalysis along with Preview/ImageCapture
+     * output surfaces. If imageAnalysis YUV surface is not supported, OEM should return null or
+     * empty list.
+     */
+    List<Size> getSupportedYuvAnalysisResolutions(String cameraId);
+
+    /**
+     * Returns a processor for activating extension sessions. It implements all the interactions
+     * required for starting a extension and cleanup.
+     */
+    SessionProcessorImpl createSessionProcessor();
+
+    /**
+     * Returns a list of orthogonal capture request keys.
+     *
+     * <p>Any keys included in the list will be configurable by clients of the extension and will
+     * affect the extension functionality.</p>
+     *
+     * <p>Please note that the keys {@link CaptureRequest#JPEG_QUALITY} and
+     * {@link CaptureRequest#JPEG_ORIENTATION} are always supported regardless being added in the
+     * list or not. To support common camera operations like zoom, tap-to-focus, flash and
+     * exposure compensation, we recommend supporting the following keys if possible.
+     * <pre>
+     *  zoom:  {@link CaptureRequest#CONTROL_ZOOM_RATIO}
+     *         {@link CaptureRequest#SCALER_CROP_REGION}
+     *  tap-to-focus:
+     *         {@link CaptureRequest#CONTROL_AF_MODE}
+     *         {@link CaptureRequest#CONTROL_AF_TRIGGER}
+     *         {@link CaptureRequest#CONTROL_AF_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AE_REGIONS}
+     *         {@link CaptureRequest#CONTROL_AWB_REGIONS}
+     *  flash:
+     *         {@link CaptureRequest#CONTROL_AE_MODE}
+     *         {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER}
+     *         {@link CaptureRequest#FLASH_MODE}
+     *  exposure compensation:
+     *         {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION}
+     * </pre>
+     *
+     * @return List of supported orthogonal capture keys, or an empty list if no capture settings
+     * are not supported.
+     * @since 1.3
+     */
+    List<CaptureRequest.Key> getAvailableCaptureRequestKeys();
+
+    /**
+     * Returns a list of supported capture result keys.
+     *
+     * <p>Any keys included in this list must be available as part of the registered
+     * {@link SessionProcessorImpl.CaptureCallback#onCaptureCompleted} callback.</p>
+     *
+     * <p>At the very minimum, it is expected that the result key list is a superset of the
+     * capture request keys.</p>
+     *
+     * @return List of supported capture result keys, or
+     * an empty list if capture results are not supported.
+     * @since 1.3
+     */
+    List<CaptureResult.Key> getAvailableCaptureResultKeys();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
new file mode 100644
index 0000000..c67dc79
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+
+import androidx.camera.extensions.impl.serviceforward.ForwardAdvancedExtender;
+
+/**
+ * Stub advanced extender implementation for auto.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.2
+ */
+@SuppressLint("UnknownNullness")
+public class AutoAdvancedExtenderImpl extends ForwardAdvancedExtender {
+    public AutoAdvancedExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_AUTOMATIC);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
new file mode 100644
index 0000000..2e10e25
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+
+import androidx.camera.extensions.impl.serviceforward.ForwardAdvancedExtender;
+
+/**
+ * Stub advanced extender implementation for beauty.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.2
+ */
+@SuppressLint("UnknownNullness")
+public class BeautyAdvancedExtenderImpl extends ForwardAdvancedExtender {
+    public BeautyAdvancedExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_BEAUTY);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
new file mode 100644
index 0000000..ae55790
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+
+import androidx.camera.extensions.impl.serviceforward.ForwardAdvancedExtender;
+
+/**
+ * Stub advanced extender implementation for bokeh.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.2
+ */
+@SuppressLint("UnknownNullness")
+public class BokehAdvancedExtenderImpl extends ForwardAdvancedExtender {
+    public BokehAdvancedExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_BOKEH);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
new file mode 100644
index 0000000..68de01b
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+
+import java.util.List;
+
+/**
+ * A config representing a {@link android.hardware.camera2.params.OutputConfiguration} where
+ * Surface will be created by the information in this config.
+ */
+@SuppressLint("UnknownNullness")
+public interface Camera2OutputConfigImpl {
+    /**
+     * Gets thd id of this output config. The id can be used to identify the stream in vendor
+     * implementations.
+     */
+    int getId();
+
+    /**
+     * Gets the surface group id. Vendor can use the surface group id to share memory between
+     * Surfaces.
+     */
+    int getSurfaceGroupId();
+
+    /**
+     * Gets the physical camera id. Returns null if not specified.
+     */
+    String getPhysicalCameraId();
+
+    /**
+     * If non-null, enable surface sharing and add the surface constructed by the return
+     * Camera2OutputConfig.
+     */
+    List<Camera2OutputConfigImpl> getSurfaceSharingOutputConfigs();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java
new file mode 100644
index 0000000..718870f
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.util.Size;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A builder implementation to help OEM build the {@link Camera2OutputConfigImpl} instance.
+ */
+@SuppressLint("UnknownNullness")
+public class Camera2OutputConfigImplBuilder {
+    static AtomicInteger sLastId = new AtomicInteger(0);
+    private OutputConfigImplImpl mOutputConfig;
+    private int mSurfaceGroupId = OutputConfiguration.SURFACE_GROUP_ID_NONE;
+    private int mOutputConfigId = -1;
+    private String mPhysicalCameraId;
+    private List<Camera2OutputConfigImpl> mSurfaceSharingConfigs;
+
+    private Camera2OutputConfigImplBuilder(OutputConfigImplImpl outputConfig) {
+        mOutputConfig = outputConfig;
+    }
+
+    private int getNextId() {
+        return sLastId.getAndIncrement();
+    }
+
+    /**
+     * Creates a {@link Camera2OutputConfigImpl} that represents a {@link android.media.ImageReader}
+     * with the given parameters.
+     */
+    public static Camera2OutputConfigImplBuilder newImageReaderConfig(
+            Size size, int imageFormat, int maxImages) {
+        return new Camera2OutputConfigImplBuilder(
+                new ImageReaderOutputConfigImplImpl(size, imageFormat, maxImages));
+    }
+
+    /**
+     * Creates a {@link Camera2OutputConfigImpl} that represents a MultiResolutionImageReader with
+     * the given parameters.
+     */
+    public static Camera2OutputConfigImplBuilder newMultiResolutionImageReaderConfig(
+            int imageFormat, int maxImages) {
+        return new Camera2OutputConfigImplBuilder(
+                new MultiResolutionImageReaderOutputConfigImplImpl(imageFormat, maxImages));
+    }
+
+    /**
+     * Creates a {@link Camera2OutputConfigImpl} that contains the Surface directly.
+     */
+    public static Camera2OutputConfigImplBuilder newSurfaceConfig(Surface surface) {
+        return new Camera2OutputConfigImplBuilder(new SurfaceOutputConfigImplImpl(surface));
+    }
+
+    /**
+     * Adds a {@link Camera2SessionConfigImpl} to be shared with current config.
+     */
+    public Camera2OutputConfigImplBuilder addSurfaceSharingOutputConfig(
+            Camera2OutputConfigImpl camera2OutputConfig) {
+        if (mSurfaceSharingConfigs == null) {
+            mSurfaceSharingConfigs = new ArrayList<>();
+        }
+
+        mSurfaceSharingConfigs.add(camera2OutputConfig);
+        return this;
+    }
+
+    /**
+     * Sets a physical camera id.
+     */
+    public Camera2OutputConfigImplBuilder setPhysicalCameraId(String physicalCameraId) {
+        mPhysicalCameraId = physicalCameraId;
+        return this;
+    }
+
+    /**
+     * Sets surface group id.
+     */
+    public Camera2OutputConfigImplBuilder setSurfaceGroupId(int surfaceGroupId) {
+        mSurfaceGroupId = surfaceGroupId;
+        return this;
+    }
+
+    /**
+     * Sets output config id.
+     */
+    public Camera2OutputConfigImplBuilder setOutputConfigId(int outputConfigId) {
+        mOutputConfigId = outputConfigId;
+        return this;
+    }
+
+
+    /**
+     * Build a {@link Camera2OutputConfigImpl} instance.
+     */
+    public Camera2OutputConfigImpl build() {
+        // Use the Atomic Integer if setOutputConfigId is never called
+        if(mOutputConfigId == -1) {
+            mOutputConfig.setId(getNextId());
+        }
+        else {
+            mOutputConfig.setId(mOutputConfigId);
+        }
+        mOutputConfig.setPhysicalCameraId(mPhysicalCameraId);
+        mOutputConfig.setSurfaceGroup(mSurfaceGroupId);
+        mOutputConfig.setSurfaceSharingConfigs(mSurfaceSharingConfigs);
+        return mOutputConfig;
+    }
+
+    private static class OutputConfigImplImpl implements Camera2OutputConfigImpl {
+        private int mId;
+        private int mSurfaceGroup;
+        private String mPhysicalCameraId;
+        private List<Camera2OutputConfigImpl> mSurfaceSharingConfigs;
+
+        OutputConfigImplImpl() {
+            mId = -1;
+            mSurfaceGroup = 0;
+            mPhysicalCameraId = null;
+            mSurfaceSharingConfigs = null;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+
+        @Override
+        public int getSurfaceGroupId() {
+            return mSurfaceGroup;
+        }
+
+        @Override
+        public String getPhysicalCameraId() {
+            return mPhysicalCameraId;
+        }
+
+        @Override
+        public List<Camera2OutputConfigImpl> getSurfaceSharingOutputConfigs() {
+            return mSurfaceSharingConfigs;
+        }
+
+        public void setId(int id) {
+            mId = id;
+        }
+
+        public void setSurfaceGroup(int surfaceGroup) {
+            mSurfaceGroup = surfaceGroup;
+        }
+
+        public void setPhysicalCameraId(String physicalCameraId) {
+            mPhysicalCameraId = physicalCameraId;
+        }
+
+        public void setSurfaceSharingConfigs(
+                List<Camera2OutputConfigImpl> surfaceSharingConfigs) {
+            mSurfaceSharingConfigs = surfaceSharingConfigs;
+        }
+    }
+
+    private static class SurfaceOutputConfigImplImpl extends OutputConfigImplImpl
+            implements SurfaceOutputConfigImpl {
+        private Surface mSurface;
+
+        SurfaceOutputConfigImplImpl(Surface surface) {
+            mSurface = surface;
+        }
+
+        @Override
+        public Surface getSurface() {
+            return mSurface;
+        }
+    }
+
+    private static class ImageReaderOutputConfigImplImpl extends OutputConfigImplImpl
+            implements ImageReaderOutputConfigImpl {
+        private Size mSize;
+        private int mImageFormat;
+        private int mMaxImages;
+
+        ImageReaderOutputConfigImplImpl(Size size, int imageFormat, int maxImages) {
+            mSize = size;
+            mImageFormat = imageFormat;
+            mMaxImages = maxImages;
+        }
+
+        @Override
+        public Size getSize() {
+            return mSize;
+        }
+
+        @Override
+        public int getImageFormat() {
+            return mImageFormat;
+        }
+
+        @Override
+        public int getMaxImages() {
+            return mMaxImages;
+        }
+    }
+
+    private static class MultiResolutionImageReaderOutputConfigImplImpl extends OutputConfigImplImpl
+            implements MultiResolutionImageReaderOutputConfigImpl {
+        private int mImageFormat;
+        private int mMaxImages;
+
+        MultiResolutionImageReaderOutputConfigImplImpl(int imageFormat, int maxImages) {
+            mImageFormat = imageFormat;
+            mMaxImages = maxImages;
+        }
+
+        @Override
+        public int getImageFormat() {
+            return mImageFormat;
+        }
+
+        @Override
+        public int getMaxImages() {
+            return mMaxImages;
+        }
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
new file mode 100644
index 0000000..d121717
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CaptureRequest;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A config representing a {@link android.hardware.camera2.params.SessionConfiguration}
+ */
+@SuppressLint("UnknownNullness")
+public interface Camera2SessionConfigImpl {
+    /**
+     * Returns all the {@link Camera2OutputConfigImpl}s that will be used to create
+     * {@link android.hardware.camera2.params.OutputConfiguration}.
+     */
+    List<Camera2OutputConfigImpl> getOutputConfigs();
+
+    /**
+     * Gets all the parameters to create the session parameters with.
+     */
+    Map<CaptureRequest.Key<?>, Object> getSessionParameters();
+
+    /**
+     * Gets the template id used for creating {@link CaptureRequest}s to be passed in
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters}.
+     */
+    int getSessionTemplateId();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
new file mode 100644
index 0000000..a301166
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder implementation to help OEM build the {@link Camera2SessionConfigImpl} instance.
+ */
+@SuppressLint("UnknownNullness")
+public class Camera2SessionConfigImplBuilder {
+    private int mSessionTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+    Map<CaptureRequest.Key<?>, Object> mSessionParameters = new HashMap<>();
+    List<Camera2OutputConfigImpl> mCamera2OutputConfigs = new ArrayList<>();
+
+    public Camera2SessionConfigImplBuilder() {
+    }
+
+    /**
+     * Adds a output config.
+     */
+    public Camera2SessionConfigImplBuilder addOutputConfig(
+            Camera2OutputConfigImpl outputConfig) {
+        mCamera2OutputConfigs.add(outputConfig);
+        return this;
+    }
+
+    /**
+     * Sets session parameters.
+     */
+    public <T> Camera2SessionConfigImplBuilder addSessionParameter(
+            CaptureRequest.Key<T> key, T value) {
+        mSessionParameters.put(key, value);
+        return this;
+    }
+
+    /**
+     * Sets the template id for session parameters request.
+     */
+    public Camera2SessionConfigImplBuilder setSessionTemplateId(int templateId) {
+        mSessionTemplateId = templateId;
+        return this;
+    }
+
+    /**
+     * Gets the session template id.
+     */
+    public int getSessionTemplateId() {
+        return mSessionTemplateId;
+    }
+
+    /**
+     * Gets the session parameters.
+     */
+    public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
+        return mSessionParameters;
+    }
+
+    /**
+     * Gets all the output configs.
+     */
+    public List<Camera2OutputConfigImpl> getCamera2OutputConfigs() {
+        return mCamera2OutputConfigs;
+    }
+
+    /**
+     * Builds a {@link Camera2SessionConfigImpl} instance.
+     */
+    public Camera2SessionConfigImpl build() {
+        return new Camera2SessionConfigImplImpl(this);
+    }
+
+    private static class Camera2SessionConfigImplImpl implements
+            Camera2SessionConfigImpl {
+        int mSessionTemplateId;
+        Map<CaptureRequest.Key<?>, Object> mSessionParameters;
+        List<Camera2OutputConfigImpl> mCamera2OutputConfigs;
+
+        Camera2SessionConfigImplImpl(Camera2SessionConfigImplBuilder builder) {
+            mSessionTemplateId = builder.getSessionTemplateId();
+            mSessionParameters = builder.getSessionParameters();
+            mCamera2OutputConfigs = builder.getCamera2OutputConfigs();
+        }
+
+        @Override
+        public List<Camera2OutputConfigImpl> getOutputConfigs() {
+            return mCamera2OutputConfigs;
+        }
+
+        @Override
+        public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
+            return mSessionParameters;
+        }
+
+        @Override
+        public int getSessionTemplateId() {
+            return mSessionTemplateId;
+        }
+    }
+}
+
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
new file mode 100644
index 0000000..e7dbb7d
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+
+import androidx.camera.extensions.impl.serviceforward.ForwardAdvancedExtender;
+
+/**
+ * Stub advanced extender implementation for hdr.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.2
+ */
+@SuppressLint("UnknownNullness")
+public class HdrAdvancedExtenderImpl extends ForwardAdvancedExtender {
+    public HdrAdvancedExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_HDR);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
new file mode 100644
index 0000000..037e947
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+
+/**
+ * A interface to receive and process the upcoming next available Image.
+ *
+ * <p>Implemented by OEM.
+ */
+@SuppressLint("UnknownNullness")
+public interface ImageProcessorImpl {
+    /**
+     * The reference count will not be decremented when this method returns. Extensions must
+     * decrement it when the image is no longer needed.
+     *
+     * <p>If OEM is not closing(decrement) the image fast enough, the imageReference passed
+     * in this method might contain null image meaning that the Image was closed to prevent
+     * preview from stalling.
+     *
+     * @param outputConfigId the id of {@link Camera2OutputConfigImpl} which identifies
+     *                       corresponding Surface
+     * @param timestampNs    the timestamp in nanoseconds associated with this image
+     * @param imageReference A reference to the {@link android.media.Image} which might contain
+     *                       null if OEM close(decrement) the image too slowly
+     * @param physicalCameraId used to distinguish which physical camera id the image comes from
+     *                         when the output configuration is
+     *                         MultiResolutionImageReaderOutputConfigImpl. It is also set if
+     *                         physicalCameraId is set in other Camera2OutputConfigImpl types.
+     *
+     */
+    void onNextImageAvailable(
+            int outputConfigId,
+            long timestampNs,
+            ImageReferenceImpl imageReference,
+            String physicalCameraId
+            );
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
new file mode 100644
index 0000000..ca4dcaf
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.util.Size;
+
+/**
+ * Surface will be created by constructing a ImageReader.
+ */
+@SuppressLint("UnknownNullness")
+public interface ImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
+    /**
+     * Returns the size of the surface.
+     */
+    Size getSize();
+
+    /**
+     * Gets the image format of the surface.
+     */
+    int getImageFormat();
+
+    /**
+     * Gets the capacity for TYPE_IMAGEREADER.
+     */
+    int getMaxImages();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
new file mode 100644
index 0000000..95f2c3b
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.media.Image;
+
+/**
+ * A Image reference container that enables the Image sharing between Camera2/CameraX and OEM
+ * using reference counting. The wrapped Image will be closed once the reference count
+ * reaches 0.
+ *
+ * <p>Implemented by Camera2/CameraX.
+ */
+@SuppressLint("UnknownNullness")
+public interface ImageReferenceImpl {
+
+    /**
+     * Increment the reference count. Returns true if the value was incremented.
+     * (returns false if the reference count has already reached zero.)
+     */
+    boolean increment();
+
+    /**
+     * Decrement the reference count. Image will be closed if reference count reaches 0.
+     * Returns true if the value was decremented (returns false if the reference count has
+     * already reached zero)
+     */
+    boolean decrement();
+
+    /**
+     * Return the Android image. This object MUST not be closed directly.
+     * Returns null when the reference count is zero.
+     */
+    Image get();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
new file mode 100644
index 0000000..c3ad61b
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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;
+
+/**
+ * Surface will be created by constructing a MultiResolutionImageReader.
+ */
+public interface MultiResolutionImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
+    /**
+     * Gets the image format of the surface.
+     */
+    int getImageFormat();
+
+    /**
+     * Gets the max images of the ImageReader.
+     */
+    int getMaxImages();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
new file mode 100644
index 0000000..be29844
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+
+import androidx.camera.extensions.impl.serviceforward.ForwardAdvancedExtender;
+
+/**
+ * Stub advanced extender implementation for night.
+ *
+ * <p>This class should be implemented by OEM and deployed to the target devices.
+ *
+ * @since 1.2
+ */
+@SuppressLint("UnknownNullness")
+public class NightAdvancedExtenderImpl extends ForwardAdvancedExtender {
+    public NightAdvancedExtenderImpl() {
+        super(CameraExtensionCharacteristics.EXTENSION_NIGHT);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
new file mode 100644
index 0000000..f692029
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.util.Size;
+import android.view.Surface;
+
+/**
+ * For specifying output surface of the extension.
+ */
+@SuppressLint("UnknownNullness")
+public interface OutputSurfaceImpl {
+    /**
+     * Gets the surface.
+     */
+    Surface getSurface();
+
+    /**
+     * Gets the size.
+     */
+    Size getSize();
+
+    /**
+     * Gets the image format.
+     */
+    int getImageFormat();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
new file mode 100644
index 0000000..5185333
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An Interface to execute Camera2 capture requests.
+ */
+@SuppressLint("UnknownNullness")
+public interface RequestProcessorImpl {
+    /**
+     * Sets a {@link ImageProcessorImpl} to receive {@link ImageReferenceImpl} to process.
+     */
+    void setImageProcessor(int outputconfigId, ImageProcessorImpl imageProcessor);
+
+    /**
+     * Submits a request.
+     * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
+     *         or receives an invalid argument.
+     */
+    int submit(Request request, Callback callback);
+
+    /**
+     * Submits a list of requests.
+     * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
+     *         or receives an invalid argument.
+     */
+    int submit(List<Request> requests, Callback callback);
+
+    /**
+     * Set repeating requests.
+     * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
+     *         or receives an invalid argument.
+     */
+    int setRepeating(Request request, Callback callback);
+
+
+    /**
+     * Abort captures.
+     */
+    void abortCaptures();
+
+    /**
+     * Stop Repeating.
+     */
+    void stopRepeating();
+
+    /**
+     * A interface representing a capture request configuration used for submitting requests in
+     * {@link RequestProcessorImpl}.
+     */
+    interface Request {
+        /**
+         * Gets the target ids of {@link Camera2OutputConfigImpl} which identifies corresponding
+         * Surface to be the targeted for the request.
+         */
+        List<Integer> getTargetOutputConfigIds();
+
+        /**
+         * Gets all the parameters.
+         */
+        Map<CaptureRequest.Key<?>, Object> getParameters();
+
+        /**
+         * Gets the template id.
+         */
+        Integer getTemplateId();
+    }
+
+    /**
+     * Callback to be invoked during the capture.
+     */
+    interface Callback {
+        void onCaptureStarted(
+                Request request,
+                long frameNumber,
+                long timestamp);
+
+        void onCaptureProgressed(
+                Request request,
+                CaptureResult partialResult);
+
+        void onCaptureCompleted(
+                Request request,
+                TotalCaptureResult totalCaptureResult);
+
+        void onCaptureFailed(
+                Request request,
+                CaptureFailure captureFailure);
+
+        void onCaptureBufferLost(
+                Request request,
+                long frameNumber,
+                int outputStreamId);
+
+        void onCaptureSequenceCompleted(int sequenceId, long frameNumber);
+
+        void onCaptureSequenceAborted(int sequenceId);
+
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
new file mode 100644
index 0000000..fabfc2b
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.view.Surface;
+
+import java.util.Map;
+
+/**
+ * Interface for creating Camera2 CameraCaptureSessions with extension enabled based on
+ * advanced vendor implementation.
+ *
+ * <p><pre>
+ * The flow of a extension session is shown below:
+ * (1) {@link #initSession}: CameraX prepares streams configuration for creating
+ *     CameraCaptureSession. Output surfaces for Preview, ImageCapture and ImageAnalysis are passed
+ *     in and vendor is responsible for outputting the results to these surfaces.
+ *
+ * (2) {@link #onCaptureSessionStart}: It is called after CameraCaptureSession is configured.
+ *     A {@link RequestProcessorImpl} is passed for vendor to send repeating requests and
+ *     single requests.
+ *
+ * (3) {@link #startRepeating}:  CameraX will call this method to start the repeating request
+ *     after CameraCaptureSession is called. Vendor should start the repeating request by
+ *     {@link RequestProcessorImpl}. Vendor can also update the repeating request if needed later.
+ *
+ * (4) {@link #setParameters(Map)}: The passed parameters will be attached to the repeating request
+ *     and single requests but vendor can choose to apply some of them only.
+ *
+ * (5) {@link #startCapture(CaptureCallback)}: It is called when apps want to
+ *     start a multi-frame image capture.  {@link CaptureCallback} will be called
+ *     to report the status and the output image will be written to the capture output surface
+ *     specified in {@link #initSession}.
+ *
+ * (5) {@link #onCaptureSessionEnd}: It is called right BEFORE CameraCaptureSession.close() is
+ *     called.
+ *
+ * (6) {@link #deInitSession}: called when CameraCaptureSession is closed.
+ * </pre>
+ */
+@SuppressLint("UnknownNullness")
+public interface SessionProcessorImpl {
+    /**
+     * Initializes the session for the extension. This is where the OEMs allocate resources for
+     * preparing a CameraCaptureSession. After initSession() is called, the camera ID,
+     * cameraCharacteristics and context will not change until deInitSession() has been called.
+     *
+     * <p>CameraX specifies the output surface configurations for preview, image capture and image
+     * analysis[optional]. And OEM returns a {@link Camera2SessionConfigImpl} which consists of a
+     * list of {@link Camera2OutputConfigImpl} and session parameters. The
+     * {@link Camera2SessionConfigImpl} will be used to configure the CameraCaptureSession.
+     *
+     * <p>OEM is responsible for outputting correct camera images output to these output surfaces.
+     * OEM can have the following options to enable the output:
+     * <pre>
+     * (1) Add these output surfaces in CameraCaptureSession directly using
+     * {@link Camera2OutputConfigImplBuilder#newSurfaceConfig(Surface)} }. Processing is done in
+     * HAL.
+     *
+     * (2) Use surface sharing with other surface by calling
+     * {@link Camera2OutputConfigImplBuilder#addSurfaceSharingOutputConfig(Camera2OutputConfigImpl)}
+     * to add the output surface to the other {@link Camera2OutputConfigImpl}.
+     *
+     * (3) Process output from other surfaces (RAW, YUV..) and write the result to the output
+     * surface. The output surface won't be contained in the returned
+     * {@link Camera2SessionConfigImpl}.
+     * </pre>
+     *
+     * <p>{@link Camera2OutputConfigImplBuilder} and {@link Camera2SessionConfigImplBuilder}
+     * implementations are provided in the stub for OEM to construct the
+     * {@link Camera2OutputConfigImpl} and {@link Camera2SessionConfigImpl} instances.
+     *
+     * @param previewSurfaceConfig       output surface for preview
+     * @param imageCaptureSurfaceConfig  output surface for image capture.
+     * @param imageAnalysisSurfaceConfig an optional output config for image analysis
+     *                                   (YUV_420_888).
+     * @return a {@link Camera2SessionConfigImpl} consisting of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters which will decide the
+     * {@link android.hardware.camera2.params.SessionConfiguration} for configuring the
+     * CameraCaptureSession. Please note that the OutputConfiguration list may not be part of any
+     * supported or mandatory stream combination BUT OEM must ensure this list will always
+     * produce a valid camera capture session.
+     */
+    Camera2SessionConfigImpl initSession(
+            String cameraId,
+            Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            Context context,
+            OutputSurfaceImpl previewSurfaceConfig,
+            OutputSurfaceImpl imageCaptureSurfaceConfig,
+            OutputSurfaceImpl imageAnalysisSurfaceConfig);
+
+    /**
+     * Notify to de-initialize the extension. This callback will be invoked after
+     * CameraCaptureSession is closed. After onDeInit() was called, it is expected that the
+     * camera ID, cameraCharacteristics will no longer hold and tear down any resources allocated
+     * for this extension. Aborts all pending captures.
+     */
+    void deInitSession();
+
+    /**
+     * CameraX / Camera2 would call these API’s to pass parameters from the app to the OEM. It’s
+     * expected that the OEM would (eventually) update the repeating request if the keys are
+     * supported. Setting a value to null explicitly un-sets the value.
+     */
+    void setParameters(Map<CaptureRequest.Key<?>, Object> parameters);
+
+    /**
+     * CameraX / Camera2 will call this interface in response to client requests involving
+     * the output preview surface. Typical examples include requests that include AF/AE triggers.
+     * Extensions can disregard any capture request keys that were not advertised in
+     * {@link AdvancedExtenderImpl#getAvailableCaptureRequestKeys}.
+     *
+     * @param triggers Capture request key value map.
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     *
+     * @throws IllegalArgumentException If there are no valid settings that can be applied
+     *
+     * @since 1.3
+     */
+    int startTrigger(Map<CaptureRequest.Key<?>, Object> triggers, CaptureCallback callback);
+
+    /**
+     * This will be invoked once after the {@link android.hardware.camera2.CameraCaptureSession}
+     * has been created. {@link RequestProcessorImpl} is passed for OEM to submit single
+     * requests or set repeating requests. This ExtensionRequestProcessor will be valid to use
+     * until onCaptureSessionEnd is called.
+     */
+    void onCaptureSessionStart(RequestProcessorImpl requestProcessor);
+
+    /**
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+     * closed. {@link RequestProcessorImpl} passed in onCaptureSessionStart will no longer
+     * accept any requests after onCaptureSessionEnd() returns.
+     */
+    void onCaptureSessionEnd();
+
+    /**
+     * Starts the repeating request after CameraCaptureSession is called. Vendor should start the
+     * repeating request by {@link RequestProcessorImpl}. Vendor can also update the
+     * repeating request when needed later.
+     *
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     */
+    int startRepeating(CaptureCallback callback);
+
+    /**
+     * Stop the repeating request. To prevent OEM from not calling stopRepeating, CameraX will
+     * first stop the repeating request of current CameraCaptureSession and call this API to signal
+     * OEM that the repeating request was stopped and going forward calling
+     * {@link RequestProcessorImpl#setRepeating} will simply do nothing.
+     */
+    void stopRepeating();
+
+    /**
+     * Start a multi-frame capture.
+     *
+     * When the capture is completed, {@link CaptureCallback#onCaptureSequenceCompleted}
+     * is called and {@code OnImageAvailableListener#onImageAvailable}
+     * will also be called on the ImageReader that creates the image capture output surface.
+     *
+     * <p>Only one capture can perform at a time. Starting a capture when another capture is running
+     * will cause onCaptureFailed to be called immediately.
+     *
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     */
+    int startCapture(CaptureCallback callback);
+
+    /**
+     * Abort all capture tasks.
+     */
+    void abortCapture(int captureSequenceId);
+
+    /**
+     * Callback for notifying the status of {@link #startCapture(CaptureCallback)} and
+     * {@link #startRepeating(CaptureCallback)}.
+     */
+    interface CaptureCallback {
+        /**
+         * This method is called when the camera device has started capturing the initial input
+         * image.
+         *
+         * For a multi-frame capture, the method is called when the
+         * CameraCaptureSession.CaptureCallback onCaptureStarted of first frame is called and its
+         * timestamp is directly forwarded to timestamp parameter of
+         * this method.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         * @param timestamp         the timestamp at start of capture for repeating
+         *                          request or the timestamp at start of capture of the
+         *                          first frame in a multi-frame capture, in nanoseconds.
+         */
+        void onCaptureStarted(int captureSequenceId, long timestamp);
+
+        /**
+         * This method is called when an image (or images in case of multi-frame
+         * capture) is captured and device-specific extension processing is triggered.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureProcessStarted(int captureSequenceId);
+
+        /**
+         * This method is called instead of
+         * {@link #onCaptureProcessStarted} when the camera device failed
+         * to produce the required input for the device-specific extension. The
+         * cause could be a failed camera capture request, a failed
+         * capture result or dropped camera frame.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureFailed(int captureSequenceId);
+
+        /**
+         * This method is called independently of the others in the CaptureCallback, when a capture
+         * sequence finishes.
+         *
+         * <p>In total, there will be at least one
+         * {@link #onCaptureProcessStarted}/{@link #onCaptureFailed}
+         * invocation before this callback is triggered. If the capture
+         * sequence is aborted before any requests have begun processing,
+         * {@link #onCaptureSequenceAborted} is invoked instead.</p>
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureSequenceCompleted(int captureSequenceId);
+
+        /**
+         * This method is called when a capture sequence aborts.
+         *
+         * @param captureSequenceId id of the current capture sequence
+         */
+        void onCaptureSequenceAborted(int captureSequenceId);
+
+        /**
+         * Capture result callback that needs to be called when the process capture results are
+         * ready as part of frame post-processing.
+         *
+         * This callback will fire after {@link #onCaptureStarted}, {@link #onCaptureProcessStarted}
+         * and before {@link #onCaptureSequenceCompleted}. The callback is not expected to fire
+         * in case of capture failure  {@link #onCaptureFailed} or capture abort
+         * {@link #onCaptureSequenceAborted}.
+         *
+         * @param timestamp            The timestamp at start of capture. The same timestamp value
+         *                             passed to {@link #onCaptureStarted}.
+         * @param captureSequenceId    the capture id of the request that generated the capture
+         *                             results. This is the return value of either
+         *                             {@link #startRepeating} or {@link #startCapture}.
+         * @param result               Map containing the supported capture results. Do note
+         *                             that if results 'android.jpeg.quality' and
+         *                             'android.jpeg.orientation' are present in the process
+         *                             capture input results, then the values must also be passed
+         *                             as part of this callback. Both Camera2 and CameraX guarantee
+         *                             that those two settings and results are always supported and
+         *                             applied by the corresponding framework.
+         */
+        void onCaptureCompleted(long timestamp, int captureSequenceId,
+                Map<CaptureResult.Key, Object> result);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
new file mode 100644
index 0000000..7b8d83c
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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.annotation.SuppressLint;
+import android.view.Surface;
+
+/**
+ * Use Surface directly to create the OutputConfiguration.
+ */
+@SuppressLint("UnknownNullness")
+public interface SurfaceOutputConfigImpl extends Camera2OutputConfigImpl {
+    /**
+     * Get the {@link Surface}. It'll return valid surface only when type is TYPE_SURFACE.
+     */
+    Surface getSurface();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraMetadataWrapper.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraMetadataWrapper.aidl
new file mode 100644
index 0000000..5be2e73
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraMetadataWrapper.aidl
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+
+parcelable CameraMetadataWrapper;
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraMetadataWrapper.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraMetadataWrapper.java
new file mode 100644
index 0000000..02f8314
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraMetadataWrapper.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 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.service;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.camera.extensions.impl.serviceforward.PlatformApi;
+
+import java.util.ArrayList;
+
+public class CameraMetadataWrapper implements Parcelable {
+    private CameraMetadataNative mCameraMetadataNative;
+    private long mVendorId = Long.MAX_VALUE;
+
+    public CameraMetadataWrapper(CameraCharacteristics cameraCharacteristics) {
+        mCameraMetadataNative = new CameraMetadataNative();
+        if (cameraCharacteristics != null) {
+            setVendorId(cameraCharacteristics);
+        }
+    }
+
+    public CameraMetadataWrapper(CameraMetadataNative cameraMetadataNative) {
+        mCameraMetadataNative = cameraMetadataNative;
+    }
+
+    protected CameraMetadataWrapper(Parcel in) {
+        mCameraMetadataNative = in.readParcelable(CameraMetadataNative.class.getClassLoader());
+    }
+
+    public static final Creator<CameraMetadataWrapper> CREATOR =
+            new Creator<CameraMetadataWrapper>() {
+                @Override
+                public CameraMetadataWrapper createFromParcel(Parcel in) {
+                    return new CameraMetadataWrapper(in);
+                }
+
+                @Override
+                public CameraMetadataWrapper[] newArray(int size) {
+                    return new CameraMetadataWrapper[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mCameraMetadataNative, flags);
+    }
+
+    private void setVendorId(CameraCharacteristics chars) {
+        Object thisClass = CameraCharacteristics.Key.class;
+        Class<CameraCharacteristics.Key<?>> keyClass =
+                (Class<CameraCharacteristics.Key<?>>) thisClass;
+        ArrayList<CameraCharacteristics.Key<?>> vendorKeys =
+                chars.getNativeMetadata().getAllVendorKeys(keyClass);
+        if ((vendorKeys != null) && !vendorKeys.isEmpty()) {
+            mVendorId = vendorKeys.get(0).getVendorId();
+            mCameraMetadataNative.setVendorId(mVendorId);
+        }
+    }
+
+    public <T> T get(CaptureRequest.Key<T> key) {
+        return mCameraMetadataNative.get(key);
+    }
+
+    public <T> void set(CaptureRequest.Key<T> key, T value) {
+        mCameraMetadataNative.set(key, value);
+    }
+
+    public <T> void set(CaptureResult.Key<T> key, T value) {
+        mCameraMetadataNative.set(key, value);
+    }
+
+    public CaptureRequest toCaptureRequest() {
+        CameraMetadataNative cameraMetadataNative = new CameraMetadataNative(mCameraMetadataNative);
+        return PlatformApi.createCaptureRequest(cameraMetadataNative);
+    }
+
+    public TotalCaptureResult toTotalCaptureResult() {
+        CameraMetadataNative cameraMetadataNative = new CameraMetadataNative(mCameraMetadataNative);
+        return PlatformApi.createTotalCaptureResult(cameraMetadataNative);
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraOutputConfig.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraOutputConfig.aidl
new file mode 100644
index 0000000..0c00848
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraOutputConfig.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.Size;
+import android.view.Surface;
+
+parcelable CameraOutputConfig
+{
+    Size size;
+    Surface surface;
+    int imageFormat;
+    int capacity;
+
+    const int TYPE_SURFACE = 0;
+    const int TYPE_IMAGEREADER = 1;
+    const int TYPE_MULTIRES_IMAGEREADER = 2;
+    int type;
+
+    int outputId;
+    int surfaceGroupId;
+    String physicalCameraId;
+    List<CameraOutputConfig> sharedSurfaceConfigs;
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraSessionConfig.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraSessionConfig.aidl
new file mode 100644
index 0000000..c0d1dec
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CameraSessionConfig.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.CameraOutputConfig;
+
+parcelable CameraSessionConfig
+{
+    List<CameraOutputConfig> outputConfigs;
+    CameraMetadataWrapper sessionParameter;
+    int sessionTemplateId;
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureBundle.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureBundle.aidl
new file mode 100644
index 0000000..6b21485
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureBundle.aidl
@@ -0,0 +1,27 @@
+
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.ImageWrapper;
+
+parcelable CaptureBundle
+{
+    int stageId;
+    CameraMetadataWrapper captureResult;
+    ImageWrapper captureImage;
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureFailureWrapper.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureFailureWrapper.aidl
new file mode 100644
index 0000000..bf3b331
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureFailureWrapper.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 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.service;
+
+parcelable CaptureFailureWrapper;
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureFailureWrapper.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureFailureWrapper.java
new file mode 100644
index 0000000..7deb089
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureFailureWrapper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 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.service;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+
+import android.util.Log;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureFailure;
+
+public class CaptureFailureWrapper implements Parcelable {
+    private CaptureRequest mRequest;
+    private int mReason;
+    private boolean mWasImageCaptured;
+    private int mSequenceId;
+    private long mFrameNumber;
+    private String mErrorPhysicalCameraId;
+
+    public CaptureFailureWrapper(CaptureFailure captureFailure) {
+        mRequest = captureFailure.getRequest();
+        mReason = captureFailure.getReason();
+        mWasImageCaptured = captureFailure.wasImageCaptured();
+        mSequenceId = captureFailure.getSequenceId();
+        mFrameNumber = captureFailure.getFrameNumber();
+        mErrorPhysicalCameraId = captureFailure.getPhysicalCameraId();
+    }
+
+    public CaptureFailureWrapper(Parcel parcel) {
+        mRequest = parcel.readParcelable(CaptureRequest.class.getClassLoader());
+        mReason = parcel.readInt();
+        mWasImageCaptured = parcel.readBoolean();
+        mSequenceId = parcel.readInt();
+        mFrameNumber = parcel.readLong();
+        mErrorPhysicalCameraId = parcel.readString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mRequest, flags);
+        parcel.writeInt(mReason);
+        parcel.writeBoolean(mWasImageCaptured);
+        parcel.writeInt(mSequenceId);
+        parcel.writeLong(mFrameNumber);
+        parcel.writeString(mErrorPhysicalCameraId);
+    }
+
+    public static final Parcelable.Creator<CaptureFailureWrapper> CREATOR
+            = new Parcelable.Creator<CaptureFailureWrapper>() {
+        @Override
+        public CaptureFailureWrapper createFromParcel(Parcel parcel) {
+            return new CaptureFailureWrapper(parcel);
+        }
+
+        @Override
+        public CaptureFailureWrapper[] newArray(int size) {
+            return new CaptureFailureWrapper[size];
+        }
+    };
+
+    public CaptureFailure toCaptureFailure() {
+        return new CaptureFailure(mRequest, mReason, mWasImageCaptured, mSequenceId,
+                mFrameNumber, mErrorPhysicalCameraId);
+    }
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureResultWrapper.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureResultWrapper.aidl
new file mode 100644
index 0000000..6a01aa3
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureResultWrapper.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 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.service;
+
+parcelable CaptureResultWrapper;
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureResultWrapper.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureResultWrapper.java
new file mode 100644
index 0000000..44b5bfa
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureResultWrapper.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 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.service;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+
+import android.util.Log;
+
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+
+public class CaptureResultWrapper implements Parcelable {
+    private String mCameraId;
+    private CameraMetadataNative mResults;
+    private CaptureRequest mRequest;
+    private int mSequenceId;
+    private long mFrameNumber;
+
+    public CaptureResultWrapper(CaptureResult captureResult) {
+        mCameraId = captureResult.getCameraId();
+        mResults = captureResult.getNativeMetadata();
+        mRequest = captureResult.getRequest();
+        mSequenceId = captureResult.getSequenceId();
+        mFrameNumber = captureResult.getFrameNumber();
+    }
+
+    public CaptureResultWrapper(Parcel parcel) {
+        mCameraId = parcel.readString();
+        mResults = parcel.readParcelable(CameraMetadataNative.class.getClassLoader());
+        mRequest = parcel.readParcelable(CaptureRequest.class.getClassLoader());
+        mSequenceId = parcel.readInt();
+        mFrameNumber = parcel.readLong();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mCameraId);
+        parcel.writeParcelable(mResults, flags);
+        parcel.writeParcelable(mRequest, flags);
+        parcel.writeInt(mSequenceId);
+        parcel.writeLong(mFrameNumber);
+    }
+
+    public static final Parcelable.Creator<CaptureResultWrapper> CREATOR =
+            new Parcelable.Creator<CaptureResultWrapper>() {
+                @Override
+                public CaptureResultWrapper createFromParcel(Parcel parcel) {
+                    return new CaptureResultWrapper(parcel);
+                }
+
+                @Override
+                public CaptureResultWrapper[] newArray(int size) {
+                    return new CaptureResultWrapper[size];
+                }
+            };
+
+    public CaptureResult toCaptureResult() {
+        return new CaptureResult(mCameraId, mResults, mRequest, mSequenceId, mFrameNumber);
+    }
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureStageImplWrapper.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureStageImplWrapper.aidl
new file mode 100644
index 0000000..3092d76
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/CaptureStageImplWrapper.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+
+parcelable CaptureStageImplWrapper
+{
+    int id;
+    CameraMetadataWrapper parameters;
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IAdvancedExtenderImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IAdvancedExtenderImpl.aidl
new file mode 100644
index 0000000..3595609
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IAdvancedExtenderImpl.aidl
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.LatencyRange;
+import androidx.camera.extensions.impl.service.SizeList;
+import androidx.camera.extensions.impl.service.Size;
+import androidx.camera.extensions.impl.service.ISessionProcessorImpl;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+
+interface IAdvancedExtenderImpl {
+    boolean isExtensionAvailable(in String cameraId);
+    void init(in String cameraId);
+    LatencyRange getEstimatedCaptureLatencyRange(
+          in String cameraId, in Size outputSize, int format);
+    @nullable List<SizeList> getSupportedPreviewOutputResolutions(in String cameraId);
+    @nullable List<SizeList> getSupportedCaptureOutputResolutions(in String cameraId);
+    @nullable List<SizeList> getSupportedYuvAnalysisResolutions(in String cameraId);
+    ISessionProcessorImpl getSessionProcessor();
+    CameraMetadataWrapper getAvailableCaptureRequestKeys();
+    CameraMetadataWrapper getAvailableCaptureResultKeys();
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ICaptureCallback.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ICaptureCallback.aidl
new file mode 100644
index 0000000..0cf067f
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ICaptureCallback.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+// Declare any non-default types here with import statements
+
+interface ICaptureCallback {
+    void onCaptureStarted(int captureSequenceId, long timestamp);
+    void onCaptureProcessStarted(int captureSequenceId);
+    void onCaptureFailed(int captureSequenceId);
+    void onCaptureSequenceCompleted(int captureSequenceId);
+    void onCaptureSequenceAborted(int captureSequenceId);
+    void onCaptureCompleted(long shutterTimestamp, int captureSequenceId, in CameraMetadataWrapper results);
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ICaptureProcessorImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ICaptureProcessorImpl.aidl
new file mode 100644
index 0000000..d3d7358
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ICaptureProcessorImpl.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.CaptureBundle;
+import androidx.camera.extensions.impl.service.IProcessResultImpl;
+import androidx.camera.extensions.impl.service.Size;
+
+import android.view.Surface;
+
+interface ICaptureProcessorImpl
+{
+    void onOutputSurface(in Surface surface, int imageFormat);
+    void onResolutionUpdate(in Size size);
+    void onImageFormatUpdate(int imageFormat);
+    void process(in List<CaptureBundle> capturelist, in IProcessResultImpl resultCallback);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IExtensionsService.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IExtensionsService.aidl
new file mode 100644
index 0000000..5f0bc57
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IExtensionsService.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.IOnExtensionsInitializedCallback;
+import androidx.camera.extensions.impl.service.IOnExtensionsDeinitializedCallback;
+import androidx.camera.extensions.impl.service.IAdvancedExtenderImpl;
+import androidx.camera.extensions.impl.service.IPreviewExtenderImpl;
+import androidx.camera.extensions.impl.service.IImageCaptureExtenderImpl;
+
+import androidx.camera.extensions.impl.service.Size;
+
+interface IExtensionsService {
+    boolean isAdvancedExtenderImplemented();
+    void initialize(in String version, in IOnExtensionsInitializedCallback callback);
+    void deInitialize(in IOnExtensionsDeinitializedCallback callback);
+    IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType);
+    IPreviewExtenderImpl initializePreviewExtension(int extensionType);
+    IImageCaptureExtenderImpl initializeImageCaptureExtension(int extensionType);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IImageCaptureExtenderImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IImageCaptureExtenderImpl.aidl
new file mode 100644
index 0000000..0ce4856
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IImageCaptureExtenderImpl.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.SizeList;
+import androidx.camera.extensions.impl.service.ICaptureProcessorImpl;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+import androidx.camera.extensions.impl.service.LatencyRange;
+import androidx.camera.extensions.impl.service.Size;
+
+
+interface IImageCaptureExtenderImpl
+{
+    void onInit(in String cameraId);
+    void onDeInit();
+    @nullable CaptureStageImplWrapper onPresetSession();
+    @nullable CaptureStageImplWrapper onEnableSession();
+    @nullable CaptureStageImplWrapper onDisableSession();
+    boolean isExtensionAvailable(in String cameraId);
+    void init(in String cameraId);
+    @nullable ICaptureProcessorImpl getCaptureProcessor();
+    List<CaptureStageImplWrapper> getCaptureStages();
+    int getMaxCaptureStage();
+    @nullable List<SizeList> getSupportedResolutions();
+    @nullable LatencyRange getEstimatedCaptureLatencyRange(in Size outputSize);
+    CameraMetadataWrapper getAvailableCaptureRequestKeys();
+    CameraMetadataWrapper getAvailableCaptureResultKeys();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IImageProcessorImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IImageProcessorImpl.aidl
new file mode 100644
index 0000000..940e0f1
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IImageProcessorImpl.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.ImageWrapper;
+
+interface IImageProcessorImpl
+{
+    void onNextImageAvailable(int outputConfigId, in ImageWrapper imageWrapper,
+            in String physicalCameraId);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IOnExtensionsDeinitializedCallback.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IOnExtensionsDeinitializedCallback.aidl
new file mode 100644
index 0000000..c8aa43c
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IOnExtensionsDeinitializedCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.service;
+
+interface IOnExtensionsDeinitializedCallback {
+    void onSuccess();
+    void onFailure(int error);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IOnExtensionsInitializedCallback.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IOnExtensionsInitializedCallback.aidl
new file mode 100644
index 0000000..ada15d8
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IOnExtensionsInitializedCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.service;
+
+interface IOnExtensionsInitializedCallback {
+    void onSuccess();
+    void onFailure(int error);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IPreviewExtenderImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IPreviewExtenderImpl.aidl
new file mode 100644
index 0000000..193c4dc
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IPreviewExtenderImpl.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.SizeList;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+import androidx.camera.extensions.impl.service.IPreviewImageProcessorImpl;
+import androidx.camera.extensions.impl.service.IRequestUpdateProcessorImpl;
+
+interface IPreviewExtenderImpl
+{
+    void onInit(in String cameraId);
+    void onDeInit();
+    @nullable CaptureStageImplWrapper onPresetSession();
+    @nullable CaptureStageImplWrapper onEnableSession();
+    @nullable CaptureStageImplWrapper onDisableSession();
+    boolean isExtensionAvailable(in String cameraId);
+    void init(in String cameraId);
+    @nullable CaptureStageImplWrapper getCaptureStage();
+    const int PROCESSOR_TYPE_REQUEST_UPDATE_ONLY = 0;
+    const int PROCESSOR_TYPE_IMAGE_PROCESSOR = 1;
+    const int PROCESSOR_TYPE_NONE = 2;
+    int getProcessorType();
+    @nullable IPreviewImageProcessorImpl getPreviewImageProcessor();
+    @nullable IRequestUpdateProcessorImpl getRequestUpdateProcessor();
+    @nullable List<SizeList> getSupportedResolutions();
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IPreviewImageProcessorImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IPreviewImageProcessorImpl.aidl
new file mode 100644
index 0000000..22ccfd1
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IPreviewImageProcessorImpl.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.TotalCaptureResultWrapper;
+import androidx.camera.extensions.impl.service.IProcessResultImpl;
+import androidx.camera.extensions.impl.service.Size;
+import androidx.camera.extensions.impl.service.ImageWrapper;
+import android.view.Surface;
+
+
+interface IPreviewImageProcessorImpl
+{
+    void onOutputSurface(in Surface surface, int imageFormat);
+    void onResolutionUpdate(in Size size);
+    void onImageFormatUpdate(int imageFormat);
+    void process(in ImageWrapper image, in TotalCaptureResultWrapper result,
+            in IProcessResultImpl resultCallback);
+
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IProcessResultImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IProcessResultImpl.aidl
new file mode 100644
index 0000000..f921ea0
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IProcessResultImpl.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+
+interface IProcessResultImpl
+{
+    void onCaptureCompleted(long shutterTimestamp, in CameraMetadataWrapper results);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestCallback.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestCallback.aidl
new file mode 100644
index 0000000..8abed9e
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestCallback.aidl
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 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.service;
+
+import android.hardware.camera2.CaptureRequest;
+import androidx.camera.extensions.impl.service.CaptureFailureWrapper;
+import androidx.camera.extensions.impl.service.CaptureResultWrapper;
+import androidx.camera.extensions.impl.service.TotalCaptureResultWrapper;
+
+interface IRequestCallback
+{
+    void onCaptureStarted(int requestId, long frameNumber, long timestamp);
+    void onCaptureProgressed(int requestId, in CaptureResultWrapper partialResult);
+    void onCaptureCompleted(int requestId, in TotalCaptureResultWrapper totalCaptureResult);
+    void onCaptureFailed(int requestId, in CaptureFailureWrapper captureFailure);
+    void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId);
+    void onCaptureSequenceCompleted(int sequenceId, long frameNumber);
+    void onCaptureSequenceAborted(int sequenceId);
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestProcessorImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestProcessorImpl.aidl
new file mode 100644
index 0000000..70b30fa
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestProcessorImpl.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.IImageProcessorImpl;
+import androidx.camera.extensions.impl.service.IRequestCallback;
+import androidx.camera.extensions.impl.service.Request;
+
+interface IRequestProcessorImpl {
+    void setImageProcessor(int outputConfigId, in IImageProcessorImpl imageProcessor);
+    int submit(in Request request, in IRequestCallback callback);
+    int submitBurst(in List<Request> requests, in IRequestCallback callback);
+    int setRepeating(in Request request, in IRequestCallback callback);
+    void abortCaptures();
+    void stopRepeating();
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestUpdateProcessorImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestUpdateProcessorImpl.aidl
new file mode 100644
index 0000000..f6e78d2
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/IRequestUpdateProcessorImpl.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.service;
+
+import androidx.camera.extensions.impl.service.TotalCaptureResultWrapper;
+import androidx.camera.extensions.impl.service.Size;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+import android.view.Surface;
+
+
+interface IRequestUpdateProcessorImpl
+{
+    @nullable CaptureStageImplWrapper process(in TotalCaptureResultWrapper result);
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ISessionProcessorImpl.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ISessionProcessorImpl.aidl
new file mode 100644
index 0000000..376a4a4
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ISessionProcessorImpl.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.CameraSessionConfig;
+import androidx.camera.extensions.impl.service.OutputSurface;
+import androidx.camera.extensions.impl.service.IRequestProcessorImpl;
+import androidx.camera.extensions.impl.service.ICaptureCallback;
+import android.hardware.camera2.CaptureRequest;
+
+interface ISessionProcessorImpl {
+    CameraSessionConfig initSession(in String cameraId,
+                in OutputSurface previewSurface,
+                in OutputSurface imageCaptureSurface,
+                in OutputSurface imageAnalysisSurface);
+    void deInitSession();
+    void onCaptureSessionStart(IRequestProcessorImpl requestProcessor);
+    void onCaptureSessionEnd();
+    int startRepeating(in ICaptureCallback callback);
+    void stopRepeating();
+    int startCapture(in ICaptureCallback callback);
+    void setParameters(in CaptureRequest captureRequest);
+    int startTrigger(in CaptureRequest captureRequest, in ICaptureCallback callback);
+    void abortCapture(int captureSequenceId);
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ImageWrapper.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ImageWrapper.aidl
new file mode 100644
index 0000000..09b7b48
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ImageWrapper.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 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.service;
+
+parcelable ImageWrapper;
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ImageWrapper.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ImageWrapper.java
new file mode 100644
index 0000000..882980d
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/ImageWrapper.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 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.service;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+
+import android.util.Log;
+import android.graphics.Rect;
+
+import android.graphics.GraphicBuffer;
+import android.media.ImageReader;
+import android.hardware.SyncFence;
+
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+
+import androidx.camera.extensions.impl.advanced.ImageReferenceImpl;
+
+public class ImageWrapper implements Parcelable, ImageReferenceImpl  {
+    private static final String TAG = "ImageWrapper";
+    private int mFormat;
+    private int mWidth;
+    private int mHeight;
+    private int mTransform;
+    private int mScalingMode;
+    private long mTimestamp;
+    private int mPlaneCount;
+    private Rect mCrop;
+    private HardwareBuffer mBuffer;
+    private ParcelFileDescriptor mFence;
+
+    public ImageWrapper(Image image) {
+        mFormat = image.getFormat();
+        mWidth = image.getWidth();
+        mHeight = image.getHeight();
+        mTransform = image.getTransform();
+        mScalingMode = image.getScalingMode();
+        mTimestamp = image.getTimestamp();
+        if (image.getPlaneCount() <= 0) {
+            mPlaneCount = image.getPlanes().length;
+        } else {
+            mPlaneCount = image.getPlaneCount();
+        }
+
+        mCrop = image.getCropRect();
+        mBuffer = image.getHardwareBuffer();
+        try {
+            SyncFence fd = image.getFence();
+            if (fd.isValid()) {
+                mFence = fd.getFdDup();
+            }
+        } catch (java.io.IOException e) {
+            Log.e(TAG, "Failed to parcel buffer fence!");
+        }
+    }
+
+    public ImageWrapper(Parcel parcel) {
+        mFormat = parcel.readInt();
+        mWidth = parcel.readInt();
+        mHeight = parcel.readInt();
+        mTransform = parcel.readInt();
+        mScalingMode = parcel.readInt();
+        mTimestamp = parcel.readLong();
+        mPlaneCount = parcel.readInt();
+        mCrop = parcel.readParcelable(Rect.class.getClassLoader());
+        mBuffer = parcel.readParcelable(HardwareBuffer.class.getClassLoader());
+        mFence = parcel.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mFormat);
+        parcel.writeInt(mWidth);
+        parcel.writeInt(mHeight);
+        parcel.writeInt(mTransform);
+        parcel.writeInt(mScalingMode);
+        parcel.writeLong(mTimestamp);
+        parcel.writeInt(mPlaneCount);
+        parcel.writeParcelable(mCrop, flags);
+        parcel.writeParcelable(mBuffer, flags);
+        parcel.writeParcelable(mFence, flags);
+    }
+
+    public static final Parcelable.Creator<ImageWrapper> CREATOR
+            = new Parcelable.Creator<ImageWrapper>() {
+        @Override
+        public ImageWrapper createFromParcel(Parcel parcel) {
+            return new ImageWrapper(parcel);
+        }
+
+        @Override
+        public ImageWrapper[] newArray(int size) {
+            return new ImageWrapper[size];
+        }
+    };
+
+
+    // ImageReferenceImpl implementations.
+    private Image mImage = null;
+    private int mRefCount = 1;
+    private final Object mImageLock = new Object();
+
+    @Override
+    public Image get() {
+        if (mImage == null) {
+            mImage = new ExtensionImage(this);
+        }
+        return mImage;
+    }
+
+    @Override
+    public boolean increment() {
+        synchronized (mImageLock) {
+            if (mRefCount <= 0) {
+                return false;
+            }
+            mRefCount++;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean decrement() {
+        synchronized (mImageLock) {
+            if (mRefCount <= 0) {
+                return false;
+            }
+            mRefCount--;
+            if (mRefCount <= 0) {
+                mImage.close();
+            }
+        }
+        return true;
+    }
+
+    private static class ExtensionImage extends android.media.Image {
+        private final ImageWrapper mImageWrapper;
+        private GraphicBuffer mGraphicBuffer;
+        private ImageReader.ImagePlane[] mPlanes;
+
+        private ExtensionImage(ImageWrapper imageWrapper) {
+            mImageWrapper = imageWrapper;
+            mIsImageValid = true;
+        }
+
+        @Override
+        public int getFormat() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mFormat;
+        }
+
+        @Override
+        public int getWidth() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mWidth;
+        }
+
+        @Override
+        public HardwareBuffer getHardwareBuffer() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mBuffer;
+        }
+
+        @Override
+        public int getHeight() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mHeight;
+        }
+
+        @Override
+        public long getTimestamp() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mTimestamp;
+        }
+
+        @Override
+        public int getTransform() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mTransform;
+        }
+
+        @Override
+        public int getScalingMode() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mScalingMode;
+        }
+
+        @Override
+        public Plane[] getPlanes() {
+            throwISEIfImageIsInvalid();
+            if (mPlanes == null) {
+                int fenceFd = mImageWrapper.mFence != null ? mImageWrapper.mFence.getFd() : -1;
+                mGraphicBuffer = GraphicBuffer.createFromHardwareBuffer(mImageWrapper.mBuffer);
+                mPlanes = ImageReader.initializeImagePlanes(mImageWrapper.mPlaneCount,
+                        mGraphicBuffer,
+                        fenceFd, mImageWrapper.mFormat, mImageWrapper.mTimestamp,
+                        mImageWrapper.mTransform, mImageWrapper.mScalingMode, mImageWrapper.mCrop);
+
+            }
+            // Shallow copy is fine.
+            return mPlanes.clone();
+        }
+
+        @Override
+        protected final void finalize() throws Throwable {
+            try {
+                close();
+            } finally {
+                super.finalize();
+            }
+        }
+
+        @Override
+        public boolean isAttachable() {
+            throwISEIfImageIsInvalid();
+            // Clients must always detach parcelable images
+            return true;
+        }
+
+        @Override
+        public Rect getCropRect() {
+            throwISEIfImageIsInvalid();
+            return mImageWrapper.mCrop;
+        }
+
+        @Override
+        public void close() {
+            mIsImageValid = false;
+            if (mGraphicBuffer != null) {
+                ImageReader.unlockGraphicBuffer(mGraphicBuffer);
+                mGraphicBuffer.destroy();
+                mGraphicBuffer = null;
+            }
+
+            if (mPlanes != null) {
+                mPlanes = null;
+            }
+
+            if (mImageWrapper.mBuffer != null) {
+                mImageWrapper.mBuffer.close();
+                mImageWrapper.mBuffer = null;
+            }
+
+            if (mImageWrapper.mFence != null) {
+                try {
+                    mImageWrapper.mFence.close();
+                } catch (java.io.IOException e) {
+                    e.printStackTrace();
+                }
+                mImageWrapper.mFence = null;
+            }
+        }
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/LatencyRange.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/LatencyRange.aidl
new file mode 100644
index 0000000..9d9e22c
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/LatencyRange.aidl
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 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.service;
+
+parcelable LatencyRange
+{
+    long min;
+    long max;
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/OutputSurface.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/OutputSurface.aidl
new file mode 100644
index 0000000..558928c
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/OutputSurface.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 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.service;
+
+// Declare any non-default types here with import statements
+import androidx.camera.extensions.impl.service.Size;
+import android.view.Surface;
+
+parcelable OutputSurface {
+    Surface surface;
+    Size size;
+    int imageFormat;
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/Request.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/Request.aidl
new file mode 100644
index 0000000..08dff96
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/Request.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+
+parcelable Request
+{
+    int[] targetOutputConfigIds;
+    CameraMetadataWrapper parameters;
+    int templateId;
+    int requestId;
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/Size.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/Size.aidl
new file mode 100644
index 0000000..5341b60
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/Size.aidl
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 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.service;
+
+parcelable Size
+{
+    int width;
+    int height;
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/SizeList.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/SizeList.aidl
new file mode 100644
index 0000000..443c769
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/SizeList.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 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.service;
+
+import androidx.camera.extensions.impl.service.Size;
+
+parcelable SizeList
+{
+    int format;
+    List<Size> sizes;
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/TotalCaptureResultWrapper.aidl b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/TotalCaptureResultWrapper.aidl
new file mode 100644
index 0000000..b28861d
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/TotalCaptureResultWrapper.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 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.service;
+
+parcelable TotalCaptureResultWrapper;
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/TotalCaptureResultWrapper.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/TotalCaptureResultWrapper.java
new file mode 100644
index 0000000..28bbcb2
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/service/TotalCaptureResultWrapper.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 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.service;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import androidx.camera.extensions.impl.service.CaptureResultWrapper;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+
+public class TotalCaptureResultWrapper implements Parcelable {
+    private String mLogicalCameraId;
+    private CameraMetadataNative mResults;
+    private CaptureRequest mRequest;
+    private int mSequenceId;
+    private long mFrameNumber;
+    private List<CaptureResultWrapper> mPartials = new ArrayList<>();
+    private int mSessionId;
+    private List<PhysicalCaptureResultInfo> mPhysicalResultList = new ArrayList<>();
+
+    public TotalCaptureResultWrapper(TotalCaptureResult totalResult) {
+        mLogicalCameraId = totalResult.getCameraId();
+        mResults = totalResult.getNativeMetadata();
+        mRequest = totalResult.getRequest();
+        mSequenceId = totalResult.getSequenceId();
+        mFrameNumber = totalResult.getFrameNumber();
+        mSessionId = totalResult.getSessionId();
+        for (CaptureResult partial : totalResult.getPartialResults()) {
+            mPartials.add(new CaptureResultWrapper(partial));
+        }
+        Map<String, TotalCaptureResult> physicalResults =
+                totalResult.getPhysicalCameraTotalResults();
+        for (TotalCaptureResult physicalResult : physicalResults.values()) {
+            mPhysicalResultList.add(new PhysicalCaptureResultInfo(physicalResult.getCameraId(),
+                    physicalResult.getNativeMetadata()));
+        }
+    }
+
+    public TotalCaptureResultWrapper(Parcel parcel) {
+        mLogicalCameraId = parcel.readString();
+        mResults = parcel.readParcelable(CameraMetadataNative.class.getClassLoader());
+        mRequest = parcel.readParcelable(CaptureRequest.class.getClassLoader());
+        mSequenceId = parcel.readInt();
+        mFrameNumber = parcel.readLong();
+        parcel.readParcelableList(mPartials, CaptureResultWrapper.class.getClassLoader());
+        mSessionId = parcel.readInt();
+        parcel.readParcelableList(mPhysicalResultList,
+                PhysicalCaptureResultInfo.class.getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mLogicalCameraId);
+        parcel.writeParcelable(mResults, flags);
+        parcel.writeParcelable(mRequest, flags);
+        parcel.writeInt(mSequenceId);
+        parcel.writeLong(mFrameNumber);
+        parcel.writeParcelableList(mPartials, flags);
+        parcel.writeInt(mSessionId);
+        parcel.writeParcelableList(mPhysicalResultList, flags);
+    }
+
+    public static final Parcelable.Creator<TotalCaptureResultWrapper> CREATOR
+            = new Parcelable.Creator<TotalCaptureResultWrapper>() {
+        @Override
+        public TotalCaptureResultWrapper createFromParcel(Parcel parcel) {
+            return new TotalCaptureResultWrapper(parcel);
+        }
+
+        @Override
+        public TotalCaptureResultWrapper[] newArray(int size) {
+            return new TotalCaptureResultWrapper[size];
+        }
+    };
+
+    public TotalCaptureResult toTotalCaptureResult() {
+        PhysicalCaptureResultInfo[] physicalResults = new PhysicalCaptureResultInfo[0];
+        if ((mPhysicalResultList != null) && (!mPhysicalResultList.isEmpty())) {
+            physicalResults = new PhysicalCaptureResultInfo[mPhysicalResultList.size()];
+            physicalResults = mPhysicalResultList.toArray(physicalResults);
+        }
+        ArrayList<CaptureResult> partials = new ArrayList<>(mPartials.size());
+        for (CaptureResultWrapper resultWrapper : mPartials) {
+            partials.add(resultWrapper.toCaptureResult());
+        }
+        return new TotalCaptureResult(
+                mLogicalCameraId, mResults,
+                mRequest, mSequenceId,
+                mFrameNumber, partials, mSessionId,
+                physicalResults);
+    }
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/CaptureStageImplAdapter.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/CaptureStageImplAdapter.java
new file mode 100644
index 0000000..43ddf4a
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/CaptureStageImplAdapter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import android.hardware.camera2.CaptureRequest;
+import android.util.Pair;
+
+import androidx.camera.extensions.impl.CaptureStageImpl;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class CaptureStageImplAdapter implements CaptureStageImpl {
+    private final CaptureStageImplWrapper mCaptureStageImplWrapper;
+    CaptureStageImplAdapter(CaptureStageImplWrapper wrapper) {
+        mCaptureStageImplWrapper = wrapper;
+    }
+
+    @Override
+    public int getId() {
+        return mCaptureStageImplWrapper.id;
+    }
+
+    @Override
+    public List<Pair<CaptureRequest.Key, Object>> getParameters() {
+        CaptureRequest request = mCaptureStageImplWrapper.parameters.toCaptureRequest();
+        List<Pair<CaptureRequest.Key, Object>> result = new ArrayList<>();
+        for (CaptureRequest.Key<?> key : request.getKeys()) {
+            result.add(new Pair(key, request.get(key)));
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardAdvancedExtender.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardAdvancedExtender.java
new file mode 100644
index 0000000..dc57590
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardAdvancedExtender.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl;
+import androidx.camera.extensions.impl.advanced.SessionProcessorImpl;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.IAdvancedExtenderImpl;
+import androidx.camera.extensions.impl.service.ISessionProcessorImpl;
+import androidx.camera.extensions.impl.service.LatencyRange;
+import androidx.camera.extensions.impl.service.SizeList;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ForwardAdvancedExtender implements AdvancedExtenderImpl {
+    private static final String TAG = "ForwardAdvancedExtender";
+    private IAdvancedExtenderImpl mIAdvancedExtender;
+    private String mCameraId;
+    private final int mExtensionType;
+    private ForwardSessionProcessor mForwardSessionProcessor;
+
+    public ForwardAdvancedExtender(int extensionType) {
+        mIAdvancedExtender = ServiceManager.getInstance().createAdvancedExtenderImpl(
+                extensionType);
+        mExtensionType = extensionType;
+    }
+
+    private static ArrayList<Size> getSupportedSizes(SizeList sizesList) {
+        ArrayList<Size> ret = new ArrayList<>();
+        for (androidx.camera.extensions.impl.service.Size size : sizesList.sizes) {
+            ret.add(new Size(size.width, size.height));
+        }
+        return ret;
+    }
+
+    @Override
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        try {
+            return mIAdvancedExtender.isExtensionAvailable(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "isExtensionAvailable failed", e);
+            throw new IllegalStateException("isExtensionAvailable failed", e);
+        }
+    }
+
+    @Override
+    public void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        try {
+            mCameraId = cameraId;
+            mIAdvancedExtender.init(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "init failed", e);
+            throw new IllegalStateException("init failed", e);
+        }
+    }
+    @Override
+    @Nullable
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull String cameraId,
+            @Nullable Size captureOutputSize,
+            int imageFormat) {
+        try {
+            androidx.camera.extensions.impl.service.Size size = null;
+            if (captureOutputSize != null) {
+                size = new androidx.camera.extensions.impl.service.Size();
+                size.width = captureOutputSize.getWidth();
+                size.height = captureOutputSize.getHeight();
+            }
+            LatencyRange latencyRange =
+                    mIAdvancedExtender.getEstimatedCaptureLatencyRange(cameraId,
+                            size, imageFormat);
+            return new Range<>(latencyRange.min, latencyRange.max);
+        } catch (RemoteException e) {
+            Log.e(TAG, "getEstimatedCaptureLatencyRange failed", e);
+            throw new IllegalStateException("getEstimatedCaptureLatencyRange failed", e);
+        }
+    }
+
+    @Override
+    public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(String cameraId) {
+        try {
+            List<SizeList> sizeLists = mIAdvancedExtender.getSupportedPreviewOutputResolutions(
+                    cameraId);
+            Map<Integer, List<Size>> result = new HashMap<>();
+            for (SizeList sizeList : sizeLists) {
+                result.put(sizeList.format, getSupportedSizes(sizeList));
+            }
+            return result;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getSupportedPreviewOutputResolutions failed", e);
+            throw new IllegalStateException("getSupportedPreviewOutputResolutions failed", e);
+        }
+    }
+
+    @Override
+    public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(String cameraId) {
+        try {
+            List<SizeList> sizeLists = mIAdvancedExtender.getSupportedCaptureOutputResolutions(
+                    cameraId);
+            Map<Integer, List<Size>> result = new HashMap<>();
+            for (SizeList sizeList : sizeLists) {
+                result.put(sizeList.format, getSupportedSizes(sizeList));
+            }
+            return result;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getSupportedCaptureOutputResolutions failed", e);
+            throw new IllegalStateException("getSupportedCaptureOutputResolutions failed", e);
+        }
+    }
+
+    @Override
+    public List<Size> getSupportedYuvAnalysisResolutions(String cameraId) {
+        try {
+            List<SizeList> sizeLists = mIAdvancedExtender.getSupportedYuvAnalysisResolutions(
+                    cameraId);
+
+            if (sizeLists == null) {
+                return null;
+            }
+
+            for (SizeList sizeList : sizeLists) {
+                if (sizeList.format == ImageFormat.YUV_420_888) {
+                    return getSupportedSizes(sizeList);
+                }
+            }
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getSupportedYuvAnalysisResolutions failed", e);
+            throw new IllegalStateException("getSupportedYuvAnalysisResolutions failed", e);
+        }
+    }
+
+    /**
+     * Re-initialize IAdvancedExtenderImpl when binder died.
+     */
+    private void ensureIAdvancedExtenderImplAlive() {
+        try {
+            if (!mIAdvancedExtender.asBinder().pingBinder()) {
+                Log.e(TAG, "IAdvancedExtenderImpl binder died, recreate");
+                mIAdvancedExtender = ServiceManager.getInstance().createAdvancedExtenderImpl(
+                        mExtensionType);
+                mIAdvancedExtender.init(mCameraId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "can't create IAdvancedExtenderImpl", e);
+            throw new IllegalStateException("can't create IAdvancedExtenderImpl", e);
+        }
+    }
+    /**
+     * Re-initialize ISessionProcessImpl when binder died.
+     */
+    ISessionProcessorImpl recreateISessionProcessor() {
+        Log.e(TAG, "Recreating ISessionProcessorImpl");
+        try {
+            ensureIAdvancedExtenderImplAlive();
+            return mIAdvancedExtender.getSessionProcessor();
+        } catch (RemoteException e) {
+            Log.e(TAG, "can't get the SessionProcessor from IAdvancedExtenderImpl", e);
+            throw new IllegalStateException(
+                    "can't get the SessionProcessor from IAdvancedExtenderImpl", e);
+        }
+    }
+
+    @Override
+    @NonNull
+    public SessionProcessorImpl createSessionProcessor() {
+        try {
+            ISessionProcessorImpl sessionProcessor = mIAdvancedExtender.getSessionProcessor();
+            mForwardSessionProcessor =
+                    new ForwardSessionProcessor(this, sessionProcessor);
+            return mForwardSessionProcessor;
+        } catch (RemoteException e) {
+            Log.e(TAG, "can't get the SessionProcessor from IAdvancedExtenderImpl", e);
+            throw new IllegalStateException(
+                    "can't get the SessionProcessor from IAdvancedExtenderImpl", e);        }
+    }
+
+    @Override
+    @Nullable
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        try {
+            CameraMetadataWrapper cameraMetadataWrapper
+                    = mIAdvancedExtender.getAvailableCaptureRequestKeys();
+
+            CaptureRequest captureRequest = cameraMetadataWrapper.toCaptureRequest();
+
+            List<CaptureRequest.Key> result = new ArrayList<>();
+            for (CaptureRequest.Key<?> key : captureRequest.getKeys()) {
+                result.add(key);
+            }
+            return result;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getAvailableCaptureRequestKeys failed", e);
+            throw new IllegalStateException("getAvailableCaptureRequestKeys failed", e);
+        }
+    }
+
+    @Override
+    @Nullable
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        try {
+            CameraMetadataWrapper cameraMetadataWrapper
+                    = mIAdvancedExtender.getAvailableCaptureResultKeys();
+            TotalCaptureResult captureResult = cameraMetadataWrapper.toTotalCaptureResult();
+
+            List<CaptureResult.Key> result = new ArrayList<>();
+            for (CaptureResult.Key<?> key : captureResult.getKeys()) {
+                result.add(key);
+            }
+            return result;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getAvailableCaptureRequestKeys failed", e);
+            throw new IllegalStateException("getAvailableCaptureRequestKeys failed", e);
+        }
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardImageCaptureExtender.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardImageCaptureExtender.java
new file mode 100644
index 0000000..03de94b
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardImageCaptureExtender.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.CaptureProcessorImpl;
+import androidx.camera.extensions.impl.CaptureStageImpl;
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.ProcessResultImpl;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.CaptureBundle;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+import androidx.camera.extensions.impl.service.ICaptureProcessorImpl;
+import androidx.camera.extensions.impl.service.IImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.service.IProcessResultImpl;
+import androidx.camera.extensions.impl.service.ImageWrapper;
+import androidx.camera.extensions.impl.service.LatencyRange;
+import androidx.camera.extensions.impl.service.SizeList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+public class ForwardImageCaptureExtender implements ImageCaptureExtenderImpl {
+
+    private static final String TAG = "ForwardPreviewExtender";
+
+    private final int mExtensionType;
+    private IImageCaptureExtenderImpl mIImageCaptureExtender;
+
+    public ForwardImageCaptureExtender(int extensionType) {
+        mExtensionType = extensionType;
+        mIImageCaptureExtender = ServiceManager.getInstance()
+                .createImageCaptureExtenderImpl(extensionType);
+    }
+
+    @Nullable
+    private static CaptureStageImpl convertToCaptureStageImpl(
+            @Nullable CaptureStageImplWrapper wrapper) {
+        if (wrapper == null) {
+            return null;
+        }
+
+        return new CaptureStageImplAdapter(wrapper);
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        try {
+            mIImageCaptureExtender.onInit(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onInit failed", e);
+            throw new IllegalStateException("onInit failed", e);
+        }
+    }
+
+    @Override
+    public void onDeInit() {
+        try {
+            mIImageCaptureExtender.onDeInit();
+        } catch (RemoteException e) {
+            Log.e(TAG, "onDeInit failed", e);
+            throw new IllegalStateException("onDeInit failed", e);
+        }
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        try {
+            return convertToCaptureStageImpl(mIImageCaptureExtender.onPresetSession());
+        } catch (RemoteException e) {
+            Log.e(TAG, "onPresetSession failed", e);
+            throw new IllegalStateException("onPresetSession failed", e);
+        }
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        try {
+            return convertToCaptureStageImpl(mIImageCaptureExtender.onEnableSession());
+        } catch (RemoteException e) {
+            Log.e(TAG, "onEnableSession failed", e);
+            throw new IllegalStateException("onEnableSession failed", e);
+        }
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        try {
+            return convertToCaptureStageImpl(mIImageCaptureExtender.onDisableSession());
+        } catch (RemoteException e) {
+            Log.e(TAG, "onDisableSession failed", e);
+            throw new IllegalStateException("onDisableSession failed", e);
+        }
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId,
+            CameraCharacteristics cameraCharacteristics) {
+        try {
+            return mIImageCaptureExtender.isExtensionAvailable(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "isExtensionAvailable failed", e);
+            throw new IllegalStateException("isExtensionAvailable failed", e);
+        }
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        try {
+            mIImageCaptureExtender.init(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "init failed", e);
+            throw new IllegalStateException("init failed", e);
+        }
+    }
+
+    @Override
+    public CaptureProcessorImpl getCaptureProcessor() {
+        try {
+            ICaptureProcessorImpl captureProcessor = mIImageCaptureExtender.getCaptureProcessor();
+            if (captureProcessor == null) {
+                return null;
+            }
+            return new CaptureProcessorImplAdapter(captureProcessor);
+        } catch (RemoteException e) {
+            Log.e(TAG, "getCaptureProcessor failed", e);
+            throw new IllegalStateException("getCaptureProcessor failed", e);
+        }
+    }
+
+    @Override
+    public List<CaptureStageImpl> getCaptureStages() {
+        try {
+            List<CaptureStageImpl> results = new ArrayList<>();
+            for (CaptureStageImplWrapper wrapper : mIImageCaptureExtender.getCaptureStages()) {
+                results.add(convertToCaptureStageImpl(wrapper));
+            }
+            return results;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getCaptureStages failed", e);
+            throw new IllegalStateException("getCaptureStages failed", e);
+        }
+    }
+
+    @Override
+    public int getMaxCaptureStage() {
+        try {
+            return mIImageCaptureExtender.getMaxCaptureStage();
+        } catch (RemoteException e) {
+            Log.e(TAG, "getMaxCaptureStage failed", e);
+            throw new IllegalStateException("getMaxCaptureStage failed", e);
+        }
+    }
+
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        try {
+            List<SizeList> sizes = mIImageCaptureExtender.getSupportedResolutions();
+            if (sizes == null) {
+                return null;
+            }
+
+            List<Pair<Integer, Size[]>> list = new ArrayList<>();
+            for (SizeList sizeList : sizes) {
+                Size[] sizeArray = new Size[sizeList.sizes.size()];
+                for (int i = 0; i < sizeList.sizes.size(); i++) {
+                    sizeArray[i] = new Size(sizeList.sizes.get(i).width,
+                            sizeList.sizes.get(i).height);
+                }
+                list.add(new Pair(sizeList.format, sizeArray));
+            }
+            return list;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getSupportedResolutions failed", e);
+            throw new IllegalStateException("getSupportedResolutions failed", e);
+        }
+    }
+
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(Size captureOutputSize) {
+        try {
+            androidx.camera.extensions.impl.service.Size size = null;
+            if (captureOutputSize != null) {
+                size = new androidx.camera.extensions.impl.service.Size();
+                size.width = captureOutputSize.getWidth();
+                size.height = captureOutputSize.getHeight();
+            }
+            LatencyRange latencyRange =
+                    mIImageCaptureExtender.getEstimatedCaptureLatencyRange(size);
+            if (latencyRange == null) {
+                return null;
+            }
+
+            return new Range<Long>(latencyRange.min, latencyRange.max);
+        } catch (RemoteException e) {
+            Log.e(TAG, "getEstimatedCaptureLatencyRange failed", e);
+            throw new IllegalStateException("getEstimatedCaptureLatencyRange failed", e);
+        }
+    }
+
+    @Override
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+        try {
+            CameraMetadataWrapper cameraMetadataWrapper
+                    = mIImageCaptureExtender.getAvailableCaptureRequestKeys();
+            if (cameraMetadataWrapper == null) {
+                return null;
+            }
+
+            CaptureRequest captureRequest = cameraMetadataWrapper.toCaptureRequest();
+            List<CaptureRequest.Key> result = new ArrayList<>();
+            for (CaptureRequest.Key<?> key : captureRequest.getKeys()) {
+                result.add(key);
+            }
+            return result;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getAvailableCaptureRequestKeys failed", e);
+            throw new IllegalStateException("getAvailableCaptureRequestKeys failed", e);
+        }
+    }
+
+    @Override
+    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+        try {
+            CameraMetadataWrapper cameraMetadataWrapper
+                    = mIImageCaptureExtender.getAvailableCaptureResultKeys();
+            if (cameraMetadataWrapper == null) {
+                return null;
+            }
+
+            TotalCaptureResult captureResult = cameraMetadataWrapper.toTotalCaptureResult();
+            List<CaptureResult.Key> result = new ArrayList<>();
+            for (CaptureResult.Key<?> key : captureResult.getKeys()) {
+                result.add(key);
+            }
+            return result;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getAvailableCaptureResultKeys failed", e);
+            throw new IllegalStateException("getAvailableCaptureResultKeys failed", e);
+        }
+    }
+
+    private static class CaptureProcessorImplAdapter implements CaptureProcessorImpl {
+        private ICaptureProcessorImpl mICaptureProcessor;
+
+        private CaptureProcessorImplAdapter(ICaptureProcessorImpl iCaptureProcessor) {
+            mICaptureProcessor = iCaptureProcessor;
+        }
+
+        @Override
+        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+            process(results, null, null);
+        }
+
+        @Override
+        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                ProcessResultImpl resultCallback, Executor executor) {
+
+            try {
+                List<CaptureBundle> captureBundleList = new ArrayList<>();
+                for (Integer captureStageId : results.keySet()) {
+                    CaptureBundle bundle = new CaptureBundle();
+                    bundle.stageId = captureStageId;
+                    Pair<Image, TotalCaptureResult> pair = results.get(captureStageId);
+
+                    bundle.captureResult =
+                            new CameraMetadataWrapper(pair.second.getNativeMetadata());
+                    bundle.captureImage = new ImageWrapper(pair.first);
+                    captureBundleList.add(bundle);
+                }
+
+                IProcessResultImpl.Stub iProcessResultImpl = null;
+                if (resultCallback != null) {
+                    iProcessResultImpl = new IProcessResultImpl.Stub() {
+                        @Override
+                        public void onCaptureCompleted(long shutterTimestamp,
+                                CameraMetadataWrapper result) {
+                            List<Pair<CaptureResult.Key, Object>> resultList = new ArrayList<>();
+                            TotalCaptureResult captureResult = result.toTotalCaptureResult();
+                            for (CaptureResult.Key<?> key : captureResult.getKeys()) {
+                                resultList.add(new Pair(key, captureResult.get(key)));
+                            }
+                            if (executor == null) {
+                                resultCallback.onCaptureCompleted(shutterTimestamp,
+                                        resultList);
+                            } else {
+                                executor.execute(() -> {
+                                    resultCallback.onCaptureCompleted(shutterTimestamp,
+                                            resultList);
+                                });
+                            }
+                        }
+                    };
+                }
+                mICaptureProcessor.process(captureBundleList, iProcessResultImpl);
+            } catch (RemoteException e) {
+
+            }
+        }
+
+        @Override
+        public void onOutputSurface(Surface surface, int imageFormat) {
+            try {
+                mICaptureProcessor.onOutputSurface(surface, imageFormat);
+            } catch (RemoteException e) {
+                Log.e(TAG, "CaptureProcessor onOutputSurface failed", e);
+                throw new IllegalStateException("CaptureProcessor onOutputSurface failed", e);
+            }
+        }
+
+        @Override
+        public void onResolutionUpdate(Size size) {
+            try {
+                androidx.camera.extensions.impl.service.Size serviceSize
+                        = new androidx.camera.extensions.impl.service.Size();
+                serviceSize.width = size.getWidth();
+                serviceSize.height = size.getHeight();
+                mICaptureProcessor.onResolutionUpdate(serviceSize);
+            } catch (RemoteException e) {
+                Log.e(TAG, "CaptureProcessor onResolutionUpdate failed", e);
+                throw new IllegalStateException("CaptureProcessor onResolutionUpdate failed", e);
+            }
+        }
+
+        @Override
+        public void onImageFormatUpdate(int imageFormat) {
+            try {
+                mICaptureProcessor.onImageFormatUpdate(imageFormat);
+            } catch (RemoteException e) {
+                Log.e(TAG, "CaptureProcessor imageFormat failed", e);
+                throw new IllegalStateException("CaptureProcessor imageFormat failed", e);
+            }
+        }
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardPreviewExtender.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardPreviewExtender.java
new file mode 100644
index 0000000..81969ca
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardPreviewExtender.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.CaptureStageImpl;
+import androidx.camera.extensions.impl.PreviewExtenderImpl;
+import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
+import androidx.camera.extensions.impl.ProcessResultImpl;
+import androidx.camera.extensions.impl.ProcessorImpl;
+import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.CaptureStageImplWrapper;
+import androidx.camera.extensions.impl.service.IPreviewExtenderImpl;
+import androidx.camera.extensions.impl.service.IPreviewImageProcessorImpl;
+import androidx.camera.extensions.impl.service.IProcessResultImpl;
+import androidx.camera.extensions.impl.service.IRequestUpdateProcessorImpl;
+import androidx.camera.extensions.impl.service.ImageWrapper;
+import androidx.camera.extensions.impl.service.TotalCaptureResultWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class ForwardPreviewExtender implements PreviewExtenderImpl {
+    private static final String TAG = "ForwardPreviewExtender";
+
+    private final int mExtensionType;
+    private IPreviewExtenderImpl mIPreviewExtender;
+
+    public ForwardPreviewExtender(int extensionType) {
+        mExtensionType = extensionType;
+        mIPreviewExtender = ServiceManager.getInstance().createPreviewExtenderImpl(extensionType);
+    }
+
+    @Nullable
+    private static CaptureStageImpl convertToCaptureStageImpl(
+            @Nullable CaptureStageImplWrapper wrapper) {
+        if (wrapper == null) {
+            return null;
+        }
+
+        return new CaptureStageImplAdapter(wrapper);
+    }
+
+    @Override
+    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+            Context context) {
+        try {
+            mIPreviewExtender.onInit(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onInit failed", e);
+            throw new IllegalStateException("onInit failed", e);
+        }
+    }
+
+    @Override
+    public void onDeInit() {
+        try {
+            mIPreviewExtender.onDeInit();
+        } catch (RemoteException e) {
+            Log.e(TAG, "onDeInit failed", e);
+            throw new IllegalStateException("onDeInit failed", e);
+        }
+    }
+
+    @Override
+    public CaptureStageImpl onPresetSession() {
+        try {
+            return convertToCaptureStageImpl(mIPreviewExtender.onPresetSession());
+        } catch (RemoteException e) {
+            Log.e(TAG, "onPresetSession failed", e);
+            throw new IllegalStateException("onDeInit failed", e);
+        }
+    }
+
+    @Override
+    public CaptureStageImpl onEnableSession() {
+        try {
+            return convertToCaptureStageImpl(mIPreviewExtender.onEnableSession());
+        } catch (RemoteException e) {
+            Log.e(TAG, "onEnableSession failed", e);
+            throw new IllegalStateException("onEnableSession failed", e);
+        }
+    }
+
+    @Override
+    public CaptureStageImpl onDisableSession() {
+        try {
+            return convertToCaptureStageImpl(mIPreviewExtender.onDisableSession());
+        } catch (RemoteException e) {
+            Log.e(TAG, "onDisableSession failed", e);
+            throw new IllegalStateException("onDisableSession failed", e);
+        }
+    }
+
+    @Override
+    public boolean isExtensionAvailable(String cameraId,
+            CameraCharacteristics cameraCharacteristics) {
+        try {
+            return mIPreviewExtender.isExtensionAvailable(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "isExtensionAvailable failed", e);
+            throw new IllegalStateException("isExtensionAvailable failed", e);
+        }
+    }
+
+    @Override
+    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+        try {
+            mIPreviewExtender.init(cameraId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "init failed", e);
+            throw new IllegalStateException("init failed", e);
+        }
+    }
+
+    @Override
+    public CaptureStageImpl getCaptureStage() {
+        try {
+            return convertToCaptureStageImpl(mIPreviewExtender.getCaptureStage());
+        } catch (RemoteException e) {
+            Log.e(TAG, "getCaptureStage failed", e);
+            throw new IllegalStateException("getCaptureStage failed", e);
+        }
+    }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        try {
+            switch (mIPreviewExtender.getProcessorType()) {
+                case IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY:
+                    return ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY;
+                case IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR:
+                    return ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR;
+                case IPreviewExtenderImpl.PROCESSOR_TYPE_NONE:
+                default:
+                    return ProcessorType.PROCESSOR_TYPE_NONE;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getProcessorType failed", e);
+            throw new IllegalStateException("getProcessorType failed", e);
+        }
+    }
+
+    @Override
+    public ProcessorImpl getProcessor() {
+        try {
+            switch (getProcessorType()) {
+                case PROCESSOR_TYPE_REQUEST_UPDATE_ONLY:
+                    return new RequestUpdateProcessorAdapter(
+                            mIPreviewExtender.getRequestUpdateProcessor());
+                case PROCESSOR_TYPE_IMAGE_PROCESSOR:
+                    return new PreviewImageProcessorAdapter(
+                            mIPreviewExtender.getPreviewImageProcessor());
+                case PROCESSOR_TYPE_NONE:
+                default:
+                    return null;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getProcessorType failed", e);
+            throw new IllegalStateException("getProcessorType failed", e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+        return null;
+    }
+
+    private static class PreviewImageProcessorAdapter implements PreviewImageProcessorImpl {
+
+        private final IPreviewImageProcessorImpl mIPreviewImageProcessor;
+
+        private PreviewImageProcessorAdapter(IPreviewImageProcessorImpl iPreviewImageProcessor) {
+            mIPreviewImageProcessor = iPreviewImageProcessor;
+        }
+
+        @Override
+        public void process(Image image, TotalCaptureResult result) {
+            try {
+                mIPreviewImageProcessor.process(
+                        new ImageWrapper(image), new TotalCaptureResultWrapper(result), null);
+            } catch (RemoteException e) {
+
+            }
+        }
+
+        @Override
+        public void process(Image image, TotalCaptureResult result,
+                ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            try {
+
+                IProcessResultImpl.Stub iProcessResultImpl = null;
+                if (resultCallback != null) {
+                    iProcessResultImpl = new IProcessResultImpl.Stub() {
+                        @Override
+                        public void onCaptureCompleted(long shutterTimestamp,
+                                CameraMetadataWrapper result) {
+                            List<Pair<CaptureResult.Key, Object>> resultList = new ArrayList<>();
+                            TotalCaptureResult captureResult = result.toTotalCaptureResult();
+                            for (CaptureResult.Key<?> key : captureResult.getKeys()) {
+                                resultList.add(new Pair(key, captureResult.get(key)));
+                            }
+                            if (executor == null) {
+                                resultCallback.onCaptureCompleted(shutterTimestamp,
+                                        resultList);
+                            } else {
+                                executor.execute(() -> {
+                                    resultCallback.onCaptureCompleted(shutterTimestamp,
+                                            resultList);
+                                });
+                            }
+                        }
+                    };
+                }
+                mIPreviewImageProcessor.process(
+                        new ImageWrapper(image), new TotalCaptureResultWrapper(result),
+                        iProcessResultImpl);
+                image.close();
+            } catch (RemoteException e) {
+
+            }
+        }
+
+        @Override
+        public void onOutputSurface(Surface surface, int imageFormat) {
+            try {
+                mIPreviewImageProcessor.onOutputSurface(surface, imageFormat);
+            } catch (RemoteException e) {
+                Log.e(TAG, "PreviewImageProcessorAdapter onOutputSurface failed", e);
+                throw new IllegalStateException(
+                        "PreviewImageProcessorAdapter onOutputSurface failed", e);
+            }
+        }
+
+        @Override
+        public void onResolutionUpdate(Size size) {
+            try {
+                androidx.camera.extensions.impl.service.Size serviceSize =
+                        new androidx.camera.extensions.impl.service.Size();
+                serviceSize.width = size.getWidth();
+                serviceSize.height = size.getHeight();
+                mIPreviewImageProcessor.onResolutionUpdate(serviceSize);
+            } catch (RemoteException e) {
+                Log.e(TAG, "PreviewImageProcessorAdapter onResolutionUpdate", e);
+                throw new IllegalStateException(
+                        "PreviewImageProcessorAdapter onResolutionUpdate failed", e);
+            }
+        }
+
+        @Override
+        public void onImageFormatUpdate(int imageFormat) {
+            try {
+                mIPreviewImageProcessor.onImageFormatUpdate(imageFormat);
+            } catch (RemoteException e) {
+                Log.e(TAG, "PreviewImageProcessorAdapter onImageFormatUpdate failed", e);
+                throw new IllegalStateException(
+                        "PreviewImageProcessorAdapter onImageFormatUpdate failed", e);
+            }
+        }
+    }
+
+    private static class RequestUpdateProcessorAdapter
+            implements RequestUpdateProcessorImpl {
+        private IRequestUpdateProcessorImpl mIRequestUpdateProcessor;
+
+        private RequestUpdateProcessorAdapter(IRequestUpdateProcessorImpl iRequestUpdateProcessor) {
+            mIRequestUpdateProcessor = iRequestUpdateProcessor;
+        }
+
+        @Override
+        public void onOutputSurface(Surface surface, int imageFormat) {
+        }
+
+        @Override
+        public void onResolutionUpdate(Size size) {
+        }
+
+        @Override
+        public void onImageFormatUpdate(int imageFormat) {
+        }
+
+        @Nullable
+        @Override
+        public CaptureStageImpl process(TotalCaptureResult result) {
+            try {
+                return convertToCaptureStageImpl(
+                        mIRequestUpdateProcessor.process(new TotalCaptureResultWrapper(result)));
+            } catch (RemoteException e) {
+                Log.e(TAG, "RequestUpdateProcessorAdapter process failed", e);
+                throw new IllegalStateException("RequestUpdateProcessorAdapter process failed", e);
+            }
+        }
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardSessionProcessor.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardSessionProcessor.java
new file mode 100644
index 0000000..bdb87be
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ForwardSessionProcessor.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import static androidx.camera.extensions.impl.service.CameraOutputConfig.TYPE_IMAGEREADER;
+import static androidx.camera.extensions.impl.service.CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER;
+import static androidx.camera.extensions.impl.service.CameraOutputConfig.TYPE_SURFACE;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImplBuilder;
+import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl;
+import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImplBuilder;
+import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl;
+import androidx.camera.extensions.impl.advanced.RequestProcessorImpl;
+import androidx.camera.extensions.impl.advanced.SessionProcessorImpl;
+import androidx.camera.extensions.impl.service.CameraMetadataWrapper;
+import androidx.camera.extensions.impl.service.CameraOutputConfig;
+import androidx.camera.extensions.impl.service.CameraSessionConfig;
+import androidx.camera.extensions.impl.service.ICaptureCallback;
+import androidx.camera.extensions.impl.service.ISessionProcessorImpl;
+import androidx.camera.extensions.impl.service.OutputSurface;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ForwardSessionProcessor implements SessionProcessorImpl {
+    private static final String TAG = "ForwardSessionProcessor";
+    private final ForwardAdvancedExtender mForwardAdvancedExtender;
+
+    private ISessionProcessorImpl mISessionProcessor;
+    public ForwardSessionProcessor(@NonNull ForwardAdvancedExtender forwardAdvancedExtender,
+            @NonNull ISessionProcessorImpl sessionProcessor) {
+        mForwardAdvancedExtender = forwardAdvancedExtender;
+        mISessionProcessor = sessionProcessor;
+    }
+
+    private OutputSurface getOutputSurface(OutputSurfaceImpl outputSurfaceImpl) {
+        OutputSurface outputSurface = new OutputSurface();
+
+        androidx.camera.extensions.impl.service.Size extSize =
+                new androidx.camera.extensions.impl.service.Size();
+        extSize.width = outputSurfaceImpl.getSize().getWidth();
+        extSize.height = outputSurfaceImpl.getSize().getHeight();
+        outputSurface.size = extSize;
+        outputSurface.imageFormat = outputSurfaceImpl.getImageFormat();
+        outputSurface.surface = outputSurfaceImpl.getSurface();
+        return outputSurface;
+    }
+
+    @Override
+    @NonNull
+    public Camera2SessionConfigImpl initSession(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            @NonNull Context context,
+            @NonNull OutputSurfaceImpl previewSurfaceConfig,
+            @NonNull OutputSurfaceImpl imageCaptureSurfaceConfig,
+            @Nullable OutputSurfaceImpl imageAnalysisSurfaceConfig) {
+        return initSession(cameraId, cameraCharacteristicsMap, context, previewSurfaceConfig,
+                imageCaptureSurfaceConfig, imageAnalysisSurfaceConfig,
+                /* isRecoveringFromBinderDeath */ false);
+    }
+
+    @NonNull
+    private Camera2SessionConfigImpl initSession(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            @NonNull Context context,
+            @NonNull OutputSurfaceImpl previewSurfaceConfig,
+            @NonNull OutputSurfaceImpl imageCaptureSurfaceConfig,
+            @Nullable OutputSurfaceImpl imageAnalysisSurfaceConfig,
+            boolean isRecoveringFromBinderDeath) {
+        try {
+            OutputSurface outputSurfacePreview = getOutputSurface(previewSurfaceConfig);
+            OutputSurface outputSurfaceCapture = getOutputSurface(imageCaptureSurfaceConfig);
+            OutputSurface outputSurfaceAnalysis = null;
+            if (imageAnalysisSurfaceConfig != null) {
+                outputSurfaceAnalysis = getOutputSurface(imageAnalysisSurfaceConfig);
+            }
+
+            CameraSessionConfig sessionConfig = mISessionProcessor.initSession(
+                    cameraId,
+                    outputSurfacePreview,
+                    outputSurfaceCapture,
+                    outputSurfaceAnalysis);
+
+            Camera2SessionConfigImplBuilder sessionConfigBuilder =
+                    new Camera2SessionConfigImplBuilder();
+            CaptureRequest captureRequest = sessionConfig.sessionParameter.toCaptureRequest();
+            for (CaptureRequest.Key<?> key : captureRequest.getKeys()) {
+                CaptureRequest.Key<Object> objKey = (CaptureRequest.Key<Object>) key;
+                sessionConfigBuilder.addSessionParameter(objKey, captureRequest.get(objKey));
+            }
+            for (CameraOutputConfig outputConfig : sessionConfig.outputConfigs) {
+                Camera2OutputConfigImplBuilder builder =
+                        getCamera2OutputConfigImplBuilder(outputConfig);
+                if (outputConfig.sharedSurfaceConfigs != null &&
+                        (!outputConfig.sharedSurfaceConfigs.isEmpty())) {
+                    for (CameraOutputConfig sharedSurfaceConfig :
+                            outputConfig.sharedSurfaceConfigs) {
+                        builder.addSurfaceSharingOutputConfig(
+                                getCamera2OutputConfigImplBuilder(sharedSurfaceConfig).build());
+                    }
+                }
+                sessionConfigBuilder.addOutputConfig(builder.build());
+            }
+
+            sessionConfigBuilder.setSessionTemplateId(sessionConfig.sessionTemplateId);
+            return sessionConfigBuilder.build();
+        } catch (RemoteException e) {
+            if ((e instanceof DeadObjectException) && !isRecoveringFromBinderDeath) {
+                // service died, reinitialize.
+                mISessionProcessor = mForwardAdvancedExtender.recreateISessionProcessor();
+                return initSession(cameraId, cameraCharacteristicsMap, context, previewSurfaceConfig
+                        , imageCaptureSurfaceConfig, imageAnalysisSurfaceConfig,
+                        /* isRecoveringFromBinderDeath */ true );
+            }
+            Log.e(TAG, "initSession failed", e);
+            throw new IllegalStateException("initSession failed", e);
+        }
+    }
+
+    private static Camera2OutputConfigImplBuilder getCamera2OutputConfigImplBuilder(
+            CameraOutputConfig outputConfig) {
+        switch (outputConfig.type) {
+            case TYPE_SURFACE:
+                return Camera2OutputConfigImplBuilder
+                        .newSurfaceConfig(outputConfig.surface)
+                        .setPhysicalCameraId(outputConfig.physicalCameraId)
+                        .setSurfaceGroupId(outputConfig.surfaceGroupId)
+                        .setOutputConfigId(outputConfig.outputId);
+            case TYPE_IMAGEREADER:
+                android.util.Size size = new android.util.Size(
+                        outputConfig.size.width, outputConfig.size.height);
+                return Camera2OutputConfigImplBuilder
+                        .newImageReaderConfig(size, outputConfig.imageFormat, outputConfig.capacity)
+                        .setPhysicalCameraId(outputConfig.physicalCameraId)
+                        .setSurfaceGroupId(outputConfig.surfaceGroupId)
+                        .setOutputConfigId(outputConfig.outputId);
+            case TYPE_MULTIRES_IMAGEREADER:
+            default:
+                throw new UnsupportedOperationException("Output config type not supported");
+        }
+    }
+
+    @Override
+    public void deInitSession() {
+        try {
+            mISessionProcessor.deInitSession();
+        } catch (RemoteException e) {
+            Log.e(TAG, "deInitSession failed", e);
+            throw new IllegalStateException("deInitSession failed", e);
+        }
+    }
+
+    @Override
+    public void setParameters(Map<CaptureRequest.Key<?>, Object> parameters) {
+        try {
+            mISessionProcessor.setParameters(PlatformApi.createCaptureRequest(parameters));
+        } catch (RemoteException e) {
+            Log.e(TAG, "setParameters failed", e);
+            // still capture normally will invoke setParameters first and then startCapture.
+            // We want to fail the startCapture not the setParameters so that the capture failure
+            // can be propagated to the app.
+        }
+    }
+
+    @Override
+    public int startTrigger(Map<CaptureRequest.Key<?>, Object> triggers, CaptureCallback callback) {
+        try {
+            return mISessionProcessor.startTrigger(PlatformApi.createCaptureRequest(triggers),
+                    new CaptureCallbackAdapter(callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "startTrigger failed", e);
+            throw new IllegalStateException("startTrigger failed", e);
+        }
+    }
+
+    @Override
+    public void onCaptureSessionStart(RequestProcessorImpl requestProcessor) {
+        try {
+            mISessionProcessor.onCaptureSessionStart(
+                    new RequestProcessorAdapter(requestProcessor));
+        } catch (RemoteException e) {
+            Log.e(TAG, "onCaptureSessionStart failed", e);
+            throw new IllegalStateException("onCaptureSessionStart failed", e);
+        }
+    }
+
+    @Override
+    public void onCaptureSessionEnd() {
+        try {
+            mISessionProcessor.onCaptureSessionEnd();
+        } catch (RemoteException e) {
+            Log.e(TAG, "onCaptureSessionEnd failed", e);
+            throw new IllegalStateException("onCaptureSessionEnd failed", e);
+        }
+    }
+
+    @Override
+    public int startRepeating(CaptureCallback callback) {
+        try {
+            return mISessionProcessor.startRepeating(new CaptureCallbackAdapter(callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "startRepeating failed", e);
+            // notify the onCaptureFailed callback so that app is notified of the error.
+            callback.onCaptureFailed(0);
+            return 0;
+        }
+    }
+
+    @Override
+    public void stopRepeating() {
+        try {
+            mISessionProcessor.stopRepeating();
+        } catch (RemoteException e) {
+            Log.e(TAG, "stopRepeating failed", e);
+            throw new IllegalStateException("startRepeating failed", e);
+        }
+    }
+
+    @Override
+    public int startCapture(CaptureCallback callback) {
+        try {
+            return mISessionProcessor.startCapture(new CaptureCallbackAdapter(callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "startCapture failed", e);
+            // notify the onCaptureFailed callback so that app is notified of the error.
+            callback.onCaptureFailed(0);
+            return 0;
+        }
+    }
+
+    @Override
+    public void abortCapture(int captureSequenceId) {
+        try {
+            mISessionProcessor.abortCapture(captureSequenceId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "abortCapture failed", e);
+            throw new IllegalStateException("abortCapture failed", e);
+        }
+    }
+
+    private static class CaptureCallbackAdapter extends ICaptureCallback.Stub {
+        private final SessionProcessorImpl.CaptureCallback mImplCaptureCallback;
+
+        CaptureCallbackAdapter(SessionProcessorImpl.CaptureCallback implCaptureCallback) {
+            mImplCaptureCallback = implCaptureCallback;
+        }
+
+        @Override
+        public void onCaptureStarted(int captureSequenceId, long timeStamp) throws RemoteException {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mImplCaptureCallback.onCaptureStarted(captureSequenceId, timeStamp);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onCaptureProcessStarted(int captureSequenceId) throws RemoteException {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mImplCaptureCallback.onCaptureProcessStarted(captureSequenceId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onCaptureFailed(int captureSequenceId) throws RemoteException {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mImplCaptureCallback.onCaptureFailed(captureSequenceId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onCaptureSequenceCompleted(int captureSequenceId) throws RemoteException {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mImplCaptureCallback.onCaptureSequenceCompleted(captureSequenceId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onCaptureSequenceAborted(int captureSequenceId) throws RemoteException {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mImplCaptureCallback.onCaptureSequenceAborted(captureSequenceId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onCaptureCompleted(long shutterTimestamp, int captureSequenceId,
+                CameraMetadataWrapper cameraMetadataWrapper)
+                throws RemoteException {
+            TotalCaptureResult captureResult = cameraMetadataWrapper.toTotalCaptureResult();
+
+            Map<CaptureResult.Key, Object> resultmap = new HashMap<>();
+            for (CaptureResult.Key key : captureResult.getKeys()) {
+                resultmap.put(key, captureResult.get(key));
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mImplCaptureCallback.onCaptureCompleted(shutterTimestamp, captureSequenceId,
+                        resultmap);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/PlatformApi.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/PlatformApi.java
new file mode 100644
index 0000000..a40a16d
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/PlatformApi.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import java.util.Map;
+
+public class PlatformApi {
+    public static CaptureRequest createCaptureRequest(
+            Map<CaptureRequest.Key<?>, Object> parameters) {
+        CameraMetadataNative metadataNative = new CameraMetadataNative();
+        CaptureRequest.Builder builder =
+                new CaptureRequest.Builder(metadataNative, false, -1, "0", null);
+
+        for (CaptureRequest.Key<?> key : parameters.keySet()) {
+            CaptureRequest.Key<Object> objKey = (CaptureRequest.Key<Object>) key;
+            builder.set(objKey, parameters.get(objKey));
+        }
+        return builder.build();
+    }
+
+    public static CaptureRequest createCaptureRequest(CameraMetadataNative cameraMetadataNative) {
+        CaptureRequest.Builder builder =
+                new CaptureRequest.Builder(cameraMetadataNative, false, -1, "0", null);
+        return builder.build();
+    }
+
+    public static TotalCaptureResult createTotalCaptureResult(
+            CameraMetadataNative cameraMetadataNative) {
+        return new TotalCaptureResult(cameraMetadataNative, 0);
+    }
+
+    public static CameraMetadataNative createCameraMetadataNative(
+            Map<CaptureRequest.Key<?>, Object> parameters) {
+        CameraMetadataNative metadataNative = new CameraMetadataNative();
+        for (CaptureRequest.Key<?> key : parameters.keySet()) {
+            CaptureRequest.Key<Object> objKey = (CaptureRequest.Key<Object>) key;
+            metadataNative.set(objKey, parameters.get(objKey));
+        }
+        return metadataNative;
+    }
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/RequestProcessorAdapter.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/RequestProcessorAdapter.java
new file mode 100644
index 0000000..3cc1d24
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/RequestProcessorAdapter.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.camera.extensions.impl.advanced.ImageProcessorImpl;
+import androidx.camera.extensions.impl.advanced.ImageReferenceImpl;
+import androidx.camera.extensions.impl.advanced.RequestProcessorImpl;
+import androidx.camera.extensions.impl.service.CaptureFailureWrapper;
+import androidx.camera.extensions.impl.service.CaptureResultWrapper;
+import androidx.camera.extensions.impl.service.IImageProcessorImpl;
+import androidx.camera.extensions.impl.service.IRequestCallback;
+import androidx.camera.extensions.impl.service.IRequestProcessorImpl;
+import androidx.camera.extensions.impl.service.ImageWrapper;
+import androidx.camera.extensions.impl.service.Request;
+import androidx.camera.extensions.impl.service.TotalCaptureResultWrapper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class RequestProcessorAdapter extends IRequestProcessorImpl.Stub {
+    private static final String TAG = "RequestProcessorAdapter";
+
+    private RequestProcessorImpl mRequestProcessorImpl;
+    RequestProcessorAdapter(RequestProcessorImpl requestProcessorImpl) {
+        mRequestProcessorImpl = requestProcessorImpl;
+    }
+    @Override
+    public void setImageProcessor(int outputfigId, IImageProcessorImpl imageProcessor)
+            throws RemoteException {
+        mRequestProcessorImpl.setImageProcessor(outputfigId, new ImageProcessorImpl() {
+            @Override
+            public void onNextImageAvailable(int outputConfigId, long timestampNs,
+                    ImageReferenceImpl imageReference, String physicalCameraId) {
+                try {
+                    imageProcessor.onNextImageAvailable(outputConfigId,
+                            new ImageWrapper(imageReference.get()), physicalCameraId);
+                    imageReference.decrement();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Can't connect to the binder!", e);
+                }
+            }
+        });
+    }
+
+    @Override
+    public int submit(Request request, IRequestCallback requestCallback)
+            throws RemoteException {
+        return submitBurst(Arrays.asList(request), requestCallback);
+    }
+
+    @Override
+    public int submitBurst(List<Request> requests, IRequestCallback requestCallback)
+            throws RemoteException {
+        List<RequestProcessorImpl.Request> implRequests = new ArrayList<>();
+        Map<RequestProcessorImpl.Request, Request> requestsMap = new HashMap<>();
+        for (Request request : requests) {
+            RequestProcessorImpl.Request implRequest = new ImplRequestAdapter(request);
+            implRequests.add(implRequest);
+            requestsMap.put(implRequest,  request);
+        }
+        return mRequestProcessorImpl.submit(implRequests,
+                new ImplCaptureCallbackAdapter(requestsMap, requestCallback));
+    }
+
+    @Override
+    public int setRepeating(Request request, IRequestCallback requestCallback)
+            throws RemoteException {
+        Map<RequestProcessorImpl.Request, Request> requestsMap = new HashMap<>();
+        RequestProcessorImpl.Request implRequest = new ImplRequestAdapter(request);
+        requestsMap.put(implRequest, request);
+
+        return mRequestProcessorImpl.setRepeating(implRequest,
+                new ImplCaptureCallbackAdapter(requestsMap, requestCallback));
+    }
+
+    @Override
+    public void abortCaptures() throws RemoteException {
+        mRequestProcessorImpl.abortCaptures();
+    }
+
+    @Override
+    public void stopRepeating() throws RemoteException {
+        mRequestProcessorImpl.stopRepeating();
+    }
+
+    private static class ImplRequestAdapter implements RequestProcessorImpl.Request {
+        private Request mRequest;
+        ImplRequestAdapter(Request request) {
+            mRequest = request;
+        }
+
+        @Override
+        public List<Integer> getTargetOutputConfigIds() {
+            List<Integer> result = new ArrayList<>(mRequest.targetOutputConfigIds.length);
+            for (int id : mRequest.targetOutputConfigIds) {
+                result.add(id);
+            }
+            return result;
+        }
+
+        @Override
+        public Map<CaptureRequest.Key<?>, Object> getParameters() {
+            CaptureRequest captureRequest = mRequest.parameters.toCaptureRequest();
+            Map<CaptureRequest.Key<?>, Object> parameters = new HashMap<>();
+            for (CaptureRequest.Key<?> key : captureRequest.getKeys()) {
+                parameters.put(key, captureRequest.get(key));
+            }
+            return parameters;
+        }
+
+        @Override
+        public Integer getTemplateId() {
+            return mRequest.templateId;
+        }
+    }
+
+    private static class ImplCaptureCallbackAdapter implements RequestProcessorImpl.Callback {
+        private Map<RequestProcessorImpl.Request, Request> mRequestsMap;
+        private IRequestCallback mRequestCallback;
+        ImplCaptureCallbackAdapter(Map<RequestProcessorImpl.Request, Request> requestsMap,
+                IRequestCallback requestCallback) {
+            mRequestCallback = requestCallback;
+            mRequestsMap = requestsMap;
+        }
+
+        private Request getRequest(RequestProcessorImpl.Request implRequest) {
+            return mRequestsMap.get(implRequest);
+        }
+
+        @Override
+        public void onCaptureStarted(RequestProcessorImpl.Request implRequest, long frameNumber,
+                long timestamp) {
+            try {
+                mRequestCallback.onCaptureStarted(getRequest(implRequest).requestId,
+                        frameNumber, timestamp);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Can't connect to the binder!", e);
+            }
+        }
+
+        @Override
+        public void onCaptureProgressed(RequestProcessorImpl.Request implRequest,
+                CaptureResult partialResult) {
+            try {
+                mRequestCallback.onCaptureProgressed(getRequest(implRequest).requestId,
+                        new CaptureResultWrapper(partialResult));
+            } catch(RemoteException e) {
+                Log.e(TAG, "Can't connect to the binder!", e);
+            }
+        }
+
+        @Override
+        public void onCaptureCompleted(RequestProcessorImpl.Request implRequest,
+                TotalCaptureResult totalCaptureResult) {
+            try {
+                mRequestCallback.onCaptureCompleted(getRequest(implRequest).requestId,
+                        new TotalCaptureResultWrapper(totalCaptureResult));
+            } catch(RemoteException e) {
+                Log.e(TAG, "Can't connect to the binder!", e);
+            }
+        }
+
+        @Override
+        public void onCaptureFailed(RequestProcessorImpl.Request implRequest,
+                CaptureFailure captureFailure) {
+            try {
+
+                mRequestCallback.onCaptureFailed(getRequest(implRequest).requestId,
+                        new CaptureFailureWrapper(captureFailure));
+            } catch(RemoteException e) {
+                Log.e(TAG, "Can't connect to the binder!", e);
+            }
+        }
+
+        @Override
+        public void onCaptureBufferLost(RequestProcessorImpl.Request implRequest, long frameNumber,
+                int outputStreamId) {
+            try {
+                mRequestCallback.onCaptureBufferLost(getRequest(implRequest).requestId,
+                        frameNumber, outputStreamId);
+            } catch(RemoteException e) {
+                Log.e(TAG, "Can't connect to the binder!", e);
+            }
+        }
+
+        @Override
+        public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
+            try {
+                mRequestCallback.onCaptureSequenceCompleted(sequenceId, frameNumber);
+            } catch(RemoteException e) {
+                Log.e(TAG, "Can't connect to the binder!", e);
+            }
+        }
+
+        @Override
+        public void onCaptureSequenceAborted(int sequenceId) {
+            try {
+                mRequestCallback.onCaptureSequenceAborted(sequenceId);
+            } catch(RemoteException e) {
+                Log.e(TAG, "Can't connect to the binder!", e);
+            }
+        }
+    }
+
+}
diff --git a/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ServiceManager.java b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ServiceManager.java
new file mode 100644
index 0000000..76de1c9
--- /dev/null
+++ b/camera2/extensions/service_based_sample/oem_library/src/java/androidx/camera/extensions/impl/serviceforward/ServiceManager.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 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.serviceforward;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.AsyncTask;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.extensions.impl.InitializerImpl;
+import androidx.camera.extensions.impl.service.IAdvancedExtenderImpl;
+import androidx.camera.extensions.impl.service.IImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.service.IPreviewExtenderImpl;
+import androidx.camera.extensions.impl.service.IExtensionsService;
+import androidx.camera.extensions.impl.service.IOnExtensionsDeinitializedCallback;
+import androidx.camera.extensions.impl.service.IOnExtensionsInitializedCallback;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+public class ServiceManager {
+    private static final String TAG = "ServiceManager";
+    private static final int SERVICE_DELAY_MS = 1000;
+    private static final String SERVICE_PACKAGE_NAME = "com.android.oemextensions";
+    private static final String SERVICE_SERVICE_NAME =
+            "com.android.oemextensions.ExtensionsService";
+
+    private static ServiceManager sServiceManager;
+    private static final Object mLock = new Object();
+
+    public static void init(@Nullable Context context, @NonNull String version,
+            @Nullable InitializerImpl.OnExtensionsInitializedCallback callback,
+            @Nullable Executor executor) {
+        synchronized (mLock) {
+            if (sServiceManager == null) {
+                sServiceManager = new ServiceManager(context, version);
+            }
+            sServiceManager.bindServiceSync(context);
+        }
+
+        try {
+            Executor executorForCallback =
+                    (executor != null)? executor: (cmd) -> cmd.run();
+
+            sServiceManager.mExtensionService.initialize(version,
+                    new IOnExtensionsInitializedCallback.Stub() {
+                @Override
+                public void onSuccess() throws RemoteException {
+                    executorForCallback.execute( () -> {
+                        if (callback != null) {
+                            callback.onSuccess();
+                        }
+                        Log.d(TAG, "initialize success!");
+                    });
+                }
+
+                @Override
+                public void onFailure(int error) throws RemoteException {
+                    executorForCallback.execute( () -> {
+                        if (callback != null) {
+                            callback.onFailure(error);
+                        }
+                        Log.d(TAG, "initialize failed! error=" + error);
+                    });
+                }
+            });
+        } catch (RemoteException e){
+            throw new IllegalStateException("Failed to connect to extensions service", e);
+        }
+    }
+
+    @NonNull
+    public static ServiceManager getInstance() {
+        return sServiceManager;
+    }
+
+    public ServiceManager(@NonNull Context context, @NonNull String version) {
+        mContext = context;
+        mVersion = version;
+    }
+
+    private final Context mContext;
+    private final String mVersion;
+
+    private ServiceConnection mServiceConnection;
+    private IExtensionsService mExtensionService;
+
+    void bindServiceSync(Context context) {
+        if (mServiceConnection == null) {
+            CountDownLatch countDownLatch = new CountDownLatch(1);
+            mServiceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName componentName, IBinder binder) {
+                    mExtensionService = IExtensionsService.Stub.asInterface(binder);
+                    Log.d(TAG, "service connected");
+                    countDownLatch.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName componentName) {
+                    Log.e(TAG, "service disconnected");
+                    mExtensionService = null;
+                    mServiceConnection = null;
+                }
+            };
+
+            Intent intent = new Intent();
+            intent.setClassName(SERVICE_PACKAGE_NAME, SERVICE_SERVICE_NAME);
+            Log.d(TAG, "bindService start. intent = " + intent);
+            context.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT |
+                    Context.BIND_ABOVE_CLIENT, AsyncTask.THREAD_POOL_EXECUTOR,
+                    mServiceConnection);
+
+           try {
+                boolean success = countDownLatch.await(SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
+                if (!success) {
+                    Log.e(TAG, "Timed out while initializing proxy service!");
+                }
+           } catch (InterruptedException e) {
+                Log.e(TAG, "Interrupted while initializing proxy service!");
+           }
+        }
+    }
+
+    public void deinit(@NonNull InitializerImpl.OnExtensionsDeinitializedCallback callback,
+            @NonNull Executor executor) {
+        try {
+            mExtensionService.deInitialize(new IOnExtensionsDeinitializedCallback.Stub() {
+                @Override
+                public void onSuccess() throws RemoteException {
+                    executor.execute( () -> {
+                        callback.onSuccess();
+                    });
+                }
+
+                @Override
+                public void onFailure(int error) throws RemoteException {
+                    executor.execute( () -> {
+                        callback.onFailure(error);
+                    });
+                }
+            });
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to connect to extensions service", e);
+        }
+    }
+
+    @NonNull
+    public IAdvancedExtenderImpl createAdvancedExtenderImpl(int extensionType) {
+        try {
+            synchronized (mLock) {
+                if (mExtensionService == null) {
+                    init(mContext, mVersion, null, null);
+                }
+            }
+            return mExtensionService.initializeAdvancedExtension(extensionType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "initializeAdvancedExtension failed", e);
+            throw new IllegalStateException("initializeAdvancedExtension failed", e);
+        }
+    }
+
+    @NonNull
+    public IImageCaptureExtenderImpl createImageCaptureExtenderImpl(int extensionType) {
+        try {
+            synchronized (mLock) {
+                if (mExtensionService == null) {
+                    bindServiceSync(mContext);
+                }
+            }
+            return mExtensionService.initializeImageCaptureExtension(extensionType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "initializeImageCaptureExtender failed", e);
+            throw new IllegalStateException("initializeImageCaptureExtender failed", e);
+        }
+    }
+
+    @NonNull
+    public IPreviewExtenderImpl createPreviewExtenderImpl(int extensionType) {
+        try {
+            synchronized (mLock) {
+                if (mExtensionService == null) {
+                    bindServiceSync(mContext);
+                }
+            }
+            return mExtensionService.initializePreviewExtension(extensionType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "initializePreviewExtension failed", e);
+            throw new IllegalStateException("initializePreviewExtension failed", e);
+        }
+    }
+}
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index ccb0dac..bd60570 100755
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -95,6 +95,11 @@
         throw new RuntimeException("Stub, replace with implementation.");
     }
 
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
     @Nullable
     @Override
     public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
@@ -113,4 +118,23 @@
         throw new RuntimeException("Stub, replace with implementation.");
     }
 
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
index 100f665..0c4577a 100755
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
@@ -93,4 +93,9 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index 2d26639..50c8040 100755
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -95,6 +95,11 @@
         throw new RuntimeException("Stub, replace with implementation.");
     }
 
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
     @Nullable
     @Override
     public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
@@ -112,4 +117,24 @@
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
index bc3e48d..1f50174 100755
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
@@ -93,4 +93,9 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index 66c5839..ee777cf 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -95,6 +95,11 @@
         throw new RuntimeException("Stub, replace with implementation.");
     }
 
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
     @Nullable
     @Override
     public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
@@ -113,4 +118,23 @@
         throw new RuntimeException("Stub, replace with implementation.");
     }
 
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
index ff58862..1dc5ed7 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -91,4 +91,9 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
index 3eee146..f4719b8 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 import android.util.Pair;
+import android.util.Size;
 import android.view.Surface;
 
 import java.util.Map;
@@ -46,6 +47,29 @@
     void process(Map<Integer, Pair<Image, TotalCaptureResult>> results);
 
     /**
+     * Informs the CaptureProcessorImpl where it should write the postview output to.
+     * This will only be invoked once if a valid postview surface was set.
+     *
+     * @param surface A valid {@link ImageFormat#YUV_420_888} {@link Surface}
+     *                that the CaptureProcessorImpl should write data into.
+     * @since 1.4
+     */
+    void onPostviewOutputSurface(Surface surface);
+
+    /**
+     * Invoked when the Camera Framework changes the configured output resolution for
+     * still capture and postview.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
+     * input for still capture and postview to be at the specified resolutions.
+     *
+     * @param size for the surface for still capture.
+     * @param postviewSize for the surface for postview.
+     * @since 1.4
+     */
+    void onResolutionUpdate(Size size, Size postviewSize);
+
+    /**
      * Process a set images captured that were requested.
      *
      * <p> The result of the processing step should be written to the {@link Surface} that was
@@ -63,4 +87,30 @@
      */
     void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
             ProcessResultImpl resultCallback, Executor executor);
+
+    /**
+     * Process a set images captured that were requested for both postview and
+     * still capture.
+     *
+     * <p> This processing method will be called if a postview was requested, therefore the
+     * processed postview should be written to the
+     * {@link Surface} received by {@link #onPostviewOutputSurface(Surface, int)}.
+     * The final result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}. Since postview should be available
+     * before the capture, it should be processed and written to the surface before
+     * the final capture is processed.
+     *
+     * @param results             The map of {@link ImageFormat#YUV_420_888} format images and
+     *                            metadata to process. The {@link Image} that are contained within
+     *                            the map will become invalid after this method completes, so no
+     *                            references to them should be kept.
+     * @param resultCallback      Capture result callback to be called once the capture result
+     *                            values of the processed image are ready.
+     * @param executor            The executor to run the callback on. If null then the callback
+     *                            will run on any arbitrary executor.
+     * @throws RuntimeException   if postview feature is not supported
+     * @since 1.4
+     */
+    void processWithPostview(Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            ProcessResultImpl resultCallback, Executor executor);
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
index 2879568..4a3b01c 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
@@ -80,4 +80,20 @@
      * @return The request information to customize the session.
      */
     CaptureStageImpl onDisableSession();
+
+    /**
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+     * initialized and must return a valid camera session type
+     * {@link android.hardware.camera2.params.SessionConfiguration#getSessionType}
+     * to be used to configure camera capture session. Both the preview and the image capture
+     * extender must return the same session type value for a specific extension type. If there
+     * is inconsistency between the session type values from preview and image extenders, then
+     * the session configuration will fail.
+     *
+     * @since 1.4
+     * @return Camera capture session type. Regular and vendor specific types are supported but
+     * not high speed values. The extension can return -1 in which case the camera capture session
+     * will be configured to use the default regular type.
+     */
+    int onSessionType();
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index f1191dc..f3fd2f3 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -95,6 +95,11 @@
         throw new RuntimeException("Stub, replace with implementation.");
     }
 
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
     @Nullable
     @Override
     public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
@@ -112,4 +117,24 @@
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
index 0eb4a61..af48464 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -93,4 +93,9 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index 88bd105..70c1804 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -85,6 +85,21 @@
     List<Pair<Integer, Size[]>> getSupportedResolutions();
 
     /**
+     * Returns the customized supported postview resolutions for a still capture using
+     * its size.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     *
+     * <p>The returned resolutions should be subset of the supported sizes retrieved from
+     * {@link android.hardware.camera2.params.StreamConfigurationMap} for the camera device.
+     *
+     * @return the customized supported resolutions, or null to support all sizes retrieved from
+     *         {@link android.hardware.camera2.params.StreamConfigurationMap}.
+     * @since 1.4
+     */
+    List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize);
+
+    /**
      * Returns the estimated capture latency range in milliseconds for the target capture
      * resolution.
      *
@@ -159,4 +174,44 @@
      * @since 1.3
      */
     List<CaptureResult.Key> getAvailableCaptureResultKeys();
+
+    /**
+     * Advertise support for {@link ProcessResultImpl#onCaptureProcessProgressed}.
+     *
+     * @return {@code true} in case the process progress callback is supported and is expected to
+     * be triggered, {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isCaptureProcessProgressAvailable();
+
+    /**
+     * Returns the dynamically calculated capture latency pair in milliseconds.
+     *
+     * <p>In contrast to {@link #getEstimatedCaptureLatencyRange} this method is guaranteed to be
+     * called after the camera capture session is initialized and camera preview is enabled.
+     * The measurement is expected to take in to account dynamic parameters such as the current
+     * scene, the state of 3A algorithms, the state of internal HW modules and return a more
+     * accurate assessment of the still capture latency.</p>
+     *
+     * @return pair that includes the estimated input frame/frames camera capture latency as the
+     * first field and the estimated post-processing latency {@link CaptureProcessorImpl#process}
+     * as the second pair field. Both first and second fields will be in milliseconds. The total
+     * still capture latency will be the sum of both the first and second values.
+     * The pair is expected to be null if the dynamic latency estimation is not supported.
+     * If clients have not configured a still capture output, then this method can also return a
+     * null pair.
+     * @since 1.4
+     */
+    Pair<Long, Long> getRealtimeCaptureLatency();
+
+    /**
+     * Indicates whether the extension supports the postview for still capture feature.
+     * If the extension is using HAL processing, false should be returned since the
+     * postview feature is not currently supported for this case.
+     *
+     * @return {@code true} in case postview for still capture is supported
+     * {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isPostviewAvailable();
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index c8ac978..6f0eaef 100755
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -95,6 +95,11 @@
         throw new RuntimeException("Stub, replace with implementation.");
     }
 
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
     @Nullable
     @Override
     public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
@@ -112,4 +117,24 @@
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
index a5809f6..825994f 100755
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
@@ -93,4 +93,9 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public int onSessionType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java
index d0e3605..0e15445 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java
@@ -25,7 +25,6 @@
 /**
  * Allows clients to receive information about the capture result values of processed frames.
  *
- * @since 1.3
  */
 @SuppressLint("UnknownNullness")
 public interface ProcessResultImpl {
@@ -40,6 +39,22 @@
      *                             must also be passed as part of this callback. Both Camera2 and
      *                             CameraX guarantee that those two settings and results are always
      *                             supported and applied by the corresponding framework.
+     * @since 1.3
      */
     void onCaptureCompleted(long shutterTimestamp, List<Pair<CaptureResult.Key, Object>> result);
+
+    /**
+     * Capture progress callback that needs to be called when the process capture is
+     * ongoing and includes the estimated progress of the processing.
+     *
+     * <p>Extensions must ensure that they always call this callback with monotonically increasing
+     * values.</p>
+     *
+     * <p>Extensions are allowed to trigger this callback multiple times but at the minimum the
+     * callback is expected to be called once when processing is done with value 100.</p>
+     *
+     * @param progress             Value between 0 and 100.
+     * @since 1.4
+     */
+    void onCaptureProcessProgressed(int progress);
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
index 465bfe8..d13efc8 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
@@ -124,6 +124,17 @@
     Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(String cameraId);
 
     /**
+     * Returns supported output format/size map for postview image. OEM is required to support
+     * both JPEG and YUV_420_888 format output.
+     *
+     * <p>The surface created with this supported format/size could configure
+     * intermediate surfaces(YUV/RAW..) and write the output to the output surface.</p>
+     *
+     * @since 1.4
+     */
+    Map<Integer, List<Size>> getSupportedPostviewResolutions(Size captureSize);
+
+    /**
      * Returns supported output sizes for Image Analysis (YUV_420_888 format).
      *
      * <p>OEM can optionally support a YUV surface for ImageAnalysis along with Preview/ImageCapture
@@ -185,4 +196,24 @@
      * @since 1.3
      */
     List<CaptureResult.Key> getAvailableCaptureResultKeys();
+
+    /**
+     * Advertise support for {@link SessionProcessorImpl#onCaptureProcessProgressed}.
+     *
+     * @return {@code true} in case the process progress callback is supported and is expected to
+     * be triggered, {@code false} otherwise.
+     * @since 1.4
+     */
+    public boolean isCaptureProcessProgressAvailable();
+
+    /**
+     * Indicates whether the extension supports the postview for still capture feature.
+     * If the extension is using HAL processing, false should be returned since the
+     * postview feature is not currently supported for this case.
+     *
+     * @return {@code true} in case postview for still capture is supported
+     * {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isPostviewAvailable();
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
index 0d3bd4a..8c3ac11 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
@@ -69,6 +69,12 @@
     }
 
     @Override
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
     public List<Size> getSupportedYuvAnalysisResolutions(
             String cameraId) {
         throw new RuntimeException("Stub, replace with implementation.");
@@ -88,4 +94,14 @@
     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.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
index 1dec326..135306c 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
@@ -69,6 +69,12 @@
     }
 
     @Override
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
     public List<Size> getSupportedYuvAnalysisResolutions(
             String cameraId) {
         throw new RuntimeException("Stub, replace with implementation.");
@@ -88,4 +94,14 @@
     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.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
index bc41b4e..fa4ad0d 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
@@ -69,6 +69,12 @@
     }
 
     @Override
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
     public List<Size> getSupportedYuvAnalysisResolutions(
             String cameraId) {
         throw new RuntimeException("Stub, replace with implementation.");
@@ -88,4 +94,14 @@
     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.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
index d121717..850f0e1 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
@@ -43,4 +43,16 @@
      * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters}.
      */
     int getSessionTemplateId();
+
+
+    /**
+     * Retrieves the session type to be used when initializing the
+     * {@link android.hardware.camera2.CameraCaptureSession}.
+     *
+     * @since 1.4
+     * @return Camera capture session type. Regular and vendor specific types are supported but
+     * not high speed values. The extension can return -1 in which case the camera capture session
+     * will be configured to use the default regular type.
+     */
+    int getSessionType();
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
index a301166..dc1fecc 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
@@ -20,6 +20,7 @@
 import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -32,6 +33,7 @@
 @SuppressLint("UnknownNullness")
 public class Camera2SessionConfigImplBuilder {
     private int mSessionTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+    private int mSessionType = SessionConfiguration.SESSION_REGULAR;
     Map<CaptureRequest.Key<?>, Object> mSessionParameters = new HashMap<>();
     List<Camera2OutputConfigImpl> mCamera2OutputConfigs = new ArrayList<>();
 
@@ -86,6 +88,13 @@
     }
 
     /**
+     * Gets the camera capture session type.
+     */
+    public int getSessionType() {
+        return mSessionType;
+    }
+
+    /**
      * Builds a {@link Camera2SessionConfigImpl} instance.
      */
     public Camera2SessionConfigImpl build() {
@@ -95,6 +104,7 @@
     private static class Camera2SessionConfigImplImpl implements
             Camera2SessionConfigImpl {
         int mSessionTemplateId;
+        int mSessionType;
         Map<CaptureRequest.Key<?>, Object> mSessionParameters;
         List<Camera2OutputConfigImpl> mCamera2OutputConfigs;
 
@@ -102,6 +112,7 @@
             mSessionTemplateId = builder.getSessionTemplateId();
             mSessionParameters = builder.getSessionParameters();
             mCamera2OutputConfigs = builder.getCamera2OutputConfigs();
+            mSessionType = builder.getSessionType();
         }
 
         @Override
@@ -118,6 +129,11 @@
         public int getSessionTemplateId() {
             return mSessionTemplateId;
         }
+
+        @Override
+        public int getSessionType() {
+            return mSessionType;
+        }
     }
 }
 
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
index 06157dc..dc5b2b6 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
@@ -70,6 +70,12 @@
     }
 
     @Override
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
     public List<Size> getSupportedYuvAnalysisResolutions(
             String cameraId) {
         throw new RuntimeException("Stub, replace with implementation.");
@@ -89,4 +95,14 @@
     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.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
index ce17c4f..037e947 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
@@ -26,8 +26,7 @@
 @SuppressLint("UnknownNullness")
 public interface ImageProcessorImpl {
     /**
-     * The reference count will be decremented when this method returns. If an extension wants
-     * to hold onto the image it should increment the reference count in this method and
+     * The reference count will not be decremented when this method returns. Extensions must
      * decrement it when the image is no longer needed.
      *
      * <p>If OEM is not closing(decrement) the image fast enough, the imageReference passed
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
index 97da5c1..5b0ed8e 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
@@ -69,6 +69,12 @@
     }
 
     @Override
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
     public List<Size> getSupportedYuvAnalysisResolutions(
             String cameraId) {
         throw new RuntimeException("Stub, replace with implementation.");
@@ -88,4 +94,14 @@
     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.");
+    }
 }
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java
new file mode 100644
index 0000000..723f0f4
--- /dev/null
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 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.annotation.SuppressLint;
+
+/**
+ * For specifying the output surface configurations for the extension.
+ *
+ * @since 1.4
+ */
+@SuppressLint("UnknownNullness")
+public interface OutputSurfaceConfigurationImpl {
+    public OutputSurfaceImpl getPreviewOutputSurface();
+
+    public OutputSurfaceImpl getImageCaptureOutputSurface();
+
+    public OutputSurfaceImpl getImageAnalysisOutputSurface();
+
+    public OutputSurfaceImpl getPostviewOutputSurface();
+}
diff --git a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
index fabfc2b..0627081 100644
--- a/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
+++ b/camera2/extensions/stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.util.Pair;
 import android.view.Surface;
 
 import java.util.Map;
@@ -64,9 +65,59 @@
      * preparing a CameraCaptureSession. After initSession() is called, the camera ID,
      * cameraCharacteristics and context will not change until deInitSession() has been called.
      *
-     * <p>CameraX specifies the output surface configurations for preview, image capture and image
-     * analysis[optional]. And OEM returns a {@link Camera2SessionConfigImpl} which consists of a
-     * list of {@link Camera2OutputConfigImpl} and session parameters. The
+     * <p>CameraX / Camera2 specifies the output surface configurations for preview using
+     * {@link OutputSurfaceConfigurationImpl#getPreviewOutputSurface}, image capture using
+     * {@link OutputSurfaceConfigurationImpl#getImageCaptureOutputSurface}, and image analysis
+     * [optional] using {@link OutputSurfaceConfigurationImpl#getImageAnalysisOutputSurface}.
+     * And OEM returns a {@link Camera2SessionConfigImpl} which consists of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters. The {@link Camera2SessionConfigImpl}
+     * will be used to configure the CameraCaptureSession.
+     *
+     * <p>OEM is responsible for outputting correct camera images output to these output surfaces.
+     * OEM can have the following options to enable the output:
+     * <pre>
+     * (1) Add these output surfaces in CameraCaptureSession directly using
+     * {@link Camera2OutputConfigImplBuilder#newSurfaceConfig(Surface)} }. Processing is done in
+     * HAL.
+     *
+     * (2) Use surface sharing with other surface by calling
+     * {@link Camera2OutputConfigImplBuilder#addSurfaceSharingOutputConfig(Camera2OutputConfigImpl)}
+     * to add the output surface to the other {@link Camera2OutputConfigImpl}.
+     *
+     * (3) Process output from other surfaces (RAW, YUV..) and write the result to the output
+     * surface. The output surface won't be contained in the returned
+     * {@link Camera2SessionConfigImpl}.
+     * </pre>
+     *
+     * <p>{@link Camera2OutputConfigImplBuilder} and {@link Camera2SessionConfigImplBuilder}
+     * implementations are provided in the stub for OEM to construct the
+     * {@link Camera2OutputConfigImpl} and {@link Camera2SessionConfigImpl} instances.
+     *
+     * @param surfaceConfigs contains output surfaces for preview, image capture, and an
+     *                       optional output config for image analysis (YUV_420_888).
+     * @return a {@link Camera2SessionConfigImpl} consisting of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters which will decide the
+     * {@link android.hardware.camera2.params.SessionConfiguration} for configuring the
+     * CameraCaptureSession. Please note that the OutputConfiguration list may not be part of any
+     * supported or mandatory stream combination BUT OEM must ensure this list will always
+     * produce a valid camera capture session.
+     *
+     * @since 1.4
+     */
+    Camera2SessionConfigImpl initSession(
+            String cameraId,
+            Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            Context context,
+            OutputSurfaceConfigurationImpl surfaceConfigs);
+
+    /**
+     * Initializes the session for the extension. This is where the OEMs allocate resources for
+     * preparing a CameraCaptureSession. After initSession() is called, the camera ID,
+     * cameraCharacteristics and context will not change until deInitSession() has been called.
+     *
+     * <p>CameraX / Camera 2 specifies the output surface configurations for preview, image capture
+     * and image analysis[optional]. And OEM returns a {@link Camera2SessionConfigImpl} which
+     * consists of a list of {@link Camera2OutputConfigImpl} and session parameters. The
      * {@link Camera2SessionConfigImpl} will be used to configure the CameraCaptureSession.
      *
      * <p>OEM is responsible for outputting correct camera images output to these output surfaces.
@@ -188,11 +239,54 @@
     int startCapture(CaptureCallback callback);
 
     /**
+     * Start a multi-frame capture with a postview. {@link #startCapture(CaptureCallback)}
+     * will be used for captures without a postview request.
+     *
+     * Postview will be available before the capture. Upon postview completion,
+     * {@code OnImageAvailableListener#onImageAvailable} will be called on the ImageReader
+     * that creates the postview output surface. When the capture is completed,
+     * {@link CaptureCallback#onCaptureSequenceCompleted} is called and
+     * {@code OnImageAvailableListener#onImageAvailable} will also be called on the ImageReader
+     * that creates the image capture output surface.
+     *
+     * <p>Only one capture can perform at a time. Starting a capture when another capture is
+     * running will cause onCaptureFailed to be called immediately.
+     *
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     * @since 1.4
+     */
+    int startCaptureWithPostview(CaptureCallback callback);
+
+    /**
      * Abort all capture tasks.
      */
     void abortCapture(int captureSequenceId);
 
     /**
+     * Returns the dynamically calculated capture latency pair in milliseconds.
+     *
+     * <p>In contrast to {@link AdvancedExtenderImpl#getEstimatedCaptureLatencyRange} this method is
+     * guaranteed to be called after {@link #onCaptureSessionStart}.
+     * The measurement is expected to take in to account dynamic parameters such as the current
+     * scene, the state of 3A algorithms, the state of internal HW modules and return a more
+     * accurate assessment of the still capture latency.</p>
+     *
+     * @return pair that includes the estimated input frame/frames camera capture latency as the
+     * first field. This is the time between {@link #onCaptureStarted} and
+     * {@link #onCaptureProcessStarted}. The second field value includes the estimated
+     * post-processing latency. This is the time between {@link #onCaptureProcessStarted} until
+     * the processed frame returns back to the client registered surface.
+     * Both first and second values will be in milliseconds. The total still capture latency will be
+     * the sum of both the first and second values of the pair.
+     * The pair is expected to be null if the dynamic latency estimation is not supported.
+     * If clients have not configured a still capture output, then this method can also return a
+     * null pair.
+     * @since 1.4
+     */
+    Pair<Long, Long> getRealtimeCaptureLatency();
+
+    /**
      * Callback for notifying the status of {@link #startCapture(CaptureCallback)} and
      * {@link #startRepeating(CaptureCallback)}.
      */
@@ -277,5 +371,20 @@
          */
         void onCaptureCompleted(long timestamp, int captureSequenceId,
                 Map<CaptureResult.Key, Object> result);
+
+        /**
+         * Capture progress callback that needs to be called when the process capture is
+         * ongoing and includes the estimated progress of the processing.
+         *
+         * <p>Extensions must ensure that they always call this callback with monotonically
+         * increasing values.</p>
+         *
+         * <p>Extensions are allowed to trigger this callback multiple times but at the minimum the
+         * callback is expected to be called once when processing is done with value 100.</p>
+         *
+         * @param progress             Value between 0 and 100.
+         * @since 1.4
+         */
+        void onCaptureProcessProgressed(int progress);
     }
 }
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java b/camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java
index bc77259..bf9e6b1 100644
--- a/camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java
+++ b/camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java
@@ -22,6 +22,7 @@
 
 import com.android.ex.camera2.portability.debug.Log;
 
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.LinkedList;
 import java.util.Queue;
 
@@ -30,14 +31,14 @@
     private static final long MAX_MESSAGE_QUEUE_LENGTH = 256;
 
     private final Queue<Runnable> mJobQueue;
-    private Boolean mIsEnded;
+    private AtomicBoolean mIsEnded;
     private Handler mCameraHandler;
     private HandlerThread mCameraHandlerThread;
 
     public DispatchThread(Handler cameraHandler, HandlerThread cameraHandlerThread) {
         super("Camera Job Dispatch Thread");
         mJobQueue = new LinkedList<Runnable>();
-        mIsEnded = new Boolean(false);
+        mIsEnded = new AtomicBoolean(false);
         mCameraHandler = cameraHandler;
         mCameraHandlerThread = cameraHandlerThread;
     }
@@ -92,18 +93,14 @@
      * Gracefully ends this thread. Will stop after all jobs are processed.
      */
     public void end() {
-        synchronized (mIsEnded) {
-            mIsEnded = true;
-        }
+        mIsEnded.set(true);
         synchronized(mJobQueue) {
             mJobQueue.notifyAll();
         }
     }
 
     private boolean isEnded() {
-        synchronized (mIsEnded) {
-            return mIsEnded;
-        }
+        return mIsEnded.get();
     }
 
     @Override
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
index 21014d0..5b5a38d 100644
--- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
@@ -89,7 +89,7 @@
         }
     }
 
-    private final CameraManager mManager;
+    protected final CameraManager mManager;
 
     /**
      * Create a new blocking camera manager.
@@ -168,7 +168,7 @@
      * <p>Time out after {@link #OPEN_TIME_OUT_MS} and unblock. Clean up camera if it arrives
      * later.</p>
      */
-    private class OpenListener extends CameraDevice.StateCallback {
+    protected class OpenListener extends CameraDevice.StateCallback {
         private static final int ERROR_UNINITIALIZED = -1;
 
         private final String mCameraId;
@@ -186,9 +186,13 @@
         private boolean mNoReply = true; // Start with no reply until proven otherwise
         private boolean mTimedOut = false;
 
-        OpenListener(CameraManager manager, String cameraId,
-                CameraDevice.StateCallback listener, Handler handler)
-                throws CameraAccessException {
+        protected OpenListener(String cameraId, CameraDevice.StateCallback listener) {
+            mCameraId = cameraId;
+            mProxy = listener;
+        }
+
+        OpenListener(CameraManager manager, String cameraId, CameraDevice.StateCallback listener,
+                Handler handler) throws CameraAccessException {
             mCameraId = cameraId;
             mProxy = listener;
             manager.openCamera(cameraId, this, handler);
@@ -281,7 +285,7 @@
             if (mProxy != null) mProxy.onClosed(camera);
         }
 
-        CameraDevice blockUntilOpen() throws BlockingOpenException {
+        public CameraDevice blockUntilOpen() throws BlockingOpenException {
             /**
              * Block until onOpened, onError, or onDisconnected
              */
diff --git a/common/BUILD b/common/BUILD
index 80893a0..149da99 100644
--- a/common/BUILD
+++ b/common/BUILD
@@ -1,6 +1,6 @@
 # TODO(b/198224074): auto-generate this file using bp2build.
 load("@rules_android//rules:rules.bzl", "android_library")
-load("//build/make/tools:event_log_tags.bzl", "event_log_tags")
+load("//build/bazel/rules/java:event_log_tags.bzl", "event_log_tags")
 
 event_log_tags(
     name = "genlogtags",
diff --git a/framesequence/Android.bp b/framesequence/Android.bp
index 9a6d0f5..8d44f1d 100644
--- a/framesequence/Android.bp
+++ b/framesequence/Android.bp
@@ -22,7 +22,4 @@
     name: "android-common-framesequence",
     sdk_version: "8",
     srcs: ["src/**/*.java"],
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
 }
diff --git a/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/SamplesList.java b/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/SamplesList.java
index c67b83c..36cc784 100644
--- a/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/SamplesList.java
+++ b/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/SamplesList.java
@@ -41,11 +41,11 @@
         return ret;
     }
 
-    @SuppressWarnings("serial")
-    static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
-            add(makeSample("GIF animation", FrameSequenceTest.class, R.raw.animated_gif));
-            add(makeSample("WEBP animation", FrameSequenceTest.class, R.raw.animated_webp));
-    }};
+    static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<>();
+    static {
+        SAMPLES.add(makeSample("GIF animation", FrameSequenceTest.class, R.raw.animated_gif));
+        SAMPLES.add(makeSample("WEBP animation", FrameSequenceTest.class, R.raw.animated_webp));
+    }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {