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