Camera: Add external camera provider

Bug: 64874137 63873538

Change-Id: I4309874a7dedd3dd71d4bd0c2004d460421db679
diff --git a/camera/common/1.0/default/HandleImporter.cpp b/camera/common/1.0/default/HandleImporter.cpp
index fd8b943..e9741ef 100644
--- a/camera/common/1.0/default/HandleImporter.cpp
+++ b/camera/common/1.0/default/HandleImporter.cpp
@@ -134,6 +134,65 @@
     }
 }
 
+YCbCrLayout HandleImporter::lockYCbCr(
+        buffer_handle_t& buf, uint64_t cpuUsage,
+        const IMapper::Rect& accessRegion) {
+    Mutex::Autolock lock(mLock);
+    YCbCrLayout layout = {};
+
+    if (!mInitialized) {
+        initializeLocked();
+    }
+
+    if (mMapper == nullptr) {
+        ALOGE("%s: mMapper is null!", __FUNCTION__);
+        return layout;
+    }
+
+    hidl_handle acquireFenceHandle;
+    auto buffer = const_cast<native_handle_t*>(buf);
+    mMapper->lockYCbCr(buffer, cpuUsage, accessRegion, acquireFenceHandle,
+            [&](const auto& tmpError, const auto& tmpLayout) {
+                if (tmpError == MapperError::NONE) {
+                    layout = tmpLayout;
+                } else {
+                    ALOGE("%s: failed to lockYCbCr error %d!", __FUNCTION__, tmpError);
+                }
+           });
+
+    ALOGV("%s: layout y %p cb %p cr %p y_str %d c_str %d c_step %d",
+            __FUNCTION__, layout.y, layout.cb, layout.cr,
+            layout.yStride, layout.cStride, layout.chromaStep);
+    return layout;
+}
+
+int HandleImporter::unlock(buffer_handle_t& buf) {
+    int releaseFence = -1;
+    auto buffer = const_cast<native_handle_t*>(buf);
+    mMapper->unlock(
+        buffer, [&](const auto& tmpError, const auto& tmpReleaseFence) {
+            if (tmpError == MapperError::NONE) {
+                auto fenceHandle = tmpReleaseFence.getNativeHandle();
+                if (fenceHandle) {
+                    if (fenceHandle->numInts != 0 || fenceHandle->numFds != 1) {
+                        ALOGE("%s: bad release fence numInts %d numFds %d",
+                                __FUNCTION__, fenceHandle->numInts, fenceHandle->numFds);
+                        return;
+                    }
+                    releaseFence = dup(fenceHandle->data[0]);
+                    if (releaseFence <= 0) {
+                        ALOGE("%s: bad release fence FD %d",
+                                __FUNCTION__, releaseFence);
+                    }
+                }
+            } else {
+                ALOGE("%s: failed to unlock error %d!", __FUNCTION__, tmpError);
+            }
+        });
+
+    return releaseFence;
+}
+
 } // namespace helper
 } // namespace V1_0
 } // namespace common
diff --git a/camera/common/1.0/default/include/HandleImporter.h b/camera/common/1.0/default/include/HandleImporter.h
index e47397c..443362d 100644
--- a/camera/common/1.0/default/include/HandleImporter.h
+++ b/camera/common/1.0/default/include/HandleImporter.h
@@ -22,6 +22,7 @@
 #include <cutils/native_handle.h>
 
 using android::hardware::graphics::mapper::V2_0::IMapper;
+using android::hardware::graphics::mapper::V2_0::YCbCrLayout;
 
 namespace android {
 namespace hardware {
@@ -43,6 +44,12 @@
     bool importFence(const native_handle_t* handle, int& fd) const;
     void closeFence(int fd) const;
 
+    // Assume caller has done waiting for acquire fences
+    YCbCrLayout lockYCbCr(buffer_handle_t& buf, uint64_t cpuUsage,
+                          const IMapper::Rect& accessRegion);
+
+    int unlock(buffer_handle_t& buf); // returns release fence
+
 private:
     void initializeLocked();
     void cleanup();
@@ -60,4 +67,4 @@
 } // namespace hardware
 } // namespace android
 
-#endif // CAMERA_COMMON_1_0_HANDLEIMPORTED_H
\ No newline at end of file
+#endif // CAMERA_COMMON_1_0_HANDLEIMPORTED_H
diff --git a/camera/device/3.4/default/Android.bp b/camera/device/3.4/default/Android.bp
index a0ab167..61ac244 100644
--- a/camera/device/3.4/default/Android.bp
+++ b/camera/device/3.4/default/Android.bp
@@ -17,7 +17,13 @@
 cc_library_headers {
     name: "camera.device@3.4-impl_headers",
     vendor: true,
-    export_include_dirs: ["include/device_v3_4_impl"],
+    export_include_dirs: ["include/device_v3_4_impl"]
+}
+
+cc_library_headers {
+    name: "camera.device@3.4-external-impl_headers",
+    vendor: true,
+    export_include_dirs: ["include/ext_device_v3_4_impl"]
 }
 
 cc_library_shared {
@@ -55,3 +61,40 @@
         "libfmq",
     ],
 }
