Bug 890427 - Let the Camera app know when the preview has actually started/stopped. r=jst, a=leo+
authorMike Habicher <mikeh@mozilla.com>
Wed, 07 Aug 2013 19:51:54 -0400
changeset 119839 1433d653b701fcec58d146f92c90d4630beec958
parent 119838 c0295995da59ae372e7733b6da5d24178ba3a9a5
child 119840 88d329180da4c2c506280e03d9dcafada3728d63
push id1022
push userryanvm@gmail.com
push dateWed, 07 Aug 2013 23:53:41 +0000
reviewersjst, leo
bugs890427
milestone18.1
Bug 890427 - Let the Camera app know when the preview has actually started/stopped. r=jst, a=leo+
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlImpl.h
dom/camera/DOMCameraControl.cpp
dom/camera/GonkCameraControl.cpp
dom/camera/ICameraControl.h
dom/camera/nsIDOMCameraManager.idl
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -1,35 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
+#include "mozilla/Assertions.h"
 #include "DOMCameraPreview.h"
 #include "CameraRecorderProfiles.h"
 #include "CameraControlImpl.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 
 CameraControlImpl::CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread, uint64_t aWindowId)
   : mCameraId(aCameraId)
   , mCameraThread(aCameraThread)
   , mWindowId(aWindowId)
   , mFileFormat()
   , mMaxMeteringAreas(0)
   , mMaxFocusAreas(0)
+  , mPreviewState(PREVIEW_STOPPED)
   , mDOMPreview(nullptr)
   , mAutoFocusOnSuccessCb(nullptr)
   , mAutoFocusOnErrorCb(nullptr)
   , mTakePictureOnSuccessCb(nullptr)
   , mTakePictureOnErrorCb(nullptr)
   , mOnShutterCb(nullptr)
   , mOnClosedCb(nullptr)
   , mOnRecorderStateChangeCb(nullptr)
+  , mOnPreviewStateChangeCb(nullptr)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
 CameraControlImpl::~CameraControlImpl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
@@ -229,16 +232,30 @@ CameraControlImpl::Set(nsICameraRecorder
 
 nsresult
 CameraControlImpl::Get(nsICameraRecorderStateChange** aOnRecorderStateChange)
 {
   *aOnRecorderStateChange = mOnRecorderStateChangeCb;
   return NS_OK;
 }
 
+nsresult
+CameraControlImpl::Set(nsICameraPreviewStateChange* aOnPreviewStateChange)
+{
+  mOnPreviewStateChangeCb = new nsMainThreadPtrHolder<nsICameraPreviewStateChange>(aOnPreviewStateChange);
+  return NS_OK;
+}
+
+nsresult
+CameraControlImpl::Get(nsICameraPreviewStateChange** aOnPreviewStateChange)
+{
+  *aOnPreviewStateChange = mOnPreviewStateChangeCb;
+  return NS_OK;
+}
+
 already_AddRefed<RecorderProfileManager>
 CameraControlImpl::GetRecorderProfileManager()
 {
   return GetRecorderProfileManagerImpl();
 }
 
 void
 CameraControlImpl::Shutdown()
@@ -246,16 +263,17 @@ CameraControlImpl::Shutdown()
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
   mAutoFocusOnSuccessCb = nullptr;
   mAutoFocusOnErrorCb = nullptr;
   mTakePictureOnSuccessCb = nullptr;
   mTakePictureOnErrorCb = nullptr;
   mOnShutterCb = nullptr;
   mOnClosedCb = nullptr;
   mOnRecorderStateChangeCb = nullptr;
+  mOnPreviewStateChangeCb = nullptr;
 }
 
 void
 CameraControlImpl::OnShutterInternal()
 {
   DOM_CAMERA_LOGI("** SNAP **\n");
   if (mOnShutterCb.get()) {
     mOnShutterCb->HandleEvent();
@@ -319,16 +337,49 @@ CameraControlImpl::OnRecorderStateChange
 
   nsCOMPtr<nsIRunnable> onRecorderStateChange = new CameraRecorderStateChange(mOnRecorderStateChangeCb, aStateMsg, aStatus, aTrackNumber, mWindowId);
   nsresult rv = NS_DispatchToMainThread(onRecorderStateChange);
   if (NS_FAILED(rv)) {
     DOM_CAMERA_LOGE("Failed to dispatch onRecorderStateChange event to main thread (%d)\n", rv);
   }
 }
 
+void
+CameraControlImpl::OnPreviewStateChange(PreviewState aNewState)
+{
+  if (aNewState == mPreviewState) {
+    DOM_CAMERA_LOGI("OnPreviewStateChange: state did not change from %d\n", mPreviewState);
+    return;
+  }
+
+  nsString msg;
+  switch (aNewState) {
+    case PREVIEW_STOPPED:
+      msg = NS_LITERAL_STRING("stopped");
+      break;
+
+    case PREVIEW_STARTED:
+      msg = NS_LITERAL_STRING("started");
+      break;
+
+    default:
+      MOZ_NOT_REACHED("Preview state can only be PREVIEW_STOPPED or _STARTED!");
+  }
+
+  // const nsString& aStateMsg)
+  DOM_CAMERA_LOGI("OnPreviewStateChange: '%s'\n", NS_ConvertUTF16toUTF8(msg).get());
+  mPreviewState = aNewState;
+
+  nsCOMPtr<nsIRunnable> onPreviewStateChange = new CameraPreviewStateChange(mOnPreviewStateChangeCb, msg, mWindowId);
+  nsresult rv = NS_DispatchToMainThread(onPreviewStateChange);
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGE("Failed to dispatch onPreviewStateChange event to main thread (%d)\n", rv);
+  }
+}
+
 nsresult
 CameraControlImpl::GetPreviewStream(CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
 {
   nsCOMPtr<nsIRunnable> getPreviewStreamTask = new GetPreviewStreamTask(this, aSize, onSuccess, onError);
   return mCameraThread->Dispatch(getPreviewStreamTask, NS_DISPATCH_NORMAL);
 }
 
 nsresult
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -67,16 +67,18 @@ public:
   nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit);
   nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue);
   nsresult Set(nsICameraShutterCallback* aOnShutter);
   nsresult Get(nsICameraShutterCallback** aOnShutter);
   nsresult Set(nsICameraClosedCallback* aOnClosed);
   nsresult Get(nsICameraClosedCallback** aOnClosed);
   nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange);
   nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange);
