External camera: add device config file

Also remove sizes cannot be cropped from maximal
size.

Bug: 72261897
Change-Id: Icb50cfa58a12e80be3cacc49569fac90be03c8e5
diff --git a/camera/device/3.4/default/Android.bp b/camera/device/3.4/default/Android.bp
index a936dae..272bf42 100644
--- a/camera/device/3.4/default/Android.bp
+++ b/camera/device/3.4/default/Android.bp
@@ -69,7 +69,8 @@
     vendor: true,
     srcs: [
         "ExternalCameraDevice.cpp",
-        "ExternalCameraDeviceSession.cpp"
+        "ExternalCameraDeviceSession.cpp",
+        "ExternalCameraUtils.cpp",
     ],
     shared_libs: [
         "libhidlbase",
@@ -91,6 +92,7 @@
         "libyuv",
         "libjpeg",
         "libexif",
+        "libtinyxml2"
     ],
     static_libs: [
         "android.hardware.camera.common@1.0-helper",
diff --git a/camera/device/3.4/default/ExternalCameraDevice.cpp b/camera/device/3.4/default/ExternalCameraDevice.cpp
index e6e0ae3..569acfd 100644
--- a/camera/device/3.4/default/ExternalCameraDevice.cpp
+++ b/camera/device/3.4/default/ExternalCameraDevice.cpp
@@ -15,9 +15,10 @@
  */
 
 #define LOG_TAG "ExtCamDev@3.4"
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 #include <log/log.h>
 
+#include <algorithm>
 #include <array>
 #include <linux/videodev2.h>
 #include "android-base/macros.h"
@@ -25,7 +26,6 @@
 #include "../../3.2/default/include/convert.h"
 #include "ExternalCameraDevice_3_4.h"
 
-
 namespace android {
 namespace hardware {
 namespace camera {
@@ -42,16 +42,12 @@
     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 = {5 * 1024 * 1024};  // 5MB
 } // anonymous namespace
 
 ExternalCameraDevice::ExternalCameraDevice(const std::string& cameraId) :
-        mCameraId(cameraId) {
+        mCameraId(cameraId),
+        mCfg(ExternalCameraDeviceConfig::loadFromCfg()) {
+
     status_t ret = initCameraCharacteristics();
     if (ret != OK) {
         ALOGE("%s: init camera characteristics failed: errorno %d", __FUNCTION__, ret);
@@ -133,7 +129,8 @@
     }
 
     session = new ExternalCameraDeviceSession(
-            callback, mSupportedFormats, mCameraCharacteristics, std::move(fd));
+            callback, mCfg, mSupportedFormats, mCroppingType,
+            mCameraCharacteristics, std::move(fd));
     if (session == nullptr) {
         ALOGE("%s: camera device session allocation failed", __FUNCTION__);
         mLock.unlock();
@@ -283,7 +280,7 @@
     UPDATE(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES, jpegAvailableThumbnailSizes,
            ARRAY_SIZE(jpegAvailableThumbnailSizes));
 
-    const int32_t jpegMaxSize = kMaxJpegSize;
+    const int32_t jpegMaxSize = mCfg.maxJpegBufSize;
     UPDATE(ANDROID_JPEG_MAX_SIZE, &jpegMaxSize, 1);
 
     const uint8_t jpegQuality = 90;
@@ -692,7 +689,7 @@
 #undef UPDATE
 
 void ExternalCameraDevice::getFrameRateList(
-        int fd, SupportedV4L2Format* format) {
+        int fd, float fpsUpperBound, SupportedV4L2Format* format) {
     format->frameRates.clear();
 
     v4l2_frmivalenum frameInterval {
@@ -709,7 +706,10 @@
             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",
+                if (framerate > fpsUpperBound) {
+                    continue;
+                }
+                ALOGI("index:%d, format:%c%c%c%c, w %d, h %d, framerate %f",
                     frameInterval.index,
                     frameInterval.pixel_format & 0xFF,
                     (frameInterval.pixel_format >> 8) & 0xFF,
@@ -732,6 +732,63 @@
     }
 }
 
+CroppingType ExternalCameraDevice::initCroppingType(
+        /*inout*/std::vector<SupportedV4L2Format>* pSortedFmts) {
+    std::vector<SupportedV4L2Format>& sortedFmts = *pSortedFmts;
+    const auto& maxSize = sortedFmts[sortedFmts.size() - 1];
+    float maxSizeAr = ASPECT_RATIO(maxSize);
+    float minAr = kMaxAspectRatio;
+    float maxAr = kMinAspectRatio;
+    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;
+        }
+
+        // Remove formats that has aspect ratio not croppable from largest size
+        std::vector<SupportedV4L2Format> out;
+        for (const auto& fmt : sortedFmts) {
+            float ar = ASPECT_RATIO(fmt);
+            if (isAspectRatioClose(ar, maxSizeAr)) {
+                out.push_back(fmt);
+            } else if (ct == HORIZONTAL && ar < maxSizeAr) {
+                out.push_back(fmt);
+            } else if (ct == VERTICAL && ar > maxSizeAr) {
+                out.push_back(fmt);
+            } else {
+                ALOGD("%s: size (%d,%d) is removed due to unable to crop %s from (%d,%d)",
+                    __FUNCTION__, fmt.width, fmt.height,
+                    ct == VERTICAL ? "vertically" : "horizontally",
+                    maxSize.width, maxSize.height);
+            }
+        }
+        sortedFmts = out;
+    }
+    ALOGI("%s: camera croppingType is %s", __FUNCTION__,
+            ct == VERTICAL ? "VERTICAL" : "HORIZONTAL");
+    return ct;
+}
+
 void ExternalCameraDevice::initSupportedFormatsLocked(int fd) {
     struct v4l2_fmtdesc fmtdesc {
         .index = 0,
@@ -755,7 +812,7 @@
                 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,
+                        ALOGV("index:%d, format:%c%c%c%c, w %d, h %d", frameSize.index,
                             fmtdesc.pixelformat & 0xFF,
                             (fmtdesc.pixelformat >> 8) & 0xFF,
                             (fmtdesc.pixelformat >> 16) & 0xFF,
@@ -771,7 +828,20 @@
                             .height = frameSize.discrete.height,
                             .fourcc = fmtdesc.pixelformat
                         };
-                        getFrameRateList(fd, &format);
+
+                        float fpsUpperBound = -1.0;
+                        for (const auto& limit : mCfg.fpsLimits) {
+                            if (format.width <= limit.size.width &&
+                                    format.height <= limit.size.height) {
+                                fpsUpperBound = limit.fpsUpperBound;
+                                break;
+                            }
+                        }
+                        if (fpsUpperBound < 0.f) {
+                            continue;
+                        }
+
+                        getFrameRateList(fd, fpsUpperBound, &format);
                         if (!format.frameRates.empty()) {
                             mSupportedFormats.push_back(format);
                         }
@@ -781,6 +851,16 @@
         }
         fmtdesc.index++;
     }
+
+    std::sort(mSupportedFormats.begin(), mSupportedFormats.end(),
+            [](const SupportedV4L2Format& a, const SupportedV4L2Format& b) -> bool {
+                if (a.width == b.width) {
+                    return a.height < b.height;
+                }
+                return a.width < b.width;
+            });
+
+    mCroppingType = initCroppingType(&mSupportedFormats);
 }
 
 }  // namespace implementation
diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
index ff55489..a46bcac 100644
--- a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
+++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
@@ -21,9 +21,7 @@
 #include "ExternalCameraDeviceSession.h"
 
 #include "android-base/macros.h"
-#include "algorithm"
 #include <utils/Timers.h>
-#include <cmath>
 #include <linux/videodev2.h>
 #include <sync/sync.h>
 
@@ -40,98 +38,40 @@
 namespace V3_4 {
 namespace implementation {
 
+namespace {
 // 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
+static constexpr size_t kMetadataMsgQueueSize = 1 << 18 /* 256kB */;
+
 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;
+} // Anonymous namespace
 
+// Static instances
+const int ExternalCameraDeviceSession::kMaxProcessedStream;
+const int ExternalCameraDeviceSession::kMaxStallStream;
 HandleImporter ExternalCameraDeviceSession::sHandleImporter;
 
-bool isAspectRatioClose(float ar1, float ar2) {
-    const float kAspectRatioMatchThres = 0.025f; // This threshold is good enough to distinguish
-                                                // 4:3/16:9/20:9
-                                                // 1.33 / 1.78 / 2
-    return (std::abs(ar1 - ar2) < kAspectRatioMatchThres);
-}
-
 ExternalCameraDeviceSession::ExternalCameraDeviceSession(
         const sp<ICameraDeviceCallback>& callback,
-        const std::vector<SupportedV4L2Format>& supportedFormats,
+        const ExternalCameraDeviceConfig& cfg,
+        const std::vector<SupportedV4L2Format>& sortedFormats,
+        const CroppingType& croppingType,
         const common::V1_0::helper::CameraMetadata& chars,
         unique_fd v4l2Fd) :
         mCallback(callback),
+        mCfg(cfg),
         mCameraCharacteristics(chars),
+        mSupportedFormats(sortedFormats),
+        mCroppingType(croppingType),
         mV4l2Fd(std::move(v4l2Fd)),
-        mSupportedFormats(sortFormats(supportedFormats)),
-        mCroppingType(initCroppingType(mSupportedFormats)),
         mOutputThread(new OutputThread(this, mCroppingType)),
         mMaxThumbResolution(getMaxThumbResolution()),
         mMaxJpegResolution(getMaxJpegResolution()) {
     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 = kMaxAspectRatio;
-    float maxAr = kMinAspectRatio;
-    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());
@@ -1995,8 +1935,8 @@
         return BAD_VALUE;
     }
 
-    uint32_t v4lBufferCount = (v4l2Fmt.width <= kMaxVideoSize.width &&
-            v4l2Fmt.height <= kMaxVideoSize.height) ? kNumVideoBuffers : kNumStillBuffers;
+    uint32_t v4lBufferCount = (fps >= kDefaultFps) ?
+            mCfg.numVideoBuffers : mCfg.numStillBuffers;
     // VIDIOC_REQBUFS: create buffers
     v4l2_requestbuffers req_buffers{};
     req_buffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -2537,113 +2477,6 @@
 #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
diff --git a/camera/device/3.4/default/ExternalCameraUtils.cpp b/camera/device/3.4/default/ExternalCameraUtils.cpp
new file mode 100644
index 0000000..124f0bd
--- /dev/null
+++ b/camera/device/3.4/default/ExternalCameraUtils.cpp
@@ -0,0 +1,262 @@
+/*
+ * 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 "ExtCamUtils@3.4"
+//#define LOG_NDEBUG 0
+#include <log/log.h>
+
+#include <cmath>
+#include <sys/mman.h>
+#include <linux/videodev2.h>
+#include "ExternalCameraUtils.h"
+#include "tinyxml2.h" // XML parsing
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace device {
+namespace V3_4 {
+namespace implementation {
+
+namespace {
+    const int  kDefaultJpegBufSize = 5 << 20; // 5MB
+    const int  kDefaultNumVideoBuffer = 4;
+    const int  kDefaultNumStillBuffer = 2;
+} // anonymous namespace
+
+const char* ExternalCameraDeviceConfig::kDefaultCfgPath = "/vendor/etc/external_camera_config.xml";
+
+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;
+    }
+
+    std::lock_guard<std::mutex> lk(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() {
+    std::lock_guard<std::mutex> lk(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) {
+    std::lock_guard<std::mutex> lk(mLock);
+    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;
+    }
+
+    std::lock_guard<std::mutex> lk(mLock);
+    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;
+}
+
+
+ExternalCameraDeviceConfig ExternalCameraDeviceConfig::loadFromCfg(const char* cfgPath) {
+    using namespace tinyxml2;
+    ExternalCameraDeviceConfig ret;
+
+    XMLDocument configXml;
+    XMLError err = configXml.LoadFile(cfgPath);
+    if (err != XML_SUCCESS) {
+        ALOGE("%s: Unable to load external camera config file '%s'. Error: %s",
+                __FUNCTION__, cfgPath, XMLDocument::ErrorIDToName(err));
+        return ret;
+    } else {
+        ALOGI("%s: load external camera config succeed!", __FUNCTION__);
+    }
+
+    XMLElement *extCam = configXml.FirstChildElement("ExternalCamera");
+    if (extCam == nullptr) {
+        ALOGI("%s: no external camera config specified", __FUNCTION__);
+        return ret;
+    }
+
+    XMLElement *deviceCfg = extCam->FirstChildElement("Device");
+    if (deviceCfg == nullptr) {
+        ALOGI("%s: no external camera device config specified", __FUNCTION__);
+        return ret;
+    }
+
+    XMLElement *jpegBufSz = deviceCfg->FirstChildElement("MaxJpegBufferSize");
+    if (jpegBufSz == nullptr) {
+        ALOGI("%s: no max jpeg buffer size specified", __FUNCTION__);
+    } else {
+        ret.maxJpegBufSize = jpegBufSz->UnsignedAttribute("bytes", /*Default*/kDefaultJpegBufSize);
+    }
+
+    XMLElement *numVideoBuf = deviceCfg->FirstChildElement("NumVideoBuffers");
+    if (numVideoBuf == nullptr) {
+        ALOGI("%s: no num video buffers specified", __FUNCTION__);
+    } else {
+        ret.numVideoBuffers =
+                numVideoBuf->UnsignedAttribute("count", /*Default*/kDefaultNumVideoBuffer);
+    }
+
+    XMLElement *numStillBuf = deviceCfg->FirstChildElement("NumStillBuffers");
+    if (numStillBuf == nullptr) {
+        ALOGI("%s: no num still buffers specified", __FUNCTION__);
+    } else {
+        ret.numStillBuffers =
+                numStillBuf->UnsignedAttribute("count", /*Default*/kDefaultNumStillBuffer);
+    }
+
+    XMLElement *fpsList = deviceCfg->FirstChildElement("FpsList");
+    if (fpsList == nullptr) {
+        ALOGI("%s: no fps list specified", __FUNCTION__);
+    } else {
+        std::vector<FpsLimitation> limits;
+        XMLElement *row = fpsList->FirstChildElement("Limit");
+        while (row != nullptr) {
+            FpsLimitation prevLimit {{0, 0}, 1000.0};
+            FpsLimitation limit;
+            limit.size = {
+                row->UnsignedAttribute("width", /*Default*/0),
+                row->UnsignedAttribute("height", /*Default*/0)};
+            limit.fpsUpperBound = row->FloatAttribute("fpsBound", /*Default*/1000.0);
+            if (limit.size.width <= prevLimit.size.width ||
+                    limit.size.height <= prevLimit.size.height ||
+                    limit.fpsUpperBound >= prevLimit.fpsUpperBound) {
+                ALOGE("%s: FPS limit list must have increasing size and decreasing fps!"
+                        " Prev %dx%d@%f, Current %dx%d@%f", __FUNCTION__,
+                        prevLimit.size.width, prevLimit.size.height, prevLimit.fpsUpperBound,
+                        limit.size.width, limit.size.height, limit.fpsUpperBound);
+                return ret;
+            }
+            limits.push_back(limit);
+            row = row->NextSiblingElement("Limit");
+        }
+        ret.fpsLimits = limits;
+    }
+
+    ALOGI("%s: external camera cfd loaded: maxJpgBufSize %d,"
+            " num video buffers %d, num still buffers %d",
+            __FUNCTION__, ret.maxJpegBufSize,
+            ret.numVideoBuffers, ret.numStillBuffers);
+    for (const auto& limit : ret.fpsLimits) {
+        ALOGI("%s: fpsLimitList: %dx%d@%f", __FUNCTION__,
+                limit.size.width, limit.size.height, limit.fpsUpperBound);
+    }
+    return ret;
+}
+
+ExternalCameraDeviceConfig::ExternalCameraDeviceConfig() :
+        maxJpegBufSize(kDefaultJpegBufSize),
+        numVideoBuffers(kDefaultNumVideoBuffer),
+        numStillBuffers(kDefaultNumStillBuffer) {
+    fpsLimits.push_back({/*Size*/{ 640,  480}, /*FPS upper bound*/30.0});
+    fpsLimits.push_back({/*Size*/{1280,  720}, /*FPS upper bound*/15.0});
+    fpsLimits.push_back({/*Size*/{1920, 1080}, /*FPS upper bound*/10.0});
+    fpsLimits.push_back({/*Size*/{4096, 3072}, /*FPS upper bound*/5.0});
+}
+
+bool isAspectRatioClose(float ar1, float ar2) {
+    const float kAspectRatioMatchThres = 0.025f; // This threshold is good enough to distinguish
+                                                // 4:3/16:9/20:9
+                                                // 1.33 / 1.78 / 2
+    return (std::abs(ar1 - ar2) < kAspectRatioMatchThres);
+}
+
+}  // 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
index 5856306..e1e1198 100644
--- 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
@@ -35,6 +35,7 @@
 #include "utils/Mutex.h"
 #include "utils/Thread.h"
 #include "android-base/unique_fd.h"
