2 * Copyright (C) 2013 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 //#define LOG_NDEBUG 0
17 #define LOG_TAG "EmulatedCamera_HotplugThread"
18 #include <cutils/log.h>
20 #include <sys/types.h>
23 #include <sys/inotify.h>
25 #include "EmulatedCameraHotplugThread.h"
26 #include "EmulatedCameraFactory.h"
28 #define FAKE_HOTPLUG_FILE "/data/misc/media/emulator.camera.hotplug"
30 #define EVENT_SIZE (sizeof(struct inotify_event))
31 #define EVENT_BUF_LEN (1024*(EVENT_SIZE+16))
33 #define SubscriberInfo EmulatedCameraHotplugThread::SubscriberInfo
37 EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
38 const int* cameraIdArray,
40 Thread(/*canCallJava*/false) {
45 for (size_t i = 0; i < size; ++i) {
46 int id = cameraIdArray[i];
48 if (createFileIfNotExists(id)) {
49 mSubscribedCameraIds.push_back(id);
54 EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {
57 status_t EmulatedCameraHotplugThread::requestExitAndWait() {
58 ALOGE("%s: Not implemented. Use requestExit + join instead",
60 return INVALID_OPERATION;
63 void EmulatedCameraHotplugThread::requestExit() {
64 Mutex::Autolock al(mMutex);
66 ALOGV("%s: Requesting thread exit", __FUNCTION__);
69 bool rmWatchFailed = false;
70 Vector<SubscriberInfo>::iterator it;
71 for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
73 if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) {
75 ALOGE("%s: Could not remove watch for camID '%d',"
77 __FUNCTION__, it->CameraID, strerror(errno),
80 rmWatchFailed = true ;
82 ALOGV("%s: Removed watch for camID '%d'",
83 __FUNCTION__, it->CameraID);
87 if (rmWatchFailed) { // unlikely
88 // Give the thread a fighting chance to error out on the next
90 if (close(mInotifyFd) == -1) {
91 ALOGE("%s: close failure error: '%s' (%d)",
92 __FUNCTION__, strerror(errno), errno);
96 ALOGV("%s: Request exit complete.", __FUNCTION__);
99 status_t EmulatedCameraHotplugThread::readyToRun() {
100 Mutex::Autolock al(mMutex);
105 ALOGV("%s: Initializing inotify", __FUNCTION__);
107 mInotifyFd = inotify_init();
108 if (mInotifyFd == -1) {
109 ALOGE("%s: inotify_init failure error: '%s' (%d)",
110 __FUNCTION__, strerror(errno), errno);
116 * For each fake camera file, add a watch for when
117 * the file is closed (if it was written to)
119 Vector<int>::const_iterator it, end;
120 it = mSubscribedCameraIds.begin();
121 end = mSubscribedCameraIds.end();
122 for (; it != end; ++it) {
124 if (!addWatch(cameraId)) {
132 status_t err = -errno;
134 if (mInotifyFd != -1) {
144 bool EmulatedCameraHotplugThread::threadLoop() {
146 // If requestExit was already called, mRunning will be false
148 char buffer[EVENT_BUF_LEN];
149 int length = TEMP_FAILURE_RETRY(
150 read(mInotifyFd, buffer, EVENT_BUF_LEN));
153 ALOGE("%s: Error reading from inotify FD, error: '%s' (%d)",
154 __FUNCTION__, strerror(errno),
160 ALOGV("%s: Read %d bytes from inotify FD", __FUNCTION__, length);
164 inotify_event* event = (inotify_event*) &buffer[i];
166 if (event->mask & IN_IGNORED) {
167 Mutex::Autolock al(mMutex);
169 ALOGV("%s: Shutting down thread", __FUNCTION__);
172 ALOGE("%s: File was deleted, aborting",
177 } else if (event->mask & IN_CLOSE_WRITE) {
178 int cameraId = getCameraId(event->wd);
181 ALOGE("%s: Got bad camera ID from WD '%d",
182 __FUNCTION__, event->wd);
184 // Check the file for the new hotplug event
185 String8 filePath = getFilePath(cameraId);
187 * NOTE: we carefully avoid getting an inotify
188 * for the same exact file because it's opened for
189 * read-only, but our inotify is for write-only
191 int newStatus = readFile(filePath);
198 int halStatus = newStatus ?
199 CAMERA_DEVICE_STATUS_PRESENT :
200 CAMERA_DEVICE_STATUS_NOT_PRESENT;
201 gEmulatedCameraFactory.onStatusChanged(cameraId,
206 ALOGW("%s: Unknown mask 0x%x",
207 __FUNCTION__, event->mask);
210 i += EVENT_SIZE + event->len;
222 String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
223 return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId);
226 bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const
228 String8 filePath = getFilePath(cameraId);
229 // make sure this file exists and we have access to it
230 int fd = TEMP_FAILURE_RETRY(
231 open(filePath.string(), O_WRONLY | O_CREAT | O_TRUNC,
232 /* mode = ug+rwx */ S_IRWXU | S_IRWXG ));
234 ALOGE("%s: Could not create file '%s', error: '%s' (%d)",
235 __FUNCTION__, filePath.string(), strerror(errno), errno);
239 // File has '1' by default since we are plugged in by default
240 if (TEMP_FAILURE_RETRY(write(fd, "1\n", /*count*/2)) == -1) {
241 ALOGE("%s: Could not write '1' to file '%s', error: '%s' (%d)",
242 __FUNCTION__, filePath.string(), strerror(errno), errno);
250 int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const {
251 Vector<int>::const_iterator it, end;
252 it = mSubscribedCameraIds.begin();
253 end = mSubscribedCameraIds.end();
254 for (; it != end; ++it) {
255 String8 camPath = getFilePath(*it);
257 if (camPath == filePath) {
262 return NAME_NOT_FOUND;
265 int EmulatedCameraHotplugThread::getCameraId(int wd) const {
266 for (size_t i = 0; i < mSubscribers.size(); ++i) {
267 if (mSubscribers[i].WatchID == wd) {
268 return mSubscribers[i].CameraID;
272 return NAME_NOT_FOUND;
275 SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId)
277 for (size_t i = 0; i < mSubscribers.size(); ++i) {
278 if (mSubscribers[i].CameraID == cameraId) {
279 return (SubscriberInfo*)&mSubscribers[i];
286 bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
287 String8 camPath = getFilePath(cameraId);
288 int wd = inotify_add_watch(mInotifyFd,
293 ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)",
294 __FUNCTION__, camPath.string(), strerror(errno),
301 ALOGV("%s: Watch added for camID='%d', wd='%d'",
302 __FUNCTION__, cameraId, wd);
304 SubscriberInfo si = { cameraId, wd };
305 mSubscribers.push_back(si);
310 bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
311 SubscriberInfo* si = getSubscriberInfo(cameraId);
313 if (!si) return false;
315 if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) {
317 ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)",
318 __FUNCTION__, cameraId, strerror(errno),
324 Vector<SubscriberInfo>::iterator it;
325 for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
326 if (it->CameraID == cameraId) {
331 if (it != mSubscribers.end()) {
332 mSubscribers.erase(it);
338 int EmulatedCameraHotplugThread::readFile(String8 filePath) const {
340 int fd = TEMP_FAILURE_RETRY(
341 open(filePath.string(), O_RDONLY, /*mode*/0));
343 ALOGE("%s: Could not open file '%s', error: '%s' (%d)",
344 __FUNCTION__, filePath.string(), strerror(errno), errno);
351 length = TEMP_FAILURE_RETRY(
352 read(fd, buffer, sizeof(buffer)));
356 ALOGV("%s: Read file '%s', length='%d', buffer='%c'",
357 __FUNCTION__, filePath.string(), length, buffer[0]);
359 if (length == 0) { // EOF
360 retval = 0; // empty file is the same thing as 0
361 } else if (buffer[0] == '0') {
363 } else { // anything non-empty that's not beginning with '0'
372 } //namespace android