1 /*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16 //#define LOG_NDEBUG 0
17 #define LOG_TAG "EmulatedCamera_HotplugThread"
18 #include <cutils/log.h>
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <sys/inotify.h>
24
25 #include "EmulatedCameraHotplugThread.h"
26 #include "EmulatedCameraFactory.h"
27
28 #define FAKE_HOTPLUG_FILE "/data/misc/media/emulator.camera.hotplug"
29
30 #define EVENT_SIZE (sizeof(struct inotify_event))
31 #define EVENT_BUF_LEN (1024*(EVENT_SIZE+16))
32
33 #define SubscriberInfo EmulatedCameraHotplugThread::SubscriberInfo
34
35 namespace android {
36
EmulatedCameraHotplugThread(const int * cameraIdArray,size_t size)37 EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
38 const int* cameraIdArray,
39 size_t size) :
40 Thread(/*canCallJava*/false) {
41
42 mRunning = true;
43 mInotifyFd = 0;
44
45 for (size_t i = 0; i < size; ++i) {
46 int id = cameraIdArray[i];
47
48 if (createFileIfNotExists(id)) {
49 mSubscribedCameraIds.push_back(id);
50 }
51 }
52 }
53
~EmulatedCameraHotplugThread()54 EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {
55 }
56
requestExitAndWait()57 status_t EmulatedCameraHotplugThread::requestExitAndWait() {
58 ALOGE("%s: Not implemented. Use requestExit + join instead",
59 __FUNCTION__);
60 return INVALID_OPERATION;
61 }
62
requestExit()63 void EmulatedCameraHotplugThread::requestExit() {
64 Mutex::Autolock al(mMutex);
65
66 ALOGV("%s: Requesting thread exit", __FUNCTION__);
67 mRunning = false;
68
69 bool rmWatchFailed = false;
70 Vector<SubscriberInfo>::iterator it;
71 for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
72
73 if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) {
74
75 ALOGE("%s: Could not remove watch for camID '%d',"
76 " error: '%s' (%d)",
77 __FUNCTION__, it->CameraID, strerror(errno),
78 errno);
79
80 rmWatchFailed = true ;
81 } else {
82 ALOGV("%s: Removed watch for camID '%d'",
83 __FUNCTION__, it->CameraID);
84 }
85 }
86
87 if (rmWatchFailed) { // unlikely
88 // Give the thread a fighting chance to error out on the next
89 // read
90 if (TEMP_FAILURE_RETRY(close(mInotifyFd)) == -1) {
91 ALOGE("%s: close failure error: '%s' (%d)",
92 __FUNCTION__, strerror(errno), errno);
93 }
94 }
95
96 ALOGV("%s: Request exit complete.", __FUNCTION__);
97 }
98
readyToRun()99 status_t EmulatedCameraHotplugThread::readyToRun() {
100 Mutex::Autolock al(mMutex);
101
102 mInotifyFd = -1;
103
104 do {
105 ALOGV("%s: Initializing inotify", __FUNCTION__);
106
107 mInotifyFd = inotify_init();
108 if (mInotifyFd == -1) {
109 ALOGE("%s: inotify_init failure error: '%s' (%d)",
110 __FUNCTION__, strerror(errno), errno);
111 mRunning = false;
112 break;
113 }
114
115 /**
116 * For each fake camera file, add a watch for when
117 * the file is closed (if it was written to)
118 */
119 Vector<int>::const_iterator it, end;
120 it = mSubscribedCameraIds.begin();
121 end = mSubscribedCameraIds.end();
122 for (; it != end; ++it) {
123 int cameraId = *it;
124 if (!addWatch(cameraId)) {
125 mRunning = false;
126 break;
127 }
128 }
129 } while(false);
130
131 if (!mRunning) {
132 status_t err = -errno;
133
134 if (mInotifyFd != -1) {
135 TEMP_FAILURE_RETRY(close(mInotifyFd));
136 }
137
138 return err;
139 }
140
141 return OK;
142 }
143
threadLoop()144 bool EmulatedCameraHotplugThread::threadLoop() {
145
146 // If requestExit was already called, mRunning will be false
147 while (mRunning) {
148 char buffer[EVENT_BUF_LEN];
149 int length = TEMP_FAILURE_RETRY(
150 read(mInotifyFd, buffer, EVENT_BUF_LEN));
151
152 if (length < 0) {
153 ALOGE("%s: Error reading from inotify FD, error: '%s' (%d)",
154 __FUNCTION__, strerror(errno),
155 errno);
156 mRunning = false;
157 break;
158 }
159
160 ALOGV("%s: Read %d bytes from inotify FD", __FUNCTION__, length);
161
162 int i = 0;
163 while (i < length) {
164 inotify_event* event = (inotify_event*) &buffer[i];
165
166 if (event->mask & IN_IGNORED) {
167 Mutex::Autolock al(mMutex);
168 if (!mRunning) {
169 ALOGV("%s: Shutting down thread", __FUNCTION__);
170 break;
171 } else {
172 ALOGE("%s: File was deleted, aborting",
173 __FUNCTION__);
174 mRunning = false;
175 break;
176 }
177 } else if (event->mask & IN_CLOSE_WRITE) {
178 int cameraId = getCameraId(event->wd);
179
180 if (cameraId < 0) {
181 ALOGE("%s: Got bad camera ID from WD '%d",
182 __FUNCTION__, event->wd);
183 } else {
184 // Check the file for the new hotplug event
185 String8 filePath = getFilePath(cameraId);
186 /**
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
190 */
191 int newStatus = readFile(filePath);
192
193 if (newStatus < 0) {
194 mRunning = false;
195 break;
196 }
197
198 int halStatus = newStatus ?
199 CAMERA_DEVICE_STATUS_PRESENT :
200 CAMERA_DEVICE_STATUS_NOT_PRESENT;
201 gEmulatedCameraFactory.onStatusChanged(cameraId,
202 halStatus);
203 }
204
205 } else {
206 ALOGW("%s: Unknown mask 0x%x",
207 __FUNCTION__, event->mask);
208 }
209
210 i += EVENT_SIZE + event->len;
211 }
212 }
213
214 if (!mRunning) {
215 TEMP_FAILURE_RETRY(close(mInotifyFd));
216 return false;
217 }
218
219 return true;
220 }
221
getFilePath(int cameraId) const222 String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
223 return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId);
224 }
225
createFileIfNotExists(int cameraId) const226 bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const
227 {
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 ));
233 if (fd == -1) {
234 ALOGE("%s: Could not create file '%s', error: '%s' (%d)",
235 __FUNCTION__, filePath.string(), strerror(errno), errno);
236 return false;
237 }
238
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);
243 return false;
244 }
245
246 TEMP_FAILURE_RETRY(close(fd));
247 return true;
248 }
249
getCameraId(String8 filePath) const250 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);
256
257 if (camPath == filePath) {
258 return *it;
259 }
260 }
261
262 return NAME_NOT_FOUND;
263 }
264
getCameraId(int wd) const265 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;
269 }
270 }
271
272 return NAME_NOT_FOUND;
273 }
274
getSubscriberInfo(int cameraId)275 SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId)
276 {
277 for (size_t i = 0; i < mSubscribers.size(); ++i) {
278 if (mSubscribers[i].CameraID == cameraId) {
279 return (SubscriberInfo*)&mSubscribers[i];
280 }
281 }
282
283 return NULL;
284 }
285
addWatch(int cameraId)286 bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
287 String8 camPath = getFilePath(cameraId);
288 int wd = inotify_add_watch(mInotifyFd,
289 camPath.string(),
290 IN_CLOSE_WRITE);
291
292 if (wd == -1) {
293 ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)",
294 __FUNCTION__, camPath.string(), strerror(errno),
295 errno);
296
297 mRunning = false;
298 return false;
299 }
300
301 ALOGV("%s: Watch added for camID='%d', wd='%d'",
302 __FUNCTION__, cameraId, wd);
303
304 SubscriberInfo si = { cameraId, wd };
305 mSubscribers.push_back(si);
306
307 return true;
308 }
309
removeWatch(int cameraId)310 bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
311 SubscriberInfo* si = getSubscriberInfo(cameraId);
312
313 if (!si) return false;
314
315 if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) {
316
317 ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)",
318 __FUNCTION__, cameraId, strerror(errno),
319 errno);
320
321 return false;
322 }
323
324 Vector<SubscriberInfo>::iterator it;
325 for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
326 if (it->CameraID == cameraId) {
327 break;
328 }
329 }
330
331 if (it != mSubscribers.end()) {
332 mSubscribers.erase(it);
333 }
334
335 return true;
336 }
337
readFile(String8 filePath) const338 int EmulatedCameraHotplugThread::readFile(String8 filePath) const {
339
340 int fd = TEMP_FAILURE_RETRY(
341 open(filePath.string(), O_RDONLY, /*mode*/0));
342 if (fd == -1) {
343 ALOGE("%s: Could not open file '%s', error: '%s' (%d)",
344 __FUNCTION__, filePath.string(), strerror(errno), errno);
345 return -1;
346 }
347
348 char buffer[1];
349 int length;
350
351 length = TEMP_FAILURE_RETRY(
352 read(fd, buffer, sizeof(buffer)));
353
354 int retval;
355
356 ALOGV("%s: Read file '%s', length='%d', buffer='%c'",
357 __FUNCTION__, filePath.string(), length, buffer[0]);
358
359 if (length == 0) { // EOF
360 retval = 0; // empty file is the same thing as 0
361 } else if (buffer[0] == '0') {
362 retval = 0;
363 } else { // anything non-empty that's not beginning with '0'
364 retval = 1;
365 }
366
367 TEMP_FAILURE_RETRY(close(fd));
368
369 return retval;
370 }
371
372 } //namespace android
373