+
+cc_library_shared {
+    name: "camera.device@3.4-external-impl",
+    defaults: ["hidl_defaults"],
+    proprietary: true,
+    vendor: true,
+    srcs: [
+        "ExternalCameraDevice.cpp",
+        "ExternalCameraDeviceSession.cpp"
+    ],
+    shared_libs: [
+        "libhidlbase",
+        "libhidltransport",
+        "libutils",
+        "libcutils",
+        "camera.device@3.2-impl",
+        "camera.device@3.3-impl",
+        "android.hardware.camera.device@3.2",
+        "android.hardware.camera.device@3.3",
+        "android.hardware.camera.device@3.4",
+        "android.hardware.camera.provider@2.4",
+        "android.hardware.graphics.mapper@2.0",
+        "liblog",
+        "libhardware",
+        "libcamera_metadata",
+        "libfmq",
+        "libsync",
+        "libyuv",
+    ],
+    static_libs: [
+        "android.hardware.camera.common@1.0-helper",
+    ],
+    local_include_dirs: ["include/ext_device_v3_4_impl"],
+    export_shared_lib_headers: [
+        "libfmq",
+    ],
+}
diff --git a/camera/device/3.4/default/ExternalCameraDevice.cpp b/camera/device/3.4/default/ExternalCameraDevice.cpp
new file mode 100644
index 0000000..4ad1768
--- /dev/null
+++ b/camera/device/3.4/default/ExternalCameraDevice.cpp
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_TAG "ExtCamDev@3.4"
+#define LOG_NDEBUG 0
+#include <log/log.h>
+
+#include <array>
+#include <linux/videodev2.h>
+#include "android-base/macros.h"
+#include "CameraMetadata.h"
+#include "../../3.2/default/include/convert.h"
+#include "ExternalCameraDevice_3_4.h"
+
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace device {
+namespace V3_4 {
+namespace implementation {
+
+namespace {
+// Only support MJPEG for now as it seems to be the one supports higher fps
+// Other formats to consider in the future:
+// * V4L2_PIX_FMT_YVU420 (== YV12)
+// * V4L2_PIX_FMT_YVYU (YVYU: can be converted to YV12 or other YUV420_888 formats)
+const std::array<uint32_t, /*size*/1> kSupportedFourCCs {{
+    V4L2_PIX_FMT_MJPEG
+}}; // double braces required in C++11
+
+// TODO: b/72261897
+//       Define max size/fps this Android device can advertise (and streaming at reasonable speed)
+//       Also make sure that can be done without editing source code
+
+// TODO: b/72261675: make it dynamic since this affects memory usage
+const int kMaxJpegSize = {13 * 1024 * 1024};  // 13MB
+} // anonymous namespace
+
+ExternalCameraDevice::ExternalCameraDevice(const std::string& cameraId) :
+        mCameraId(cameraId) {
+    status_t ret = initCameraCharacteristics();
+    if (ret != OK) {
+        ALOGE("%s: init camera characteristics failed: errorno %d", __FUNCTION__, ret);
+        mInitFailed = true;
+    }
+}
+
+ExternalCameraDevice::~ExternalCameraDevice() {}
+
+bool ExternalCameraDevice::isInitFailed() {
+    return mInitFailed;
+}
+
+Return<void> ExternalCameraDevice::getResourceCost(getResourceCost_cb _hidl_cb) {
+    CameraResourceCost resCost;
+    resCost.resourceCost = 100;
+    _hidl_cb(Status::OK, resCost);
+    return Void();
+}
+
+Return<void> ExternalCameraDevice::getCameraCharacteristics(
+        getCameraCharacteristics_cb _hidl_cb) {
+    Mutex::Autolock _l(mLock);
+    V3_2::CameraMetadata hidlChars;
+
+    if (isInitFailed()) {
+        _hidl_cb(Status::INTERNAL_ERROR, hidlChars);
+        return Void();
+    }
+
+    const camera_metadata_t* rawMetadata = mCameraCharacteristics.getAndLock();
+    V3_2::implementation::convertToHidl(rawMetadata, &hidlChars);
+    _hidl_cb(Status::OK, hidlChars);
+    mCameraCharacteristics.unlock(rawMetadata);
+    return Void();
+}
+
+Return<Status> ExternalCameraDevice::setTorchMode(TorchMode) {
+    return Status::METHOD_NOT_SUPPORTED;
+}
+
+Return<void> ExternalCameraDevice::open(
+        const sp<ICameraDeviceCallback>& callback, open_cb _hidl_cb) {
+    Status status = Status::OK;
+    sp<ExternalCameraDeviceSession> session = nullptr;
+
+    if (callback == nullptr) {
+        ALOGE("%s: cannot open camera %s. callback is null!",
+                __FUNCTION__, mCameraId.c_str());
+        _hidl_cb(Status::ILLEGAL_ARGUMENT, nullptr);
+        return Void();
+    }
+
+    if (isInitFailed()) {
+        ALOGE("%s: cannot open camera %s. camera init failed!",
+                __FUNCTION__, mCameraId.c_str());
+        _hidl_cb(Status::INTERNAL_ERROR, nullptr);
+        return Void();
+    }
+
+    mLock.lock();
+
+    ALOGV("%s: Initializing device for camera %s", __FUNCTION__, mCameraId.c_str());
+    session = mSession.promote();
+    if (session != nullptr && !session->isClosed()) {
+        ALOGE("%s: cannot open an already opened camera!", __FUNCTION__);
+        mLock.unlock();
+        _hidl_cb(Status::CAMERA_IN_USE, nullptr);
+        return Void();
+    }
+
+    unique_fd fd(::open(mCameraId.c_str(), O_RDWR));
+    if (fd.get() < 0) {
+        ALOGE("%s: v4l2 device open %s failed: %s",
+                __FUNCTION__, mCameraId.c_str(), strerror(errno));
+        mLock.unlock();
+        _hidl_cb(Status::INTERNAL_ERROR, nullptr);
+        return Void();
+    }
+
+    session = new ExternalCameraDeviceSession(
+            callback, mSupportedFormats, mCameraCharacteristics, std::move(fd));
+    if (session == nullptr) {
+        ALOGE("%s: camera device session allocation failed", __FUNCTION__);
+        mLock.unlock();
+        _hidl_cb(Status::INTERNAL_ERROR, nullptr);
+        return Void();
+    }
+    if (session->isInitFailed()) {
+        ALOGE("%s: camera device session init failed", __FUNCTION__);
+        session = nullptr;
+        mLock.unlock();
+        _hidl_cb(Status::INTERNAL_ERROR, nullptr);
+        return Void();
+    }
+    mSession = session;
+
+    mLock.unlock();
+
+    _hidl_cb(status, session->getInterface());
+    return Void();
+}
+
+Return<void> ExternalCameraDevice::dumpState(const ::android::hardware::hidl_handle& handle) {
+    Mutex::Autolock _l(mLock);
+    if (handle.getNativeHandle() == nullptr) {
+        ALOGE("%s: handle must not be null", __FUNCTION__);
+        return Void();
+    }
+    if (handle->numFds != 1 || handle->numInts != 0) {
+        ALOGE("%s: handle must contain 1 FD and 0 integers! Got %d FDs and %d ints",
+                __FUNCTION__, handle->numFds, handle->numInts);
+        return Void();
+    }
+    int fd = handle->data[0];
+    if (mSession == nullptr) {
+        dprintf(fd, "No active camera device session instance\n");
+        return Void();
+    }
+    auto session = mSession.promote();
+    if (session == nullptr) {
+        dprintf(fd, "No active camera device session instance\n");
+        return Void();
+    }
+    // Call into active session to dump states
+    session->dumpState(handle);
+    return Void();
+}
+
+
+status_t ExternalCameraDevice::initCameraCharacteristics() {
+    if (mCameraCharacteristics.isEmpty()) {
+        // init camera characteristics
+        unique_fd fd(::open(mCameraId.c_str(), O_RDWR));
+        if (fd.get() < 0) {
+            ALOGE("%s: v4l2 device open %s failed", __FUNCTION__, mCameraId.c_str());
+            return DEAD_OBJECT;
+        }
+
+        status_t ret;
+        ret = initDefaultCharsKeys(&mCameraCharacteristics);
+        if (ret != OK) {
+            ALOGE("%s: init default characteristics key failed: errorno %d", __FUNCTION__, ret);
+            mCameraCharacteristics.clear();
+            return ret;
+        }
+
+        ret = initCameraControlsCharsKeys(fd.get(), &mCameraCharacteristics);
+        if (ret != OK) {
+            ALOGE("%s: init camera control characteristics key failed: errorno %d", __FUNCTION__, ret);
+            mCameraCharacteristics.clear();
+            return ret;
+        }
+
+        ret = initOutputCharsKeys(fd.get(), &mCameraCharacteristics);
+        if (ret != OK) {
+            ALOGE("%s: init output characteristics key failed: errorno %d", __FUNCTION__, ret);
+            mCameraCharacteristics.clear();
+            return ret;
+        }
+    }
+    return OK;
+}
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#define UPDATE(tag, data, size)                    \
+do {                                               \
+  if (metadata->update((tag), (data), (size))) {   \
+    ALOGE("Update " #tag " failed!");              \
+    return -EINVAL;                                \
+  }                                                \
+} while (0)
+
+status_t ExternalCameraDevice::initDefaultCharsKeys(
+        ::android::hardware::camera::common::V1_0::helper::CameraMetadata* metadata) {
+    // TODO: changed to HARDWARELEVEL_EXTERNAL later
+    const uint8_t hardware_level = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+    UPDATE(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL, &hardware_level, 1);
+
+    // android.colorCorrection
+    const uint8_t availableAberrationModes[] = {
+        ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF};
+    UPDATE(ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
+           availableAberrationModes, ARRAY_SIZE(availableAberrationModes));
+
+    // android.control
+    const uint8_t antibandingMode =
+        ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO;
+    UPDATE(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
+           &antibandingMode, 1);
+
+    const int32_t controlMaxRegions[] = {/*AE*/ 0, /*AWB*/ 0, /*AF*/ 0};
+    UPDATE(ANDROID_CONTROL_MAX_REGIONS, controlMaxRegions,
+           ARRAY_SIZE(controlMaxRegions));
+
+    const uint8_t videoStabilizationMode =
+        ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+    UPDATE(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+           &videoStabilizationMode, 1);
+
+    const uint8_t awbAvailableMode = ANDROID_CONTROL_AWB_MODE_AUTO;
+    UPDATE(ANDROID_CONTROL_AWB_AVAILABLE_MODES, &awbAvailableMode, 1);
+
+    const uint8_t aeAvailableMode = ANDROID_CONTROL_AE_MODE_ON;
+    UPDATE(ANDROID_CONTROL_AE_AVAILABLE_MODES, &aeAvailableMode, 1);
+
+    const uint8_t availableFffect = ANDROID_CONTROL_EFFECT_MODE_OFF;
+    UPDATE(ANDROID_CONTROL_AVAILABLE_EFFECTS, &availableFffect, 1);
+
+    const uint8_t controlAvailableModes[] = {ANDROID_CONTROL_MODE_OFF,
+                                             ANDROID_CONTROL_MODE_AUTO};
+    UPDATE(ANDROID_CONTROL_AVAILABLE_MODES, controlAvailableModes,
+           ARRAY_SIZE(controlAvailableModes));
+
+    // android.edge
+    const uint8_t edgeMode = ANDROID_EDGE_MODE_OFF;
+    UPDATE(ANDROID_EDGE_AVAILABLE_EDGE_MODES, &edgeMode, 1);
+
+    // android.flash
+    const uint8_t flashInfo = ANDROID_FLASH_INFO_AVAILABLE_FALSE;
+    UPDATE(ANDROID_FLASH_INFO_AVAILABLE, &flashInfo, 1);
+
+    // android.hotPixel
+    const uint8_t hotPixelMode = ANDROID_HOT_PIXEL_MODE_OFF;
+    UPDATE(ANDROID_HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES, &hotPixelMode, 1);
+
+    // android.jpeg
+    // TODO: b/72261675 See if we can provide thumbnail size for all jpeg aspect ratios
+    const int32_t jpegAvailableThumbnailSizes[] = {0, 0, 240, 180};
+    UPDATE(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES, jpegAvailableThumbnailSizes,
+           ARRAY_SIZE(jpegAvailableThumbnailSizes));
+
+    const int32_t jpegMaxSize = kMaxJpegSize;
+    UPDATE(ANDROID_JPEG_MAX_SIZE, &jpegMaxSize, 1);
+
+    const uint8_t jpegQuality = 90;
+    UPDATE(ANDROID_JPEG_QUALITY, &jpegQuality, 1);
+    UPDATE(ANDROID_JPEG_THUMBNAIL_QUALITY, &jpegQuality, 1);
+
+    const int32_t jpegOrientation = 0;
+    UPDATE(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);
+
+    // android.lens
+    const uint8_t focusDistanceCalibration =
+            ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED;
+    UPDATE(ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION, &focusDistanceCalibration, 1);
+
+    const uint8_t opticalStabilizationMode =
+        ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
+    UPDATE(ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
+           &opticalStabilizationMode, 1);
+
+    const uint8_t facing = ANDROID_LENS_FACING_EXTERNAL;
+    UPDATE(ANDROID_LENS_FACING, &facing, 1);
+
+    // android.noiseReduction
+    const uint8_t noiseReductionMode = ANDROID_NOISE_REDUCTION_MODE_OFF;
+    UPDATE(ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+           &noiseReductionMode, 1);
+    UPDATE(ANDROID_NOISE_REDUCTION_MODE, &noiseReductionMode, 1);
+
+    // android.request
+    const uint8_t availableCapabilities[] = {
+        ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE};
+    UPDATE(ANDROID_REQUEST_AVAILABLE_CAPABILITIES, availableCapabilities,
+           ARRAY_SIZE(availableCapabilities));
+
+    const int32_t partialResultCount = 1;
+    UPDATE(ANDROID_REQUEST_PARTIAL_RESULT_COUNT, &partialResultCount, 1);
+
+    // This means pipeline latency of X frame intervals. The maximum number is 4.
+    const uint8_t requestPipelineMaxDepth = 4;
+    UPDATE(ANDROID_REQUEST_PIPELINE_MAX_DEPTH, &requestPipelineMaxDepth, 1);
+    UPDATE(ANDROID_REQUEST_PIPELINE_DEPTH, &requestPipelineMaxDepth, 1);
+
+    // Three numbers represent the maximum numbers of different types of output
+    // streams simultaneously. The types are raw sensor, processed (but not
+    // stalling), and processed (but stalling). For usb limited mode, raw sensor
+    // is not supported. Stalling stream is JPEG. Non-stalling streams are
+    // YUV_420_888 or YV12.
+    const int32_t requestMaxNumOutputStreams[] = {
+            /*RAW*/0,
+            /*Processed*/ExternalCameraDeviceSession::kMaxProcessedStream,
+            /*Stall*/ExternalCameraDeviceSession::kMaxStallStream};
+    UPDATE(ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS, requestMaxNumOutputStreams,
+           ARRAY_SIZE(requestMaxNumOutputStreams));
+
+    // Limited mode doesn't support reprocessing.
+    const int32_t requestMaxNumInputStreams = 0;
+    UPDATE(ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS, &requestMaxNumInputStreams,
+           1);
+
+    // android.scaler
+    // TODO: b/72263447 V4L2_CID_ZOOM_*
+    const float scalerAvailableMaxDigitalZoom[] = {1};
+    UPDATE(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+           scalerAvailableMaxDigitalZoom,
+           ARRAY_SIZE(scalerAvailableMaxDigitalZoom));
+
+    const uint8_t croppingType = ANDROID_SCALER_CROPPING_TYPE_CENTER_ONLY;
+    UPDATE(ANDROID_SCALER_CROPPING_TYPE, &croppingType, 1);
+
+    const int32_t testPatternModes[] = {
+        ANDROID_SENSOR_TEST_PATTERN_MODE_OFF};
+    UPDATE(ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES, testPatternModes,
+           ARRAY_SIZE(testPatternModes));
+    UPDATE(ANDROID_SENSOR_TEST_PATTERN_MODE, &testPatternModes[0], 1);
+
+    const uint8_t timestampSource = ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN;
+    UPDATE(ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE, &timestampSource, 1);
+
+    // Orientation probably isn't useful for external facing camera?
+    const int32_t orientation = 0;
+    UPDATE(ANDROID_SENSOR_ORIENTATION, &orientation, 1);
+
+    // android.shading
+    const uint8_t availabeMode = ANDROID_SHADING_MODE_OFF;
+    UPDATE(ANDROID_SHADING_AVAILABLE_MODES, &availabeMode, 1);
+
+    // android.statistics
+    const uint8_t faceDetectMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF;
+    UPDATE(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, &faceDetectMode,
+           1);
+
+    const int32_t maxFaceCount = 0;
+    UPDATE(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, &maxFaceCount, 1);
+
+    const uint8_t availableHotpixelMode =
+        ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF;
+    UPDATE(ANDROID_STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES,
+           &availableHotpixelMode, 1);
+
+    const uint8_t lensShadingMapMode =
+        ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF;
+    UPDATE(ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
+           &lensShadingMapMode, 1);
+
+    // android.sync
+    const int32_t maxLatency = ANDROID_SYNC_MAX_LATENCY_UNKNOWN;
+    UPDATE(ANDROID_SYNC_MAX_LATENCY, &maxLatency, 1);
+
+    /* Other sensor/RAW realted keys:
+     * android.sensor.info.colorFilterArrangement -> no need if we don't do RAW
+     * android.sensor.info.physicalSize           -> not available
+     * android.sensor.info.whiteLevel             -> not available/not needed
+     * android.sensor.info.lensShadingApplied     -> not needed
+     * android.sensor.info.preCorrectionActiveArraySize -> not available/not needed
+     * android.sensor.blackLevelPattern           -> not available/not needed
+     */
+
+    const int32_t availableRequestKeys[] = {
+        ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
+        ANDROID_CONTROL_AE_ANTIBANDING_MODE,
+        ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
+        ANDROID_CONTROL_AE_LOCK,
+        ANDROID_CONTROL_AE_MODE,
+        ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+        ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
+        ANDROID_CONTROL_AF_MODE,
+        ANDROID_CONTROL_AF_TRIGGER,
+        ANDROID_CONTROL_AWB_LOCK,
+        ANDROID_CONTROL_AWB_MODE,
+        ANDROID_CONTROL_CAPTURE_INTENT,
+        ANDROID_CONTROL_EFFECT_MODE,
+        ANDROID_CONTROL_MODE,
+        ANDROID_CONTROL_SCENE_MODE,
+        ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
+        ANDROID_FLASH_MODE,
+        ANDROID_JPEG_ORIENTATION,
+        ANDROID_JPEG_QUALITY,
+        ANDROID_JPEG_THUMBNAIL_QUALITY,
+        ANDROID_JPEG_THUMBNAIL_SIZE,
+        ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+        ANDROID_NOISE_REDUCTION_MODE,
+        ANDROID_SCALER_CROP_REGION,
+        ANDROID_SENSOR_TEST_PATTERN_MODE,
+        ANDROID_STATISTICS_FACE_DETECT_MODE,
+        ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE};
+    UPDATE(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS, availableRequestKeys,
+           ARRAY_SIZE(availableRequestKeys));
+
+    const int32_t availableResultKeys[] = {
+        ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
+        ANDROID_CONTROL_AE_ANTIBANDING_MODE,
+        ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
+        ANDROID_CONTROL_AE_LOCK,
+        ANDROID_CONTROL_AE_MODE,
+        ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+        ANDROID_CONTROL_AE_STATE,
+        ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
+        ANDROID_CONTROL_AF_MODE,
+        ANDROID_CONTROL_AF_STATE,
+        ANDROID_CONTROL_AF_TRIGGER,
+        ANDROID_CONTROL_AWB_LOCK,
+        ANDROID_CONTROL_AWB_MODE,
+        ANDROID_CONTROL_AWB_STATE,
+        ANDROID_CONTROL_CAPTURE_INTENT,
+        ANDROID_CONTROL_EFFECT_MODE,
+        ANDROID_CONTROL_MODE,
+        ANDROID_CONTROL_SCENE_MODE,
+        ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
+        ANDROID_FLASH_MODE,
+        ANDROID_FLASH_STATE,
+        ANDROID_JPEG_ORIENTATION,
+        ANDROID_JPEG_QUALITY,
+        ANDROID_JPEG_THUMBNAIL_QUALITY,
+        ANDROID_JPEG_THUMBNAIL_SIZE,
+        ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+        ANDROID_NOISE_REDUCTION_MODE,
+        ANDROID_REQUEST_PIPELINE_DEPTH,
+        ANDROID_SCALER_CROP_REGION,
+        ANDROID_SENSOR_TIMESTAMP,
+        ANDROID_STATISTICS_FACE_DETECT_MODE,
+        ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE,
+        ANDROID_STATISTICS_LENS_SHADING_MAP_MODE,
+        ANDROID_STATISTICS_SCENE_FLICKER};
+    UPDATE(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS, availableResultKeys,
+           ARRAY_SIZE(availableResultKeys));
+
+    const int32_t availableCharacteristicsKeys[] = {
+        ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
+        ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
+        ANDROID_CONTROL_AE_AVAILABLE_MODES,
+        ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
+        ANDROID_CONTROL_AE_COMPENSATION_RANGE,
+        ANDROID_CONTROL_AE_COMPENSATION_STEP,
+        ANDROID_CONTROL_AE_LOCK_AVAILABLE,
+        ANDROID_CONTROL_AF_AVAILABLE_MODES,
+        ANDROID_CONTROL_AVAILABLE_EFFECTS,
+        ANDROID_CONTROL_AVAILABLE_MODES,
+        ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
+        ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+        ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+        ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
+        ANDROID_CONTROL_MAX_REGIONS,
+        ANDROID_FLASH_INFO_AVAILABLE,
+        ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL,
+        ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
+        ANDROID_LENS_FACING,
+        ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
+        ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION,
+        ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+        ANDROID_REQUEST_AVAILABLE_CAPABILITIES,
+        ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS,
+        ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS,
+        ANDROID_REQUEST_PARTIAL_RESULT_COUNT,
+        ANDROID_REQUEST_PIPELINE_MAX_DEPTH,
+        ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+        ANDROID_SCALER_CROPPING_TYPE,
+        ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+        ANDROID_SENSOR_INFO_MAX_FRAME_DURATION,
+        ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
+        ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
+        ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE,
+        ANDROID_SENSOR_ORIENTATION,
+        ANDROID_SHADING_AVAILABLE_MODES,
+        ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
+        ANDROID_STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES,
+        ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
+        ANDROID_STATISTICS_INFO_MAX_FACE_COUNT,
+        ANDROID_SYNC_MAX_LATENCY};
+    UPDATE(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+           availableCharacteristicsKeys,
+           ARRAY_SIZE(availableCharacteristicsKeys));
+
+    return OK;
+}
+
+status_t ExternalCameraDevice::initCameraControlsCharsKeys(int,
+        ::android::hardware::camera::common::V1_0::helper::CameraMetadata* metadata) {
+    /**
+     * android.sensor.info.sensitivityRange   -> V4L2_CID_ISO_SENSITIVITY
+     * android.sensor.info.exposureTimeRange  -> V4L2_CID_EXPOSURE_ABSOLUTE
+     * android.sensor.info.maxFrameDuration   -> TBD
+     * android.lens.info.minimumFocusDistance -> V4L2_CID_FOCUS_ABSOLUTE
+     * android.lens.info.hyperfocalDistance
+     * android.lens.info.availableFocalLengths -> not available?
+     */
+
+    // android.control
+    // No AE compensation support for now.
+    // TODO: V4L2_CID_EXPOSURE_BIAS
+    const int32_t controlAeCompensationRange[] = {0, 0};
+    UPDATE(ANDROID_CONTROL_AE_COMPENSATION_RANGE, controlAeCompensationRange,
+           ARRAY_SIZE(controlAeCompensationRange));
+    const camera_metadata_rational_t controlAeCompensationStep[] = {{0, 1}};
+    UPDATE(ANDROID_CONTROL_AE_COMPENSATION_STEP, controlAeCompensationStep,
+           ARRAY_SIZE(controlAeCompensationStep));
+
+
+    // TODO: Check V4L2_CID_AUTO_FOCUS_*.
+    const uint8_t afAvailableModes[] = {ANDROID_CONTROL_AF_MODE_AUTO,
+                                        ANDROID_CONTROL_AF_MODE_OFF};
+    UPDATE(ANDROID_CONTROL_AF_AVAILABLE_MODES, afAvailableModes,
+           ARRAY_SIZE(afAvailableModes));
+
+    // TODO: V4L2_CID_SCENE_MODE
+    const uint8_t availableSceneMode = ANDROID_CONTROL_SCENE_MODE_DISABLED;
+    UPDATE(ANDROID_CONTROL_AVAILABLE_SCENE_MODES, &availableSceneMode, 1);
+
+    // TODO: V4L2_CID_3A_LOCK
+    const uint8_t aeLockAvailable = ANDROID_CONTROL_AE_LOCK_AVAILABLE_FALSE;
+    UPDATE(ANDROID_CONTROL_AE_LOCK_AVAILABLE, &aeLockAvailable, 1);
+    const uint8_t awbLockAvailable = ANDROID_CONTROL_AWB_LOCK_AVAILABLE_FALSE;
+    UPDATE(ANDROID_CONTROL_AWB_LOCK_AVAILABLE, &awbLockAvailable, 1);
+
+    // TODO: V4L2_CID_ZOOM_*
+    const float scalerAvailableMaxDigitalZoom[] = {1};
+    UPDATE(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+           scalerAvailableMaxDigitalZoom,
+           ARRAY_SIZE(scalerAvailableMaxDigitalZoom));
+
+    return OK;
+}
+
+status_t ExternalCameraDevice::initOutputCharsKeys(int fd,
+        ::android::hardware::camera::common::V1_0::helper::CameraMetadata* metadata) {
+    initSupportedFormatsLocked(fd);
+    if (mSupportedFormats.empty()) {
+        ALOGE("%s: Init supported format list failed", __FUNCTION__);
+        return UNKNOWN_ERROR;
+    }
+
+    std::vector<int32_t> streamConfigurations;
+    std::vector<int64_t> minFrameDurations;
+    std::vector<int64_t> stallDurations;
+    int64_t maxFrameDuration = 0;
+    int32_t maxFps = std::numeric_limits<int32_t>::min();
+    int32_t minFps = std::numeric_limits<int32_t>::max();
+    std::set<int32_t> framerates;
+
+    std::array<int, /*size*/3> halFormats{{
+        HAL_PIXEL_FORMAT_BLOB,
+        HAL_PIXEL_FORMAT_YCbCr_420_888,
+        HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED}};
+
+    for (const auto& supportedFormat : mSupportedFormats) {
+        for (const auto& format : halFormats) {
+            streamConfigurations.push_back(format);
+            streamConfigurations.push_back(supportedFormat.width);
+            streamConfigurations.push_back(supportedFormat.height);
+            streamConfigurations.push_back(
+                    ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT);
+        }
+
+        int64_t min_frame_duration = std::numeric_limits<int64_t>::max();
+        for (const auto& frameRate : supportedFormat.frameRates) {
+            int64_t frame_duration = 1000000000LL / frameRate;
+            if (frame_duration < min_frame_duration) {
+                min_frame_duration = frame_duration;
+            }
+            if (frame_duration > maxFrameDuration) {
+                maxFrameDuration = frame_duration;
+            }
+            int32_t frameRateInt = static_cast<int32_t>(frameRate);
+            if (minFps > frameRateInt) {
+                minFps = frameRateInt;
+            }
+            if (maxFps < frameRateInt) {
+                maxFps = frameRateInt;
+            }
+            framerates.insert(frameRateInt);
+        }
+
+        for (const auto& format : halFormats) {
+            minFrameDurations.push_back(format);
+            minFrameDurations.push_back(supportedFormat.width);
+            minFrameDurations.push_back(supportedFormat.height);
+            minFrameDurations.push_back(min_frame_duration);
+        }
+
+        // The stall duration is 0 for non-jpeg formats. For JPEG format, stall
+        // duration can be 0 if JPEG is small. Here we choose 1 sec for JPEG.
+        // TODO: b/72261675. Maybe set this dynamically
+        for (const auto& format : halFormats) {
+            const int64_t NS_TO_SECOND = 1000000000;
+            int64_t stall_duration =
+                    (format == HAL_PIXEL_FORMAT_BLOB) ? NS_TO_SECOND : 0;
+            stallDurations.push_back(format);
+            stallDurations.push_back(supportedFormat.width);
+            stallDurations.push_back(supportedFormat.height);
+            stallDurations.push_back(stall_duration);
+        }
+    }
+
+    // The document in aeAvailableTargetFpsRanges section says the minFps should
+    // not be larger than 15.
+    // We cannot support fixed 30fps but Android requires (min, max) and
+    // (max, max) ranges.
+    // TODO: populate more, right now this does not support 30,30 if the device
+    //       has higher than 30 fps modes
+    std::vector<int32_t> fpsRanges;
+    // Variable range
+    fpsRanges.push_back(minFps);
+    fpsRanges.push_back(maxFps);
+    // Fixed ranges
+    for (const auto& framerate : framerates) {
+        fpsRanges.push_back(framerate);
+        fpsRanges.push_back(framerate);
+    }
+    UPDATE(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, fpsRanges.data(),
+           fpsRanges.size());
+
+    UPDATE(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+           streamConfigurations.data(), streamConfigurations.size());
+
+    UPDATE(ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS,
+           minFrameDurations.data(), minFrameDurations.size());
+
+    UPDATE(ANDROID_SCALER_AVAILABLE_STALL_DURATIONS, stallDurations.data(),
+           stallDurations.size());
+
+    UPDATE(ANDROID_SENSOR_INFO_MAX_FRAME_DURATION, &maxFrameDuration, 1);
+
+    SupportedV4L2Format maximumFormat {.width = 0, .height = 0};
+    for (const auto& supportedFormat : mSupportedFormats) {
+        if (supportedFormat.width >= maximumFormat.width &&
+            supportedFormat.height >= maximumFormat.height) {
+            maximumFormat = supportedFormat;
+        }
+    }
+    int32_t activeArraySize[] = {0, 0,
+                                 static_cast<int32_t>(maximumFormat.width),
+                                 static_cast<int32_t>(maximumFormat.height)};
+    UPDATE(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
+           activeArraySize, ARRAY_SIZE(activeArraySize));
+    UPDATE(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE, activeArraySize,
+           ARRAY_SIZE(activeArraySize));
+
+    int32_t pixelArraySize[] = {static_cast<int32_t>(maximumFormat.width),
+                                static_cast<int32_t>(maximumFormat.height)};
+    UPDATE(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE, pixelArraySize,
+           ARRAY_SIZE(pixelArraySize));
+    return OK;
+}
+
+#undef ARRAY_SIZE
+#undef UPDATE
+
+void ExternalCameraDevice::getFrameRateList(
+        int fd, SupportedV4L2Format* format) {
+    format->frameRates.clear();
+
+    v4l2_frmivalenum frameInterval {
+        .pixel_format = format->fourcc,
+        .width = format->width,
+        .height = format->height,
+        .index = 0
+    };
+
+    for (frameInterval.index = 0;
+            TEMP_FAILURE_RETRY(ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameInterval)) == 0;
+            ++frameInterval.index) {
+        if (frameInterval.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
+            if (frameInterval.discrete.numerator != 0) {
+                float framerate = frameInterval.discrete.denominator /
+                        static_cast<float>(frameInterval.discrete.numerator);
+                ALOGV("index:%d, format:%c%c%c%c, w %d, h %d, framerate %f",
+                    frameInterval.index,
+                    frameInterval.pixel_format & 0xFF,
+                    (frameInterval.pixel_format >> 8) & 0xFF,
+                    (frameInterval.pixel_format >> 16) & 0xFF,
+                    (frameInterval.pixel_format >> 24) & 0xFF,
+                    frameInterval.width, frameInterval.height, framerate);
+                format->frameRates.push_back(framerate);
+            }
+        }
+    }
+
+    if (format->frameRates.empty()) {
+        ALOGE("%s: failed to get supported frame rates for format:%c%c%c%c w %d h %d",
+                __FUNCTION__,
+                frameInterval.pixel_format & 0xFF,
+                (frameInterval.pixel_format >> 8) & 0xFF,
+                (frameInterval.pixel_format >> 16) & 0xFF,
+                (frameInterval.pixel_format >> 24) & 0xFF,
+                frameInterval.width, frameInterval.height);
+    }
+}
+
+void ExternalCameraDevice::initSupportedFormatsLocked(int fd) {
+    struct v4l2_fmtdesc fmtdesc {
+        .index = 0,
+        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE};
+    int ret = 0;
+    while (ret == 0) {
+        ret = TEMP_FAILURE_RETRY(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc));
+        ALOGD("index:%d,ret:%d, format:%c%c%c%c", fmtdesc.index, ret,
+                fmtdesc.pixelformat & 0xFF,
+                (fmtdesc.pixelformat >> 8) & 0xFF,
+                (fmtdesc.pixelformat >> 16) & 0xFF,
+                (fmtdesc.pixelformat >> 24) & 0xFF);
+        if (ret == 0 && !(fmtdesc.flags & V4L2_FMT_FLAG_EMULATED)) {
+            auto it = std::find (
+                    kSupportedFourCCs.begin(), kSupportedFourCCs.end(), fmtdesc.pixelformat);
+            if (it != kSupportedFourCCs.end()) {
+                // Found supported format
+                v4l2_frmsizeenum frameSize {
+                        .index = 0,
+                        .pixel_format = fmtdesc.pixelformat};
+                for (; TEMP_FAILURE_RETRY(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frameSize)) == 0;
+                        ++frameSize.index) {
+                    if (frameSize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+                        ALOGD("index:%d, format:%c%c%c%c, w %d, h %d", frameSize.index,
+                            fmtdesc.pixelformat & 0xFF,
+                            (fmtdesc.pixelformat >> 8) & 0xFF,
+                            (fmtdesc.pixelformat >> 16) & 0xFF,
+                            (fmtdesc.pixelformat >> 24) & 0xFF,
+                            frameSize.discrete.width, frameSize.discrete.height);
+                        // Disregard h > w formats so all aspect ratio (h/w) <= 1.0
+                        // This will simplify the crop/scaling logic down the road
+                        if (frameSize.discrete.height > frameSize.discrete.width) {
+                            continue;
+                        }
+                        SupportedV4L2Format format {
+                            .width = frameSize.discrete.width,
+                            .height = frameSize.discrete.height,
+                            .fourcc = fmtdesc.pixelformat
+                        };
+                        getFrameRateList(fd, &format);
+                        if (!format.frameRates.empty()) {
+                            mSupportedFormats.push_back(format);
+                        }
+                    }
+                }
+            }
+        }
+        fmtdesc.index++;
+    }
+}
+
+}  // namespace implementation
+}  // namespace V3_4
+}  // namespace device
+}  // namespace camera
+}  // namespace hardware
+}  // namespace android
+
diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
new file mode 100644
index 0000000..9589782
--- /dev/null
+++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
@@ -0,0 +1,1990 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#define LOG_TAG "ExtCamDevSsn@3.4"
+//#define LOG_NDEBUG 0
+#include <log/log.h>
+
+#include <inttypes.h>
+#include "ExternalCameraDeviceSession.h"
+
+#include "android-base/macros.h"
+#include "algorithm"
+#include <utils/Timers.h>
+#include <cmath>
+#include <linux/videodev2.h>
+#include <sync/sync.h>
+
+#define HAVE_JPEG // required for libyuv.h to export MJPEG decode APIs
+#include <libyuv.h>
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace device {
+namespace V3_4 {
+namespace implementation {
+
+// Size of request/result metadata fast message queue. Change to 0 to always use hwbinder buffer.
+static constexpr size_t kMetadataMsgQueueSize = 1 << 20 /* 1MB */;
+const int ExternalCameraDeviceSession::kMaxProcessedStream;
+const int ExternalCameraDeviceSession::kMaxStallStream;
+const Size kMaxVideoSize = {1920, 1088}; // Maybe this should be programmable
+const int kNumVideoBuffers = 4; // number of v4l2 buffers when streaming <= kMaxVideoSize
+const int kNumStillBuffers = 2; // number of v4l2 buffers when streaming > kMaxVideoSize
+const int kBadFramesAfterStreamOn = 1; // drop x frames after streamOn to get rid of some initial
+                                       // bad frames. TODO: develop a better bad frame detection
+                                       // method
+
+// Aspect ratio is defined as width/height here and ExternalCameraDevice
+// will guarantee all supported sizes has width >= height (so aspect ratio >= 1.0)
+#define ASPECT_RATIO(sz) (static_cast<float>((sz).width) / (sz).height)
+const float kMaxAspectRatio = std::numeric_limits<float>::max();
+const float kMinAspectRatio = 1.f;
+
+HandleImporter ExternalCameraDeviceSession::sHandleImporter;
+
+bool isAspectRatioClose(float ar1, float ar2) {
+    const float kAspectRatioMatchThres = 0.01f; // This threshold is good enough to distinguish
+                                                // 4:3/16:9/20:9
+    return (std::abs(ar1 - ar2) < kAspectRatioMatchThres);
+}
+
+ExternalCameraDeviceSession::ExternalCameraDeviceSession(
+        const sp<ICameraDeviceCallback>& callback,
+        const std::vector<SupportedV4L2Format>& supportedFormats,
+        const common::V1_0::helper::CameraMetadata& chars,
+        unique_fd v4l2Fd) :
+        mCallback(callback),
+        mCameraCharacteristics(chars),
+        mV4l2Fd(std::move(v4l2Fd)),
+        mSupportedFormats(sortFormats(supportedFormats)),
+        mCroppingType(initCroppingType(mSupportedFormats)),
+        mOutputThread(new OutputThread(this, mCroppingType)) {
+    mInitFail = initialize();
+}
+
+std::vector<SupportedV4L2Format> ExternalCameraDeviceSession::sortFormats(
+            const std::vector<SupportedV4L2Format>& inFmts) {
+    std::vector<SupportedV4L2Format> fmts = inFmts;
+    std::sort(fmts.begin(), fmts.end(),
+            [](const SupportedV4L2Format& a, const SupportedV4L2Format& b) -> bool {
+                if (a.width == b.width) {
+                    return a.height < b.height;
+                }
+                return a.width < b.width;
+            });
+    return fmts;
+}
+
+CroppingType ExternalCameraDeviceSession::initCroppingType(
+        const std::vector<SupportedV4L2Format>& sortedFmts) {
+    const auto& maxSize = sortedFmts[sortedFmts.size() - 1];
+    float maxSizeAr = ASPECT_RATIO(maxSize);
+    float minAr = kMinAspectRatio;
+    float maxAr = kMaxAspectRatio;
+    for (const auto& fmt : sortedFmts) {
+        float ar = ASPECT_RATIO(fmt);
+        if (ar < minAr) {
+            minAr = ar;
+        }
+        if (ar > maxAr) {
+            maxAr = ar;
+        }
+    }
+
+    CroppingType ct = VERTICAL;
+    if (isAspectRatioClose(maxSizeAr, maxAr)) {
+        // Ex: 16:9 sensor, cropping horizontally to get to 4:3
+        ct = HORIZONTAL;
+    } else if (isAspectRatioClose(maxSizeAr, minAr)) {
+        // Ex: 4:3 sensor, cropping vertically to get to 16:9
+        ct = VERTICAL;
+    } else {
+        ALOGI("%s: camera maxSizeAr %f is not close to minAr %f or maxAr %f",
+                __FUNCTION__, maxSizeAr, minAr, maxAr);
+        if ((maxSizeAr - minAr) < (maxAr - maxSizeAr)) {
+            ct = VERTICAL;
+        } else {
+            ct = HORIZONTAL;
+        }
+    }
+    ALOGI("%s: camera croppingType is %d", __FUNCTION__, ct);
+    return ct;
+}
+
+
+bool ExternalCameraDeviceSession::initialize() {
+    if (mV4l2Fd.get() < 0) {
+        ALOGE("%s: invalid v4l2 device fd %d!", __FUNCTION__, mV4l2Fd.get());
+        return true;
+    }
+
+    status_t status = initDefaultRequests();
+    if (status != OK) {
+        ALOGE("%s: init default requests failed!", __FUNCTION__);
+        return true;
+    }
+
+    mRequestMetadataQueue = std::make_unique<RequestMetadataQueue>(
+            kMetadataMsgQueueSize, false /* non blocking */);
+    if (!mRequestMetadataQueue->isValid()) {
+        ALOGE("%s: invalid request fmq", __FUNCTION__);
+        return true;
+    }
+    mResultMetadataQueue = std::make_shared<RequestMetadataQueue>(
+            kMetadataMsgQueueSize, false /* non blocking */);
+    if (!mResultMetadataQueue->isValid()) {
+        ALOGE("%s: invalid result fmq", __FUNCTION__);
+        return true;
+    }
+
+    // TODO: check is PRIORITY_DISPLAY enough?
+    mOutputThread->run("ExtCamOut", PRIORITY_DISPLAY);
+    return false;
+}
+
+Status ExternalCameraDeviceSession::initStatus() const {
+    Mutex::Autolock _l(mLock);
+    Status status = Status::OK;
+    if (mInitFail || mClosed) {
+        ALOGI("%s: sesssion initFailed %d closed %d", __FUNCTION__, mInitFail, mClosed);
+        status = Status::INTERNAL_ERROR;
+    }
+    return status;
+}
+
+ExternalCameraDeviceSession::~ExternalCameraDeviceSession() {
+    if (!isClosed()) {
+        ALOGE("ExternalCameraDeviceSession deleted before close!");
+        close();
+    }
+}
+
+void ExternalCameraDeviceSession::dumpState(const native_handle_t*) {
+    // TODO: b/72261676 dump more runtime information
+}
+
+Return<void> ExternalCameraDeviceSession::constructDefaultRequestSettings(
+        RequestTemplate type,
+        ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb) {
+    CameraMetadata emptyMd;
+    Status status = initStatus();
+    if (status != Status::OK) {
+        _hidl_cb(status, emptyMd);
+        return Void();
+    }
+
+    switch (type) {
+        case RequestTemplate::PREVIEW:
+        case RequestTemplate::STILL_CAPTURE:
+        case RequestTemplate::VIDEO_RECORD:
+        case RequestTemplate::VIDEO_SNAPSHOT:
+            _hidl_cb(Status::OK, mDefaultRequests[static_cast<int>(type)]);
+            break;
+        case RequestTemplate::MANUAL:
+        case RequestTemplate::ZERO_SHUTTER_LAG:
+            // Don't support MANUAL or ZSL template
+            _hidl_cb(Status::ILLEGAL_ARGUMENT, emptyMd);
+            break;
+        default:
+            ALOGE("%s: unknown request template type %d", __FUNCTION__, static_cast<int>(type));
+            _hidl_cb(Status::ILLEGAL_ARGUMENT, emptyMd);
+            break;
+    }
+    return Void();
+}
+
+Return<void> ExternalCameraDeviceSession::configureStreams(
+        const V3_2::StreamConfiguration& streams,
+        ICameraDeviceSession::configureStreams_cb _hidl_cb) {
+    V3_2::HalStreamConfiguration outStreams;
+    V3_3::HalStreamConfiguration outStreams_v33;
+    Mutex::Autolock _il(mInterfaceLock);
+
+    Status status = configureStreams(streams, &outStreams_v33);
+    size_t size = outStreams_v33.streams.size();
+    outStreams.streams.resize(size);
+    for (size_t i = 0; i < size; i++) {
+        outStreams.streams[i] = outStreams_v33.streams[i].v3_2;
+    }
+    _hidl_cb(status, outStreams);
+    return Void();
+}
+
+Return<void> ExternalCameraDeviceSession::configureStreams_3_3(
+        const V3_2::StreamConfiguration& streams,
+        ICameraDeviceSession::configureStreams_3_3_cb _hidl_cb) {
+    V3_3::HalStreamConfiguration outStreams;
+    Mutex::Autolock _il(mInterfaceLock);
+
+    Status status = configureStreams(streams, &outStreams);
+    _hidl_cb(status, outStreams);
+    return Void();
+}
+
+Return<void> ExternalCameraDeviceSession::configureStreams_3_4(
+        const V3_4::StreamConfiguration& requestedConfiguration,
+        ICameraDeviceSession::configureStreams_3_4_cb _hidl_cb)  {
+    V3_2::StreamConfiguration config_v32;
+    V3_3::HalStreamConfiguration outStreams_v33;
+    Mutex::Autolock _il(mInterfaceLock);
+
+    config_v32.operationMode = requestedConfiguration.operationMode;
+    config_v32.streams.resize(requestedConfiguration.streams.size());
+    for (size_t i = 0; i < config_v32.streams.size(); i++) {
+        config_v32.streams[i] = requestedConfiguration.streams[i].v3_2;
+    }
+
+    // Ignore requestedConfiguration.sessionParams. External camera does not support it
+    Status status = configureStreams(config_v32, &outStreams_v33);
+
+    V3_4::HalStreamConfiguration outStreams;
+    outStreams.streams.resize(outStreams_v33.streams.size());
+    for (size_t i = 0; i < outStreams.streams.size(); i++) {
+        outStreams.streams[i].v3_3 = outStreams_v33.streams[i];
+    }
+    _hidl_cb(status, outStreams);
+    return Void();
+}
+
+Return<void> ExternalCameraDeviceSession::getCaptureRequestMetadataQueue(
+    ICameraDeviceSession::getCaptureRequestMetadataQueue_cb _hidl_cb) {
+    Mutex::Autolock _il(mInterfaceLock);
+    _hidl_cb(*mRequestMetadataQueue->getDesc());
+    return Void();
+}
+
+Return<void> ExternalCameraDeviceSession::getCaptureResultMetadataQueue(
+    ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) {
+    Mutex::Autolock _il(mInterfaceLock);
+    _hidl_cb(*mResultMetadataQueue->getDesc());
+    return Void();
+}
+
+Return<void> ExternalCameraDeviceSession::processCaptureRequest(
+        const hidl_vec<CaptureRequest>& requests,
+        const hidl_vec<BufferCache>& cachesToRemove,
+        ICameraDeviceSession::processCaptureRequest_cb _hidl_cb) {
+    Mutex::Autolock _il(mInterfaceLock);
+    updateBufferCaches(cachesToRemove);
+
+    uint32_t numRequestProcessed = 0;
+    Status s = Status::OK;
+    for (size_t i = 0; i < requests.size(); i++, numRequestProcessed++) {
+        s = processOneCaptureRequest(requests[i]);
+        if (s != Status::OK) {
+            break;
+        }
+    }
+
+    _hidl_cb(s, numRequestProcessed);
+    return Void();
+}
+
+Return<void> ExternalCameraDeviceSession::processCaptureRequest_3_4(
+        const hidl_vec<V3_4::CaptureRequest>& requests,
+        const hidl_vec<V3_2::BufferCache>& cachesToRemove,
+        ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb) {
+    Mutex::Autolock _il(mInterfaceLock);
+    updateBufferCaches(cachesToRemove);
+
+    uint32_t numRequestProcessed = 0;
+    Status s = Status::OK;
+    for (size_t i = 0; i < requests.size(); i++, numRequestProcessed++) {
+        s = processOneCaptureRequest(requests[i].v3_2);
+        if (s != Status::OK) {
+            break;
+        }
+    }
+
+    _hidl_cb(s, numRequestProcessed);
+    return Void();
+}
+
+Return<Status> ExternalCameraDeviceSession::flush() {
+    return Status::OK;
+}
+
+Return<void> ExternalCameraDeviceSession::close() {
+    Mutex::Autolock _il(mInterfaceLock);
+    Mutex::Autolock _l(mLock);
+    if (!mClosed) {
+        // TODO: b/72261676 Cleanup inflight buffers/V4L2 buffer queue
+        ALOGV("%s: closing V4L2 camera FD %d", __FUNCTION__, mV4l2Fd.get());
+        mV4l2Fd.reset();
+        mOutputThread->requestExit(); // TODO: join?
+
+        // free all imported buffers
+        for(auto& pair : mCirculatingBuffers) {
+            CirculatingBuffers& buffers = pair.second;
+            for (auto& p2 : buffers) {
+                sHandleImporter.freeBuffer(p2.second);
+            }
+        }
+
+        mClosed = true;
+    }
+    return Void();
+}
+
+Status ExternalCameraDeviceSession::importRequest(
+        const CaptureRequest& request,
+        hidl_vec<buffer_handle_t*>& allBufPtrs,
+        hidl_vec<int>& allFences) {
+    size_t numOutputBufs = request.outputBuffers.size();
+    size_t numBufs = numOutputBufs;
+    // Validate all I/O buffers
+    hidl_vec<buffer_handle_t> allBufs;
+    hidl_vec<uint64_t> allBufIds;
+    allBufs.resize(numBufs);
+    allBufIds.resize(numBufs);
+    allBufPtrs.resize(numBufs);
+    allFences.resize(numBufs);
+    std::vector<int32_t> streamIds(numBufs);
+
+    for (size_t i = 0; i < numOutputBufs; i++) {
+        allBufs[i] = request.outputBuffers[i].buffer.getNativeHandle();
+        allBufIds[i] = request.outputBuffers[i].bufferId;
+        allBufPtrs[i] = &allBufs[i];
+        streamIds[i] = request.outputBuffers[i].streamId;
+    }
+
+    for (size_t i = 0; i < numBufs; i++) {
+        buffer_handle_t buf = allBufs[i];
+        uint64_t bufId = allBufIds[i];
+        CirculatingBuffers& cbs = mCirculatingBuffers[streamIds[i]];
+        if (cbs.count(bufId) == 0) {
+            if (buf == nullptr) {
+                ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId);
+                return Status::ILLEGAL_ARGUMENT;
+            }
+            // Register a newly seen buffer
+            buffer_handle_t importedBuf = buf;
+            sHandleImporter.importBuffer(importedBuf);
+            if (importedBuf == nullptr) {
+                ALOGE("%s: output buffer %zu is invalid!", __FUNCTION__, i);
+                return Status::INTERNAL_ERROR;
+            } else {
+                cbs[bufId] = importedBuf;
+            }
+        }
+        allBufPtrs[i] = &cbs[bufId];
+    }
+
+    // All buffers are imported. Now validate output buffer acquire fences
+    for (size_t i = 0; i < numOutputBufs; i++) {
+        if (!sHandleImporter.importFence(
+                request.outputBuffers[i].acquireFence, allFences[i])) {
+            ALOGE("%s: output buffer %zu acquire fence is invalid", __FUNCTION__, i);
+            cleanupInflightFences(allFences, i);
+            return Status::INTERNAL_ERROR;
+        }
+    }
+    return Status::OK;
+}
+
+void ExternalCameraDeviceSession::cleanupInflightFences(
+        hidl_vec<int>& allFences, size_t numFences) {
+    for (size_t j = 0; j < numFences; j++) {
+        sHandleImporter.closeFence(allFences[j]);
+    }
+}
+
+Status ExternalCameraDeviceSession::processOneCaptureRequest(const CaptureRequest& request)  {
+    Status status = initStatus();
+    if (status != Status::OK) {
+        return status;
+    }
+
+    if (request.inputBuffer.streamId != -1) {
+        ALOGE("%s: external camera does not support reprocessing!", __FUNCTION__);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    Mutex::Autolock _l(mLock);
+    if (!mV4l2Streaming) {
+        ALOGE("%s: cannot process request in streamOff state!", __FUNCTION__);
+        return Status::INTERNAL_ERROR;
+    }
+
+    const camera_metadata_t *rawSettings = nullptr;
+    bool converted = true;
+    CameraMetadata settingsFmq;  // settings from FMQ
+    if (request.fmqSettingsSize > 0) {
+        // non-blocking read; client must write metadata before calling
+        // processOneCaptureRequest
+        settingsFmq.resize(request.fmqSettingsSize);
+        bool read = mRequestMetadataQueue->read(settingsFmq.data(), request.fmqSettingsSize);
+        if (read) {
+            converted = V3_2::implementation::convertFromHidl(settingsFmq, &rawSettings);
+        } else {
+            ALOGE("%s: capture request settings metadata couldn't be read from fmq!", __FUNCTION__);
+            converted = false;
+        }
+    } else {
+        converted = V3_2::implementation::convertFromHidl(request.settings, &rawSettings);
+    }
+
+    if (converted && rawSettings != nullptr) {
+        mLatestReqSetting = rawSettings;
+    }
+
+    if (!converted) {
+        ALOGE("%s: capture request settings metadata is corrupt!", __FUNCTION__);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    if (mFirstRequest && rawSettings == nullptr) {
+        ALOGE("%s: capture request settings must not be null for first request!",
+                __FUNCTION__);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    hidl_vec<buffer_handle_t*> allBufPtrs;
+    hidl_vec<int> allFences;
+    size_t numOutputBufs = request.outputBuffers.size();
+
+    if (numOutputBufs == 0) {
+        ALOGE("%s: capture request must have at least one output buffer!", __FUNCTION__);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    status = importRequest(request, allBufPtrs, allFences);
+    if (status != Status::OK) {
+        return status;
+    }
+
+    // TODO: program fps range per capture request here
+    //       or limit the set of availableFpsRange
+
+    sp<V4L2Frame> frameIn = dequeueV4l2FrameLocked();
+    if ( frameIn == nullptr) {
+        ALOGE("%s: V4L2 deque frame failed!", __FUNCTION__);
+        return Status::INTERNAL_ERROR;
+    }
+    // TODO: This can probably be replaced by use v4lbuffer timestamp
+    //       if the device supports it
+    nsecs_t shutterTs = systemTime(SYSTEM_TIME_MONOTONIC);
+
+
+    // TODO: reduce object copy in this path
+    HalRequest halReq = {
+            .frameNumber = request.frameNumber,
+            .setting = mLatestReqSetting,
+            .frameIn = frameIn,
+            .shutterTs = shutterTs};
+    halReq.buffers.resize(numOutputBufs);
+    for (size_t i = 0; i < numOutputBufs; i++) {
+        HalStreamBuffer& halBuf = halReq.buffers[i];
+        int streamId = halBuf.streamId = request.outputBuffers[i].streamId;
+        halBuf.bufferId = request.outputBuffers[i].bufferId;
+        const Stream& stream = mStreamMap[streamId];
+        halBuf.width = stream.width;
+        halBuf.height = stream.height;
+        halBuf.format = stream.format;
+        halBuf.usage = stream.usage;
+        halBuf.bufPtr = allBufPtrs[i];
+        halBuf.acquireFence = allFences[i];
+        halBuf.fenceTimeout = false;
+    }
+    mInflightFrames.insert(halReq.frameNumber);
+    // Send request to OutputThread for the rest of processing
+    mOutputThread->submitRequest(halReq);
+    mFirstRequest = false;
+    return Status::OK;
+}
+
+void ExternalCameraDeviceSession::notifyShutter(uint32_t frameNumber, nsecs_t shutterTs) {
+    NotifyMsg msg;
+    msg.type = MsgType::SHUTTER;
+    msg.msg.shutter.frameNumber = frameNumber;
+    msg.msg.shutter.timestamp = shutterTs;
+    mCallback->notify({msg});
+}
+
+void ExternalCameraDeviceSession::notifyError(
+        uint32_t frameNumber, int32_t streamId, ErrorCode ec) {
+    NotifyMsg msg;
+    msg.type = MsgType::ERROR;
+    msg.msg.error.frameNumber = frameNumber;
+    msg.msg.error.errorStreamId = streamId;
+    msg.msg.error.errorCode = ec;
+    mCallback->notify({msg});
+}
+
+//TODO: refactor with processCaptureResult
+Status ExternalCameraDeviceSession::processCaptureRequestError(HalRequest& req) {
+    // Return V4L2 buffer to V4L2 buffer queue
+    enqueueV4l2Frame(req.frameIn);
+
+    // NotifyShutter
+    notifyShutter(req.frameNumber, req.shutterTs);
+
+    notifyError(/*frameNum*/req.frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST);
+
+    // Fill output buffers
+    hidl_vec<CaptureResult> results;
+    results.resize(1);
+    CaptureResult& result = results[0];
+    result.frameNumber = req.frameNumber;
+    result.partialResult = 1;
+    result.inputBuffer.streamId = -1;
+    result.outputBuffers.resize(req.buffers.size());
+    for (size_t i = 0; i < req.buffers.size(); i++) {
+        result.outputBuffers[i].streamId = req.buffers[i].streamId;
+        result.outputBuffers[i].bufferId = req.buffers[i].bufferId;
+        result.outputBuffers[i].status = BufferStatus::ERROR;
+        if (req.buffers[i].acquireFence >= 0) {
+            native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
+            handle->data[0] = req.buffers[i].acquireFence;
+            result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/true);
+        }
+    }
+
+    // update inflight records
+    {
+        Mutex::Autolock _l(mLock);
+        mInflightFrames.erase(req.frameNumber);
+    }
+
+    // Callback into framework
+    invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
+    freeReleaseFences(results);
+    return Status::OK;
+}
+
+Status ExternalCameraDeviceSession::processCaptureResult(HalRequest& req) {
+    // Return V4L2 buffer to V4L2 buffer queue
+    enqueueV4l2Frame(req.frameIn);
+
+    // NotifyShutter
+    notifyShutter(req.frameNumber, req.shutterTs);
+
+    // Fill output buffers
+    hidl_vec<CaptureResult> results;
+    results.resize(1);
+    CaptureResult& result = results[0];
+    result.frameNumber = req.frameNumber;
+    result.partialResult = 1;
+    result.inputBuffer.streamId = -1;
+    result.outputBuffers.resize(req.buffers.size());
+    for (size_t i = 0; i < req.buffers.size(); i++) {
+        result.outputBuffers[i].streamId = req.buffers[i].streamId;
+        result.outputBuffers[i].bufferId = req.buffers[i].bufferId;
+        if (req.buffers[i].fenceTimeout) {
+            result.outputBuffers[i].status = BufferStatus::ERROR;
+            native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
+            handle->data[0] = req.buffers[i].acquireFence;
+            result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/true);
+            notifyError(req.frameNumber, req.buffers[i].streamId, ErrorCode::ERROR_BUFFER);
+        } else {
+            result.outputBuffers[i].status = BufferStatus::OK;
+            // TODO: refactor
+            if (req.buffers[i].acquireFence > 0) {
+                native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
+                handle->data[0] = req.buffers[i].acquireFence;
+                result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/true);
+            }
+        }
+    }
+
+    // Fill capture result metadata
+    fillCaptureResult(req.setting, req.shutterTs);
+    const camera_metadata_t *rawResult = req.setting.getAndLock();
+    V3_2::implementation::convertToHidl(rawResult, &result.result);
+    req.setting.unlock(rawResult);
+
+    // update inflight records
+    {
+        Mutex::Autolock _l(mLock);
+        mInflightFrames.erase(req.frameNumber);
+    }
+
+    // Callback into framework
+    invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
+    freeReleaseFences(results);
+    return Status::OK;
+}
+
+void ExternalCameraDeviceSession::invokeProcessCaptureResultCallback(
+        hidl_vec<CaptureResult> &results, bool tryWriteFmq) {
+    if (mProcessCaptureResultLock.tryLock() != OK) {
+        const nsecs_t NS_TO_SECOND = 1000000000;
+        ALOGV("%s: previous call is not finished! waiting 1s...", __FUNCTION__);
+        if (mProcessCaptureResultLock.timedLock(/* 1s */NS_TO_SECOND) != OK) {
+            ALOGE("%s: cannot acquire lock in 1s, cannot proceed",
+                    __FUNCTION__);
+            return;
+        }
+    }
+    if (tryWriteFmq && mResultMetadataQueue->availableToWrite() > 0) {
+        for (CaptureResult &result : results) {
+            if (result.result.size() > 0) {
+                if (mResultMetadataQueue->write(result.result.data(), result.result.size())) {
+                    result.fmqResultSize = result.result.size();
+                    result.result.resize(0);
+                } else {
+                    ALOGW("%s: couldn't utilize fmq, fall back to hwbinder", __FUNCTION__);
+                    result.fmqResultSize = 0;
+                }
+            } else {
+                result.fmqResultSize = 0;
+            }
+        }
+    }
+    mCallback->processCaptureResult(results);
+    mProcessCaptureResultLock.unlock();
+}
+
+void ExternalCameraDeviceSession::freeReleaseFences(hidl_vec<CaptureResult>& results) {
+    for (auto& result : results) {
+        if (result.inputBuffer.releaseFence.getNativeHandle() != nullptr) {
+            native_handle_t* handle = const_cast<native_handle_t*>(
+                    result.inputBuffer.releaseFence.getNativeHandle());
+            native_handle_close(handle);
+            native_handle_delete(handle);
+        }
+        for (auto& buf : result.outputBuffers) {
+            if (buf.releaseFence.getNativeHandle() != nullptr) {
+                native_handle_t* handle = const_cast<native_handle_t*>(
+                        buf.releaseFence.getNativeHandle());
+                native_handle_close(handle);
+                native_handle_delete(handle);
+            }
+        }
+    }
+    return;
+}
+
+ExternalCameraDeviceSession::OutputThread::OutputThread(
+        wp<ExternalCameraDeviceSession> parent,
+        CroppingType ct) : mParent(parent), mCroppingType(ct) {}
+
+ExternalCameraDeviceSession::OutputThread::~OutputThread() {}
+
+uint32_t ExternalCameraDeviceSession::OutputThread::getFourCcFromLayout(
+        const YCbCrLayout& layout) {
+    intptr_t cb = reinterpret_cast<intptr_t>(layout.cb);
+    intptr_t cr = reinterpret_cast<intptr_t>(layout.cr);
+    if (std::abs(cb - cr) == 1 && layout.chromaStep == 2) {
+        // Interleaved format
+        if (layout.cb > layout.cr) {
+            return V4L2_PIX_FMT_NV21;
+        } else {
+            return V4L2_PIX_FMT_NV12;
+        }
+    } else if (layout.chromaStep == 1) {
+        // Planar format
+        if (layout.cb > layout.cr) {
+            return V4L2_PIX_FMT_YVU420; // YV12
+        } else {
+            return V4L2_PIX_FMT_YUV420; // YU12
+        }
+    } else {
+        return FLEX_YUV_GENERIC;
+    }
+}
+
+int ExternalCameraDeviceSession::OutputThread::getCropRect(
+        CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out) {
+    if (out == nullptr) {
+        ALOGE("%s: out is null", __FUNCTION__);
+        return -1;
+    }
+    uint32_t inW = inSize.width;
+    uint32_t inH = inSize.height;
+    uint32_t outW = outSize.width;
+    uint32_t outH = outSize.height;
+
+    if (ct == VERTICAL) {
+        uint64_t scaledOutH = static_cast<uint64_t>(outH) * inW / outW;
+        if (scaledOutH > inH) {
+            ALOGE("%s: Output size %dx%d cannot be vertically cropped from input size %dx%d",
+                    __FUNCTION__, outW, outH, inW, inH);
+            return -1;
+        }
+        scaledOutH = scaledOutH & ~0x1; // make it multiple of 2
+
+        out->left = 0;
+        out->top = ((inH - scaledOutH) / 2) & ~0x1;
+        out->width = inW;
+        out->height = static_cast<int32_t>(scaledOutH);
+        ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledH %d",
+                __FUNCTION__, inW, inH, outW, outH, out->top, static_cast<int32_t>(scaledOutH));
+    } else {
+        uint64_t scaledOutW = static_cast<uint64_t>(outW) * inH / outH;
+        if (scaledOutW > inW) {
+            ALOGE("%s: Output size %dx%d cannot be horizontally cropped from input size %dx%d",
+                    __FUNCTION__, outW, outH, inW, inH);
+            return -1;
+        }
+        scaledOutW = scaledOutW & ~0x1; // make it multiple of 2
+
+        out->left = ((inW - scaledOutW) / 2) & ~0x1;
+        out->top = 0;
+        out->width = static_cast<int32_t>(scaledOutW);
+        out->height = inH;
+        ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledW %d",
+                __FUNCTION__, inW, inH, outW, outH, out->top, static_cast<int32_t>(scaledOutW));
+    }
+
+    return 0;
+}
+
+int ExternalCameraDeviceSession::OutputThread::cropAndScaleLocked(
+        sp<AllocatedFrame>& in, const HalStreamBuffer& halBuf, YCbCrLayout* out) {
+    Size inSz = {in->mWidth, in->mHeight};
+    Size outSz = {halBuf.width, halBuf.height};
+    int ret;
+    if (inSz == outSz) {
+        ret = in->getLayout(out);
+        if (ret != 0) {
+            ALOGE("%s: failed to get input image layout", __FUNCTION__);
+            return ret;
+        }
+        return ret;
+    }
+
+    // Cropping to output aspect ratio
+    IMapper::Rect inputCrop;
+    ret = getCropRect(mCroppingType, inSz, outSz, &inputCrop);
+    if (ret != 0) {
+        ALOGE("%s: failed to compute crop rect for output size %dx%d",
+                __FUNCTION__, outSz.width, outSz.height);
+        return ret;
+    }
+
+    YCbCrLayout croppedLayout;
+    ret = in->getCroppedLayout(inputCrop, &croppedLayout);
+    if (ret != 0) {
+        ALOGE("%s: failed to crop input image %dx%d to output size %dx%d",
+                __FUNCTION__, inSz.width, inSz.height, outSz.width, outSz.height);
+        return ret;
+    }
+
+    if ((mCroppingType == VERTICAL && inSz.width == outSz.width) ||
+            (mCroppingType == HORIZONTAL && inSz.height == outSz.height)) {
+        // No scale is needed
+        *out = croppedLayout;
+        return 0;
+    }
+
+    auto it = mScaledYu12Frames.find(outSz);
+    sp<AllocatedFrame> scaledYu12Buf;
+    if (it != mScaledYu12Frames.end()) {
+        scaledYu12Buf = it->second;
+    } else {
+        it = mIntermediateBuffers.find(outSz);
+        if (it == mIntermediateBuffers.end()) {
+            ALOGE("%s: failed to find intermediate buffer size %dx%d",
+                    __FUNCTION__, outSz.width, outSz.height);
+            return -1;
+        }
+        scaledYu12Buf = it->second;
+    }
+    // Scale
+    YCbCrLayout outLayout;
+    ret = scaledYu12Buf->getLayout(&outLayout);
+    if (ret != 0) {
+        ALOGE("%s: failed to get output buffer layout", __FUNCTION__);
+        return ret;
+    }
+
+    ret = libyuv::I420Scale(
+            static_cast<uint8_t*>(croppedLayout.y),
+            croppedLayout.yStride,
+            static_cast<uint8_t*>(croppedLayout.cb),
+            croppedLayout.cStride,
+            static_cast<uint8_t*>(croppedLayout.cr),
+            croppedLayout.cStride,
+            inputCrop.width,
+            inputCrop.height,
+            static_cast<uint8_t*>(outLayout.y),
+            outLayout.yStride,
+            static_cast<uint8_t*>(outLayout.cb),
+            outLayout.cStride,
+            static_cast<uint8_t*>(outLayout.cr),
+            outLayout.cStride,
+            outSz.width,
+            outSz.height,
+            // TODO: b/72261744 see if we can use better filter without losing too much perf
+            libyuv::FilterMode::kFilterNone);
+
+    if (ret != 0) {
+        ALOGE("%s: failed to scale buffer from %dx%d to %dx%d. Ret %d",
+                __FUNCTION__, inputCrop.width, inputCrop.height,
+                outSz.width, outSz.height, ret);
+        return ret;
+    }
+
+    *out = outLayout;
+    mScaledYu12Frames.insert({outSz, scaledYu12Buf});
+    return 0;
+}
+
+int ExternalCameraDeviceSession::OutputThread::formatConvertLocked(
+        const YCbCrLayout& in, const YCbCrLayout& out, Size sz, uint32_t format) {
+    int ret = 0;
+    switch (format) {
+        case V4L2_PIX_FMT_NV21:
+            ret = libyuv::I420ToNV21(
+                    static_cast<uint8_t*>(in.y),
+                    in.yStride,
+                    static_cast<uint8_t*>(in.cb),
+                    in.cStride,
+                    static_cast<uint8_t*>(in.cr),
+                    in.cStride,
+                    static_cast<uint8_t*>(out.y),
+                    out.yStride,
+                    static_cast<uint8_t*>(out.cr),
+                    out.cStride,
+                    sz.width,
+                    sz.height);
+            if (ret != 0) {
+                ALOGE("%s: convert to NV21 buffer failed! ret %d",
+                            __FUNCTION__, ret);
+                return ret;
+            }
+            break;
+        case V4L2_PIX_FMT_NV12:
+            ret = libyuv::I420ToNV12(
+                    static_cast<uint8_t*>(in.y),
+                    in.yStride,
+                    static_cast<uint8_t*>(in.cb),
+                    in.cStride,
+                    static_cast<uint8_t*>(in.cr),
+                    in.cStride,
+                    static_cast<uint8_t*>(out.y),
+                    out.yStride,
+                    static_cast<uint8_t*>(out.cb),
+                    out.cStride,
+                    sz.width,
+                    sz.height);
+            if (ret != 0) {
+                ALOGE("%s: convert to NV12 buffer failed! ret %d",
+                            __FUNCTION__, ret);
+                return ret;
+            }
+            break;
+        case V4L2_PIX_FMT_YVU420: // YV12
+        case V4L2_PIX_FMT_YUV420: // YU12
+            // TODO: maybe we can speed up here by somehow save this copy?
+            ret = libyuv::I420Copy(
+                    static_cast<uint8_t*>(in.y),
+                    in.yStride,
+                    static_cast<uint8_t*>(in.cb),
+                    in.cStride,
+                    static_cast<uint8_t*>(in.cr),
+                    in.cStride,
+                    static_cast<uint8_t*>(out.y),
+                    out.yStride,
+                    static_cast<uint8_t*>(out.cb),
+                    out.cStride,
+                    static_cast<uint8_t*>(out.cr),
+                    out.cStride,
+                    sz.width,
+                    sz.height);
+            if (ret != 0) {
+                ALOGE("%s: copy to YV12 or YU12 buffer failed! ret %d",
+                            __FUNCTION__, ret);
+                return ret;
+            }
+            break;
+        case FLEX_YUV_GENERIC:
+            // TODO: b/72261744 write to arbitrary flexible YUV layout. Slow.
+            ALOGE("%s: unsupported flexible yuv layout"
+                    " y %p cb %p cr %p y_str %d c_str %d c_step %d",
+                    __FUNCTION__, out.y, out.cb, out.cr,
+                    out.yStride, out.cStride, out.chromaStep);
+            return -1;
+        default:
+            ALOGE("%s: unknown YUV format 0x%x!", __FUNCTION__, format);
+            return -1;
+    }
+    return 0;
+}
+
+bool ExternalCameraDeviceSession::OutputThread::threadLoop() {
+    HalRequest req;
+    auto parent = mParent.promote();
+    if (parent == nullptr) {
+       ALOGE("%s: session has been disconnected!", __FUNCTION__);
+       return false;
+    }
+
+    // TODO: maybe we need to setup a sensor thread to dq/enq v4l frames
+    //       regularly to prevent v4l buffer queue filled with stale buffers
+    //       when app doesn't program a preveiw request
+    waitForNextRequest(&req);
+    if (req.frameIn == nullptr) {
+        // No new request, wait again
+        return true;
+    }
+
+    if (req.frameIn->mFourcc != V4L2_PIX_FMT_MJPEG) {
+        ALOGE("%s: do not support V4L2 format %c%c%c%c", __FUNCTION__,
+                req.frameIn->mFourcc & 0xFF,
+                (req.frameIn->mFourcc >> 8) & 0xFF,
+                (req.frameIn->mFourcc >> 16) & 0xFF,
+                (req.frameIn->mFourcc >> 24) & 0xFF);
+        parent->notifyError(
+                /*frameNum*/req.frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
+        return false;
+    }
+
+    std::unique_lock<std::mutex> lk(mLock);
+
+    // Convert input V4L2 frame to YU12 of the same size
+    // TODO: see if we can save some computation by converting to YV12 here
+    uint8_t* inData;
+    size_t inDataSize;
+    req.frameIn->map(&inData, &inDataSize);
+    // TODO: profile
+    // TODO: in some special case maybe we can decode jpg directly to gralloc output?
+    int res = libyuv::MJPGToI420(
+            inData, inDataSize,
+            static_cast<uint8_t*>(mYu12FrameLayout.y),
+            mYu12FrameLayout.yStride,
+            static_cast<uint8_t*>(mYu12FrameLayout.cb),
+            mYu12FrameLayout.cStride,
+            static_cast<uint8_t*>(mYu12FrameLayout.cr),
+            mYu12FrameLayout.cStride,
+            mYu12Frame->mWidth, mYu12Frame->mHeight,
+            mYu12Frame->mWidth, mYu12Frame->mHeight);
+
+    if (res != 0) {
+        // For some webcam, the first few V4L2 frames might be malformed...
+        ALOGE("%s: Convert V4L2 frame to YU12 failed! res %d", __FUNCTION__, res);
+        lk.unlock();
+        Status st = parent->processCaptureRequestError(req);
+        if (st != Status::OK) {
+            ALOGE("%s: failed to process capture request error!", __FUNCTION__);
+            parent->notifyError(
+                    /*frameNum*/req.frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
+            return false;
+        }
+        return true;
+    }
+
+    ALOGV("%s processing new request", __FUNCTION__);
+    const int kSyncWaitTimeoutMs = 500;
+    for (auto& halBuf : req.buffers) {
+        if (halBuf.acquireFence != -1) {
+            int ret = sync_wait(halBuf.acquireFence, kSyncWaitTimeoutMs);
+            if (ret) {
+                halBuf.fenceTimeout = true;
+            } else {
+                ::close(halBuf.acquireFence);
+            }
+        }
+
+        if (halBuf.fenceTimeout) {
+            continue;
+        }
+
+        // Gralloc lockYCbCr the buffer
+        switch (halBuf.format) {
+            case PixelFormat::BLOB:
+                // TODO: b/72261675 implement JPEG output path
+                break;
+            case PixelFormat::YCBCR_420_888:
+            case PixelFormat::YV12: {
+                IMapper::Rect outRect {0, 0,
+                        static_cast<int32_t>(halBuf.width),
+                        static_cast<int32_t>(halBuf.height)};
+                YCbCrLayout outLayout = sHandleImporter.lockYCbCr(
+                        *(halBuf.bufPtr), halBuf.usage, outRect);
+                ALOGV("%s: outLayout y %p cb %p cr %p y_str %d c_str %d c_step %d",
+                        __FUNCTION__, outLayout.y, outLayout.cb, outLayout.cr,
+                        outLayout.yStride, outLayout.cStride, outLayout.chromaStep);
+
+                // Convert to output buffer size/format
+                uint32_t outputFourcc = getFourCcFromLayout(outLayout);
+                ALOGV("%s: converting to format %c%c%c%c", __FUNCTION__,
+                        outputFourcc & 0xFF,
+                        (outputFourcc >> 8) & 0xFF,
+                        (outputFourcc >> 16) & 0xFF,
+                        (outputFourcc >> 24) & 0xFF);
+
+                YCbCrLayout cropAndScaled;
+                int ret = cropAndScaleLocked(
+                        mYu12Frame, halBuf, &cropAndScaled);
+                if (ret != 0) {
+                    ALOGE("%s: crop and scale failed!", __FUNCTION__);
+                    lk.unlock();
+                    parent->notifyError(
+                            /*frameNum*/req.frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
+                    return false;
+                }
+
+                Size sz {halBuf.width, halBuf.height};
+                ret = formatConvertLocked(cropAndScaled, outLayout, sz, outputFourcc);
+                if (ret != 0) {
+                    ALOGE("%s: format coversion failed!", __FUNCTION__);
+                    lk.unlock();
+                    parent->notifyError(
+                            /*frameNum*/req.frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
+                    return false;
+                }
+                int relFence = sHandleImporter.unlock(*(halBuf.bufPtr));
+                if (relFence > 0) {
+                    halBuf.acquireFence = relFence;
+                }
+            } break;
+            default:
+                ALOGE("%s: unknown output format %x", __FUNCTION__, halBuf.format);
+                lk.unlock();
+                parent->notifyError(
+                        /*frameNum*/req.frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
+                return false;
+        }
+    } // for each buffer
+    mScaledYu12Frames.clear();
+
+    // Don't hold the lock while calling back to parent
+    lk.unlock();
+    Status st = parent->processCaptureResult(req);
+    if (st != Status::OK) {
+        ALOGE("%s: failed to process capture result!", __FUNCTION__);
+        parent->notifyError(
+                /*frameNum*/req.frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
+        return false;
+    }
+    return true;
+}
+
+Status ExternalCameraDeviceSession::OutputThread::allocateIntermediateBuffers(
+        const Size& v4lSize, const hidl_vec<Stream>& streams) {
+    std::lock_guard<std::mutex> lk(mLock);
+    if (mScaledYu12Frames.size() != 0) {
+        ALOGE("%s: intermediate buffer pool has %zu inflight buffers! (expect 0)",
+                __FUNCTION__, mScaledYu12Frames.size());
+        return Status::INTERNAL_ERROR;
+    }
+
+    // Allocating intermediate YU12 frame
+    if (mYu12Frame == nullptr || mYu12Frame->mWidth != v4lSize.width ||
+            mYu12Frame->mHeight != v4lSize.height) {
+        mYu12Frame.clear();
+        mYu12Frame = new AllocatedFrame(v4lSize.width, v4lSize.height);
+        int ret = mYu12Frame->allocate(&mYu12FrameLayout);
+        if (ret != 0) {
+            ALOGE("%s: allocating YU12 frame failed!", __FUNCTION__);
+            return Status::INTERNAL_ERROR;
+        }
+    }
+
+    // Allocating scaled buffers
+    for (const auto& stream : streams) {
+        Size sz = {stream.width, stream.height};
+        if (sz == v4lSize) {
+            continue; // Don't need an intermediate buffer same size as v4lBuffer
+        }
+        if (mIntermediateBuffers.count(sz) == 0) {
+            // Create new intermediate buffer
+            sp<AllocatedFrame> buf = new AllocatedFrame(stream.width, stream.height);
+            int ret = buf->allocate();
+            if (ret != 0) {
+                ALOGE("%s: allocating intermediate YU12 frame %dx%d failed!",
+                            __FUNCTION__, stream.width, stream.height);
+                return Status::INTERNAL_ERROR;
+            }
+            mIntermediateBuffers[sz] = buf;
+        }
+    }
+
+    // Remove unconfigured buffers
+    auto it = mIntermediateBuffers.begin();
+    while (it != mIntermediateBuffers.end()) {
+        bool configured = false;
+        auto sz = it->first;
+        for (const auto& stream : streams) {
+            if (stream.width == sz.width && stream.height == sz.height) {
+                configured = true;
+                break;
+            }
+        }
+        if (configured) {
+            it++;
+        } else {
+            it = mIntermediateBuffers.erase(it);
+        }
+    }
+    return Status::OK;
+}
+
+Status ExternalCameraDeviceSession::OutputThread::submitRequest(const HalRequest& req) {
+    std::lock_guard<std::mutex> lk(mLock);
+    // TODO: reduce object copy in this path
+    mRequestList.push_back(req);
+    mRequestCond.notify_one();
+    return Status::OK;
+}
+
+void ExternalCameraDeviceSession::OutputThread::flush() {
+    std::lock_guard<std::mutex> lk(mLock);
+    // TODO: send buffer/request errors back to framework
+    mRequestList.clear();
+}
+
+void ExternalCameraDeviceSession::OutputThread::waitForNextRequest(HalRequest* out) {
+    if (out == nullptr) {
+        ALOGE("%s: out is null", __FUNCTION__);
+        return;
+    }
+
+    std::unique_lock<std::mutex> lk(mLock);
+    while (mRequestList.empty()) {
+        std::chrono::seconds timeout = std::chrono::seconds(kReqWaitTimeoutSec);
+        auto st = mRequestCond.wait_for(lk, timeout);
+        if (st == std::cv_status::timeout) {
+            // no new request, return
+            return;
+        }
+    }
+    *out = mRequestList.front();
+    mRequestList.pop_front();
+}
+
+void ExternalCameraDeviceSession::cleanupBuffersLocked(int id) {
+    for (auto& pair : mCirculatingBuffers.at(id)) {
+        sHandleImporter.freeBuffer(pair.second);
+    }
+    mCirculatingBuffers[id].clear();
+    mCirculatingBuffers.erase(id);
+}
+
+void ExternalCameraDeviceSession::updateBufferCaches(const hidl_vec<BufferCache>& cachesToRemove) {
+    Mutex::Autolock _l(mLock);
+    for (auto& cache : cachesToRemove) {
+        auto cbsIt = mCirculatingBuffers.find(cache.streamId);
+        if (cbsIt == mCirculatingBuffers.end()) {
+            // The stream could have been removed
+            continue;
+        }
+        CirculatingBuffers& cbs = cbsIt->second;
+        auto it = cbs.find(cache.bufferId);
+        if (it != cbs.end()) {
+            sHandleImporter.freeBuffer(it->second);
+            cbs.erase(it);
+        } else {
+            ALOGE("%s: stream %d buffer %" PRIu64 " is not cached",
+                    __FUNCTION__, cache.streamId, cache.bufferId);
+        }
+    }
+}
+
+bool ExternalCameraDeviceSession::isSupported(const Stream& stream) {
+    int32_t ds = static_cast<int32_t>(stream.dataSpace);
+    PixelFormat fmt = stream.format;
+    uint32_t width = stream.width;
+    uint32_t height = stream.height;
+    // TODO: check usage flags
+
+    if (stream.streamType != StreamType::OUTPUT) {
+        ALOGE("%s: does not support non-output stream type", __FUNCTION__);
+        return false;
+    }
+
+    if (stream.rotation != StreamRotation::ROTATION_0) {
+        ALOGE("%s: does not support stream rotation", __FUNCTION__);
+        return false;
+    }
+
+    if (ds & Dataspace::DEPTH) {
+        ALOGI("%s: does not support depth output", __FUNCTION__);
+        return false;
+    }
+
+    switch (fmt) {
+        case PixelFormat::BLOB:
+            if (ds != static_cast<int32_t>(Dataspace::V0_JFIF)) {
+                ALOGI("%s: BLOB format does not support dataSpace %x", __FUNCTION__, ds);
+                return false;
+            }
+        case PixelFormat::IMPLEMENTATION_DEFINED:
+        case PixelFormat::YCBCR_420_888:
+        case PixelFormat::YV12:
+            // TODO: check what dataspace we can support here.
+            // intentional no-ops.
+            break;
+        default:
+            ALOGI("%s: does not support format %x", __FUNCTION__, fmt);
+            return false;
+    }
+
+    // Assume we can convert any V4L2 format to any of supported output format for now, i.e,
+    // ignoring v4l2Fmt.fourcc for now. Might need more subtle check if we support more v4l format
+    // in the futrue.
+    for (const auto& v4l2Fmt : mSupportedFormats) {
+        if (width == v4l2Fmt.width && height == v4l2Fmt.height) {
+            return true;
+        }
+    }
+    ALOGI("%s: resolution %dx%d is not supported", __FUNCTION__, width, height);
+    return false;
+}
+
+int ExternalCameraDeviceSession::v4l2StreamOffLocked() {
+    if (!mV4l2Streaming) {
+        return OK;
+    }
+
+    {
+        std::lock_guard<std::mutex> lk(mV4l2BufferLock);
+        if (mNumDequeuedV4l2Buffers != 0)  {
+            ALOGE("%s: there are %zu inflight V4L buffers",
+                __FUNCTION__, mNumDequeuedV4l2Buffers);
+            return -1;
+        }
+    }
+    mV4l2Buffers.clear(); // VIDIOC_REQBUFS will fail if FDs are not clear first
+
+    // VIDIOC_STREAMOFF
+    v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMOFF, &capture_type)) < 0) {
+        ALOGE("%s: STREAMOFF failed: %s", __FUNCTION__, strerror(errno));
+        return -errno;
+    }
+
+    // VIDIOC_REQBUFS: clear buffers
+    v4l2_requestbuffers req_buffers{};
+    req_buffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    req_buffers.memory = V4L2_MEMORY_MMAP;
+    req_buffers.count = 0;
+    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_REQBUFS, &req_buffers)) < 0) {
+        ALOGE("%s: REQBUFS failed: %s", __FUNCTION__, strerror(errno));
+        return -errno;
+    }
+
+    mV4l2Streaming = false;
+    return OK;
+}
+
+int ExternalCameraDeviceSession::configureV4l2StreamLocked(const SupportedV4L2Format& v4l2Fmt) {
+    int ret = v4l2StreamOffLocked();
+    if (ret != OK) {
+        ALOGE("%s: stop v4l2 streaming failed: ret %d", __FUNCTION__, ret);
+        return ret;
+    }
+
+    // VIDIOC_S_FMT w/h/fmt
+    v4l2_format fmt;
+    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    fmt.fmt.pix.width = v4l2Fmt.width;
+    fmt.fmt.pix.height = v4l2Fmt.height;
+    fmt.fmt.pix.pixelformat = v4l2Fmt.fourcc;
+    ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_FMT, &fmt));
+    if (ret < 0) {
+        ALOGE("%s: S_FMT ioctl failed: %s", __FUNCTION__, strerror(errno));
+        return -errno;
+    }
+
+    if (v4l2Fmt.width != fmt.fmt.pix.width || v4l2Fmt.height != fmt.fmt.pix.height ||
+            v4l2Fmt.fourcc != fmt.fmt.pix.pixelformat) {
+        ALOGE("%s: S_FMT expect %c%c%c%c %dx%d, got %c%c%c%c %dx%d instead!", __FUNCTION__,
+                v4l2Fmt.fourcc & 0xFF,
+                (v4l2Fmt.fourcc >> 8) & 0xFF,
+                (v4l2Fmt.fourcc >> 16) & 0xFF,
+                (v4l2Fmt.fourcc >> 24) & 0xFF,
+                v4l2Fmt.width, v4l2Fmt.height,
+                fmt.fmt.pix.pixelformat & 0xFF,
+                (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
+                (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
+                (fmt.fmt.pix.pixelformat >> 24) & 0xFF,
+                fmt.fmt.pix.width, fmt.fmt.pix.height);
+        return -EINVAL;
+    }
+    uint32_t bufferSize = fmt.fmt.pix.sizeimage;
+    ALOGI("%s: V4L2 buffer size is %d", __FUNCTION__, bufferSize);
+
+    float maxFps = -1.f;
+    float fps = 1000.f;
+    const float kDefaultFps = 30.f;
+    // Try to pick the slowest fps that is at least 30
+    for (const auto& f : v4l2Fmt.frameRates) {
+        if (maxFps < f) {
+            maxFps = f;
+        }
+        if (f >= kDefaultFps && f < fps) {
+            fps = f;
+        }
+    }
+    if (fps == 1000.f) {
+        fps = maxFps;
+    }
+
+    // VIDIOC_G_PARM/VIDIOC_S_PARM: set fps
+    v4l2_streamparm streamparm = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
+    // The following line checks that the driver knows about framerate get/set.
+    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_G_PARM, &streamparm)) >= 0) {
+        // Now check if the device is able to accept a capture framerate set.
+        if (streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) {
+            // |frame_rate| is float, approximate by a fraction.
+            const int kFrameRatePrecision = 10000;
+            streamparm.parm.capture.timeperframe.numerator = kFrameRatePrecision;
+            streamparm.parm.capture.timeperframe.denominator =
+                (fps * kFrameRatePrecision);
+
+            if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_PARM, &streamparm)) < 0) {
+                ALOGE("%s: failed to set framerate to %f", __FUNCTION__, fps);
+                return UNKNOWN_ERROR;
+            }
+        }
+    }
+    float retFps = streamparm.parm.capture.timeperframe.denominator /
+                streamparm.parm.capture.timeperframe.numerator;
+    if (std::fabs(fps - retFps) > std::numeric_limits<float>::epsilon()) {
+        ALOGE("%s: expect fps %f, got %f instead", __FUNCTION__, fps, retFps);
+        return BAD_VALUE;
+    }
+
+    uint32_t v4lBufferCount = (v4l2Fmt.width <= kMaxVideoSize.width &&
+            v4l2Fmt.height <= kMaxVideoSize.height) ? kNumVideoBuffers : kNumStillBuffers;
+    // VIDIOC_REQBUFS: create buffers
+    v4l2_requestbuffers req_buffers{};
+    req_buffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    req_buffers.memory = V4L2_MEMORY_MMAP;
+    req_buffers.count = v4lBufferCount;
+    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_REQBUFS, &req_buffers)) < 0) {
+        ALOGE("%s: VIDIOC_REQBUFS failed: %s", __FUNCTION__, strerror(errno));
+        return -errno;
+    }
+
+    // Driver can indeed return more buffer if it needs more to operate
+    if (req_buffers.count < v4lBufferCount) {
+        ALOGE("%s: VIDIOC_REQBUFS expected %d buffers, got %d instead",
+                __FUNCTION__, v4lBufferCount, req_buffers.count);
+        return NO_MEMORY;
+    }
+
+    // VIDIOC_EXPBUF:  export buffers as FD
+    // VIDIOC_QBUF: send buffer to driver
+    mV4l2Buffers.resize(req_buffers.count);
+    for (uint32_t i = 0; i < req_buffers.count; i++) {
+        v4l2_exportbuffer expbuf {};
+        expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+        expbuf.index = i;
+        if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_EXPBUF, &expbuf)) < 0) {
+            ALOGE("%s: EXPBUF %d failed: %s", __FUNCTION__, i,  strerror(errno));
+            return -errno;
+        }
+        mV4l2Buffers[i].reset(expbuf.fd);
+
+        v4l2_buffer buffer = {
+            .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+            .index = i,
+            .memory = V4L2_MEMORY_MMAP};
+
+        if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_QBUF, &buffer)) < 0) {
+            ALOGE("%s: QBUF %d failed: %s", __FUNCTION__, i,  strerror(errno));
+            return -errno;
+        }
+    }
+
+    // VIDIOC_STREAMON: start streaming
+    v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMON, &capture_type)) < 0) {
+        ALOGE("%s: VIDIOC_STREAMON failed: %s", __FUNCTION__, strerror(errno));
+        return -errno;
+    }
+
+    // Swallow first few frames after streamOn to account for bad frames from some devices
+    for (int i = 0; i < kBadFramesAfterStreamOn; i++) {
+        v4l2_buffer buffer{};
+        buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+        buffer.memory = V4L2_MEMORY_MMAP;
+        if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_DQBUF, &buffer)) < 0) {
+            ALOGE("%s: DQBUF fails: %s", __FUNCTION__, strerror(errno));
+            return -errno;
+        }
+
+        if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_QBUF, &buffer)) < 0) {
+            ALOGE("%s: QBUF index %d fails: %s", __FUNCTION__, buffer.index, strerror(errno));
+            return -errno;
+        }
+    }
+
+    mV4l2StreamingFmt = v4l2Fmt;
+    mV4l2Streaming = true;
+    return OK;
+}
+
+sp<V4L2Frame> ExternalCameraDeviceSession::dequeueV4l2FrameLocked() {
+    sp<V4L2Frame> ret = nullptr;
+
+    {
+        std::unique_lock<std::mutex> lk(mV4l2BufferLock);
+        if (mNumDequeuedV4l2Buffers == mV4l2Buffers.size()) {
+            std::chrono::seconds timeout = std::chrono::seconds(kBufferWaitTimeoutSec);
+            mLock.unlock();
+            auto st = mV4L2BufferReturned.wait_for(lk, timeout);
+            mLock.lock();
+            if (st == std::cv_status::timeout) {
+                ALOGE("%s: wait for V4L2 buffer return timeout!", __FUNCTION__);
+                return ret;
+            }
+        }
+    }
+
+    v4l2_buffer buffer{};
+    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buffer.memory = V4L2_MEMORY_MMAP;
+    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_DQBUF, &buffer)) < 0) {
+        ALOGE("%s: DQBUF fails: %s", __FUNCTION__, strerror(errno));
+        return ret;
+    }
+
+    if (buffer.index >= mV4l2Buffers.size()) {
+        ALOGE("%s: Invalid buffer id: %d", __FUNCTION__, buffer.index);
+        return ret;
+    }
+
+    if (buffer.flags & V4L2_BUF_FLAG_ERROR) {
+        ALOGE("%s: v4l2 buf error! buf flag 0x%x", __FUNCTION__, buffer.flags);
+        // TODO: try to dequeue again
+    }
+
+    {
+        std::lock_guard<std::mutex> lk(mV4l2BufferLock);
+        mNumDequeuedV4l2Buffers++;
+    }
+    return new V4L2Frame(
+            mV4l2StreamingFmt.width, mV4l2StreamingFmt.height, mV4l2StreamingFmt.fourcc,
+            buffer.index, mV4l2Buffers[buffer.index].get(), buffer.bytesused);
+}
+
+void ExternalCameraDeviceSession::enqueueV4l2Frame(const sp<V4L2Frame>& frame) {
+    Mutex::Autolock _l(mLock);
+    frame->unmap();
+    v4l2_buffer buffer{};
+    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buffer.memory = V4L2_MEMORY_MMAP;
+    buffer.index = frame->mBufferIndex;
+    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_QBUF, &buffer)) < 0) {
+        ALOGE("%s: QBUF index %d fails: %s", __FUNCTION__, frame->mBufferIndex, strerror(errno));
+        return;
+    }
+
+    {
+        std::lock_guard<std::mutex> lk(mV4l2BufferLock);
+        mNumDequeuedV4l2Buffers--;
+        mV4L2BufferReturned.notify_one();
+    }
+}
+
+Status ExternalCameraDeviceSession::configureStreams(
+        const V3_2::StreamConfiguration& config, V3_3::HalStreamConfiguration* out) {
+    if (config.operationMode != StreamConfigurationMode::NORMAL_MODE) {
+        ALOGE("%s: unsupported operation mode: %d", __FUNCTION__, config.operationMode);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    if (config.streams.size() == 0) {
+        ALOGE("%s: cannot configure zero stream", __FUNCTION__);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    int numProcessedStream = 0;
+    int numStallStream = 0;
+    for (const auto& stream : config.streams) {
+        // Check if the format/width/height combo is supported
+        if (!isSupported(stream)) {
+            return Status::ILLEGAL_ARGUMENT;
+        }
+        if (stream.format == PixelFormat::BLOB) {
+            numStallStream++;
+        } else {
+            numProcessedStream++;
+        }
+    }
+
+    if (numProcessedStream > kMaxProcessedStream) {
+        ALOGE("%s: too many processed streams (expect <= %d, got %d)", __FUNCTION__,
+                kMaxProcessedStream, numProcessedStream);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    if (numStallStream > kMaxStallStream) {
+        ALOGE("%s: too many stall streams (expect <= %d, got %d)", __FUNCTION__,
+                kMaxStallStream, numStallStream);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    Status status = initStatus();
+    if (status != Status::OK) {
+        return status;
+    }
+
+    Mutex::Autolock _l(mLock);
+    if (!mInflightFrames.empty()) {
+        ALOGE("%s: trying to configureStreams while there are still %zu inflight frames!",
+                __FUNCTION__, mInflightFrames.size());
+        return Status::INTERNAL_ERROR;
+    }
+
+    // Add new streams
+    for (const auto& stream : config.streams) {
+        if (mStreamMap.count(stream.id) == 0) {
+            mStreamMap[stream.id] = stream;
+            mCirculatingBuffers.emplace(stream.id, CirculatingBuffers{});
+        }
+    }
+
+    // Cleanup removed streams
+    for(auto it = mStreamMap.begin(); it != mStreamMap.end();) {
+        int id = it->first;
+        bool found = false;
+        for (const auto& stream : config.streams) {
+            if (id == stream.id) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            // Unmap all buffers of deleted stream
+            cleanupBuffersLocked(id);
+            it = mStreamMap.erase(it);
+        } else {
+            ++it;
+        }
+    }
+
+    // Now select a V4L2 format to produce all output streams
+    float desiredAr = (mCroppingType == VERTICAL) ? kMaxAspectRatio : kMinAspectRatio;
+    uint32_t maxDim = 0;
+    for (const auto& stream : config.streams) {
+        float aspectRatio = ASPECT_RATIO(stream);
+        if ((mCroppingType == VERTICAL && aspectRatio < desiredAr) ||
+                (mCroppingType == HORIZONTAL && aspectRatio > desiredAr)) {
+            desiredAr = aspectRatio;
+        }
+
+        // The dimension that's not cropped
+        uint32_t dim = (mCroppingType == VERTICAL) ? stream.width : stream.height;
+        if (dim > maxDim) {
+            maxDim = dim;
+        }
+    }
+    // Find the smallest format that matches the desired aspect ratio and is wide/high enough
+    SupportedV4L2Format v4l2Fmt {.width = 0, .height = 0};
+    for (const auto& fmt : mSupportedFormats) {
+        uint32_t dim = (mCroppingType == VERTICAL) ? fmt.width : fmt.height;
+        if (dim >= maxDim) {
+            float aspectRatio = ASPECT_RATIO(fmt);
+            if (isAspectRatioClose(aspectRatio, desiredAr)) {
+                v4l2Fmt = fmt;
+                // since mSupportedFormats is sorted by width then height, the first matching fmt
+                // will be the smallest one with matching aspect ratio
+                break;
+            }
+        }
+    }
+    if (v4l2Fmt.width == 0) {
+        // Cannot find exact good aspect ratio candidate, try to find a close one
+        for (const auto& fmt : mSupportedFormats) {
+            uint32_t dim = (mCroppingType == VERTICAL) ? fmt.width : fmt.height;
+            if (dim >= maxDim) {
+                float aspectRatio = ASPECT_RATIO(fmt);
+                if ((mCroppingType == VERTICAL && aspectRatio < desiredAr) ||
+                        (mCroppingType == HORIZONTAL && aspectRatio > desiredAr)) {
+                    v4l2Fmt = fmt;
+                    break;
+                }
+            }
+        }
+    }
+
+    if (v4l2Fmt.width == 0) {
+        ALOGE("%s: unable to find a resolution matching (%s at least %d, aspect ratio %f)"
+                , __FUNCTION__, (mCroppingType == VERTICAL) ? "width" : "height",
+                maxDim, desiredAr);
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    if (configureV4l2StreamLocked(v4l2Fmt) != 0) {
+        ALOGE("V4L configuration failed!, format:%c%c%c%c, w %d, h %d",
+            v4l2Fmt.fourcc & 0xFF,
+            (v4l2Fmt.fourcc >> 8) & 0xFF,
+            (v4l2Fmt.fourcc >> 16) & 0xFF,
+            (v4l2Fmt.fourcc >> 24) & 0xFF,
+            v4l2Fmt.width, v4l2Fmt.height);
+        return Status::INTERNAL_ERROR;
+    }
+
+    Size v4lSize = {v4l2Fmt.width, v4l2Fmt.height};
+    status = mOutputThread->allocateIntermediateBuffers(v4lSize, config.streams);
+    if (status != Status::OK) {
+        ALOGE("%s: allocating intermediate buffers failed!", __FUNCTION__);
+        return status;
+    }
+
+    out->streams.resize(config.streams.size());
+    for (size_t i = 0; i < config.streams.size(); i++) {
+        out->streams[i].overrideDataSpace = config.streams[i].dataSpace;
+        out->streams[i].v3_2.id = config.streams[i].id;
+        // TODO: double check should we add those CAMERA flags
+        mStreamMap[config.streams[i].id].usage =
+                out->streams[i].v3_2.producerUsage = config.streams[i].usage |
+                BufferUsage::CPU_WRITE_OFTEN |
+                BufferUsage::CAMERA_OUTPUT;
+        out->streams[i].v3_2.consumerUsage = 0;
+        out->streams[i].v3_2.maxBuffers  = mV4l2Buffers.size();
+
+        switch (config.streams[i].format) {
+            case PixelFormat::BLOB:
+            case PixelFormat::YCBCR_420_888:
+                // No override
+                out->streams[i].v3_2.overrideFormat = config.streams[i].format;
+                break;
+            case PixelFormat::IMPLEMENTATION_DEFINED:
+                // Override based on VIDEO or not
+                out->streams[i].v3_2.overrideFormat =
+                        (config.streams[i].usage & BufferUsage::VIDEO_ENCODER) ?
+                        PixelFormat::YCBCR_420_888 : PixelFormat::YV12;
+                // Save overridden formt in mStreamMap
+                mStreamMap[config.streams[i].id].format = out->streams[i].v3_2.overrideFormat;
+                break;
+            default:
+                ALOGE("%s: unsupported format %x", __FUNCTION__, config.streams[i].format);
+                return Status::ILLEGAL_ARGUMENT;
+        }
+    }
+
+    mFirstRequest = true;
+    return Status::OK;
+}
+
+bool ExternalCameraDeviceSession::isClosed() {
+    Mutex::Autolock _l(mLock);
+    return mClosed;
+}
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#define UPDATE(md, tag, data, size)               \
+do {                                              \
+    if ((md).update((tag), (data), (size))) {     \
+        ALOGE("Update " #tag " failed!");         \
+        return BAD_VALUE;                         \
+    }                                             \
+} while (0)
+
+status_t ExternalCameraDeviceSession::initDefaultRequests() {
+    ::android::hardware::camera::common::V1_0::helper::CameraMetadata md;
+
+    const uint8_t aberrationMode = ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF;
+    UPDATE(md, ANDROID_COLOR_CORRECTION_ABERRATION_MODE, &aberrationMode, 1);
+
+    const int32_t exposureCompensation = 0;
+    UPDATE(md, ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, &exposureCompensation, 1);
+
+    const uint8_t videoStabilizationMode = ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+    UPDATE(md, ANDROID_CONTROL_VIDEO_STABILIZATION_MODE, &videoStabilizationMode, 1);
+
+    const uint8_t awbMode = ANDROID_CONTROL_AWB_MODE_AUTO;
+    UPDATE(md, ANDROID_CONTROL_AWB_MODE, &awbMode, 1);
+
+    const uint8_t aeMode = ANDROID_CONTROL_AE_MODE_ON;
+    UPDATE(md, ANDROID_CONTROL_AE_MODE, &aeMode, 1);
+
+    const uint8_t aePrecaptureTrigger = ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_IDLE;
+    UPDATE(md, ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER, &aePrecaptureTrigger, 1);
+
+    const uint8_t afMode = ANDROID_CONTROL_AF_MODE_AUTO;
+    UPDATE(md, ANDROID_CONTROL_AF_MODE, &afMode, 1);
+
+    const uint8_t afTrigger = ANDROID_CONTROL_AF_TRIGGER_IDLE;
+    UPDATE(md, ANDROID_CONTROL_AF_TRIGGER, &afTrigger, 1);
+
+    const uint8_t sceneMode = ANDROID_CONTROL_SCENE_MODE_DISABLED;
+    UPDATE(md, ANDROID_CONTROL_SCENE_MODE, &sceneMode, 1);
+
+    const uint8_t effectMode = ANDROID_CONTROL_EFFECT_MODE_OFF;
+    UPDATE(md, ANDROID_CONTROL_EFFECT_MODE, &effectMode, 1);
+
+    const uint8_t flashMode = ANDROID_FLASH_MODE_OFF;
+    UPDATE(md, ANDROID_FLASH_MODE, &flashMode, 1);
+
+    const int32_t thumbnailSize[] = {240, 180};
+    UPDATE(md, ANDROID_JPEG_THUMBNAIL_SIZE, thumbnailSize, 2);
+
+    const uint8_t jpegQuality = 90;
+    UPDATE(md, ANDROID_JPEG_QUALITY, &jpegQuality, 1);
+    UPDATE(md, ANDROID_JPEG_THUMBNAIL_QUALITY, &jpegQuality, 1);
+
+    const int32_t jpegOrientation = 0;
+    UPDATE(md, ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);
+
+    const uint8_t oisMode = ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
+    UPDATE(md, ANDROID_LENS_OPTICAL_STABILIZATION_MODE, &oisMode, 1);
+
+    const uint8_t nrMode = ANDROID_NOISE_REDUCTION_MODE_OFF;
+    UPDATE(md, ANDROID_NOISE_REDUCTION_MODE, &nrMode, 1);
+
+    const uint8_t fdMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF;
+    UPDATE(md, ANDROID_STATISTICS_FACE_DETECT_MODE, &fdMode, 1);
+
+    const uint8_t hotpixelMode = ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF;
+    UPDATE(md, ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE, &hotpixelMode, 1);
+
+    bool support30Fps = false;
+    int32_t maxFps = std::numeric_limits<int32_t>::min();
+    for (const auto& supportedFormat : mSupportedFormats) {
+        for (const auto& frameRate : supportedFormat.frameRates) {
+            int32_t framerateInt = static_cast<int32_t>(frameRate);
+            if (maxFps < framerateInt) {
+                maxFps = framerateInt;
+            }
+            if (framerateInt == 30) {
+                support30Fps = true;
+                break;
+            }
+        }
+        if (support30Fps) {
+            break;
+        }
+    }
+    int32_t defaultFramerate = support30Fps ? 30 : maxFps;
+    int32_t defaultFpsRange[] = {defaultFramerate, defaultFramerate};
+    UPDATE(md, ANDROID_CONTROL_AE_TARGET_FPS_RANGE, defaultFpsRange, ARRAY_SIZE(defaultFpsRange));
+
+    uint8_t antibandingMode = ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO;
+    UPDATE(md, ANDROID_CONTROL_AE_ANTIBANDING_MODE, &antibandingMode, 1);
+
+    const uint8_t controlMode = ANDROID_CONTROL_MODE_AUTO;
+    UPDATE(md, ANDROID_CONTROL_MODE, &controlMode, 1);
+
+    for (int type = static_cast<int>(RequestTemplate::PREVIEW);
+            type <= static_cast<int>(RequestTemplate::VIDEO_SNAPSHOT); type++) {
+        ::android::hardware::camera::common::V1_0::helper::CameraMetadata mdCopy = md;
+        uint8_t intent = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
+        switch (type) {
+            case static_cast<int>(RequestTemplate::PREVIEW):
+                intent = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
+                break;
+            case static_cast<int>(RequestTemplate::STILL_CAPTURE):
+                intent = ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE;
+                break;
+            case static_cast<int>(RequestTemplate::VIDEO_RECORD):
+                intent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
+                break;
+            case static_cast<int>(RequestTemplate::VIDEO_SNAPSHOT):
+                intent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT;
+                break;
+            default:
+                ALOGE("%s: unknown template type %d", __FUNCTION__, type);
+                return BAD_VALUE;
+        }
+        UPDATE(mdCopy, ANDROID_CONTROL_CAPTURE_INTENT, &intent, 1);
+
+        camera_metadata_t* rawMd = mdCopy.release();
+        CameraMetadata hidlMd;
+        hidlMd.setToExternal(
+                (uint8_t*) rawMd, get_camera_metadata_size(rawMd));
+        mDefaultRequests[type] = hidlMd;
+        free_camera_metadata(rawMd);
+    }
+
+    return OK;
+}
+
+status_t ExternalCameraDeviceSession::fillCaptureResult(
+        common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp) {
+    // android.control
+    // For USB camera, we don't know the AE state. Set the state to converged to
+    // indicate the frame should be good to use. Then apps don't have to wait the
+    // AE state.
+    const uint8_t aeState = ANDROID_CONTROL_AE_STATE_CONVERGED;
+    UPDATE(md, ANDROID_CONTROL_AE_STATE, &aeState, 1);
+
+    const uint8_t ae_lock = ANDROID_CONTROL_AE_LOCK_OFF;
+    UPDATE(md, ANDROID_CONTROL_AE_LOCK, &ae_lock, 1);
+
+
+    // TODO: b/72261912 AF should stay LOCKED until cancel is seen
+    bool afTrigger = false;
+    if (md.exists(ANDROID_CONTROL_AF_TRIGGER)) {
+        camera_metadata_entry entry = md.find(ANDROID_CONTROL_AF_TRIGGER);
+        if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_START) {
+            afTrigger = true;
+        } else if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_CANCEL) {
+            afTrigger = false;
+        }
+    }
+
+    // For USB camera, the USB camera handles everything and we don't have control
+    // over AF. We only simply fake the AF metadata based on the request
+    // received here.
+    uint8_t afState;
+    if (afTrigger) {
+        afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+    } else {
+        afState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+    }
+    UPDATE(md, ANDROID_CONTROL_AF_STATE, &afState, 1);
+
+    // Set AWB state to converged to indicate the frame should be good to use.
+    const uint8_t awbState = ANDROID_CONTROL_AWB_STATE_CONVERGED;
+    UPDATE(md, ANDROID_CONTROL_AWB_STATE, &awbState, 1);
+
+    const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
+    UPDATE(md, ANDROID_CONTROL_AWB_LOCK, &awbLock, 1);
+
+    camera_metadata_ro_entry active_array_size =
+        mCameraCharacteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+
+    if (active_array_size.count == 0) {
+        ALOGE("%s: cannot find active array size!", __FUNCTION__);
+        return -EINVAL;
+    }
+
+    // android.scaler
+    const int32_t crop_region[] = {
+          active_array_size.data.i32[0], active_array_size.data.i32[1],
+          active_array_size.data.i32[2], active_array_size.data.i32[3],
+    };
+    UPDATE(md, ANDROID_SCALER_CROP_REGION, crop_region, ARRAY_SIZE(crop_region));
+
+    // android.sensor
+    UPDATE(md, ANDROID_SENSOR_TIMESTAMP, &timestamp, 1);
+
+    // android.statistics
+    const uint8_t lensShadingMapMode = ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF;
+    UPDATE(md, ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, &lensShadingMapMode, 1);
+
+    const uint8_t sceneFlicker = ANDROID_STATISTICS_SCENE_FLICKER_NONE;
+    UPDATE(md, ANDROID_STATISTICS_SCENE_FLICKER, &sceneFlicker, 1);
+
+    return OK;
+}
+
+#undef ARRAY_SIZE
+#undef UPDATE
+
+V4L2Frame::V4L2Frame(
+        uint32_t w, uint32_t h, uint32_t fourcc,
+        int bufIdx, int fd, uint32_t dataSize) :
+        mWidth(w), mHeight(h), mFourcc(fourcc),
+        mBufferIndex(bufIdx), mFd(fd), mDataSize(dataSize) {}
+
+int V4L2Frame::map(uint8_t** data, size_t* dataSize) {
+    if (data == nullptr || dataSize == nullptr) {
+        ALOGI("%s: V4L2 buffer map bad argument: data %p, dataSize %p",
+                __FUNCTION__, data, dataSize);
+        return -EINVAL;
+    }
+
+    Mutex::Autolock _l(mLock);
+    if (!mMapped) {
+        void* addr = mmap(NULL, mDataSize, PROT_READ, MAP_SHARED, mFd, 0);
+        if (addr == MAP_FAILED) {
+            ALOGE("%s: V4L2 buffer map failed: %s", __FUNCTION__, strerror(errno));
+            return -EINVAL;
+        }
+        mData = static_cast<uint8_t*>(addr);
+        mMapped = true;
+    }
+    *data = mData;
+    *dataSize = mDataSize;
+    ALOGV("%s: V4L map FD %d, data %p size %zu", __FUNCTION__, mFd, mData, mDataSize);
+    return 0;
+}
+
+int V4L2Frame::unmap() {
+    Mutex::Autolock _l(mLock);
+    if (mMapped) {
+        ALOGV("%s: V4L unmap data %p size %zu", __FUNCTION__, mData, mDataSize);
+        if (munmap(mData, mDataSize) != 0) {
+            ALOGE("%s: V4L2 buffer unmap failed: %s", __FUNCTION__, strerror(errno));
+            return -EINVAL;
+        }
+        mMapped = false;
+    }
+    return 0;
+}
+
+V4L2Frame::~V4L2Frame() {
+    unmap();
+}
+
+AllocatedFrame::AllocatedFrame(
+        uint32_t w, uint32_t h) :
+        mWidth(w), mHeight(h), mFourcc(V4L2_PIX_FMT_YUV420) {};
+
+AllocatedFrame::~AllocatedFrame() {}
+
+int AllocatedFrame::allocate(YCbCrLayout* out) {
+    if ((mWidth % 2) || (mHeight % 2)) {
+        ALOGE("%s: bad dimension %dx%d (not multiple of 2)", __FUNCTION__, mWidth, mHeight);
+        return -EINVAL;
+    }
+
+    uint32_t dataSize = mWidth * mHeight * 3 / 2; // YUV420
+    if (mData.size() != dataSize) {
+        mData.resize(dataSize);
+    }
+
+    if (out != nullptr) {
+        out->y = mData.data();
+        out->yStride = mWidth;
+        uint8_t* cbStart = mData.data() + mWidth * mHeight;
+        uint8_t* crStart = cbStart + mWidth * mHeight / 4;
+        out->cb = cbStart;
+        out->cr = crStart;
+        out->cStride = mWidth / 2;
+        out->chromaStep = 1;
+    }
+    return 0;
+}
+
+int AllocatedFrame::getLayout(YCbCrLayout* out) {
+    IMapper::Rect noCrop = {0, 0,
+            static_cast<int32_t>(mWidth),
+            static_cast<int32_t>(mHeight)};
+    return getCroppedLayout(noCrop, out);
+}
+
+int AllocatedFrame::getCroppedLayout(const IMapper::Rect& rect, YCbCrLayout* out) {
+    if (out == nullptr) {
+        ALOGE("%s: null out", __FUNCTION__);
+        return -1;
+    }
+    if ((rect.left + rect.width) > static_cast<int>(mWidth) ||
+        (rect.top + rect.height) > static_cast<int>(mHeight) ||
+            (rect.left % 2) || (rect.top % 2) || (rect.width % 2) || (rect.height % 2)) {
+        ALOGE("%s: bad rect left %d top %d w %d h %d", __FUNCTION__,
+                rect.left, rect.top, rect.width, rect.height);
+        return -1;
+    }
+
+    out->y = mData.data() + mWidth * rect.top + rect.left;
+    out->yStride = mWidth;
+    uint8_t* cbStart = mData.data() + mWidth * mHeight;
+    uint8_t* crStart = cbStart + mWidth * mHeight / 4;
+    out->cb = cbStart + mWidth * rect.top / 4 + rect.left / 2;
+    out->cr = crStart + mWidth * rect.top / 4 + rect.left / 2;
+    out->cStride = mWidth / 2;
+    out->chromaStep = 1;
+    return 0;
+}
+
+}  // namespace implementation
+}  // namespace V3_4
+}  // namespace device
+}  // namespace camera
+}  // namespace hardware
+}  // namespace android
+
diff --git a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDeviceSession.h b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDeviceSession.h
new file mode 100644
index 0000000..404dfe0
--- /dev/null
+++ b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDeviceSession.h
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H
+#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H
+
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+#include <include/convert.h>
+#include <chrono>
+#include <condition_variable>
+#include <list>
+#include <unordered_map>
+#include <unordered_set>
+#include "CameraMetadata.h"
+#include "HandleImporter.h"
+#include "utils/KeyedVector.h"
+#include "utils/Mutex.h"
+#include "utils/Thread.h"
+#include "android-base/unique_fd.h"
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace device {
+namespace V3_4 {
+namespace implementation {
+
+using ::android::hardware::camera::device::V3_2::BufferCache;
+using ::android::hardware::camera::device::V3_2::BufferStatus;
+using ::android::hardware::camera::device::V3_2::CameraMetadata;
+using ::android::hardware::camera::device::V3_2::CaptureRequest;
+using ::android::hardware::camera::device::V3_2::CaptureResult;
+using ::android::hardware::camera::device::V3_2::ErrorCode;
+using ::android::hardware::camera::device::V3_2::ICameraDeviceCallback;
+using ::android::hardware::camera::device::V3_2::MsgType;
+using ::android::hardware::camera::device::V3_2::NotifyMsg;
+using ::android::hardware::camera::device::V3_2::RequestTemplate;
+using ::android::hardware::camera::device::V3_2::Stream;
+using ::android::hardware::camera::device::V3_4::StreamConfiguration;
+using ::android::hardware::camera::device::V3_2::StreamConfigurationMode;
+using ::android::hardware::camera::device::V3_2::StreamRotation;
+using ::android::hardware::camera::device::V3_2::StreamType;
+using ::android::hardware::camera::device::V3_2::DataspaceFlags;
+using ::android::hardware::camera::device::V3_4::HalStreamConfiguration;
+using ::android::hardware::camera::device::V3_4::ICameraDeviceSession;
+using ::android::hardware::camera::common::V1_0::Status;
+using ::android::hardware::camera::common::V1_0::helper::HandleImporter;
+using ::android::hardware::graphics::common::V1_0::BufferUsage;
+using ::android::hardware::graphics::common::V1_0::Dataspace;
+using ::android::hardware::graphics::common::V1_0::PixelFormat;
+using ::android::hardware::kSynchronizedReadWrite;
+using ::android::hardware::MessageQueue;
+using ::android::hardware::MQDescriptorSync;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+using ::android::sp;
+using ::android::Mutex;
+using ::android::base::unique_fd;
+
+// TODO: put V4L2 related structs into separate header?
+struct SupportedV4L2Format {
+    uint32_t width;
+    uint32_t height;
+    uint32_t fourcc;
+    // All supported frame rate for this w/h/fourcc combination
+    std::vector<float> frameRates;
+};
+
+// A class provide access to a dequeued V4L2 frame buffer (mostly in MJPG format)
+// Also contains necessary information to enqueue the buffer back to V4L2 buffer queue
+class V4L2Frame : public virtual VirtualLightRefBase {
+public:
+    V4L2Frame(uint32_t w, uint32_t h, uint32_t fourcc, int bufIdx, int fd, uint32_t dataSize);
+    ~V4L2Frame() override;
+    const uint32_t mWidth;
+    const uint32_t mHeight;
+    const uint32_t mFourcc;
+    const int mBufferIndex; // for later enqueue
+    int map(uint8_t** data, size_t* dataSize);
+    int unmap();
+private:
+    Mutex mLock;
+    const int mFd; // used for mmap but doesn't claim ownership
+    const size_t mDataSize;
+    uint8_t* mData = nullptr;
+    bool  mMapped = false;
+};
+
+// A RAII class representing a CPU allocated YUV frame used as intermeidate buffers
+// when generating output images.
+class AllocatedFrame : public virtual VirtualLightRefBase {
+public:
+    AllocatedFrame(uint32_t w, uint32_t h); // TODO: use Size?
+    ~AllocatedFrame() override;
+    const uint32_t mWidth;
+    const uint32_t mHeight;
+    const uint32_t mFourcc; // Only support YU12 format for now
+    int allocate(YCbCrLayout* out = nullptr);
+    int getLayout(YCbCrLayout* out);
+    int getCroppedLayout(const IMapper::Rect&, YCbCrLayout* out); // return non-zero for bad input
+private:
+    Mutex mLock;
+    std::vector<uint8_t> mData;
+};
+
+struct Size {
+    uint32_t width;
+    uint32_t height;
+
+    bool operator==(const Size& other) const {
+        return (width == other.width && height == other.height);
+    }
+};
+
+struct SizeHasher {
+    size_t operator()(const Size& sz) const {
+        size_t result = 1;
+        result = 31 * result + sz.width;
+        result = 31 * result + sz.height;
+        return result;
+    }
+};
+
+enum CroppingType {
+    HORIZONTAL = 0,
+    VERTICAL = 1
+};
+
+struct ExternalCameraDeviceSession : public virtual RefBase {
+
+    ExternalCameraDeviceSession(const sp<ICameraDeviceCallback>&,
+            const std::vector<SupportedV4L2Format>& supportedFormats,
+            const common::V1_0::helper::CameraMetadata& chars,
+            unique_fd v4l2Fd);
+    virtual ~ExternalCameraDeviceSession();
+    // Call by CameraDevice to dump active device states
+    void dumpState(const native_handle_t*);
+    // Caller must use this method to check if CameraDeviceSession ctor failed
+    bool isInitFailed() { return mInitFail; }
+    bool isClosed();
+
+    // Retrieve the HIDL interface, split into its own class to avoid inheritance issues when
+    // dealing with minor version revs and simultaneous implementation and interface inheritance
+    virtual sp<ICameraDeviceSession> getInterface() {
+        return new TrampolineSessionInterface_3_4(this);
+    }
+
+    static const int kMaxProcessedStream = 2;
+    static const int kMaxStallStream = 1;
+
+protected:
+
+    // Methods from ::android::hardware::camera::device::V3_2::ICameraDeviceSession follow
+
+    Return<void> constructDefaultRequestSettings(
+            RequestTemplate,
+            ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb);
+
+    Return<void> configureStreams(
+            const V3_2::StreamConfiguration&,
+            ICameraDeviceSession::configureStreams_cb);
+
+    Return<void> getCaptureRequestMetadataQueue(
+        ICameraDeviceSession::getCaptureRequestMetadataQueue_cb);
+
+    Return<void> getCaptureResultMetadataQueue(
+        ICameraDeviceSession::getCaptureResultMetadataQueue_cb);
+
+    Return<void> processCaptureRequest(
+            const hidl_vec<CaptureRequest>&,
+            const hidl_vec<BufferCache>&,
+            ICameraDeviceSession::processCaptureRequest_cb);
+
+    Return<Status> flush();
+    Return<void> close();
+
+    Return<void> configureStreams_3_3(
+            const V3_2::StreamConfiguration&,
+            ICameraDeviceSession::configureStreams_3_3_cb);
+
+    Return<void> configureStreams_3_4(
+            const V3_4::StreamConfiguration& requestedConfiguration,
+            ICameraDeviceSession::configureStreams_3_4_cb _hidl_cb);
+
+    Return<void> processCaptureRequest_3_4(
+            const hidl_vec<V3_4::CaptureRequest>& requests,
+            const hidl_vec<V3_2::BufferCache>& cachesToRemove,
+            ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb);
+
+protected:
+    struct HalStreamBuffer {
+        int32_t streamId;
+        uint64_t bufferId;
+        uint32_t width;
+        uint32_t height;
+        PixelFormat format;
+        V3_2::BufferUsageFlags usage;
+        buffer_handle_t* bufPtr;
+        int acquireFence;
+        bool fenceTimeout;
+    };
+
+    struct HalRequest {
+        uint32_t frameNumber;
+        common::V1_0::helper::CameraMetadata setting;
+        sp<V4L2Frame> frameIn;
+        nsecs_t shutterTs;
+        std::vector<HalStreamBuffer> buffers;
+    };
+
+    static std::vector<SupportedV4L2Format> sortFormats(
+            const std::vector<SupportedV4L2Format>&);
+    static CroppingType initCroppingType(const std::vector<SupportedV4L2Format>&);
+    bool initialize();
+    Status initStatus() const;
+    status_t initDefaultRequests();
+    status_t fillCaptureResult(common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp);
+    Status configureStreams(const V3_2::StreamConfiguration&, V3_3::HalStreamConfiguration* out);
+    int configureV4l2StreamLocked(const SupportedV4L2Format& fmt);
+    int v4l2StreamOffLocked();
+
+    // TODO: change to unique_ptr for better tracking
+    sp<V4L2Frame> dequeueV4l2FrameLocked(); // Called with mLock hold
+    void enqueueV4l2Frame(const sp<V4L2Frame>&);
+
+    // Check if input Stream is one of supported stream setting on this device
+    bool isSupported(const Stream&);
+
+    // Validate and import request's output buffers and acquire fence
+    Status importRequest(
+            const CaptureRequest& request,
+            hidl_vec<buffer_handle_t*>& allBufPtrs,
+            hidl_vec<int>& allFences);
+    static void cleanupInflightFences(
+            hidl_vec<int>& allFences, size_t numFences);
+    void cleanupBuffersLocked(int id);
+    void updateBufferCaches(const hidl_vec<BufferCache>& cachesToRemove);
+
+    Status processOneCaptureRequest(const CaptureRequest& request);
+
+    Status processCaptureResult(HalRequest&);
+    Status processCaptureRequestError(HalRequest&);
+    void notifyShutter(uint32_t frameNumber, nsecs_t shutterTs);
+    void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec);
+    void invokeProcessCaptureResultCallback(
+            hidl_vec<CaptureResult> &results, bool tryWriteFmq);
+    static void freeReleaseFences(hidl_vec<CaptureResult>&);
+
+    class OutputThread : public android::Thread {
+    public:
+        OutputThread(wp<ExternalCameraDeviceSession> parent, CroppingType);
+        ~OutputThread();
+
+        Status allocateIntermediateBuffers(
+                const Size& v4lSize, const hidl_vec<Stream>& streams);
+        Status submitRequest(const HalRequest&);
+        void flush();
+        virtual bool threadLoop() override;
+
+    private:
+        static const uint32_t FLEX_YUV_GENERIC = static_cast<uint32_t>('F') |
+                static_cast<uint32_t>('L') << 8 | static_cast<uint32_t>('E') << 16 |
+                static_cast<uint32_t>('X') << 24;
+        // returns FLEX_YUV_GENERIC for formats other than YV12/YU12/NV12/NV21
+        static uint32_t getFourCcFromLayout(const YCbCrLayout&);
+        static int getCropRect(
+                CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out);
+
+        static const int kReqWaitTimeoutSec = 3;
+
+        void waitForNextRequest(HalRequest* out);
+        int cropAndScaleLocked(
+                sp<AllocatedFrame>& in, const HalStreamBuffer& halBuf,
+                YCbCrLayout* out);
+
+        int formatConvertLocked(const YCbCrLayout& in, const YCbCrLayout& out,
+                Size sz, uint32_t format);
+
+        mutable std::mutex mLock;
+        std::condition_variable mRequestCond;
+        wp<ExternalCameraDeviceSession> mParent;
+        CroppingType mCroppingType;
+        std::list<HalRequest> mRequestList;
+        // V4L2 frameIn
+        // (MJPG decode)-> mYu12Frame
+        // (Scale)-> mScaledYu12Frames
+        // (Format convert) -> output gralloc frames
+        sp<AllocatedFrame> mYu12Frame;
+        std::unordered_map<Size, sp<AllocatedFrame>, SizeHasher> mIntermediateBuffers;
+        std::unordered_map<Size, sp<AllocatedFrame>, SizeHasher> mScaledYu12Frames;
+        YCbCrLayout mYu12FrameLayout;
+    };
+
+    // Protect (most of) HIDL interface methods from synchronized-entering
+    mutable Mutex mInterfaceLock;
+
+    mutable Mutex mLock; // Protect all private members except otherwise noted
+    const sp<ICameraDeviceCallback> mCallback;
+    const common::V1_0::helper::CameraMetadata mCameraCharacteristics;
+    unique_fd mV4l2Fd;
+    // device is closed either
+    //    - closed by user
+    //    - init failed
+    //    - camera disconnected
+    bool mClosed = false;
+    bool mInitFail = false;
+    bool mFirstRequest = false;
+    common::V1_0::helper::CameraMetadata mLatestReqSetting;
+
+    bool mV4l2Streaming = false;
+    SupportedV4L2Format mV4l2StreamingFmt;
+    std::vector<unique_fd> mV4l2Buffers;
+
+    static const int kBufferWaitTimeoutSec = 3; // TODO: handle long exposure (or not allowing)
+    std::mutex mV4l2BufferLock; // protect the buffer count and condition below
+    std::condition_variable mV4L2BufferReturned;
+    size_t mNumDequeuedV4l2Buffers = 0;
+
+    const std::vector<SupportedV4L2Format> mSupportedFormats;
+    const CroppingType mCroppingType;
+    sp<OutputThread> mOutputThread;
+
+    // Stream ID -> Camera3Stream cache
+    std::unordered_map<int, Stream> mStreamMap;
+    std::unordered_set<uint32_t>  mInflightFrames;
+
+    // buffers currently circulating between HAL and camera service
+    // key: bufferId sent via HIDL interface
+    // value: imported buffer_handle_t
+    // Buffer will be imported during processCaptureRequest and will be freed
+    // when the its stream is deleted or camera device session is closed
+    typedef std::unordered_map<uint64_t, buffer_handle_t> CirculatingBuffers;
+    // Stream ID -> circulating buffers map
+    std::map<int, CirculatingBuffers> mCirculatingBuffers;
+
+    static HandleImporter sHandleImporter;
+
+    /* Beginning of members not changed after initialize() */
+    using RequestMetadataQueue = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+    std::unique_ptr<RequestMetadataQueue> mRequestMetadataQueue;
+    using ResultMetadataQueue = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+    std::shared_ptr<ResultMetadataQueue> mResultMetadataQueue;
+
+    // Protect against invokeProcessCaptureResultCallback()
+    Mutex mProcessCaptureResultLock;
+
+    std::unordered_map<int, CameraMetadata> mDefaultRequests;
+    /* End of members not changed after initialize() */
+
+private:
+
+    struct TrampolineSessionInterface_3_4 : public ICameraDeviceSession {
+        TrampolineSessionInterface_3_4(sp<ExternalCameraDeviceSession> parent) :
+                mParent(parent) {}
+
+        virtual Return<void> constructDefaultRequestSettings(
+                V3_2::RequestTemplate type,
+                V3_3::ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb) override {
+            return mParent->constructDefaultRequestSettings(type, _hidl_cb);
+        }
+
+        virtual Return<void> configureStreams(
+                const V3_2::StreamConfiguration& requestedConfiguration,
+                V3_3::ICameraDeviceSession::configureStreams_cb _hidl_cb) override {
+            return mParent->configureStreams(requestedConfiguration, _hidl_cb);
+        }
+
+        virtual Return<void> processCaptureRequest(const hidl_vec<V3_2::CaptureRequest>& requests,
+                const hidl_vec<V3_2::BufferCache>& cachesToRemove,
+                V3_3::ICameraDeviceSession::processCaptureRequest_cb _hidl_cb) override {
+            return mParent->processCaptureRequest(requests, cachesToRemove, _hidl_cb);
+        }
+
+        virtual Return<void> getCaptureRequestMetadataQueue(
+                V3_3::ICameraDeviceSession::getCaptureRequestMetadataQueue_cb _hidl_cb) override  {
+            return mParent->getCaptureRequestMetadataQueue(_hidl_cb);
+        }
+
+        virtual Return<void> getCaptureResultMetadataQueue(
+                V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) override  {
+            return mParent->getCaptureResultMetadataQueue(_hidl_cb);
+        }
+
+        virtual Return<Status> flush() override {
+            return mParent->flush();
+        }
+
+        virtual Return<void> close() override {
+            return mParent->close();
+        }
+
+        virtual Return<void> configureStreams_3_3(
+                const V3_2::StreamConfiguration& requestedConfiguration,
+                configureStreams_3_3_cb _hidl_cb) override {
+            return mParent->configureStreams_3_3(requestedConfiguration, _hidl_cb);
+        }
+
+        virtual Return<void> configureStreams_3_4(
+                const V3_4::StreamConfiguration& requestedConfiguration,
+                configureStreams_3_4_cb _hidl_cb) override {
+            return mParent->configureStreams_3_4(requestedConfiguration, _hidl_cb);
+        }
+
+        virtual Return<void> processCaptureRequest_3_4(const hidl_vec<V3_4::CaptureRequest>& requests,
+                const hidl_vec<V3_2::BufferCache>& cachesToRemove,
+                ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb) override {
+            return mParent->processCaptureRequest_3_4(requests, cachesToRemove, _hidl_cb);
+        }
+
+    private:
+        sp<ExternalCameraDeviceSession> mParent;
+    };
+};
+
+}  // namespace implementation
+}  // namespace V3_4
+}  // namespace device
+}  // namespace camera
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H
diff --git a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDevice_3_4.h b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDevice_3_4.h
new file mode 100644
index 0000000..606375e
--- /dev/null
+++ b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDevice_3_4.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE_H
+#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE_H
+
+#include "utils/Mutex.h"
+#include "CameraMetadata.h"
+
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+#include <hidl/Status.h>
+#include <hidl/MQDescriptor.h>
+#include "ExternalCameraDeviceSession.h"
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace device {
+namespace V3_4 {
+namespace implementation {
+
+using namespace ::android::hardware::camera::device;
+using ::android::hardware::camera::device::V3_2::ICameraDevice;
+using ::android::hardware::camera::device::V3_2::ICameraDeviceCallback;
+using ::android::hardware::camera::common::V1_0::CameraResourceCost;
+using ::android::hardware::camera::common::V1_0::TorchMode;
+using ::android::hardware::camera::common::V1_0::Status;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+using ::android::sp;
+
+/*
+ * The camera device HAL implementation is opened lazily (via the open call)
+ */
+struct ExternalCameraDevice : public ICameraDevice {
+
+    // Called by external camera provider HAL.
+    // Provider HAL must ensure the uniqueness of CameraDevice object per cameraId, or there could
+    // be multiple CameraDevice trying to access the same physical camera.  Also, provider will have
+    // to keep track of all CameraDevice objects in order to notify CameraDevice when the underlying
+    // camera is detached.
+    ExternalCameraDevice(const std::string& cameraId);
+    ~ExternalCameraDevice();
+
+    // Caller must use this method to check if CameraDevice ctor failed
+    bool isInitFailed();
+
+    /* Methods from ::android::hardware::camera::device::V3_2::ICameraDevice follow. */
+    // The following method can be called without opening the actual camera device
+    Return<void> getResourceCost(getResourceCost_cb _hidl_cb) override;
+
+    Return<void> getCameraCharacteristics(getCameraCharacteristics_cb _hidl_cb) override;
+
+    Return<Status> setTorchMode(TorchMode) override;
+
+    // Open the device HAL and also return a default capture session
+    Return<void> open(const sp<ICameraDeviceCallback>&, open_cb) override;
+
+    // Forward the dump call to the opened session, or do nothing
+    Return<void> dumpState(const ::android::hardware::hidl_handle&) override;
+    /* End of Methods from ::android::hardware::camera::device::V3_2::ICameraDevice */
+
+protected:
+    void getFrameRateList(int fd, SupportedV4L2Format* format);
+    // Init supported w/h/format/fps in mSupportedFormats. Caller still owns fd
+    void initSupportedFormatsLocked(int fd);
+
+    status_t initCameraCharacteristics();
+    // Init non-device dependent keys
+    status_t initDefaultCharsKeys(::android::hardware::camera::common::V1_0::helper::CameraMetadata*);
+    // Init camera control chars keys. Caller still owns fd
+    status_t initCameraControlsCharsKeys(int fd,
+            ::android::hardware::camera::common::V1_0::helper::CameraMetadata*);
+    // Init camera output configuration related keys.  Caller still owns fd
+    status_t initOutputCharsKeys(int fd,
+            ::android::hardware::camera::common::V1_0::helper::CameraMetadata*);
+
+    Mutex mLock;
+    bool mInitFailed = false;
+    std::string mCameraId;
+    std::vector<SupportedV4L2Format> mSupportedFormats;
+
+    wp<ExternalCameraDeviceSession> mSession = nullptr;
+
+    ::android::hardware::camera::common::V1_0::helper::CameraMetadata mCameraCharacteristics;
+};
+
+}  // namespace implementation
+}  // namespace V3_4
+}  // namespace device
+}  // namespace camera
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE_H
diff --git a/camera/provider/2.4/default/Android.bp b/camera/provider/2.4/default/Android.bp
index 99c3e92..1f46b89 100644
--- a/camera/provider/2.4/default/Android.bp
+++ b/camera/provider/2.4/default/Android.bp
@@ -3,7 +3,8 @@
     defaults: ["hidl_defaults"],
     proprietary: true,
     relative_install_path: "hw",
