C++程序  |  346行  |  9.2 KB

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#define LOG_NDEBUG 0
#define LOG_TAG "EmulatedCamera_HotplugThread"
#include <cutils/log.h>

#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "EmulatedCameraFactory.h"
#include "EmulatedCameraHotplugThread.h"

#define FAKE_HOTPLUG_FILE "/data/misc/media/emulator.camera.hotplug"

#define EVENT_SIZE (sizeof(struct inotify_event))
#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))

#define SubscriberInfo EmulatedCameraHotplugThread::SubscriberInfo

namespace android {

EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
    size_t totalCameraCount)
    : Thread(/*canCallJava*/ false) {
  mRunning = true;
  mInotifyFd = 0;

  for (size_t id = 0; id < totalCameraCount; ++id) {
    if (createFileIfNotExists(id)) {
      mSubscribedCameraIds.push_back(id);
    }
  }
}

EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {}

status_t EmulatedCameraHotplugThread::requestExitAndWait() {
  ALOGE("%s: Not implemented. Use requestExit + join instead", __FUNCTION__);
  return INVALID_OPERATION;
}

void EmulatedCameraHotplugThread::requestExit() {
  Mutex::Autolock al(mMutex);

  ALOGV("%s: Requesting thread exit", __FUNCTION__);
  mRunning = false;

  bool rmWatchFailed = false;
  Vector<SubscriberInfo>::iterator it;
  for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
    if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) {
      ALOGE(
          "%s: Could not remove watch for camID '%d',"
          " error: '%s' (%d)",
          __FUNCTION__, it->CameraID, strerror(errno), errno);

      rmWatchFailed = true;
    } else {
      ALOGV("%s: Removed watch for camID '%d'", __FUNCTION__, it->CameraID);
    }
  }

  if (rmWatchFailed) {  // unlikely
    // Give the thread a fighting chance to error out on the next
    // read
    if (close(mInotifyFd) == -1) {
      ALOGE("%s: close failure error: '%s' (%d)", __FUNCTION__, strerror(errno),
            errno);
    }
  }

  ALOGV("%s: Request exit complete.", __FUNCTION__);
}

status_t EmulatedCameraHotplugThread::readyToRun() {
  Mutex::Autolock al(mMutex);

  mInotifyFd = -1;

  do {
    ALOGV("%s: Initializing inotify", __FUNCTION__);

    mInotifyFd = inotify_init();
    if (mInotifyFd == -1) {
      ALOGE("%s: inotify_init failure error: '%s' (%d)", __FUNCTION__,
            strerror(errno), errno);
      mRunning = false;
      break;
    }

    /**
     * For each fake camera file, add a watch for when
     * the file is closed (if it was written to)
     */
    Vector<int>::const_iterator it, end;
    it = mSubscribedCameraIds.begin();
    end = mSubscribedCameraIds.end();
    for (; it != end; ++it) {
      int cameraId = *it;
      if (!addWatch(cameraId)) {
        mRunning = false;
        break;
      }
    }
  } while (false);

  if (!mRunning) {
    status_t err = -errno;

    if (mInotifyFd != -1) {
      close(mInotifyFd);
    }

    return err;
  }

  return OK;
}

bool EmulatedCameraHotplugThread::threadLoop() {
  // If requestExit was already called, mRunning will be false
  while (mRunning) {
    char buffer[EVENT_BUF_LEN];
    int length = TEMP_FAILURE_RETRY(read(mInotifyFd, buffer, EVENT_BUF_LEN));

    if (length < 0) {
      ALOGE("%s: Error reading from inotify FD, error: '%s' (%d)", __FUNCTION__,
            strerror(errno), errno);
      mRunning = false;
      break;
    }

    ALOGV("%s: Read %d bytes from inotify FD", __FUNCTION__, length);

    int i = 0;
    while (i < length) {
      inotify_event* event = (inotify_event*)&buffer[i];

      if (event->mask & IN_IGNORED) {
        Mutex::Autolock al(mMutex);
        if (!mRunning) {
          ALOGV("%s: Shutting down thread", __FUNCTION__);
          break;
        } else {
          ALOGE("%s: File was deleted, aborting", __FUNCTION__);
          mRunning = false;
          break;
        }
      } else if (event->mask & IN_CLOSE_WRITE) {
        int cameraId = getCameraId(event->wd);

        if (cameraId < 0) {
          ALOGE("%s: Got bad camera ID from WD '%d", __FUNCTION__, event->wd);
        } else {
          // Check the file for the new hotplug event
          String8 filePath = getFilePath(cameraId);
          /**
           * NOTE: we carefully avoid getting an inotify
           * for the same exact file because it's opened for
           * read-only, but our inotify is for write-only
           */
          int newStatus = readFile(filePath);

          if (newStatus < 0) {
            mRunning = false;
            break;
          }

          int halStatus = newStatus ? CAMERA_DEVICE_STATUS_PRESENT
                                    : CAMERA_DEVICE_STATUS_NOT_PRESENT;
          EmulatedCameraFactory::Instance().onStatusChanged(cameraId,
                                                            halStatus);
        }

      } else {
        ALOGW("%s: Unknown mask 0x%x", __FUNCTION__, event->mask);
      }

      i += EVENT_SIZE + event->len;
    }
  }

  if (!mRunning) {
    close(mInotifyFd);
    return false;
  }

  return true;
}