+#include "ExternalCameraUtils.h"
 
 namespace android {
 namespace hardware {
@@ -80,79 +81,12 @@
 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 ExternalCameraDeviceConfig& cfg,
+            const std::vector<SupportedV4L2Format>& sortedFormats,
+            const CroppingType& croppingType,
             const common::V1_0::helper::CameraMetadata& chars,
             unique_fd v4l2Fd);
     virtual ~ExternalCameraDeviceSession();
@@ -238,9 +172,6 @@
     Status constructDefaultRequestSettingsRaw(RequestTemplate type,
             V3_2::CameraMetadata *outMetadata);
 
-    static std::vector<SupportedV4L2Format> sortFormats(
-            const std::vector<SupportedV4L2Format>&);
-    static CroppingType initCroppingType(const std::vector<SupportedV4L2Format>&);
     bool initialize();
     Status initStatus() const;
     status_t initDefaultRequests();
@@ -346,7 +277,10 @@
 
     mutable Mutex mLock; // Protect all private members except otherwise noted
     const sp<ICameraDeviceCallback> mCallback;
+    const ExternalCameraDeviceConfig mCfg;
     const common::V1_0::helper::CameraMetadata mCameraCharacteristics;
+    const std::vector<SupportedV4L2Format> mSupportedFormats;
+    const CroppingType mCroppingType;
     unique_fd mV4l2Fd;
     // device is closed either
     //    - closed by user
@@ -366,8 +300,6 @@
     std::condition_variable mV4L2BufferReturned;
     size_t mNumDequeuedV4l2Buffers = 0;
 
-    const std::vector<SupportedV4L2Format> mSupportedFormats;
-    const CroppingType mCroppingType;
     sp<OutputThread> mOutputThread;
 
     // Stream ID -> Camera3Stream cache
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
index 606375e..ef4b41c 100644
--- 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
@@ -76,7 +76,7 @@
     /* End of Methods from ::android::hardware::camera::device::V3_2::ICameraDevice */
 
 protected:
-    void getFrameRateList(int fd, SupportedV4L2Format* format);
+    void getFrameRateList(int fd, float fpsUpperBound, SupportedV4L2Format* format);
     // Init supported w/h/format/fps in mSupportedFormats. Caller still owns fd
     void initSupportedFormatsLocked(int fd);
 
@@ -90,10 +90,14 @@
     status_t initOutputCharsKeys(int fd,
             ::android::hardware::camera::common::V1_0::helper::CameraMetadata*);
 
+    static CroppingType initCroppingType(/*inout*/std::vector<SupportedV4L2Format>*);
+
     Mutex mLock;
     bool mInitFailed = false;
     std::string mCameraId;
+    const ExternalCameraDeviceConfig mCfg;
     std::vector<SupportedV4L2Format> mSupportedFormats;
+    CroppingType mCroppingType;
 
     wp<ExternalCameraDeviceSession> mSession = nullptr;
 
diff --git a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h
new file mode 100644
index 0000000..562dacf
--- /dev/null
+++ b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h
@@ -0,0 +1,145 @@
+/*
+ * 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_EXTCAMUTIL_H
+#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMUTIL_H
+
+#include <inttypes.h>
+#include "utils/LightRefBase.h"
+#include <mutex>
+#include <vector>
+#include <android/hardware/graphics/mapper/2.0/IMapper.h>
+
+using android::hardware::graphics::mapper::V2_0::IMapper;
+using android::hardware::graphics::mapper::V2_0::YCbCrLayout;
+
+namespace android {
+namespace hardware {
+namespace camera {
+namespace device {
+namespace V3_4 {
+namespace implementation {
+
+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:
+    std::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:
+    std::mutex mLock;
+    std::vector<uint8_t> mData;
+};
+
+enum CroppingType {
+    HORIZONTAL = 0,
+    VERTICAL = 1
+};
+
+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;
+    }
+};
+
+struct ExternalCameraDeviceConfig {
+    static const char* kDefaultCfgPath;
+    static ExternalCameraDeviceConfig loadFromCfg(const char* cfgPath = kDefaultCfgPath);
+
+    // Maximal size of a JPEG buffer, in bytes
+    uint32_t maxJpegBufSize;
+
+    // Maximum Size that can sustain 30fps streaming
+    Size maxVideoSize;
+
+    // Size of v4l2 buffer queue when streaming <= kMaxVideoSize
+    uint32_t numVideoBuffers;
+
+    // Size of v4l2 buffer queue when streaming > kMaxVideoSize
+    uint32_t numStillBuffers;
+
+    struct FpsLimitation {
+        Size size;
+        float fpsUpperBound;
+    };
+    std::vector<FpsLimitation> fpsLimits;
+
+private:
+    ExternalCameraDeviceConfig();
+};
+
+// 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;
+
+bool isAspectRatioClose(float ar1, float ar2);
+
+}  // namespace implementation
+}  // namespace V3_4
+}  // namespace device
+}  // namespace camera
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMUTIL_H
diff --git a/camera/provider/2.4/default/Android.bp b/camera/provider/2.4/default/Android.bp
index 1f46b89..31c5fdd 100644
--- a/camera/provider/2.4/default/Android.bp
+++ b/camera/provider/2.4/default/Android.bp
@@ -27,6 +27,7 @@
         "liblog",
         "libhardware",
         "libcamera_metadata",
+        "libtinyxml2"
     ],
     header_libs: [
         "camera.device@3.4-impl_headers",
diff --git a/camera/provider/2.4/default/ExternalCameraProvider.cpp b/camera/provider/2.4/default/ExternalCameraProvider.cpp
index bb5c336..7657fa3 100644
--- a/camera/provider/2.4/default/ExternalCameraProvider.cpp
+++ b/camera/provider/2.4/default/ExternalCameraProvider.cpp
@@ -24,6 +24,7 @@
 #include <linux/videodev2.h>
 #include "ExternalCameraProvider.h"
 #include "ExternalCameraDevice_3_4.h"
+#include "tinyxml2.h" // XML parsing
 
 namespace android {
 namespace hardware {
@@ -56,7 +57,8 @@
 
 } // anonymous namespace
 
-ExternalCameraProvider::ExternalCameraProvider() : mHotPlugThread(this) {
+ExternalCameraProvider::ExternalCameraProvider() :
+        mHotPlugThread(this) {
     mHotPlugThread.run("ExtCamHotPlug", PRIORITY_BACKGROUND);
 }
 
@@ -143,7 +145,7 @@
 }
 
 void ExternalCameraProvider::addExternalCamera(const char* devName) {
-    ALOGE("ExtCam: adding %s to External Camera HAL!", devName);
+    ALOGI("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;
@@ -192,8 +194,59 @@
     }
 }
 
+std::unordered_set<std::string>
+ExternalCameraProvider::HotplugThread::initInternalDevices() {
+    std::unordered_set<std::string> ret;
+    using device::V3_4::implementation::ExternalCameraDeviceConfig;
+    const char* configPath = ExternalCameraDeviceConfig::kDefaultCfgPath;
+
+    using namespace tinyxml2;
+
+    XMLDocument configXml;
+    XMLError err = configXml.LoadFile(configPath);
+    if (err != XML_SUCCESS) {
+        ALOGE("%s: Unable to load external camera config file '%s'. Error: %s",
+                __FUNCTION__, configPath, XMLDocument::ErrorIDToName(err));
+    } else {
+        ALOGI("%s: load external camera config succeed!", __FUNCTION__);
+    }
+
+    XMLElement *extCam = configXml.FirstChildElement("ExternalCamera");
+    if (extCam == nullptr) {
+        ALOGI("%s: no external camera config specified", __FUNCTION__);
+        return ret;
+    }
+
+    XMLElement *providerCfg = extCam->FirstChildElement("Provider");
+    if (providerCfg == nullptr) {
+        ALOGI("%s: no external camera provider config specified", __FUNCTION__);
+        return ret;
+    }
+
+    XMLElement *ignore = providerCfg->FirstChildElement("ignore");
+    if (ignore == nullptr) {
+        ALOGI("%s: no internal ignored device specified", __FUNCTION__);
+        return ret;
+    }
+
+    XMLElement *id = ignore->FirstChildElement("id");
+    while (id != nullptr) {
+        const char* text = id->GetText();
+        if (text != nullptr) {
+            ret.insert(text);
+            ALOGI("%s: device %s will be ignored by external camera provider",
+                    __FUNCTION__, text);
+        }
+        id = id->NextSiblingElement("id");
+    }
+
+    return ret;
+}
+
 ExternalCameraProvider::HotplugThread::HotplugThread(ExternalCameraProvider* parent) :
-        Thread(/*canCallJava*/false), mParent(parent) {}
+        Thread(/*canCallJava*/false),
+        mParent(parent),
+        mInternalDevices(initInternalDevices()) {}
 
 ExternalCameraProvider::HotplugThread::~HotplugThread() {}
 
