Camera: Hotplug support as if it was sysfs

Like sysfs, echo 1 or 0 into /data/misc/media/emulator.camera.hotplug.#
where # is the camera ID. This will toggle plugged/not plugged in state

Change-Id: I3bcc770e036648e53458b1f72080bd24fdab206b
diff --git a/camera/Android.mk b/camera/Android.mk
index f3201ab..0f1685c 100755
--- a/camera/Android.mk
+++ b/camera/Android.mk
@@ -53,6 +53,7 @@
 	QemuClient.cpp \
 	JpegCompressor.cpp \
     EmulatedCamera2.cpp \
+    EmulatedCameraHotplugThread.cpp \
 	EmulatedFakeCamera2.cpp \
 	EmulatedQemuCamera2.cpp \
 	fake-pipeline2/Scene.cpp \
diff --git a/camera/EmulatedBaseCamera.cpp b/camera/EmulatedBaseCamera.cpp
index 19d398e..5fe7d73 100644
--- a/camera/EmulatedBaseCamera.cpp
+++ b/camera/EmulatedBaseCamera.cpp
@@ -69,5 +69,21 @@
     return NO_ERROR;
 }
 
+status_t EmulatedBaseCamera::plugCamera() {
+    ALOGE("%s: not supported", __FUNCTION__);
+    return INVALID_OPERATION;
+}
+
+status_t EmulatedBaseCamera::unplugCamera() {
+    ALOGE("%s: not supported", __FUNCTION__);
+    return INVALID_OPERATION;
+}
+
+camera_device_status_t EmulatedBaseCamera::getHotplugStatus() {
+    return CAMERA_DEVICE_STATUS_PRESENT;
+}
+
+
+
 
 } /* namespace android */
diff --git a/camera/EmulatedBaseCamera.h b/camera/EmulatedBaseCamera.h
index 5888ca0..3b8a6a0 100644
--- a/camera/EmulatedBaseCamera.h
+++ b/camera/EmulatedBaseCamera.h
@@ -64,6 +64,21 @@
      */
     virtual status_t connectCamera(hw_device_t** device) = 0;
 
+
+    /* Plug the connection for the emulated camera. Until it's plugged in
+     * calls to connectCamera should fail with -ENODEV.
+     */
+    virtual status_t plugCamera();
+
+    /* Unplug the connection from underneath the emulated camera.
+     * This is similar to closing the camera, except that
+     * all function calls into the camera device will return
+     * -EPIPE errors until the camera is reopened.
+     */
+    virtual status_t unplugCamera();
+
+    virtual camera_device_status_t getHotplugStatus();
+
     /* Closes connection to the emulated camera.
      * This method is called in response to camera_device::close callback.
      * NOTE: When this method is called the object is locked.
@@ -88,10 +103,11 @@
      * mCameraDeviceVersion is >= HARDWARE_DEVICE_API_VERSION(2,0)  */
     camera_metadata_t *mCameraInfo;
 
-  private:
     /* Zero-based ID assigned to this camera. */
     int mCameraID;
 
+  private:
+
     /* Version of the camera device HAL implemented by this camera */
     int mCameraDeviceVersion;
 };
diff --git a/camera/EmulatedCamera2.cpp b/camera/EmulatedCamera2.cpp
index bbc1740..ea7424b 100644
--- a/camera/EmulatedCamera2.cpp
+++ b/camera/EmulatedCamera2.cpp
@@ -60,6 +60,8 @@
     mVendorTagOps.get_camera_vendor_tag_type =
             EmulatedCamera2::get_camera_vendor_tag_type;
     mVendorTagOps.parent = this;
+
+    mStatusPresent = true;
 }
 
 /* Destructs EmulatedCamera2 instance. */
diff --git a/camera/EmulatedCamera2.h b/camera/EmulatedCamera2.h
index 755ed0e..9f5f67b 100644
--- a/camera/EmulatedCamera2.h
+++ b/camera/EmulatedCamera2.h
@@ -247,6 +247,8 @@
     /** Mutex for calls through camera2 device interface */
     Mutex mMutex;
 
+    bool mStatusPresent;
+
     const camera2_request_queue_src_ops *mRequestQueueSrc;
     const camera2_frame_queue_dst_ops *mFrameQueueDst;
 