String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
  return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId);
}

bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const {
  String8 filePath = getFilePath(cameraId);
  // make sure this file exists and we have access to it
  int fd =
      TEMP_FAILURE_RETRY(open(filePath.string(), O_WRONLY | O_CREAT | O_TRUNC,
                              /* mode = ug+rwx */ S_IRWXU | S_IRWXG));
  if (fd == -1) {
    ALOGE("%s: Could not create file '%s', error: '%s' (%d)", __FUNCTION__,
          filePath.string(), strerror(errno), errno);
    return false;
  }

  // File has '1' by default since we are plugged in by default
  if (TEMP_FAILURE_RETRY(write(fd, "1\n", /*count*/ 2)) == -1) {
    ALOGE("%s: Could not write '1' to file '%s', error: '%s' (%d)",
          __FUNCTION__, filePath.string(), strerror(errno), errno);
    return false;
  }

  close(fd);
  return true;
}

int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const {
  Vector<int>::const_iterator it, end;
  it = mSubscribedCameraIds.begin();
  end = mSubscribedCameraIds.end();
  for (; it != end; ++it) {
    String8 camPath = getFilePath(*it);

    if (camPath == filePath) {
      return *it;
    }
  }

  return NAME_NOT_FOUND;
}

int EmulatedCameraHotplugThread::getCameraId(int wd) const {
  for (size_t i = 0; i < mSubscribers.size(); ++i) {
    if (mSubscribers[i].WatchID == wd) {
      return mSubscribers[i].CameraID;
    }
  }

  return NAME_NOT_FOUND;
}

SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId) {
  for (size_t i = 0; i < mSubscribers.size(); ++i) {
    if (mSubscribers[i].CameraID == cameraId) {
      return (SubscriberInfo*)&mSubscribers[i];
    }
  }

  return NULL;
}

bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
  String8 camPath = getFilePath(cameraId);
  int wd = inotify_add_watch(mInotifyFd, camPath.string(), IN_CLOSE_WRITE);

  if (wd == -1) {
    ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)", __FUNCTION__,
          camPath.string(), strerror(errno), errno);

    mRunning = false;
    return false;
  }

  ALOGV("%s: Watch added for camID='%d', wd='%d'", __FUNCTION__, cameraId, wd);

  SubscriberInfo si = {cameraId, wd};
  mSubscribers.push_back(si);

  return true;
}

bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
  SubscriberInfo* si = getSubscriberInfo(cameraId);

  if (!si) return false;

  if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) {
    ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)",
          __FUNCTION__, cameraId, strerror(errno), errno);

    return false;
  }

  Vector<SubscriberInfo>::iterator it;
  for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
    if (it->CameraID == cameraId) {
      break;
    }
  }

  if (it != mSubscribers.end()) {
    mSubscribers.erase(it);
  }

  return true;
}

int EmulatedCameraHotplugThread::readFile(String8 filePath) const {
  int fd = TEMP_FAILURE_RETRY(open(filePath.string(), O_RDONLY, /*mode*/ 0));
  if (fd == -1) {
    ALOGE("%s: Could not open file '%s', error: '%s' (%d)", __FUNCTION__,
          filePath.string(), strerror(errno), errno);
    return -1;
  }

  char buffer[1];
  int length;

  length = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));

  int retval;

  ALOGV("%s: Read file '%s', length='%d', buffer='%c'", __FUNCTION__,
        filePath.string(), length, buffer[0]);

  if (length == 0) {  // EOF
    retval = 0;       // empty file is the same thing as 0
  } else if (buffer[0] == '0') {
    retval = 0;
  } else {  // anything non-empty that's not beginning with '0'
    retval = 1;
  }

  close(fd);

  return retval;
}

}  // namespace android