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 <log/log.h>
19
20 #include <fcntl.h>
21 #include <sys/inotify.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24
25 #include "EmulatedCameraFactory.h"
26 #include "EmulatedCameraHotplugThread.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(size_t totalCameraCount)37 EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
38 size_t totalCameraCount)
39 : Thread(/*canCallJava*/ false) {
40 mRunning = true;
41 mInotifyFd = 0;
42
43 for (size_t id = 0; id < totalCameraCount; ++id) {
44 if (createFileIfNotExists(id)) {
45 mSubscribedCameraIds.push_back(id);
46 }
47 }
48 }
49
~EmulatedCameraHotplugThread()50 EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {}
51
requestExitAndWait()52 status_t EmulatedCameraHotplugThread::requestExitAndWait() {
53 ALOGE("%s: Not implemented. Use requestExit + join instead", __FUNCTION__);
54 return INVALID_OPERATION;
55 }
56
requestExit()57 void EmulatedCameraHotplugThread::requestExit() {
58 Mutex::Autolock al(mMutex);
59
60 ALOGV("%s: Requesting thread exit", __FUNCTION__);
61 mRunning = false;
62
63 bool rmWatchFailed = false;
64 Vector<SubscriberInfo>::iterator it;
65 for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
66 if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) {
67 ALOGE(
68 "%s: Could not remove watch for camID '%d',"
69 " error: '%s' (%d)",
70 __FUNCTION__, it->CameraID, strerror(errno), errno);
71
72 rmWatchFailed = true;
73 } else {
74 ALOGV("%s: Removed watch for camID '%d'", __FUNCTION__, it->CameraID);
75 }
76 }
77
78 if (rmWatchFailed) { // unlikely
79 // Give the thread a fighting chance to error out on the next
80 // read
81 if (close(mInotifyFd) == -1) {
82 ALOGE("%s: close failure error: '%s' (%d)", __FUNCTION__, strerror(errno),
83 errno);
84 }
85 }
86
87 ALOGV("%s: Request exit complete.", __FUNCTION__);
88 }
89
readyToRun()90 status_t EmulatedCameraHotplugThread::readyToRun() {
91 Mutex::Autolock al(mMutex);
92
93 mInotifyFd = -1;
94
95 do {
96 ALOGV("%s: Initializing inotify", __FUNCTION__);
97
98 mInotifyFd = inotify_init();
99 if (mInotifyFd == -1) {
100 ALOGE("%s: inotify_init failure error: '%s' (%d)", __FUNCTION__,
101 strerror(errno), errno);
102 mRunning = false;
103 break;
104 }
105
106 /**
107 * For each fake camera file, add a watch for when
108 * the file is closed (if it was written to)
109 */
110 Vector<int>::const_iterator it, end;
111 it = mSubscribedCameraIds.begin();
112 end = mSubscribedCameraIds.end();
113 for (; it != end; ++it) {
114 int cameraId = *it;
115 if (!addWatch(cameraId)) {
116 mRunning = false;
117 break;
118 }
119 }
120 } while (false);
121
122 if (!mRunning) {
123 status_t err = -errno;
124
125 if (mInotifyFd != -1) {
126 close(mInotifyFd);
127 }
128
129 return err;
130 }
131
132 return OK;
133 }
134
threadLoop()135 bool EmulatedCameraHotplugThread::threadLoop() {
136 // If requestExit was already called, mRunning will be false
137 while (mRunning) {
138 char buffer[EVENT_BUF_LEN];
139 int length = TEMP_FAILURE_RETRY(read(mInotifyFd, buffer, EVENT_BUF_LEN));
140
141 if (length < 0) {
142 ALOGE("%s: Error reading from inotify FD, error: '%s' (%d)", __FUNCTION__,
143 strerror(errno), errno);
144 mRunning = false;
145 break;
146 }
147
148 ALOGV("%s: Read %d bytes from inotify FD", __FUNCTION__, length);
149
150 int i = 0;
151 while (i < length) {
152 inotify_event* event = (inotify_event*)&buffer[i];
153
154 if (event->mask & IN_IGNORED) {
155 Mutex::Autolock al(mMutex);
156 if (!mRunning) {
157 ALOGV("%s: Shutting down thread", __FUNCTION__);
158 break;
159 } else {
160 ALOGE("%s: File was deleted, aborting", __FUNCTION__);
161 mRunning = false;
162 break;
163 }
164 } else if (event->mask & IN_CLOSE_WRITE) {
165 int cameraId = getCameraId(event->wd);
166
167 if (cameraId < 0) {
168 ALOGE("%s: Got bad camera ID from WD '%d", __FUNCTION__, event->wd);
169 } else {
170 // Check the file for the new hotplug event
171 String8 filePath = getFilePath(cameraId);
172 /**
173 * NOTE: we carefully avoid getting an inotify
174 * for the same exact file because it's opened for
175 * read-only, but our inotify is for write-only
176 */
177 int newStatus = readFile(filePath);
178
179 if (newStatus < 0) {
180 mRunning = false;
181 break;
182 }
183
184 int halStatus = newStatus ? CAMERA_DEVICE_STATUS_PRESENT
185 : CAMERA_DEVICE_STATUS_NOT_PRESENT;
186 EmulatedCameraFactory::Instance().onStatusChanged(cameraId,
187 halStatus);
188 }
189
190 } else {
191 ALOGW("%s: Unknown mask 0x%x", __FUNCTION__, event->mask);
192 }
193
194 i += EVENT_SIZE + event->len;
195 }
196 }
197
198 if (!mRunning) {
199 close(mInotifyFd);
200 return false;
201 }
202
203 return true;
204 }
205
getFilePath(int cameraId) const206 String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
207 return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId);
208 }
209
createFileIfNotExists(int cameraId) const210 bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const {
211 String8 filePath = getFilePath(cameraId);
212 // make sure this file exists and we have access to it
213 int fd =
214 TEMP_FAILURE_RETRY(open(filePath.string(), O_WRONLY | O_CREAT | O_TRUNC,
215 /* mode = ug+rwx */ S_IRWXU | S_IRWXG));
216 if (fd == -1) {
217 ALOGE("%s: Could not create file '%s', error: '%s' (%d)", __FUNCTION__,
218 filePath.string(), strerror(errno), errno);
219 return false;
220 }
221
222 // File has '1' by default since we are plugged in by default
223 if (TEMP_FAILURE_RETRY(write(fd, "1\n", /*count*/ 2)) == -1) {
224 ALOGE("%s: Could not write '1' to file '%s', error: '%s' (%d)",
225 __FUNCTION__, filePath.string(), strerror(errno), errno);
226 return false;
227 }
228
229 close(fd);
230 return true;
231 }
232
getCameraId(String8 filePath) const233 int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const {
234 Vector<int>::const_iterator it, end;
235 it = mSubscribedCameraIds.begin();
236 end = mSubscribedCameraIds.end();
237 for (; it != end; ++it) {
238 String8 camPath = getFilePath(*it);
239
240 if (camPath == filePath) {
241 return *it;
242 }
243 }
244
245 return NAME_NOT_FOUND;
246 }
247
getCameraId(int wd) const248 int EmulatedCameraHotplugThread::getCameraId(int wd) const {
249 for (size_t i = 0; i < mSubscribers.size(); ++i) {
250 if (mSubscribers[i].WatchID == wd) {
251 return mSubscribers[i].CameraID;
252 }
253 }
254
255 return NAME_NOT_FOUND;
256 }
257
getSubscriberInfo(int cameraId)258 SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId) {
259 for (size_t i = 0; i < mSubscribers.size(); ++i) {
260 if (mSubscribers[i].CameraID == cameraId) {
261 return (SubscriberInfo*)&mSubscribers[i];
262 }
263 }
264
265 return NULL;
266 }
267
addWatch(int cameraId)268 bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
269 String8 camPath = getFilePath(cameraId);
270 int wd = inotify_add_watch(mInotifyFd, camPath.string(), IN_CLOSE_WRITE);
271
272 if (wd == -1) {
273 ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)", __FUNCTION__,
274 camPath.string(), strerror(errno), errno);
275
276 mRunning = false;
277 return false;
278 }
279
280 ALOGV("%s: Watch added for camID='%d', wd='%d'", __FUNCTION__, cameraId, wd);
281
282 SubscriberInfo si = {cameraId, wd};
283 mSubscribers.push_back(si);
284
285 return true;
286 }
287
removeWatch(int cameraId)288 bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
289 SubscriberInfo* si = getSubscriberInfo(cameraId);
290
291 if (!si) return false;
292
293 if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) {
294 ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)",
295 __FUNCTION__, cameraId, strerror(errno), errno);
296
297 return false;
298 }
299
300 Vector<SubscriberInfo>::iterator it;
301 for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
302 if (it->CameraID == cameraId) {
303 break;
304 }
305 }
306
307 if (it != mSubscribers.end()) {
308 mSubscribers.erase(it);
309 }
310
311 return true;
312 }
313
readFile(String8 filePath) const314 int EmulatedCameraHotplugThread::readFile(String8 filePath) const {
315 int fd = TEMP_FAILURE_RETRY(open(filePath.string(), O_RDONLY, /*mode*/ 0));
316 if (fd == -1) {
317 ALOGE("%s: Could not open file '%s', error: '%s' (%d)", __FUNCTION__,
318 filePath.string(), strerror(errno), errno);
319 return -1;
320 }
321
322 char buffer[1];
323 int length;
324
325 length = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
326
327 int retval;
328
329 ALOGV("%s: Read file '%s', length='%d', buffer='%c'", __FUNCTION__,
330 filePath.string(), length, buffer[0]);
331
332 if (length == 0) { // EOF
333 retval = 0; // empty file is the same thing as 0
334 } else if (buffer[0] == '0') {
335 retval = 0;
336 } else { // anything non-empty that's not beginning with '0'
337 retval = 1;
338 }
339
340 close(fd);
341
342 return retval;
343 }
344
345 } // namespace android
346