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 <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 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 50 EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {} 51 52 status_t EmulatedCameraHotplugThread::requestExitAndWait() { 53 ALOGE("%s: Not implemented. Use requestExit + join instead", __FUNCTION__); 54 return INVALID_OPERATION; 55 } 56 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 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 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 206 String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const { 207 return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId); 208 } 209 210 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 233 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 248 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 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 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 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 314 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