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