diff --git a/camera/EmulatedCameraFactory.cpp b/camera/EmulatedCameraFactory.cpp
index 0964335..d36a215 100755
--- a/camera/EmulatedCameraFactory.cpp
+++ b/camera/EmulatedCameraFactory.cpp
@@ -26,6 +26,7 @@
 #include "EmulatedQemuCamera.h"
 #include "EmulatedFakeCamera.h"
 #include "EmulatedFakeCamera2.h"
+#include "EmulatedCameraHotplugThread.h"
 #include "EmulatedCameraFactory.h"
 
 extern camera_module_t HAL_MODULE_INFO_SYM;
@@ -42,7 +43,8 @@
           mEmulatedCameras(NULL),
           mEmulatedCameraNum(0),
           mFakeCameraNum(0),
-          mConstructedOK(false)
+          mConstructedOK(false),
+          mCallbacks(NULL)
 {
     status_t res;
     /* Connect to the factory service in the emulator, and create Qemu cameras. */
@@ -159,6 +161,17 @@
     ALOGV("%d cameras are being emulated. %d of them are fake cameras.",
           mEmulatedCameraNum, mFakeCameraNum);
 
+    /* Create hotplug thread */
+    {
+        Vector<int> cameraIdVector;
+        for (int i = 0; i < mEmulatedCameraNum; ++i) {
+            cameraIdVector.push_back(i);
+        }
+        mHotplugThread = new EmulatedCameraHotplugThread(&cameraIdVector[0],
+                                                         mEmulatedCameraNum);
+        mHotplugThread->run();
+    }
+
     mConstructedOK = true;
 }
 
@@ -172,6 +185,11 @@
         }
         delete[] mEmulatedCameras;
     }
+
+    if (mHotplugThread != NULL) {
+        mHotplugThread->requestExit();
+        mHotplugThread->join();
+    }
 }
 
 /****************************************************************************
@@ -220,6 +238,16 @@
     return mEmulatedCameras[camera_id]->getCameraInfo(info);
 }
 
+int EmulatedCameraFactory::setCallbacks(
+        const camera_module_callbacks_t *callbacks)
+{
+    ALOGV("%s: callbacks = %p", __FUNCTION__, callbacks);
+
+    mCallbacks = callbacks;
+
+    return OK;
+}
+
 /****************************************************************************
  * Camera HAL API callbacks.
  ***************************************************************************/
@@ -257,6 +285,12 @@
     return gEmulatedCameraFactory.getCameraInfo(camera_id, info);
 }
 
+int EmulatedCameraFactory::set_callbacks(
+        const camera_module_callbacks_t *callbacks)
+{
+    return gEmulatedCameraFactory.setCallbacks(callbacks);
+}
+
 /********************************************************************************
  * Internal API
  *******************************************************************************/
@@ -437,6 +471,37 @@
     return 1;
 }
 
+void EmulatedCameraFactory::onStatusChanged(int cameraId, int newStatus) {
+
+    EmulatedBaseCamera *cam = mEmulatedCameras[cameraId];
+    if (!cam) {
+        ALOGE("%s: Invalid camera ID %d", __FUNCTION__, cameraId);
+        return;
+    }
+
+    /**
+     * (Order is important)
+     * Send the callback first to framework, THEN close the camera.
+     */
+
+    if (newStatus == cam->getHotplugStatus()) {
+        ALOGW("%s: Ignoring transition to the same status", __FUNCTION__);
+        return;
+    }
+
+    const camera_module_callbacks_t* cb = mCallbacks;
+    if (cb != NULL && cb->camera_device_status_change != NULL) {
+        cb->camera_device_status_change(cb, cameraId, newStatus);
+    }
+
+    if (newStatus == CAMERA_DEVICE_STATUS_NOT_PRESENT) {
+        cam->unplugCamera();
+    } else if (newStatus == CAMERA_DEVICE_STATUS_PRESENT) {
+        cam->plugCamera();
+    }
+
+}
+
 /********************************************************************************
  * Initializer for the static member structure.
  *******************************************************************************/
