blob: ecdcb0d98bbec3b77aaae8a2f3810f1a0b777876
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 | |
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 | #if 0 |
48 | if (createFileIfNotExists(id)) { |
49 | mSubscribedCameraIds.push_back(id); |
50 | } |
51 | #endif |
52 | } |
53 | } |
54 | |
55 | EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() { |
56 | } |
57 | |
58 | status_t EmulatedCameraHotplugThread::requestExitAndWait() { |
59 | ALOGE("%s: Not implemented. Use requestExit + join instead", |
60 | __FUNCTION__); |
61 | return INVALID_OPERATION; |
62 | } |
63 | |
64 | void EmulatedCameraHotplugThread::requestExit() { |
65 | Mutex::Autolock al(mMutex); |
66 | |
67 | ALOGV("%s: Requesting thread exit", __FUNCTION__); |
68 | mRunning = false; |
69 | |
70 | bool rmWatchFailed = false; |
71 | Vector<SubscriberInfo>::iterator it; |
72 | for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) { |
73 | |
74 | #if 0 |
75 | if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) { |
76 | |
77 | ALOGE("%s: Could not remove watch for camID '%d'," |
78 | " error: '%s' (%d)", |
79 | __FUNCTION__, it->CameraID, strerror(errno), |
80 | errno); |
81 | |
82 | rmWatchFailed = true ; |
83 | } else { |
84 | ALOGV("%s: Removed watch for camID '%d'", |
85 | __FUNCTION__, it->CameraID); |
86 | } |
87 | #endif |
88 | } |
89 | |
90 | if (rmWatchFailed) { // unlikely |
91 | // Give the thread a fighting chance to error out on the next |
92 | // read |
93 | if (TEMP_FAILURE_RETRY(close(mInotifyFd)) == -1) { |
94 | ALOGE("%s: close failure error: '%s' (%d)", |
95 | __FUNCTION__, strerror(errno), errno); |
96 | } |
97 | } |
98 | if (shutdown(mSocketFd, SHUT_RD) < 0) { |
99 | CAMHAL_LOGDB("shutdown socket failed errno=%s", strerror(errno)); |
100 | } |
101 | if (close(mSocketFd) < 0) { |
102 | CAMHAL_LOGDB("close socket failed errno=%s", strerror(errno)); |
103 | } |
104 | |
105 | ALOGV("%s: Request exit complete.", __FUNCTION__); |
106 | } |
107 | |
108 | status_t EmulatedCameraHotplugThread::readyToRun() { |
109 | Mutex::Autolock al(mMutex); |
110 | |
111 | mInotifyFd = -1; |
112 | |
113 | do { |
114 | ALOGV("%s: Initializing inotify", __FUNCTION__); |
115 | |
116 | #if 0 |
117 | mInotifyFd = inotify_init(); |
118 | if (mInotifyFd == -1) { |
119 | ALOGE("%s: inotify_init failure error: '%s' (%d)", |
120 | __FUNCTION__, strerror(errno), errno); |
121 | mRunning = false; |
122 | break; |
123 | } |
124 | #endif |
125 | memset(&sa,0,sizeof(sa)); |
126 | sa.nl_family = AF_NETLINK; |
127 | sa.nl_groups = NETLINK_KOBJECT_UEVENT; |
128 | sa.nl_pid = 0;//getpid(); both is ok |
129 | |
130 | mSocketFd = socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT); |
131 | if (mSocketFd == -1) { |
132 | mRunning = false; |
133 | CAMHAL_LOGEB("socket creating failed:%s, disable the hotplug thread\n",strerror(errno)); |
134 | } |
135 | |
136 | if (bind(mSocketFd,(struct sockaddr *)&sa,sizeof(sa)) == -1) { |
137 | mRunning = false; |
138 | CAMHAL_LOGEB("bind error:%s, disable the hotplug thread\n",strerror(errno)); |
139 | } |
140 | |
141 | /** |
142 | * For each fake camera file, add a watch for when |
143 | * the file is closed (if it was written to) |
144 | */ |
145 | Vector<int>::const_iterator it, end; |
146 | it = mSubscribedCameraIds.begin(); |
147 | end = mSubscribedCameraIds.end(); |
148 | for (; it != end; ++it) { |
149 | int cameraId = *it; |
150 | if (!addWatch(cameraId)) { |
151 | mRunning = false; |
152 | break; |
153 | } |
154 | } |
155 | } while(false); |
156 | |
157 | if (!mRunning) { |
158 | status_t err = -errno; |
159 | |
160 | #if 0 |
161 | if (mInotifyFd != -1) { |
162 | TEMP_FAILURE_RETRY(close(mInotifyFd)); |
163 | } |
164 | #endif |
165 | |
166 | return err; |
167 | } |
168 | |
169 | return OK; |
170 | } |
171 | |
172 | bool EmulatedCameraHotplugThread::threadLoop() { |
173 | |
174 | // If requestExit was already called, mRunning will be false |
175 | int len; |
176 | char buf[4096]; |
177 | struct iovec iov; |
178 | struct msghdr msg; |
179 | char *video4linux_string; |
180 | char *action_string; |
181 | int i; |
182 | int cameraId; |
183 | int halStatus; |
184 | |
185 | while (mRunning) { |
186 | memset(&msg,0,sizeof(msg)); |
187 | iov.iov_base=(void *)buf; |
188 | iov.iov_len=sizeof(buf); |
189 | msg.msg_name=(void *)&sa; |
190 | msg.msg_namelen=sizeof(sa); |
191 | msg.msg_iov=&iov; |
192 | msg.msg_iovlen=1; |
193 | |
194 | len = recvmsg(mSocketFd, &msg, 0); |
195 | if (len < 0) { |
196 | break; |
197 | } else if ((len<32) || (len > (int)sizeof(buf))) { |
198 | CAMHAL_LOGDA("invalid message"); |
199 | break; |
200 | } |
201 | buf[len] = '\0'; |
202 | |
203 | CAMHAL_LOGDB("buf=%s\n", buf); |
204 | video4linux_string = strstr(buf, "video4linux"); |
205 | CAMHAL_LOGVB("video4linux=%s\n", video4linux_string); |
206 | if (video4linux_string == NULL) { |
207 | CAMHAL_LOGDA("not video event\n"); |
208 | break; |
209 | } |
210 | |
211 | CAMHAL_LOGVB("video=%s\n", video4linux_string); |
212 | action_string = strchr(video4linux_string, '\0'); |
213 | action_string ++; |
214 | CAMHAL_LOGDB("action string=%s\n", action_string); |
215 | |
216 | if (strstr(action_string, "ACTION=add") != NULL) { |
217 | halStatus = CAMERA_DEVICE_STATUS_PRESENT; |
218 | } else if (strstr(action_string, "ACTION=remove") != NULL) { |
219 | halStatus = CAMERA_DEVICE_STATUS_NOT_PRESENT; |
220 | } else { |
221 | CAMHAL_LOGDA("no find add or remove\n"); |
222 | break; |
223 | } |
224 | |
225 | //string like that: add@/devices/lm1/usb1/1-1/1-1.3/1-1.3:1.0/video4linux/video0 |
226 | video4linux_string += 17; |
227 | cameraId = strtol(video4linux_string, NULL, 10); |
228 | |
229 | gEmulatedCameraFactory.onStatusChanged(cameraId, |
230 | halStatus); |
231 | |
232 | } |
233 | |
234 | if (!mRunning) { |
235 | //TEMP_FAILURE_RETRY(close(mInotifyFd)); |
236 | return false; |
237 | } |
238 | |
239 | return true; |
240 | } |
241 | |
242 | String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const { |
243 | return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId); |
244 | } |
245 | |
246 | bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const |
247 | { |
248 | String8 filePath = getFilePath(cameraId); |
249 | // make sure this file exists and we have access to it |
250 | int fd = TEMP_FAILURE_RETRY( |
251 | open(filePath.string(), O_WRONLY | O_CREAT | O_TRUNC, |
252 | /* mode = ug+rwx */ S_IRWXU | S_IRWXG )); |
253 | if (fd == -1) { |
254 | ALOGE("%s: Could not create file '%s', error: '%s' (%d)", |
255 | __FUNCTION__, filePath.string(), strerror(errno), errno); |
256 | return false; |
257 | } |
258 | |
259 | // File has '1' by default since we are plugged in by default |
260 | if (TEMP_FAILURE_RETRY(write(fd, "1\n", /*count*/2)) == -1) { |
261 | ALOGE("%s: Could not write '1' to file '%s', error: '%s' (%d)", |
262 | __FUNCTION__, filePath.string(), strerror(errno), errno); |
263 | return false; |
264 | } |
265 | |
266 | TEMP_FAILURE_RETRY(close(fd)); |
267 | return true; |
268 | } |
269 | |
270 | int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const { |
271 | Vector<int>::const_iterator it, end; |
272 | it = mSubscribedCameraIds.begin(); |
273 | end = mSubscribedCameraIds.end(); |
274 | for (; it != end; ++it) { |
275 | String8 camPath = getFilePath(*it); |
276 | |
277 | if (camPath == filePath) { |
278 | return *it; |
279 | } |
280 | } |
281 | |
282 | return NAME_NOT_FOUND; |
283 | } |
284 | |
285 | int EmulatedCameraHotplugThread::getCameraId(int wd) const { |
286 | for (size_t i = 0; i < mSubscribers.size(); ++i) { |
287 | if (mSubscribers[i].WatchID == wd) { |
288 | return mSubscribers[i].CameraID; |
289 | } |
290 | } |
291 | |
292 | return NAME_NOT_FOUND; |
293 | } |
294 | |
295 | SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId) |
296 | { |
297 | for (size_t i = 0; i < mSubscribers.size(); ++i) { |
298 | if (mSubscribers[i].CameraID == cameraId) { |
299 | return (SubscriberInfo*)&mSubscribers[i]; |
300 | } |
301 | } |
302 | |
303 | return NULL; |
304 | } |
305 | |
306 | bool EmulatedCameraHotplugThread::addWatch(int cameraId) { |
307 | String8 camPath = getFilePath(cameraId); |
308 | int wd = 0; |
309 | #if 0 |
310 | int wd = inotify_add_watch(mInotifyFd, |
311 | camPath.string(), |
312 | IN_CLOSE_WRITE); |
313 | |
314 | if (wd == -1) { |
315 | ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)", |
316 | __FUNCTION__, camPath.string(), strerror(errno), |
317 | errno); |
318 | |
319 | mRunning = false; |
320 | return false; |
321 | } |
322 | #endif |
323 | |
324 | ALOGV("%s: Watch added for camID='%d', wd='%d'", |
325 | __FUNCTION__, cameraId, wd); |
326 | |
327 | SubscriberInfo si = { cameraId, wd }; |
328 | mSubscribers.push_back(si); |
329 | |
330 | return true; |
331 | } |
332 | |
333 | bool EmulatedCameraHotplugThread::removeWatch(int cameraId) { |
334 | SubscriberInfo* si = getSubscriberInfo(cameraId); |
335 | |
336 | if (!si) return false; |
337 | |
338 | #if 0 |
339 | if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) { |
340 | |
341 | ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)", |
342 | __FUNCTION__, cameraId, strerror(errno), |
343 | errno); |
344 | |
345 | return false; |
346 | } |
347 | #endif |
348 | |
349 | Vector<SubscriberInfo>::iterator it; |
350 | for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) { |
351 | if (it->CameraID == cameraId) { |
352 | break; |
353 | } |
354 | } |
355 | |
356 | if (it != mSubscribers.end()) { |
357 | mSubscribers.erase(it); |
358 | } |
359 | |
360 | return true; |
361 | } |
362 | |
363 | int EmulatedCameraHotplugThread::readFile(String8 filePath) const { |
364 | |
365 | int fd = TEMP_FAILURE_RETRY( |
366 | open(filePath.string(), O_RDONLY, /*mode*/0)); |
367 | if (fd == -1) { |
368 | ALOGE("%s: Could not open file '%s', error: '%s' (%d)", |
369 | __FUNCTION__, filePath.string(), strerror(errno), errno); |
370 | return -1; |
371 | } |
372 | |
373 | char buffer[1]; |
374 | int length; |
375 | |
376 | length = TEMP_FAILURE_RETRY( |
377 | read(fd, buffer, sizeof(buffer))); |
378 | |
379 | int retval; |
380 | |
381 | ALOGV("%s: Read file '%s', length='%d', buffer='%c'", |
382 | __FUNCTION__, filePath.string(), length, buffer[0]); |
383 | |
384 | if (length == 0) { // EOF |
385 | retval = 0; // empty file is the same thing as 0 |
386 | } else if (buffer[0] == '0') { |
387 | retval = 0; |
388 | } else { // anything non-empty that's not beginning with '0' |
389 | retval = 1; |
390 | } |
391 | |
392 | TEMP_FAILURE_RETRY(close(fd)); |
393 | |
394 | return retval; |
395 | } |
396 | |
397 | } //namespace android |
398 |