From a36381479c5c546a01188390f1e0d2e280a3dbd8 Mon Sep 17 00:00:00 2001 From: Igor Murashkin Date: Wed, 20 Mar 2013 15:56:31 -0700 Subject: Camera: Add hotplug support (for fixed # of cameras) * Minor: also change addListener to fire the current status upon subscription * Minor: STATUS_AVAILABLE is now an alias for STATUS_PRESENT and deprecated Change-Id: I254608a7332095e3ef201ffea64cff156cfc1b3e --- camera/tests/ProCameraTests.cpp | 18 ++- include/camera/ICameraServiceListener.h | 11 +- services/camera/libcameraservice/CameraService.cpp | 134 ++++++++++++++++++++- services/camera/libcameraservice/CameraService.h | 14 ++- 4 files changed, 162 insertions(+), 15 deletions(-) diff --git a/camera/tests/ProCameraTests.cpp b/camera/tests/ProCameraTests.cpp index ecc0854..5f8f772 100644 --- a/camera/tests/ProCameraTests.cpp +++ b/camera/tests/ProCameraTests.cpp @@ -587,14 +587,19 @@ TEST_F(ProCameraTest, DISABLED_StreamingImageSingle) { sp listener = new ServiceListener(); EXPECT_OK(ProCamera::addServiceListener(listener)); - ServiceListener::Status currentStatus = ServiceListener::STATUS_AVAILABLE; + ServiceListener::Status currentStatus; + + // when subscribing a new listener, + // we immediately get a callback to the current status + while (listener->waitForStatusChange(/*out*/currentStatus) != OK); + EXPECT_EQ(ServiceListener::STATUS_PRESENT, currentStatus); dout << "Will now stream and resume infinitely..." << std::endl; while (true) { - if (currentStatus == ServiceListener::STATUS_AVAILABLE) { + if (currentStatus == ServiceListener::STATUS_PRESENT) { - EXPECT_OK(mCamera->createStream(mDisplayW, mDisplayH, mDisplayFmt, + ASSERT_OK(mCamera->createStream(mDisplayW, mDisplayH, mDisplayFmt, surface, &depthStreamId)); EXPECT_NE(-1, depthStreamId); @@ -613,12 +618,15 @@ TEST_F(ProCameraTest, DISABLED_StreamingImageSingle) { while (listener->waitForStatusChange(/*out*/stat) != OK); if (currentStatus != stat) { - if (stat == ServiceListener::STATUS_AVAILABLE) { + if (stat == ServiceListener::STATUS_PRESENT) { dout << "Reconnecting to camera" << std::endl; mCamera = ProCamera::connect(CAMERA_ID); } else if (stat == ServiceListener::STATUS_NOT_AVAILABLE) { dout << "Disconnecting from camera" << std::endl; mCamera->disconnect(); + } else if (stat == ServiceListener::STATUS_NOT_PRESENT) { + dout << "Camera unplugged" << std::endl; + mCamera = NULL; } else { dout << "Unknown status change " << std::hex << stat << std::endl; @@ -1216,7 +1224,7 @@ TEST_F(ProCameraTest, ServiceListenersFunctional) { } EXPECT_OK(listener->waitForStatusChange(/*out*/stat)); - EXPECT_EQ(ServiceListener::STATUS_AVAILABLE, stat); + EXPECT_EQ(ServiceListener::STATUS_PRESENT, stat); EXPECT_OK(ProCamera::removeServiceListener(listener)); } diff --git a/include/camera/ICameraServiceListener.h b/include/camera/ICameraServiceListener.h index 88860dd..f2a11c2 100644 --- a/include/camera/ICameraServiceListener.h +++ b/include/camera/ICameraServiceListener.h @@ -38,9 +38,8 @@ public: * NOT_PRESENT -> PRESENT * NOT_PRESENT -> ENUMERATING * ENUMERATING -> PRESENT - * PRESENT -> AVAILABLE - * AVAILABLE -> NOT_AVAILABLE - * NOT_AVAILABLE -> AVAILABLE + * PRESENT -> NOT_AVAILABLE + * NOT_AVAILABLE -> PRESENT * * A state will never immediately transition back to itself. */ @@ -48,15 +47,17 @@ public: // Device physically unplugged STATUS_NOT_PRESENT = CAMERA_DEVICE_STATUS_NOT_PRESENT, // Device physically has been plugged in + // and the camera can be used exlusively STATUS_PRESENT = CAMERA_DEVICE_STATUS_PRESENT, // Device physically has been plugged in // but it will not be connect-able until enumeration is complete STATUS_ENUMERATING = CAMERA_DEVICE_STATUS_ENUMERATING, // Camera can be used exclusively - STATUS_AVAILABLE = 0x80000000, + STATUS_AVAILABLE = STATUS_PRESENT, // deprecated, will be removed + // Camera is in use by another app and cannot be used exclusively - STATUS_NOT_AVAILABLE, + STATUS_NOT_AVAILABLE = 0x80000000, // Use to initialize variables only STATUS_UNKNOWN = 0xFFFFFFFF, diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp index 5a6a3c8..2db5224 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -66,6 +66,20 @@ static int getCallingUid() { return IPCThreadState::self()->getCallingUid(); } +extern "C" { +static void camera_device_status_change( + const struct camera_module_callbacks* callbacks, + int camera_id, + int new_status) { + sp cs = const_cast( + static_cast(callbacks)); + + cs->onDeviceStatusChanged( + camera_id, + new_status); +} +} // extern "C" + // ---------------------------------------------------------------------------- // This is ugly and only safe if we never re-create the CameraService, but @@ -79,8 +93,10 @@ CameraService::CameraService() gCameraService = this; for (size_t i = 0; i < MAX_CAMERAS; ++i) { - mStatusList[i] = ICameraServiceListener::STATUS_AVAILABLE; + mStatusList[i] = ICameraServiceListener::STATUS_PRESENT; } + + this->camera_device_status_change = android::camera_device_status_change; } void CameraService::onFirstRef() @@ -105,6 +121,11 @@ void CameraService::onFirstRef() for (int i = 0; i < mNumberOfCameras; i++) { setCameraFree(i); } + + if (mModule->common.module_api_version >= + CAMERA_MODULE_API_VERSION_2_1) { + mModule->set_callbacks(this); + } } } @@ -118,6 +139,67 @@ CameraService::~CameraService() { gCameraService = NULL; } +void CameraService::onDeviceStatusChanged(int cameraId, + int newStatus) +{ + ALOGI("%s: Status changed for cameraId=%d, newStatus=%d", __FUNCTION__, + cameraId, newStatus); + + if (cameraId < 0 || cameraId >= MAX_CAMERAS) { + ALOGE("%s: Bad camera ID %d", __FUNCTION__, cameraId); + return; + } + + if ((int)getStatus(cameraId) == newStatus) { + ALOGE("%s: State transition to the same status 0x%x not allowed", + __FUNCTION__, (uint32_t)newStatus); + return; + } + + /* don't do this in updateStatus + since it is also called from connect and we could get into a deadlock */ + if (newStatus == CAMERA_DEVICE_STATUS_NOT_PRESENT) { + Vector > clientsToDisconnect; + { + Mutex::Autolock al(mServiceLock); + + /* Find all clients that we need to disconnect */ + sp client = mClient[cameraId].promote(); + if (client.get() != NULL) { + clientsToDisconnect.push_back(client); + } + + int i = cameraId; + for (size_t j = 0; j < mProClientList[i].size(); ++j) { + sp cl = mProClientList[i][j].promote(); + if (cl != NULL) { + clientsToDisconnect.push_back(cl); + } + } + } + + /* now disconnect them. don't hold the lock + or we can get into a deadlock */ + + for (size_t i = 0; i < clientsToDisconnect.size(); ++i) { + sp client = clientsToDisconnect[i]; + + client->disconnect(); + /** + * The remote app will no longer be able to call methods on the + * client since the client PID will be reset to 0 + */ + } + + ALOGV("%s: After unplug, disconnected %d clients", + __FUNCTION__, clientsToDisconnect.size()); + } + + updateStatus( + static_cast(newStatus), cameraId); + +} + int32_t CameraService::getNumberOfCameras() { return mNumberOfCameras; } @@ -212,6 +294,19 @@ bool CameraService::validateConnect(int cameraId, return false; } + ICameraServiceListener::Status currentStatus = getStatus(cameraId); + if (currentStatus == ICameraServiceListener::STATUS_NOT_PRESENT) { + ALOGI("Camera is not plugged in," + " connect X (pid %d) rejected", callingPid); + return false; + } else if (currentStatus == ICameraServiceListener::STATUS_ENUMERATING) { + ALOGI("Camera is enumerating," + " connect X (pid %d) rejected", callingPid); + return false; + } + // Else don't check for STATUS_NOT_AVAILABLE. + // -- It's done implicitly in canConnectUnsafe /w the mBusy array + return true; } @@ -293,6 +388,7 @@ sp CameraService::connect( // If there are other non-exclusive users of the camera, // this will tear them down before we can reuse the camera if (isValidCameraId(cameraId)) { + // transition from PRESENT -> NOT_AVAILABLE updateStatus(ICameraServiceListener::STATUS_NOT_AVAILABLE, cameraId); } @@ -321,7 +417,8 @@ sp CameraService::connect( if (!connectFinishUnsafe(client, client->asBinder())) { // this is probably not recoverable.. maybe the client can try again - updateStatus(ICameraServiceListener::STATUS_AVAILABLE, cameraId); + // OK: we can only get here if we were originally in PRESENT state + updateStatus(ICameraServiceListener::STATUS_PRESENT, cameraId); return NULL; } @@ -429,6 +526,15 @@ status_t CameraService::addListener( mListenerList.push_back(listener); + /* Immediately signal current status to this listener only */ + { + Mutex::Autolock m(mStatusMutex) ; + int numCams = getNumberOfCameras(); + for (int i = 0; i < numCams; ++i) { + listener->onStatusChanged(mStatusList[i], i); + } + } + return OK; } status_t CameraService::removeListener( @@ -719,6 +825,8 @@ CameraService::BasicClient::~BasicClient() { void CameraService::BasicClient::disconnect() { mCameraService->removeClientByRemote(mRemoteBinder); + // client shouldn't be able to call into us anymore + mClientPid = 0; } status_t CameraService::BasicClient::startCameraOps() { @@ -816,7 +924,7 @@ void CameraService::Client::notifyError() { void CameraService::Client::disconnect() { BasicClient::disconnect(); mCameraService->setCameraFree(mCameraId); - mCameraService->updateStatus(ICameraServiceListener::STATUS_AVAILABLE, + mCameraService->updateStatus(ICameraServiceListener::STATUS_PRESENT, mCameraId); } @@ -1017,6 +1125,16 @@ void CameraService::updateStatusUnsafe(ICameraServiceListener::Status status, ALOGV("%s: Status has changed for camera ID %d from 0x%x to 0x%x", __FUNCTION__, cameraId, (uint32_t)oldStatus, (uint32_t)status); + if (oldStatus == ICameraServiceListener::STATUS_NOT_PRESENT && + (status != ICameraServiceListener::STATUS_PRESENT && + status != ICameraServiceListener::STATUS_ENUMERATING)) { + + ALOGW("%s: From NOT_PRESENT can only transition into PRESENT" + " or ENUMERATING", __FUNCTION__); + mStatusList[cameraId] = oldStatus; + return; + } + /** * ProClients lose their exclusive lock. * - Done before the CameraClient can initialize the HAL device, @@ -1041,4 +1159,14 @@ void CameraService::updateStatusUnsafe(ICameraServiceListener::Status status, } } +ICameraServiceListener::Status CameraService::getStatus(int cameraId) const { + if (cameraId < 0 || cameraId >= MAX_CAMERAS) { + ALOGE("%s: Invalid camera ID %d", __FUNCTION__, cameraId); + return ICameraServiceListener::STATUS_UNKNOWN; + } + + Mutex::Autolock al(mStatusMutex); + return mStatusList[cameraId]; +} + }; // namespace android diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h index c5e495f..8cb1691 100644 --- a/services/camera/libcameraservice/CameraService.h +++ b/services/camera/libcameraservice/CameraService.h @@ -45,7 +45,8 @@ class MediaPlayer; class CameraService : public BinderService, public BnCameraService, - public IBinder::DeathRecipient + public IBinder::DeathRecipient, + public camera_module_callbacks_t { friend class BinderService; public: @@ -59,6 +60,11 @@ public: virtual ~CameraService(); ///////////////////////////////////////////////////////////////////// + // HAL Callbacks + virtual void onDeviceStatusChanged(int cameraId, + int newStatus); + + ///////////////////////////////////////////////////////////////////// // ICameraService virtual int32_t getNumberOfCameras(); virtual status_t getCameraInfo(int cameraId, @@ -327,10 +333,14 @@ private: mListenerList; // guard only mStatusList and the broadcasting of ICameraServiceListener - Mutex mStatusMutex; + mutable Mutex mStatusMutex; ICameraServiceListener::Status mStatusList[MAX_CAMERAS]; + // Read the current status (locks mStatusMutex) + ICameraServiceListener::Status + getStatus(int cameraId) const; + // Broadcast the new status if it changed (locks the service mutex) void updateStatus( ICameraServiceListener::Status status, -- cgit v1.1