-    srcs: ["CameraProvider.cpp"],
+    srcs: ["CameraProvider.cpp",
+           "ExternalCameraProvider.cpp"],
     shared_libs: [
         "libhidlbase",
         "libhidltransport",
@@ -17,6 +18,7 @@
         "camera.device@3.2-impl",
         "camera.device@3.3-impl",
         "camera.device@3.4-impl",
+        "camera.device@3.4-external-impl",
         "android.hardware.camera.provider@2.4",
         "android.hardware.camera.common@1.0",
         "android.hardware.graphics.mapper@2.0",
@@ -28,6 +30,7 @@
     ],
     header_libs: [
         "camera.device@3.4-impl_headers",
+        "camera.device@3.4-external-impl_headers"
     ],
     static_libs: [
         "android.hardware.camera.common@1.0-helper",
@@ -56,3 +59,25 @@
         "android.hardware.camera.common@1.0",
     ],
 }
+
+cc_binary {
+    name: "android.hardware.camera.provider@2.4-external-service",
+    defaults: ["hidl_defaults"],
+    proprietary: true,
+    relative_install_path: "hw",
+    srcs: ["external-service.cpp"],
+    compile_multilib: "32",
+    init_rc: ["android.hardware.camera.provider@2.4-external-service.rc"],
+    shared_libs: [
+        "libhidlbase",
+        "libhidltransport",
+        "libbinder",
+        "liblog",
+        "libutils",
+        "android.hardware.camera.device@1.0",
+        "android.hardware.camera.device@3.2",
+        "android.hardware.camera.device@3.3",
+        "android.hardware.camera.provider@2.4",
+        "android.hardware.camera.common@1.0",
+    ],
+}
diff --git a/camera/provider/2.4/default/CameraProvider.cpp b/camera/provider/2.4/default/CameraProvider.cpp
index e9588a7..2fb920c 100644
--- a/camera/provider/2.4/default/CameraProvider.cpp
+++ b/camera/provider/2.4/default/CameraProvider.cpp
@@ -19,6 +19,7 @@
 #include <android/log.h>
 
 #include "CameraProvider.h"
