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