diff --git a/camera/EmulatedCameraFactory.h b/camera/EmulatedCameraFactory.h
index 123e735..470f5ea 100755
--- a/camera/EmulatedCameraFactory.h
+++ b/camera/EmulatedCameraFactory.h
@@ -17,11 +17,14 @@
 #ifndef HW_EMULATOR_CAMERA_EMULATED_CAMERA_FACTORY_H
 #define HW_EMULATOR_CAMERA_EMULATED_CAMERA_FACTORY_H
 
+#include <utils/RefBase.h>
 #include "EmulatedBaseCamera.h"
 #include "QemuClient.h"
 
 namespace android {
 
+struct EmulatedCameraHotplugThread;
+
 /*
  * Contains declaration of a class EmulatedCameraFactory that manages cameras
  * available for the emulation. A global instance of this class is statically
@@ -72,6 +75,11 @@
      */
     int getCameraInfo(int camera_id, struct camera_info *info);
 
+    /* Sets emulated camera callbacks.
+     * This method is called in response to camera_module_t::set_callbacks callback.
+     */
+    int setCallbacks(const camera_module_callbacks_t *callbacks);
+
     /****************************************************************************
      * Camera HAL API callbacks.
      ***************************************************************************/
@@ -83,6 +91,9 @@
     /* camera_module_t::get_camera_info callback entry point. */
     static int get_camera_info(int camera_id, struct camera_info *info);
 
+    /* camera_module_t::set_callbacks callback entry point. */
+    static int set_callbacks(const camera_module_callbacks_t *callbacks);
+
 private:
     /* hw_module_methods_t::open callback entry point. */
     static int device_open(const hw_module_t* module,
@@ -119,6 +130,8 @@
         return mConstructedOK;
     }
 
+    void onStatusChanged(int cameraId, int newStatus);
+
     /****************************************************************************
      * Private API
      ***************************************************************************/
@@ -163,6 +176,12 @@
     /* Flags whether or not constructor has succeeded. */
     bool                mConstructedOK;
 
+    /* Camera callbacks (for status changing) */
+    const camera_module_callbacks_t* mCallbacks;
+
+    /* Hotplug thread (to call onStatusChanged) */
+    sp<EmulatedCameraHotplugThread> mHotplugThread;
+
 public:
     /* Contains device open entry point, as required by HAL API. */
     static struct hw_module_methods_t   mCameraModuleMethods;
diff --git a/camera/EmulatedCameraHal.cpp b/camera/EmulatedCameraHal.cpp
index aa0cb00..802a5bb 100755
--- a/camera/EmulatedCameraHal.cpp
+++ b/camera/EmulatedCameraHal.cpp
@@ -31,7 +31,7 @@
 camera_module_t HAL_MODULE_INFO_SYM = {
     common: {
          tag:                HARDWARE_MODULE_TAG,
-         module_api_version: CAMERA_MODULE_API_VERSION_2_0,
+         module_api_version: CAMERA_MODULE_API_VERSION_2_1,
          hal_api_version:    HARDWARE_HAL_API_VERSION,
          id:                 CAMERA_HARDWARE_MODULE_ID,
          name:               "Emulated Camera Module",
@@ -42,4 +42,5 @@
     },
     get_number_of_cameras:  android::EmulatedCameraFactory::get_number_of_cameras,
     get_camera_info:        android::EmulatedCameraFactory::get_camera_info,
+    set_callbacks:          android::EmulatedCameraFactory::set_callbacks,
 };
diff --git a/camera/EmulatedCameraHotplugThread.cpp b/camera/EmulatedCameraHotplugThread.cpp
new file mode 100644
index 0000000..0ce2aeb
--- /dev/null
+++ b/camera/EmulatedCameraHotplugThread.cpp
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2013 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_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_HotplugThread"
+#include <cutils/log.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/inotify.h>
+
+#include "EmulatedCameraHotplugThread.h"
+#include "EmulatedCameraFactory.h"
+
+#define FAKE_HOTPLUG_FILE "/data/misc/media/emulator.camera.hotplug"
+
+#define EVENT_SIZE (sizeof(struct inotify_event))
+#define EVENT_BUF_LEN (1024*(EVENT_SIZE+16))
+
+#define SubscriberInfo EmulatedCameraHotplugThread::SubscriberInfo
+
+namespace android {
+
+EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
+    const int* cameraIdArray,
+    size_t size) :
+        Thread(/*canCallJava*/false) {
+
+    mRunning = true;
+    mInotifyFd = 0;
+
+    for (size_t i = 0; i < size; ++i) {
+        int id = cameraIdArray[i];
+
+        if (createFileIfNotExists(id)) {
+            mSubscribedCameraIds.push_back(id);
+        }
+    }
+}
+
+EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {
+}
+
+status_t EmulatedCameraHotplugThread::requestExitAndWait() {
+    ALOGE("%s: Not implemented. Use requestExit + join instead",
+          __FUNCTION__);
+    return INVALID_OPERATION;
+}
+
+void EmulatedCameraHotplugThread::requestExit() {
+    Mutex::Autolock al(mMutex);
+
+    ALOGV("%s: Requesting thread exit", __FUNCTION__);
+    mRunning = false;
+
+    bool rmWatchFailed = false;
+    Vector<SubscriberInfo>::iterator it;
+    for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
+
+        if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) {
+
+            ALOGE("%s: Could not remove watch for camID '%d',"
+                  " error: '%s' (%d)",
+                 __FUNCTION__, it->CameraID, strerror(errno),
+                 errno);
+
+            rmWatchFailed = true ;
+        } else {
+            ALOGV("%s: Removed watch for camID '%d'",
+                __FUNCTION__, it->CameraID);
+        }
+    }
+
+    if (rmWatchFailed) { // unlikely
+        // Give the thread a fighting chance to error out on the next
+        // read
+        if (TEMP_FAILURE_RETRY(close(mInotifyFd)) == -1) {
+            ALOGE("%s: close failure error: '%s' (%d)",
+                 __FUNCTION__, strerror(errno), errno);
+        }
+    }
+
+    ALOGV("%s: Request exit complete.", __FUNCTION__);
+}
+
+status_t EmulatedCameraHotplugThread::readyToRun() {
+    Mutex::Autolock al(mMutex);
+
+    mInotifyFd = -1;
+
+    do {
+        ALOGV("%s: Initializing inotify", __FUNCTION__);
+
+        mInotifyFd = inotify_init();
+        if (mInotifyFd == -1) {
+            ALOGE("%s: inotify_init failure error: '%s' (%d)",
+                 __FUNCTION__, strerror(errno), errno);
+            mRunning = false;
+            break;
+        }
+
+        /**
+         * For each fake camera file, add a watch for when
+         * the file is closed (if it was written to)
+         */
+        Vector<int>::const_iterator it, end;
+        it = mSubscribedCameraIds.begin();
+        end = mSubscribedCameraIds.end();
+        for (; it != end; ++it) {
+            int cameraId = *it;
+            if (!addWatch(cameraId)) {
+                mRunning = false;
+                break;
+            }
+        }
+    } while(false);
+
+    if (!mRunning) {
+        status_t err = -errno;
+
+        if (mInotifyFd != -1) {
+            TEMP_FAILURE_RETRY(close(mInotifyFd));
+        }
+
+        return err;
+    }
+
+    return OK;
+}
+
+bool EmulatedCameraHotplugThread::threadLoop() {
+
+    // If requestExit was already called, mRunning will be false
+    while (mRunning) {
+        char buffer[EVENT_BUF_LEN];
+        int length = TEMP_FAILURE_RETRY(
+                        read(mInotifyFd, buffer, EVENT_BUF_LEN));
+
+        if (length < 0) {
+            ALOGE("%s: Error reading from inotify FD, error: '%s' (%d)",
+                 __FUNCTION__, strerror(errno),
+                 errno);
+            mRunning = false;
+            break;
+        }
+
+        ALOGV("%s: Read %d bytes from inotify FD", __FUNCTION__, length);
+
+        int i = 0;
+        while (i < length) {
+            inotify_event* event = (inotify_event*) &buffer[i];
+
+            if (event->mask & IN_IGNORED) {
+                Mutex::Autolock al(mMutex);
+                if (!mRunning) {
+                    ALOGV("%s: Shutting down thread", __FUNCTION__);
+                    break;
+                } else {
+                    ALOGE("%s: File was deleted, aborting",
+                          __FUNCTION__);
+                    mRunning = false;
+                    break;
+                }
+            } else if (event->mask & IN_CLOSE_WRITE) {
+                int cameraId = getCameraId(event->wd);
+
+                if (cameraId < 0) {
+                    ALOGE("%s: Got bad camera ID from WD '%d",
+                          __FUNCTION__, event->wd);
+                } else {
+                    // Check the file for the new hotplug event
+                    String8 filePath = getFilePath(cameraId);
+                    /**
+                     * NOTE: we carefully avoid getting an inotify
+                     * for the same exact file because it's opened for
+                     * read-only, but our inotify is for write-only
+                     */
+                    int newStatus = readFile(filePath);
+
+                    if (newStatus < 0) {
+                        mRunning = false;
+                        break;
+                    }
+
+                    int halStatus = newStatus ?
+                        CAMERA_DEVICE_STATUS_PRESENT :
+                        CAMERA_DEVICE_STATUS_NOT_PRESENT;
+                    gEmulatedCameraFactory.onStatusChanged(cameraId,
+                                                           halStatus);
+                }
+
+            } else {
+                ALOGW("%s: Unknown mask 0x%x",
+                      __FUNCTION__, event->mask);
+            }
+
+            i += EVENT_SIZE + event->len;
+        }
+    }
+
+    if (!mRunning) {
+        TEMP_FAILURE_RETRY(close(mInotifyFd));
+        return false;
+    }
+
+    return true;
+}
+
+String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
+    return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId);
+}
+
+bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const
+{
+    String8 filePath = getFilePath(cameraId);
+    // make sure this file exists and we have access to it
+    int fd = TEMP_FAILURE_RETRY(
+                open(filePath.string(), O_WRONLY | O_CREAT | O_TRUNC,
+                     /* mode = ug+rwx */ S_IRWXU | S_IRWXG ));
+    if (fd == -1) {
+        ALOGE("%s: Could not create file '%s', error: '%s' (%d)",
+             __FUNCTION__, filePath.string(), strerror(errno), errno);
+        return false;
+    }
+
+    // File has '1' by default since we are plugged in by default
+    if (TEMP_FAILURE_RETRY(write(fd, "1\n", /*count*/2)) == -1) {
+        ALOGE("%s: Could not write '1' to file '%s', error: '%s' (%d)",
+             __FUNCTION__, filePath.string(), strerror(errno), errno);
+        return false;
+    }
+
+    TEMP_FAILURE_RETRY(close(fd));
+    return true;
+}
+
+int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const {
+    Vector<int>::const_iterator it, end;
+    it = mSubscribedCameraIds.begin();
+    end = mSubscribedCameraIds.end();
+    for (; it != end; ++it) {
+        String8 camPath = getFilePath(*it);
+
+        if (camPath == filePath) {
+            return *it;
+        }
+    }
+
+    return NAME_NOT_FOUND;
+}
+
+int EmulatedCameraHotplugThread::getCameraId(int wd) const {
+    for (size_t i = 0; i < mSubscribers.size(); ++i) {
+        if (mSubscribers[i].WatchID == wd) {
+            return mSubscribers[i].CameraID;
+        }
+    }
+
+    return NAME_NOT_FOUND;
+}
+
+SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId)
+{
+    for (size_t i = 0; i < mSubscribers.size(); ++i) {
+        if (mSubscribers[i].CameraID == cameraId) {
+            return (SubscriberInfo*)&mSubscribers[i];
+        }
+    }
+
+    return NULL;
+}
+
+bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
+    String8 camPath = getFilePath(cameraId);
+    int wd = inotify_add_watch(mInotifyFd,
+                               camPath.string(),
+                               IN_CLOSE_WRITE);
+
+    if (wd == -1) {
+        ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)",
+             __FUNCTION__, camPath.string(), strerror(errno),
+             errno);
+
+        mRunning = false;
+        return false;
+    }
+
+    ALOGV("%s: Watch added for camID='%d', wd='%d'",
+          __FUNCTION__, cameraId, wd);
+
+    SubscriberInfo si = { cameraId, wd };
+    mSubscribers.push_back(si);
+
+    return true;
+}
+
+bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
+    SubscriberInfo* si = getSubscriberInfo(cameraId);
+
+    if (!si) return false;
+
+    if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) {
+
+        ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)",
+             __FUNCTION__, cameraId, strerror(errno),
+             errno);
+
+        return false;
+    }
+
+    Vector<SubscriberInfo>::iterator it;
+    for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
+        if (it->CameraID == cameraId) {
+            break;
+        }
+    }
+
+    if (it != mSubscribers.end()) {
+        mSubscribers.erase(it);
+    }
+
+    return true;
+}
+
+int EmulatedCameraHotplugThread::readFile(String8 filePath) const {
+
+    int fd = TEMP_FAILURE_RETRY(
+                open(filePath.string(), O_RDONLY, /*mode*/0));
+    if (fd == -1) {
+        ALOGE("%s: Could not open file '%s', error: '%s' (%d)",
+             __FUNCTION__, filePath.string(), strerror(errno), errno);
+        return -1;
+    }
+
+    char buffer[1];
+    int length;
+
+    length = TEMP_FAILURE_RETRY(
+                    read(fd, buffer, sizeof(buffer)));
+
+    int retval;
+
+    ALOGV("%s: Read file '%s', length='%d', buffer='%c'",
+         __FUNCTION__, filePath.string(), length, buffer[0]);
+
+    if (length == 0) { // EOF
+        retval = 0; // empty file is the same thing as 0
+    } else if (buffer[0] == '0') {
+        retval = 0;
+    } else { // anything non-empty that's not beginning with '0'
+        retval = 1;
+    }
+
+    TEMP_FAILURE_RETRY(close(fd));
+
+    return retval;
+}
+
+} //namespace android
diff --git a/camera/EmulatedCameraHotplugThread.h b/camera/EmulatedCameraHotplugThread.h
new file mode 100644
index 0000000..3e26e71
--- /dev/null
+++ b/camera/EmulatedCameraHotplugThread.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA_HOTPLUG_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA_HOTPLUG_H
+
+/**
+ * This class emulates hotplug events by inotifying on a file, specific
+ * to a camera ID. When the file changes between 1/0 the hotplug
+ * status goes between PRESENT and NOT_PRESENT.
+ *
+ * Refer to FAKE_HOTPLUG_FILE in EmulatedCameraHotplugThread.cpp
+ */
+
+#include "EmulatedCamera2.h"
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace android {
+class EmulatedCameraHotplugThread : public Thread {
+  public:
+    EmulatedCameraHotplugThread(const int* cameraIdArray, size_t size);
+    ~EmulatedCameraHotplugThread();
+
+    virtual void requestExit();
+    virtual status_t requestExitAndWait();
+
+  private:
+
+
+    virtual status_t readyToRun();
+    virtual bool threadLoop();
+
+    struct SubscriberInfo {
+        int CameraID;
+        int WatchID;
+    };
+
+    bool addWatch(int cameraId);
+    bool removeWatch(int cameraId);
+    SubscriberInfo* getSubscriberInfo(int cameraId);
+
+    int getCameraId(String8 filePath) const;
+    int getCameraId(int wd) const;
+
+    String8 getFilePath(int cameraId) const;
+    int readFile(String8 filePath) const;
+
+    bool createFileIfNotExists(int cameraId) const;
+
+    int mInotifyFd;
+    Vector<int> mSubscribedCameraIds;
+    Vector<SubscriberInfo> mSubscribers;
+
+    // variables above are unguarded:
+    // -- accessed in thread loop or in constructor only
+
+    Mutex mMutex;
+
+    bool mRunning;          // guarding only when it's important
+};
+} // namespace android
+
+#endif
diff --git a/camera/EmulatedFakeCamera2.cpp b/camera/EmulatedFakeCamera2.cpp
index fa5978a..ab69063 100644
--- a/camera/EmulatedFakeCamera2.cpp
+++ b/camera/EmulatedFakeCamera2.cpp
@@ -29,6 +29,10 @@
 #include <ui/GraphicBufferMapper.h>
 #include "gralloc_cb.h"
 
