camera: implement flashlight control

Implement flashlight API for module v2.4 by calling module APIs and
by for hal v2 and v3 by using CameraDeviceBase.

Bug: 2682206
Change-Id: Ib8b77f6fd462489d672f27e14fe37801d35b7544
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 485b979..488fc42 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -86,6 +86,38 @@
         camera_id,
         new_status);
 }
+
+static void torch_mode_status_change(
+        const struct camera_module_callbacks* callbacks,
+        const char* camera_id,
+        int new_status) {
+    if (!callbacks || !camera_id) {
+        ALOGE("%s invalid parameters. callbacks %p, camera_id %p", __FUNCTION__,
+                callbacks, camera_id);
+    }
+    sp<CameraService> cs = const_cast<CameraService*>(
+                                static_cast<const CameraService*>(callbacks));
+
+    ICameraServiceListener::TorchStatus status;
+    switch (new_status) {
+        case TORCH_MODE_STATUS_AVAILABLE:
+            status = ICameraServiceListener::TORCH_STATUS_AVAILABLE;
+            break;
+        case TORCH_MODE_STATUS_RESOURCE_BUSY:
+            status = ICameraServiceListener::TORCH_STATUS_NOT_AVAILABLE;
+            break;
+        case TORCH_MODE_STATUS_OFF:
+            status = ICameraServiceListener::TORCH_STATUS_OFF;
+            break;
+        default:
+            ALOGE("Unknown torch status %d", new_status);
+            return;
+    }
+
+    cs->onTorchStatusChanged(
+        String16(camera_id),
+        status);
+}
 } // extern "C"
 
 // ----------------------------------------------------------------------------
@@ -95,7 +127,7 @@
 static CameraService *gCameraService;
 
 CameraService::CameraService()
-    :mSoundRef(0), mModule(0)
+    :mSoundRef(0), mModule(0), mFlashlight(0)
 {
     ALOGI("CameraService started (pid=%d)", getpid());
     gCameraService = this;
@@ -105,6 +137,8 @@
     }
 
     this->camera_device_status_change = android::camera_device_status_change;
+    this->torch_mode_status_change = android::torch_mode_status_change;
+
 }
 
 void CameraService::onFirstRef()
@@ -121,6 +155,8 @@
     }
     else {
         mModule = new CameraModule(rawModule);
+        mFlashlight = new CameraFlashlight(*mModule, *this);
+
         const hw_module_t *common = mModule->getRawModule();
         ALOGI("Loaded \"%s\" camera module", common->name);
         mNumberOfCameras = mModule->getNumberOfCameras();
@@ -131,6 +167,12 @@
         }
         for (int i = 0; i < mNumberOfCameras; i++) {
             setCameraFree(i);
+
+            String16 cameraName = String16(String8::format("%d", i));
+            if (mFlashlight->hasFlashUnit(cameraName)) {
+                mTorchStatusMap.add(cameraName,
+                        ICameraServiceListener::TORCH_STATUS_AVAILABLE);
+            }
         }
 
         if (common->module_api_version >= CAMERA_MODULE_API_VERSION_2_1) {
@@ -225,6 +267,37 @@
 
 }
 
+void CameraService::onTorchStatusChanged(const String16& cameraId,
+        ICameraServiceListener::TorchStatus newStatus) {
+    Mutex::Autolock al(mTorchStatusMutex);
+    onTorchStatusChangedLocked(cameraId, newStatus);
+}
+
+void CameraService::onTorchStatusChangedLocked(const String16& cameraId,
+        ICameraServiceListener::TorchStatus newStatus) {
+    ALOGI("%s: Torch status changed for cameraId=%s, newStatus=%d",
+            __FUNCTION__, cameraId.string(), newStatus);
+
+    if (getTorchStatusLocked(cameraId) == newStatus) {
+        ALOGE("%s: Torch state transition to the same status 0x%x not allowed",
+              __FUNCTION__, (uint32_t)newStatus);
+        return;
+    }
+
+    status_t res = setTorchStatusLocked(cameraId, newStatus);
+    if (res) {
+        ALOGE("%s: Failed to set the torch status", __FUNCTION__,
+                (uint32_t)newStatus);
+        return;
+    }
+
+    Vector<sp<ICameraServiceListener> >::const_iterator it;
+    for (it = mListenerList.begin(); it != mListenerList.end(); ++it) {
+        (*it)->onTorchStatusChanged(newStatus, cameraId);
+    }
+}
+
+
 int32_t CameraService::getNumberOfCameras() {
     return mNumberOfCameras;
 }
@@ -676,6 +749,9 @@
         int halVersion,
         bool legacyMode) {
 
+    // give flashlight a chance to close devices if necessary.
+    mFlashlight->prepareDeviceOpen();
+
     int facing = -1;
     int deviceVersion = getDeviceVersion(cameraId, &facing);
 
@@ -852,6 +928,47 @@
     return OK;
 }
 
