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