+  nsresult Set(nsICameraPreviewStateChange* aOnPreviewStateChange);
+  nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange);
 
   nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue)
   {
     return Set(aCx, CAMERA_PARAM_FOCUSAREAS, aValue, mMaxFocusAreas);
   }
 
   nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue)
   {
@@ -98,16 +100,22 @@ public:
   virtual nsresult PushParameters() = 0;
   virtual void Shutdown();
 
   bool ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder);
   void OnShutter();
   void OnClosed();
   void OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber);
 
+  enum PreviewState {
+    PREVIEW_STOPPED,
+    PREVIEW_STARTED
+  };
+  void OnPreviewStateChange(PreviewState aNewState);
+
   uint64_t GetWindowId()
   {
     return mWindowId;
   }
 
 protected:
   virtual ~CameraControlImpl();
 
@@ -128,16 +136,17 @@ protected:
   void OnClosedInternal();
 
   uint32_t            mCameraId;
   nsCOMPtr<nsIThread> mCameraThread;
   uint64_t            mWindowId;
   nsString            mFileFormat;
   uint32_t            mMaxMeteringAreas;
   uint32_t            mMaxFocusAreas;
+  PreviewState        mPreviewState;
 
   /**
    * 'mDOMPreview' is a raw pointer to the object that will receive incoming
    * preview frames.  This is guaranteed to be valid, or null.
    *
    * It is set by a call to StartPreview(), and set to null on StopPreview().
    * It is up to the caller to ensure that the object will not disappear
    * out from under this pointer--usually by calling NS_ADDREF().
@@ -146,16 +155,17 @@ protected:
 
   nsMainThreadPtrHandle<nsICameraAutoFocusCallback>   mAutoFocusOnSuccessCb;
   nsMainThreadPtrHandle<nsICameraErrorCallback>       mAutoFocusOnErrorCb;
   nsMainThreadPtrHandle<nsICameraTakePictureCallback> mTakePictureOnSuccessCb;
   nsMainThreadPtrHandle<nsICameraErrorCallback>       mTakePictureOnErrorCb;
   nsMainThreadPtrHandle<nsICameraShutterCallback>     mOnShutterCb;
   nsMainThreadPtrHandle<nsICameraClosedCallback>      mOnClosedCb;
   nsMainThreadPtrHandle<nsICameraRecorderStateChange> mOnRecorderStateChangeCb;
+  nsMainThreadPtrHandle<nsICameraPreviewStateChange>  mOnPreviewStateChangeCb;
 
 private:
   CameraControlImpl(const CameraControlImpl&) MOZ_DELETE;
   CameraControlImpl& operator=(const CameraControlImpl&) MOZ_DELETE;
 };
 
 // Error result runnable
 class CameraErrorResult : public nsRunnable
@@ -692,11 +702,37 @@ public:
 protected:
   nsMainThreadPtrHandle<nsICameraRecorderStateChange> mOnStateChangeCb;
   const nsString mStateMsg;
   int32_t mStatus;
   int32_t mTrackNumber;
   uint64_t mWindowId;
 };
 
+// Report that the preview stream state has changed.
+class CameraPreviewStateChange : public nsRunnable
+{
+public:
+  CameraPreviewStateChange(nsMainThreadPtrHandle<nsICameraPreviewStateChange> onStateChange, const nsString& aStateMsg, uint64_t aWindowId)
+    : mOnStateChangeCb(onStateChange)
+    , mStateMsg(aStateMsg)
+    , mWindowId(aWindowId)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnStateChangeCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
+      mOnStateChangeCb->HandleStateChange(mStateMsg);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsMainThreadPtrHandle<nsICameraPreviewStateChange> mOnStateChangeCb;
+  const nsString mStateMsg;
+  uint64_t mWindowId;
+};
+
 } // namespace mozilla
 
 #endif // DOM_CAMERA_CAMERACONTROLIMPL_H
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -239,16 +239,28 @@ nsDOMCameraControl::GetOnRecorderStateCh
   return mCameraControl->Get(aOnRecorderStateChange);
 }
 NS_IMETHODIMP
 nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange)
 {
   return mCameraControl->Set(aOnRecorderStateChange);
 }
 
+/* attribute nsICameraPreviewStateChange onPreviewStateChange; */
+NS_IMETHODIMP
+nsDOMCameraControl::GetOnPreviewStateChange(nsICameraPreviewStateChange** aOnPreviewStateChange)
+{
+  return mCameraControl->Get(aOnPreviewStateChange);
+}
+NS_IMETHODIMP
+nsDOMCameraControl::SetOnPreviewStateChange(nsICameraPreviewStateChange* aOnPreviewStateChange)
+{
+  return mCameraControl->Set(aOnPreviewStateChange);
+}
+
 /* [implicit_jscontext] void startRecording (in jsval aOptions, in nsIDOMDeviceStorage storageArea, in DOMString filename, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
 NS_IMETHODIMP
 nsDOMCameraControl::StartRecording(const JS::Value& aOptions, nsIDOMDeviceStorage* storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
 {
   NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
   NS_ENSURE_TRUE(storageArea, NS_ERROR_INVALID_ARG);
 
   CameraStartRecordingOptions options;
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -666,16 +666,18 @@ nsGonkCameraControl::StartPreviewImpl(St
   if (mCameraHw->StartPreview() != OK) {
     DOM_CAMERA_LOGE("%s: failed to start preview\n", __func__);
     return NS_ERROR_FAILURE;
   }
 
   if (aStartPreview->mDOMPreview) {
     mDOMPreview->Started();
   }
+
+  OnPreviewStateChange(PREVIEW_STARTED);
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::StopPreviewInternal(bool aForced)
 {
   DOM_CAMERA_LOGI("%s: stopping preview (mDOMPreview=%p)\n", __func__, mDOMPreview);
 
@@ -684,16 +686,17 @@ nsGonkCameraControl::StopPreviewInternal
   if (mDOMPreview) {
     if (mCameraHw.get()) {
       mCameraHw->StopPreview();
     }
     mDOMPreview->Stopped(aForced);
     mDOMPreview = nullptr;
   }
 
+  OnPreviewStateChange(PREVIEW_STOPPED);
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::StopPreviewImpl(StopPreviewTask* aStopPreview)
 {
   return StopPreviewInternal();
 }
@@ -853,16 +856,20 @@ nsGonkCameraControl::TakePictureImpl(Tak
   }
 
   mDeferConfigUpdate = false;
   PushParameters();
 
   if (mCameraHw->TakePicture() != OK) {
     return NS_ERROR_FAILURE;
   }
+  
+  // In Gonk, taking a picture implicitly kills the preview stream,
+  // so we need to reflect that here.
+  OnPreviewStateChange(PREVIEW_STOPPED);
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::PushParametersImpl()
 {
   DOM_CAMERA_LOGI("Pushing camera parameters\n");
   RETURN_IF_NO_CAMERA_HW();
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -40,16 +40,18 @@ public:
   virtual nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit) = 0;
   virtual nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue) = 0;
   virtual nsresult Set(nsICameraShutterCallback* aOnShutter) = 0;
   virtual nsresult Get(nsICameraShutterCallback** aOnShutter) = 0;
   virtual nsresult Set(nsICameraClosedCallback* aOnClosed) = 0;
   virtual nsresult Get(nsICameraClosedCallback** aOnClosed) = 0;
   virtual nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange) = 0;
   virtual nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange) = 0;
+  virtual nsresult Set(nsICameraPreviewStateChange* aOnPreviewStateChange) = 0;
+  virtual nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange) = 0;
   virtual nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue) = 0;
   virtual nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue) = 0;
   virtual nsresult GetVideoSizes(nsTArray<CameraSize>& aVideoSizes) = 0;
   virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManager() = 0;
   virtual uint32_t GetCameraId() = 0;
 
   virtual const char* GetParameter(const char* aKey) = 0;
   virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
--- a/dom/camera/nsIDOMCameraManager.idl
+++ b/dom/camera/nsIDOMCameraManager.idl
@@ -240,33 +240,39 @@ interface nsICameraClosedCallback : nsIS
 };
 
 [scriptable, function, uuid(550d675a-257d-4713-8b3d-0da53eba68fc)]
 interface nsICameraRecorderStateChange : nsISupports
 {
     void handleStateChange(in DOMString newState);
 };
 
+[scriptable, function, uuid(d1634592-43fd-4117-a2b2-419aec841cc4)]
+interface nsICameraPreviewStateChange : nsISupports
+{
+    void handleStateChange(in DOMString newState);
+};
+
 [scriptable, function, uuid(f84d607b-554c-413d-8810-cf848642765a)]
 interface nsICameraReleaseCallback : nsISupports
 {
     void handleEvent();
 };
 
 [scriptable, function, uuid(a302c6c9-3776-4d1d-a395-f4105d47c3d3)]
 interface nsICameraErrorCallback : nsISupports
 {
     void handleEvent(in DOMString error);
 };
 
 /*
     attributes here affect the preview, any pictures taken, and/or
     any video recorded by the camera.
 */
