Bug 890427 - Let the Camera app know when the preview has actually started/stopped. r=jst
authorMike Habicher <mikeh@mozilla.com>
Mon, 05 Aug 2013 16:24:43 -0400
changeset 149368 4eef0297964828812429752b80505d871fcc02b6
parent 149367 db78acb0cbc3831581ec8d570d4e4eab67eaaa37
child 149369 134040a98d40dc71af0bb81ee3db3c54036de827
push id4254
push userakeybl@mozilla.com
push dateTue, 17 Sep 2013 14:18:33 +0000
treeherdermozilla-aurora@9edd56e694b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs890427
milestone25.0a1
Bug 890427 - Let the Camera app know when the preview has actually started/stopped. r=jst
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,13 +1,14 @@
 /* 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"
 #include "nsGlobalWindow.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -15,24 +16,26 @@ using namespace mozilla::idl;
 
 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);
 }
@@ -232,16 +235,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()
@@ -249,16 +266,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();
@@ -322,16 +340,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_ASSUME_UNREACHABLE("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
@@ -66,16 +66,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)
   {
@@ -97,16 +99,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();
 
@@ -127,16 +135,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().
@@ -145,16 +154,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
@@ -691,11 +701,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
@@ -234,16 +234,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);
 
   mozilla::idl::CameraStartRecordingOptions options;
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -671,16 +671,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);
 
@@ -689,16 +691,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();
 }
@@ -858,16 +861,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
@@ -39,16 +39,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<idl::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
@@ -194,33 +194,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;
 
@@ -311,16 +317,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