diff options
author | Igor Murashkin <iam@google.com> | 2013-03-20 15:22:51 -0700 |
---|---|---|
committer | Igor Murashkin <iam@google.com> | 2013-03-27 14:00:46 -0700 |
commit | 4dc85efabf08b5b700a6ec0c9b435b40334e72c4 (patch) | |
tree | 3fae8c7ddf20b225c173b3617205b1eb442198b6 | |
parent | 5bb902b4c231badea01a0571eef86322206a5228 (diff) | |
download | android_device_generic_goldfish-4dc85efabf08b5b700a6ec0c9b435b40334e72c4.tar.gz android_device_generic_goldfish-4dc85efabf08b5b700a6ec0c9b435b40334e72c4.tar.bz2 android_device_generic_goldfish-4dc85efabf08b5b700a6ec0c9b435b40334e72c4.zip |
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
-rwxr-xr-x | camera/Android.mk | 1 | ||||
-rw-r--r-- | camera/EmulatedBaseCamera.cpp | 16 | ||||
-rw-r--r-- | camera/EmulatedBaseCamera.h | 18 | ||||
-rw-r--r-- | camera/EmulatedCamera2.cpp | 2 | ||||
-rw-r--r-- | camera/EmulatedCamera2.h | 2 | ||||
-rwxr-xr-x | camera/EmulatedCameraFactory.cpp | 67 | ||||
-rwxr-xr-x | camera/EmulatedCameraFactory.h | 19 | ||||
-rwxr-xr-x | camera/EmulatedCameraHal.cpp | 3 | ||||
-rw-r--r-- | camera/EmulatedCameraHotplugThread.cpp | 372 | ||||
-rw-r--r-- | camera/EmulatedCameraHotplugThread.h | 77 | ||||
-rw-r--r-- | camera/EmulatedFakeCamera2.cpp | 120 | ||||
-rw-r--r-- | camera/EmulatedFakeCamera2.h | 6 |
12 files changed, 695 insertions, 8 deletions
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 @@ LOCAL_SRC_FILES := \ 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 @@ status_t EmulatedBaseCamera::getCameraInfo(struct camera_info* info) 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 @@ class EmulatedBaseCamera { */ 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 @@ class EmulatedBaseCamera { * 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 @@ EmulatedCamera2::EmulatedCamera2(int cameraId, 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 @@ private: /** 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 @@ EmulatedCameraFactory::EmulatedCameraFactory() 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 @@ EmulatedCameraFactory::EmulatedCameraFactory() 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 @@ EmulatedCameraFactory::~EmulatedCameraFactory() } delete[] mEmulatedCameras; } + + if (mHotplugThread != NULL) { + mHotplugThread->requestExit(); + mHotplugThread->join(); + } } /**************************************************************************** @@ -220,6 +238,16 @@ int EmulatedCameraFactory::getCameraInfo(int camera_id, struct camera_info* info 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 @@ int EmulatedCameraFactory::get_camera_info(int camera_id, 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 @@ int EmulatedCameraFactory::getFrontCameraHalVersion() 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 @@ public: */ 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 @@ public: /* 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 @@ public: return mConstructedOK; } + void onStatusChanged(int cameraId, int newStatus); + /**************************************************************************** * Private API ***************************************************************************/ @@ -163,6 +176,12 @@ private: /* 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 @@ camera_module_t HAL_MODULE_INFO_SYM = { }, 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 @@ EmulatedFakeCamera2::EmulatedFakeCamera2(int cameraId, 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 EmulatedFakeCamera2::connectCamera(hw_device_t** device) { 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 @@ status_t EmulatedFakeCamera2::connectCamera(hw_device_t** device) { 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 EmulatedFakeCamera2::closeCamera() { 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 @@ status_t EmulatedFakeCamera2::closeCamera() { mControlThread->join(); ALOGV("%s exit", __FUNCTION__); + + { + Mutex::Autolock l(mMutex); + mIsConnected = false; + } + return NO_ERROR; } @@ -223,6 +288,11 @@ int EmulatedFakeCamera2::requestQueueNotify() { 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 @@ int EmulatedFakeCamera2::constructDefaultRequest( 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 @@ int EmulatedFakeCamera2::allocateStream( 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 @@ int EmulatedFakeCamera2::registerStreamBuffers( 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 @@ int EmulatedFakeCamera2::allocateReprocessStreamFromStream( 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 @@ int EmulatedFakeCamera2::triggerAction(uint32_t trigger_id, 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 @@ bool EmulatedFakeCamera2::ConfigureThread::threadLoop() { } // Active } + if (mRequest == NULL) { Mutex::Autolock il(mInternalsMutex); @@ -1961,7 +2071,7 @@ int EmulatedFakeCamera2::ControlThread::maybeStartAeScan(uint8_t aeMode, 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 @@ int EmulatedFakeCamera2::ControlThread::updateAeScan(uint8_t aeMode, } 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 @@ void EmulatedFakeCamera2::ControlThread::updateAeState(uint8_t newState, 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 @@ public: 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 @@ protected: bool mFacingBack; private: + bool mIsConnected; + /** Stream manipulation */ uint32_t mNextStreamId; uint32_t mRawStreamCount; |