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 48 if (createFileIfNotExists(id)) { 49 mSubscribedCameraIds.push_back(id); 50 } 51 } 52 } 53 54 EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() { 55 } 56 57 status_t EmulatedCameraHotplugThread::requestExitAndWait() { 58 ALOGE("%s: Not implemented. Use requestExit + join instead", 59 __FUNCTION__); 60 return INVALID_OPERATION; 61 } 62 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 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 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 222 String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const { 223 return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId); 224 } 225 226 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 250 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 265 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 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 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 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 338 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