+#include "ExternalCameraProvider.h"
 #include "CameraDevice_1_0.h"
 #include "CameraDevice_3_3.h"
 #include "CameraDevice_3_4.h"
@@ -36,6 +37,7 @@
 
 namespace {
 const char *kLegacyProviderName = "legacy/0";
+const char *kExternalProviderName = "external/0";
 // "device@<version>/legacy/<id>"
 const std::regex kDeviceNameRE("device@([0-9]+\\.[0-9]+)/legacy/(.+)");
 const char *kHAL3_2 = "3.2";
@@ -571,20 +573,24 @@
 }
 
 ICameraProvider* HIDL_FETCH_ICameraProvider(const char* name) {
-    if (strcmp(name, kLegacyProviderName) != 0) {
-        return nullptr;
+    if (strcmp(name, kLegacyProviderName) == 0) {
+        CameraProvider* provider = new CameraProvider();
+        if (provider == nullptr) {
+            ALOGE("%s: cannot allocate camera provider!", __FUNCTION__);
+            return nullptr;
+        }
+        if (provider->isInitFailed()) {
+            ALOGE("%s: camera provider init failed!", __FUNCTION__);
+            delete provider;
+            return nullptr;
+        }
+        return provider;
+    } else if (strcmp(name, kExternalProviderName) == 0) {
+        ExternalCameraProvider* provider = new ExternalCameraProvider();
+        return provider;
     }
-    CameraProvider* provider = new CameraProvider();
-    if (provider == nullptr) {
-        ALOGE("%s: cannot allocate camera provider!", __FUNCTION__);
-        return nullptr;
-    }
-    if (provider->isInitFailed()) {
-        ALOGE("%s: camera provider init failed!", __FUNCTION__);
-        delete provider;
-        return nullptr;
-    }
-    return provider;
+    ALOGE("%s: unknown instance name: %s", __FUNCTION__, name);
+    return nullptr;
 }
 
 } // namespace implementation