+#define ERROR_CAMERA_NOT_PRESENT -EPIPE
+
+#define CAMERA2_EXT_TRIGGER_TESTING_DISCONNECT 0xFFFFFFFF
+
 namespace android {
 
 const int64_t USEC = 1000LL;
@@ -86,7 +90,8 @@
         bool facingBack,
         struct hw_module_t* module)
         : EmulatedCamera2(cameraId,module),
-          mFacingBack(facingBack)
+          mFacingBack(facingBack),
+          mIsConnected(false)
 {
     ALOGD("Constructing emulated fake camera 2 facing %s",
             facingBack ? "back" : "front");
@@ -140,6 +145,15 @@
     status_t res;
     ALOGV("%s", __FUNCTION__);
 
+    {
+        Mutex::Autolock l(mMutex);
+        if (!mStatusPresent) {
+            ALOGE("%s: Camera ID %d is unplugged", __FUNCTION__,
+                  mCameraID);
+            return -ENODEV;
+        }
+    }
+
     mConfigureThread = new ConfigureThread(this);
     mReadoutThread = new ReadoutThread(this);
     mControlThread = new ControlThread(this);
@@ -161,9 +175,50 @@
     res = mControlThread->run("EmulatedFakeCamera2::controlThread");
     if (res != NO_ERROR) return res;
 
-    return EmulatedCamera2::connectCamera(device);
+    status_t ret = EmulatedCamera2::connectCamera(device);
+
+    if (ret >= 0) {
+        mIsConnected = true;
+    }
+
+    return ret;
 }
 
