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;