-[scriptable, uuid(c8e7418d-8913-4b66-bd9f-562fba627266)]
+[scriptable, uuid(74dc7f1f-c88f-4774-860b-44aef9de5dc8)]
 interface nsICameraControl : nsISupports
 {
     readonly attribute nsICameraCapabilities capabilities;
 
     /* one of the vales chosen from capabilities.effects;
        default is "none" */
     attribute DOMString         effect;
 
@@ -357,16 +363,22 @@ interface nsICameraControl : nsISupports
        recent call to get the camera. */
     attribute nsICameraClosedCallback onClosed;
 
     /* the function to call when the recorder changes state, either because
        the recording process encountered an error, or because one of the
        recording limits (see CameraStartRecordingOptions) was reached. */
     attribute nsICameraRecorderStateChange onRecorderStateChange;
 
+    /* the function to call when the preview stream is actually started and
+       stopped; this is usually used to enable and disable the camera UI,
+       since the low-level hardware often does not support taking pictures
+       unless the preview is running. */
+    attribute nsICameraPreviewStateChange onPreviewStateChange;
+
     /* tell the camera to attempt to focus the image */
     void autoFocus(in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError);
 
     /* capture an image and return it as a blob to the 'onSuccess' callback;
        if the camera supports it, this may be invoked while the camera is
        already recording video.
 
        invoking this function will stop the preview stream, which must be