diff --git a/camera/provider/2.4/default/ExternalCameraProvider.cpp b/camera/provider/2.4/default/ExternalCameraProvider.cpp
new file mode 100644
index 0000000..bb5c336
--- /dev/null
+++ b/camera/provider/2.4/default/ExternalCameraProvider.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_TAG "CamPvdr@2.4-external"
+//#define LOG_NDEBUG 0
+#include <log/log.h>
+
+#include <regex>
+#include <sys/inotify.h>
+#include <errno.h>
+#include <linux/videodev2.h>
+#include "ExternalCameraProvider.h"
+#include "ExternalCameraDevice_3_4.h"
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace provider {
+namespace V2_4 {
+namespace implementation {
+
+namespace {
+// "device@<version>/external/<id>"
+const std::regex kDeviceNameRE("device@([0-9]+\\.[0-9]+)/external/(.+)");
+const int kMaxDevicePathLen = 256;
+const char* kDevicePath = "/dev/";
+
+bool matchDeviceName(const hidl_string& deviceName, std::string* deviceVersion,
+                     std::string* cameraId) {
+    std::string deviceNameStd(deviceName.c_str());
+    std::smatch sm;
+    if (std::regex_match(deviceNameStd, sm, kDeviceNameRE)) {
+        if (deviceVersion != nullptr) {
+            *deviceVersion = sm[1];
+        }
+        if (cameraId != nullptr) {
+            *cameraId = sm[2];
+        }
+        return true;
+    }
+    return false;
+}
+
+} // anonymous namespace
+
+ExternalCameraProvider::ExternalCameraProvider() : mHotPlugThread(this) {
+    mHotPlugThread.run("ExtCamHotPlug", PRIORITY_BACKGROUND);
+}
+
+ExternalCameraProvider::~ExternalCameraProvider() {
+    mHotPlugThread.requestExit();
+}
+
+
+Return<Status> ExternalCameraProvider::setCallback(
+        const sp<ICameraProviderCallback>& callback) {
+    Mutex::Autolock _l(mLock);
+    mCallbacks = callback;
+    return Status::OK;
+}
+
+Return<void> ExternalCameraProvider::getVendorTags(getVendorTags_cb _hidl_cb) {
+    // No vendor tag support for USB camera
+    hidl_vec<VendorTagSection> zeroSections;
+    _hidl_cb(Status::OK, zeroSections);
+    return Void();
+}
+
+Return<void> ExternalCameraProvider::getCameraIdList(getCameraIdList_cb _hidl_cb) {
+    std::vector<hidl_string> deviceNameList;
+    for (auto const& kvPair : mCameraStatusMap) {
+        if (kvPair.second == CameraDeviceStatus::PRESENT) {
+            deviceNameList.push_back(kvPair.first);
+        }
+    }
+    hidl_vec<hidl_string> hidlDeviceNameList(deviceNameList);
+    ALOGV("ExtCam: number of cameras is %zu", deviceNameList.size());
+    _hidl_cb(Status::OK, hidlDeviceNameList);
+    return Void();
+}
+
+Return<void> ExternalCameraProvider::isSetTorchModeSupported(
+        isSetTorchModeSupported_cb _hidl_cb) {
+    // No torch mode support for USB camera
+    _hidl_cb (Status::OK, false);
+    return Void();
+}
+
+Return<void> ExternalCameraProvider::getCameraDeviceInterface_V1_x(
+        const hidl_string&,
+        getCameraDeviceInterface_V1_x_cb _hidl_cb) {
+    // External Camera HAL does not support HAL1
+    _hidl_cb(Status::OPERATION_NOT_SUPPORTED, nullptr);
+    return Void();
+}
+
+Return<void> ExternalCameraProvider::getCameraDeviceInterface_V3_x(
+        const hidl_string& cameraDeviceName,
+        getCameraDeviceInterface_V3_x_cb _hidl_cb) {
+
+    std::string cameraId, deviceVersion;
+    bool match = matchDeviceName(cameraDeviceName, &deviceVersion, &cameraId);
+    if (!match) {
+        _hidl_cb(Status::ILLEGAL_ARGUMENT, nullptr);
+        return Void();
+    }
+
+    if (mCameraStatusMap.count(cameraDeviceName) == 0 ||
+            mCameraStatusMap[cameraDeviceName] != CameraDeviceStatus::PRESENT) {
+        _hidl_cb(Status::ILLEGAL_ARGUMENT, nullptr);
+        return Void();
+    }
+
+    ALOGV("Constructing v3.4 external camera device");
+    sp<device::V3_2::ICameraDevice> device;
+    sp<device::V3_4::implementation::ExternalCameraDevice> deviceImpl =
+            new device::V3_4::implementation::ExternalCameraDevice(
+                    cameraId);
+    if (deviceImpl == nullptr || deviceImpl->isInitFailed()) {
+        ALOGE("%s: camera device %s init failed!", __FUNCTION__, cameraId.c_str());
+        device = nullptr;
+        _hidl_cb(Status::INTERNAL_ERROR, nullptr);
+        return Void();
+    }
+    device = deviceImpl;
+
+    _hidl_cb (Status::OK, device);
+
+    return Void();
+}
+
+void ExternalCameraProvider::addExternalCamera(const char* devName) {
+    ALOGE("ExtCam: adding %s to External Camera HAL!", devName);
+    Mutex::Autolock _l(mLock);
+    std::string deviceName = std::string("device@3.4/external/") + devName;
+    mCameraStatusMap[deviceName] = CameraDeviceStatus::PRESENT;
+    if (mCallbacks != nullptr) {
+        mCallbacks->cameraDeviceStatusChange(deviceName, CameraDeviceStatus::PRESENT);
+    }
+}
+
+void ExternalCameraProvider::deviceAdded(const char* devName) {
+    int fd = -1;
+    if ((fd = ::open(devName, O_RDWR)) < 0) {
+        ALOGE("%s open v4l2 device %s failed:%s", __FUNCTION__, devName, strerror(errno));
+        return;
+    }
+
+    do {
+        struct v4l2_capability capability;
+        int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
+        if (ret < 0) {
+            ALOGE("%s v4l2 QUERYCAP %s failed", __FUNCTION__, devName);
+            break;
+        }
+
+        if (!(capability.device_caps & V4L2_CAP_VIDEO_CAPTURE)) {
+            ALOGW("%s device %s does not support VIDEO_CAPTURE", __FUNCTION__, devName);
+            break;
+        }
+
+        addExternalCamera(devName);
+    } while (0);
+
+    close(fd);
+    return;
+}
+
+void ExternalCameraProvider::deviceRemoved(const char* devName) {
+    Mutex::Autolock _l(mLock);
+    std::string deviceName = std::string("device@3.4/external/") + devName;
+    if (mCameraStatusMap.find(deviceName) != mCameraStatusMap.end()) {
+        mCameraStatusMap.erase(deviceName);
+        if (mCallbacks != nullptr) {
+            mCallbacks->cameraDeviceStatusChange(deviceName, CameraDeviceStatus::NOT_PRESENT);
+        }
+    } else {
+        ALOGE("%s: cannot find camera device %s", __FUNCTION__, devName);
+    }
+}
+
+ExternalCameraProvider::HotplugThread::HotplugThread(ExternalCameraProvider* parent) :
+        Thread(/*canCallJava*/false), mParent(parent) {}
+
+ExternalCameraProvider::HotplugThread::~HotplugThread() {}
+
+bool ExternalCameraProvider::HotplugThread::threadLoop() {
+    // Find existing /dev/video* devices
+    DIR* devdir = opendir(kDevicePath);
+    if(devdir == 0) {
+        ALOGE("%s: cannot open %s! Exiting threadloop", __FUNCTION__, kDevicePath);
+        return false;
+    }
+
+    struct dirent* de;
+    // This list is device dependent. TODO: b/72261897 allow setting it from setprop/device boot
+    std::string internalDevices = "0,1";
+    while ((de = readdir(devdir)) != 0) {
+        // Find external v4l devices that's existing before we start watching and add them
+        if (!strncmp("video", de->d_name, 5)) {
+            // TODO: This might reject some valid devices. Ex: internal is 33 and a device named 3
+            //       is added.
+            if (internalDevices.find(de->d_name + 5) == std::string::npos) {
+                ALOGV("Non-internal v4l device %s found", de->d_name);
+                char v4l2DevicePath[kMaxDevicePathLen];
+                snprintf(v4l2DevicePath, kMaxDevicePathLen,
+                        "%s%s", kDevicePath, de->d_name);
+                mParent->deviceAdded(v4l2DevicePath);
+            }
+        }
+    }
+    closedir(devdir);
+
+    // Watch new video devices
+    mINotifyFD = inotify_init();
+    if (mINotifyFD < 0) {
+        ALOGE("%s: inotify init failed! Exiting threadloop", __FUNCTION__);
+        return true;
+    }
+
+    mWd = inotify_add_watch(mINotifyFD, kDevicePath, IN_CREATE | IN_DELETE);
+    if (mWd < 0) {
+        ALOGE("%s: inotify add watch failed! Exiting threadloop", __FUNCTION__);
+        return true;
+    }
+
+    ALOGI("%s start monitoring new V4L2 devices", __FUNCTION__);
+
+    bool done = false;
+    char eventBuf[512];
+    while (!done) {
+        int offset = 0;
+        int ret = read(mINotifyFD, eventBuf, sizeof(eventBuf));
+        if (ret >= (int)sizeof(struct inotify_event)) {
+            while (offset < ret) {
+                struct inotify_event* event = (struct inotify_event*)&eventBuf[offset];
+                if (event->wd == mWd) {
+                    if (!strncmp("video", event->name, 5)) {
+                        char v4l2DevicePath[kMaxDevicePathLen];
+                        snprintf(v4l2DevicePath, kMaxDevicePathLen,
+                                "%s%s", kDevicePath, event->name);
+                        if (event->mask & IN_CREATE) {
+                            mParent->deviceAdded(v4l2DevicePath);
+                        }
+                        if (event->mask & IN_DELETE) {
+                            mParent->deviceRemoved(v4l2DevicePath);
+                        }
+                    }
+                }
+                offset += sizeof(struct inotify_event) + event->len;
+            }
+        }
+    }
+
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace V2_4
+}  // namespace provider
+}  // namespace camera
+}  // namespace hardware
+}  // namespace android
diff --git a/camera/provider/2.4/default/ExternalCameraProvider.h b/camera/provider/2.4/default/ExternalCameraProvider.h
new file mode 100644
index 0000000..c7ed99e
--- /dev/null
+++ b/camera/provider/2.4/default/ExternalCameraProvider.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef ANDROID_HARDWARE_CAMERA_PROVIDER_V2_4_EXTCAMERAPROVIDER_H
+#define ANDROID_HARDWARE_CAMERA_PROVIDER_V2_4_EXTCAMERAPROVIDER_H
+
+#include <unordered_map>
+#include "utils/Mutex.h"
+#include "utils/Thread.h"
+#include <android/hardware/camera/provider/2.4/ICameraProvider.h>
+#include <hidl/Status.h>
+#include <hidl/MQDescriptor.h>
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace provider {
+namespace V2_4 {
+namespace implementation {
+
+using ::android::hardware::camera::common::V1_0::CameraDeviceStatus;
+using ::android::hardware::camera::common::V1_0::Status;
+using ::android::hardware::camera::common::V1_0::VendorTagSection;
+using ::android::hardware::camera::provider::V2_4::ICameraProvider;
+using ::android::hardware::camera::provider::V2_4::ICameraProviderCallback;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+using ::android::sp;
+using ::android::Mutex;
+
+struct ExternalCameraProvider : public ICameraProvider {
+    ExternalCameraProvider();
+    ~ExternalCameraProvider();
+
+    // Methods from ::android::hardware::camera::provider::V2_4::ICameraProvider follow.
+    Return<Status> setCallback(const sp<ICameraProviderCallback>& callback) override;
+
+    Return<void> getVendorTags(getVendorTags_cb _hidl_cb) override;
+
+    Return<void> getCameraIdList(getCameraIdList_cb _hidl_cb) override;
+
+    Return<void> isSetTorchModeSupported(isSetTorchModeSupported_cb _hidl_cb) override;
+
+    Return<void> getCameraDeviceInterface_V1_x(
+            const hidl_string&,
+            getCameraDeviceInterface_V1_x_cb) override;
+    Return<void> getCameraDeviceInterface_V3_x(
+            const hidl_string&,
+            getCameraDeviceInterface_V3_x_cb) override;
+
+private:
+
+    void addExternalCamera(const char* devName);
+
+    void deviceAdded(const char* devName);
+
+    void deviceRemoved(const char* devName);
+
+    class HotplugThread : public android::Thread {
+    public:
+        HotplugThread(ExternalCameraProvider* parent);
+        ~HotplugThread();
+
+        virtual bool threadLoop() override;
+
+    private:
+        ExternalCameraProvider* mParent = nullptr;
+
+        int mINotifyFD = -1;
+        int mWd = -1;
+    } mHotPlugThread;
+
+    Mutex mLock;
+    sp<ICameraProviderCallback> mCallbacks = nullptr;
+    std::unordered_map<std::string, CameraDeviceStatus> mCameraStatusMap; // camera id -> status
+};
+
+
+
+}  // namespace implementation
+}  // namespace V2_4
+}  // namespace provider
+}  // namespace camera
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_CAMERA_PROVIDER_V2_4_EXTCAMERAPROVIDER_H
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
new file mode 100644
index 0000000..acdb200
--- /dev/null
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
@@ -0,0 +1,7 @@
+service vendor.camera-provider-2-4-ext /vendor/bin/hw/android.hardware.camera.provider@2.4-external-service
+    class hal
+    user cameraserver
+    group audio camera input drmrpc usb
+    ioprio rt 4
+    capabilities SYS_NICE
+    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
diff --git a/camera/provider/2.4/default/external-service.cpp b/camera/provider/2.4/default/external-service.cpp
new file mode 100644
index 0000000..f91aa59
--- /dev/null
+++ b/camera/provider/2.4/default/external-service.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#define LOG_TAG "android.hardware.camera.provider@2.4-external-service"
+
+#include <android/hardware/camera/provider/2.4/ICameraProvider.h>
+#include <hidl/LegacySupport.h>
+
+#include <binder/ProcessState.h>
+
+using android::hardware::camera::provider::V2_4::ICameraProvider;
+using android::hardware::defaultPassthroughServiceImplementation;
+
+int main()
+{
+    ALOGI("External camera provider service is starting.");
+    // The camera HAL may communicate to other vendor components via
+    // /dev/vndbinder
+    android::ProcessState::initWithDriver("/dev/vndbinder");
+    return defaultPassthroughServiceImplementation<ICameraProvider>("external/0", /*maxThreads*/ 6);
+}