+status_t EmulatedFakeCamera2::plugCamera() {
+    {
+        Mutex::Autolock l(mMutex);
+
+        if (!mStatusPresent) {
+            ALOGI("%s: Plugged back in", __FUNCTION__);
+            mStatusPresent = true;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::unplugCamera() {
+    {
+        Mutex::Autolock l(mMutex);
+
+        if (mStatusPresent) {
+            ALOGI("%s: Unplugged camera", __FUNCTION__);
+            mStatusPresent = false;
+        }
+    }
+
+    return closeCamera();
+}
+
+camera_device_status_t EmulatedFakeCamera2::getHotplugStatus() {
+    Mutex::Autolock l(mMutex);
+    return mStatusPresent ?
+        CAMERA_DEVICE_STATUS_PRESENT :
+        CAMERA_DEVICE_STATUS_NOT_PRESENT;
+}
+
+
+
 status_t EmulatedFakeCamera2::closeCamera() {
     {
         Mutex::Autolock l(mMutex);
@@ -171,6 +226,10 @@
         status_t res;
         ALOGV("%s", __FUNCTION__);
 
+        if (!mIsConnected) {
+            return NO_ERROR;
+        }
+
         res = mSensor->shutDown();
         if (res != NO_ERROR) {
             ALOGE("%s: Unable to shut down sensor: %d", __FUNCTION__, res);
@@ -190,6 +249,12 @@
     mControlThread->join();
 
     ALOGV("%s exit", __FUNCTION__);
+
+    {
+        Mutex::Autolock l(mMutex);
+        mIsConnected = false;
+    }
+
     return NO_ERROR;
 }
 
@@ -223,6 +288,11 @@
 int EmulatedFakeCamera2::getInProgressCount() {
     Mutex::Autolock l(mMutex);
 
+    if (!mStatusPresent) {
+        ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+        return ERROR_CAMERA_NOT_PRESENT;
+    }
+
     int requestCount = 0;
     requestCount += mConfigureThread->getInProgressCount();
     requestCount += mReadoutThread->getInProgressCount();
@@ -239,6 +309,15 @@
     if (request_template < 0 || request_template >= CAMERA2_TEMPLATE_COUNT) {
         return BAD_VALUE;
     }
+
+    {
+        Mutex::Autolock l(mMutex);
+        if (!mStatusPresent) {
+            ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+            return ERROR_CAMERA_NOT_PRESENT;
+        }
+    }
+
     status_t res;
     // Pass 1, calculate size and allocate
     res = constructDefaultRequest(request_template,
@@ -270,6 +349,11 @@
         uint32_t *max_buffers) {
     Mutex::Autolock l(mMutex);
 
+    if (!mStatusPresent) {
+        ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+        return ERROR_CAMERA_NOT_PRESENT;
+    }
+
     // Temporary shim until FORMAT_ZSL is removed
     if (format == CAMERA2_HAL_PIXEL_FORMAT_ZSL) {
         format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
@@ -382,6 +466,11 @@
             buffer_handle_t *buffers) {
     Mutex::Autolock l(mMutex);
 
+    if (!mStatusPresent) {
+        ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+        return ERROR_CAMERA_NOT_PRESENT;
+    }
+
     ALOGV("%s: Stream %d registering %d buffers", __FUNCTION__,
             stream_id, num_buffers);
     // Need to find out what the final concrete pixel format for our stream is
@@ -457,6 +546,11 @@
         uint32_t *stream_id) {
     Mutex::Autolock l(mMutex);
 
+    if (!mStatusPresent) {
+        ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+        return ERROR_CAMERA_NOT_PRESENT;
+    }
+
     ssize_t baseStreamIndex = mStreams.indexOfKey(output_stream_id);
     if (baseStreamIndex < 0) {
         ALOGE("%s: Unknown output stream id %d!", __FUNCTION__, output_stream_id);
@@ -518,6 +612,21 @@
         int32_t ext1,
         int32_t ext2) {
     Mutex::Autolock l(mMutex);
+
+    if (trigger_id == CAMERA2_EXT_TRIGGER_TESTING_DISCONNECT) {
+        ALOGI("%s: Disconnect trigger - camera must be closed", __FUNCTION__);
+        mStatusPresent = false;
+
+        gEmulatedCameraFactory.onStatusChanged(
+                mCameraID,
+                CAMERA_DEVICE_STATUS_NOT_PRESENT);
+    }
+
+    if (!mStatusPresent) {
+        ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+        return ERROR_CAMERA_NOT_PRESENT;
+    }
+
     return mControlThread->triggerAction(trigger_id,
             ext1, ext2);
 }
@@ -701,6 +810,7 @@
         }
         // Active
     }
+
     if (mRequest == NULL) {
         Mutex::Autolock il(mInternalsMutex);
 
@@ -1961,7 +2071,7 @@
                 mAeScanDuration = ((double)rand() / RAND_MAX) *
                 (kMaxAeDuration - kMinAeDuration) + kMinAeDuration;
                 aeState = ANDROID_CONTROL_AE_STATE_SEARCHING;
-                ALOGD("%s: AE scan start, duration %lld ms",
+                ALOGV("%s: AE scan start, duration %lld ms",
                         __FUNCTION__, mAeScanDuration / 1000000);
             }
         }
@@ -1978,7 +2088,7 @@
     } else if ((aeState == ANDROID_CONTROL_AE_STATE_SEARCHING) ||
             (aeState == ANDROID_CONTROL_AE_STATE_PRECAPTURE ) ) {
         if (mAeScanDuration <= 0) {
-            ALOGD("%s: AE scan done", __FUNCTION__);
+            ALOGV("%s: AE scan done", __FUNCTION__);
             aeState = aeLock ?
                     ANDROID_CONTROL_AE_STATE_LOCKED :ANDROID_CONTROL_AE_STATE_CONVERGED;
 
@@ -2006,7 +2116,7 @@
         int32_t triggerId) {
     Mutex::Autolock lock(mInputMutex);
     if (mAeState != newState) {
-        ALOGD("%s: Autoexposure state now %d, id %d", __FUNCTION__,
+        ALOGV("%s: Autoexposure state now %d, id %d", __FUNCTION__,
                 newState, triggerId);
         mAeState = newState;
         mParent->sendNotification(CAMERA2_MSG_AUTOEXPOSURE,
diff --git a/camera/EmulatedFakeCamera2.h b/camera/EmulatedFakeCamera2.h
index ee47235..17387e4 100644
--- a/camera/EmulatedFakeCamera2.h
+++ b/camera/EmulatedFakeCamera2.h
@@ -60,6 +60,10 @@
 
     virtual status_t connectCamera(hw_device_t** device);
 
+    virtual status_t plugCamera();
+    virtual status_t unplugCamera();
+    virtual camera_device_status_t getHotplugStatus();
+
     virtual status_t closeCamera();
 
     virtual status_t getCameraInfo(struct camera_info *info);
@@ -397,6 +401,8 @@
     bool mFacingBack;
 
 private:
+    bool mIsConnected;
+
     /** Stream manipulation */
     uint32_t mNextStreamId;
     uint32_t mRawStreamCount;