+status_t CameraService::setTorchMode(const String16& cameraId, bool enabled,
+        const sp<IBinder>& clientBinder) {
+    if (enabled && clientBinder == NULL) {
+        ALOGE("%s: torch client binder is NULL", __FUNCTION__);
+        return -ENOSYS;
+    }
+
+    Mutex::Autolock al(mTorchStatusMutex);
+    status_t res = mFlashlight->setTorchMode(cameraId, enabled);
+    if (res) {
+        ALOGE("%s: setting torch mode of camera %s to %d failed", __FUNCTION__,
+                cameraId.string(), enabled);
+        return res;
+    }
+
+    // update the link to client's death
+    ssize_t index = mTorchClientMap.indexOfKey(cameraId);
+    if (enabled) {
+        if (index == NAME_NOT_FOUND) {
+            mTorchClientMap.add(cameraId, clientBinder);
+        } else {
+            const sp<IBinder> oldBinder = mTorchClientMap.valueAt(index);
+            oldBinder->unlinkToDeath(this);
+
+            mTorchClientMap.replaceValueAt(index, clientBinder);
+        }
+        clientBinder->linkToDeath(this);
+    } else if (index != NAME_NOT_FOUND) {
+        sp<IBinder> oldBinder = mTorchClientMap.valueAt(index);
+        oldBinder->unlinkToDeath(this);
+    }
+
+    // notify the listeners the change.
+    ICameraServiceListener::TorchStatus status = enabled ?
+            ICameraServiceListener::TORCH_STATUS_ON :
+            ICameraServiceListener::TORCH_STATUS_OFF;
+    onTorchStatusChangedLocked(cameraId, status);
+
+    return OK;
+}
+
 status_t CameraService::connectFinishUnsafe(const sp<BasicClient>& client,
                                             const sp<IBinder>& remoteCallback) {
     status_t status = client->initialize(mModule);
@@ -977,6 +1094,9 @@
         int facing = -1;
         int deviceVersion = getDeviceVersion(cameraId, &facing);
 
+        // give flashlight a chance to close devices if necessary.
+        mFlashlight->prepareDeviceOpen();
+
         switch(deviceVersion) {
           case CAMERA_DEVICE_API_VERSION_1_0:
             ALOGW("Camera using old HAL version: %d", deviceVersion);
@@ -1048,6 +1168,16 @@
         }
     }
 
+    /* Immediately signal current torch status to this listener only */
+    {
+        Mutex::Autolock al(mTorchStatusMutex);
+        for (size_t i = 0; i < mTorchStatusMap.size(); i++ ) {
+            listener->onTorchStatusChanged(mTorchStatusMap.valueAt(i),
+                    mTorchStatusMap.keyAt(i));
+        }
+
+    }
+
     return OK;
 }
 status_t CameraService::removeListener(
@@ -1727,6 +1857,23 @@
     return NO_ERROR;
 }
 
+void CameraService::handleTorchClientBinderDied(const wp<IBinder> &who) {
+    Mutex::Autolock al(mTorchStatusMutex);
+    for (size_t i = 0; i < mTorchClientMap.size(); i++) {
+        if (mTorchClientMap[i] == who) {
+            // turn off the torch mode that was turned on by dead client
+            String16 cameraId = mTorchClientMap.keyAt(i);
+            mFlashlight->setTorchMode(cameraId, false);
+            mTorchClientMap.removeItemsAt(i);
+
+            // notify torch mode was turned off
+            onTorchStatusChangedLocked(cameraId,
+                    ICameraServiceListener::TORCH_STATUS_OFF);
+            break;
+        }
+    }
+}
+
 /*virtual*/void CameraService::binderDied(
     const wp<IBinder> &who) {
 
@@ -1737,6 +1884,10 @@
 
     ALOGV("java clients' binder died");
 
+    // check torch client
+    handleTorchClientBinderDied(who);
+
+    // check camera device client
     sp<BasicClient> cameraClient = getClientByRemote(who);
 
     if (cameraClient == 0) {
@@ -1830,4 +1981,27 @@
     return mStatusList[cameraId];
 }
 
+ICameraServiceListener::TorchStatus CameraService::getTorchStatusLocked(
+        const String16& cameraId) const {
+    ssize_t index = mTorchStatusMap.indexOfKey(cameraId);
+    if (index == NAME_NOT_FOUND) {
+        return ICameraServiceListener::TORCH_STATUS_NOT_AVAILABLE;
+    }
+
+    return mTorchStatusMap.valueAt(index);
+}
+
+status_t CameraService::setTorchStatusLocked(const String16& cameraId,
+        ICameraServiceListener::TorchStatus status) {
+    ssize_t index = mTorchStatusMap.indexOfKey(cameraId);
+    if (index == NAME_NOT_FOUND) {
+        return BAD_VALUE;
+    }
+    ICameraServiceListener::TorchStatus& item =
+            mTorchStatusMap.editValueAt(index);
+    item = status;
+
+    return OK;
+}
+
 }; // namespace android