/* * 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 <fcntl.h> #include <sys/inotify.h> #include <sys/stat.h> #include <sys/types.h> #include "EmulatedCameraFactory.h" #include "EmulatedCameraHotplugThread.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( size_t totalCameraCount) : Thread(/*canCallJava*/ false) { mRunning = true; mInotifyFd = 0; for (size_t id = 0; id < totalCameraCount; ++id) { 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 (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) { 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; EmulatedCameraFactory::Instance().onStatusChanged(cameraId, halStatus); } } else { ALOGW("%s: Unknown mask 0x%x", __FUNCTION__, event->mask); } i += EVENT_SIZE + event->len; } } if (!mRunning) { 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; } 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; } close(fd); return retval; } } // namespace android