@@ -206,14 +259,13 @@
     }
 
     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) {
+            std::string deviceId(de->d_name + 5);
+            if (mInternalDevices.count(deviceId) == 0) {
                 ALOGV("Non-internal v4l device %s found", de->d_name);
                 char v4l2DevicePath[kMaxDevicePathLen];
                 snprintf(v4l2DevicePath, kMaxDevicePathLen,
@@ -249,14 +301,17 @@
                 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);
+                        std::string deviceId(event->name + 5);
+                        if (mInternalDevices.count(deviceId) == 0) {
+                            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);
+                            }
                         }
                     }
                 }
diff --git a/camera/provider/2.4/default/ExternalCameraProvider.h b/camera/provider/2.4/default/ExternalCameraProvider.h
index c7ed99e..64a8878 100644
--- a/camera/provider/2.4/default/ExternalCameraProvider.h
+++ b/camera/provider/2.4/default/ExternalCameraProvider.h
@@ -17,7 +17,9 @@
 #ifndef ANDROID_HARDWARE_CAMERA_PROVIDER_V2_4_EXTCAMERAPROVIDER_H
 #define ANDROID_HARDWARE_CAMERA_PROVIDER_V2_4_EXTCAMERAPROVIDER_H
 
+#include <string>
 #include <unordered_map>
+#include <unordered_set>
 #include "utils/Mutex.h"
 #include "utils/Thread.h"
 #include <android/hardware/camera/provider/2.4/ICameraProvider.h>
@@ -79,15 +81,19 @@
         virtual bool threadLoop() override;
 
     private:
+        static std::unordered_set<std::string> initInternalDevices();
+
         ExternalCameraProvider* mParent = nullptr;
+        const std::unordered_set<std::string> mInternalDevices;
 
         int mINotifyFD = -1;
         int mWd = -1;
-    } mHotPlugThread;
+    };
 
     Mutex mLock;
     sp<ICameraProviderCallback> mCallbacks = nullptr;
     std::unordered_map<std::string, CameraDeviceStatus> mCameraStatusMap; // camera id -> status
+    HotplugThread mHotPlugThread;
 };