Bug 965421 - camera continuous autofocus status callback, r=jst,mikeh
authorYoungwoo Jo <hiro7998@gmail.com>
Wed, 09 Apr 2014 11:12:25 -0400
changeset 178407 3f481b3c3e7120b8c2a708b9b477f7180c23d356
parent 178406 e1c19ed25b12e8d8a03c26f7d562561d66c9a521
child 178408 e6ac52f874d097648fcf91fd2db247af3a297d6b
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjst, mikeh
bugs965421
milestone31.0a1
Bug 965421 - camera continuous autofocus status callback, r=jst,mikeh
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlImpl.h
dom/camera/CameraControlListener.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControl.h
dom/camera/DOMCameraControlListener.cpp
dom/camera/DOMCameraControlListener.h
dom/camera/FallbackCameraControl.cpp
dom/camera/GonkCameraControl.cpp
dom/camera/GonkCameraControl.h
dom/camera/GonkCameraHwMgr.cpp
dom/camera/TestGonkCameraHardware.cpp
dom/camera/TestGonkCameraHardware.h
dom/camera/test/mochitest.ini
dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
dom/webidl/CameraControl.webidl
modules/libpref/src/init/all.js
xpcom/glue/nsCycleCollectionParticipant.h
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -116,16 +116,27 @@ CameraControlImpl::OnAutoFocusComplete(b
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
     CameraControlListener* l = mListeners[i];
     l->OnAutoFocusComplete(aAutoFocusSucceeded);
   }
 }
 
 void
+CameraControlImpl::OnAutoFocusMoving(bool aIsMoving)
+{
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnAutoFocusMoving(aIsMoving);
+  }
+}
+
+void
 CameraControlImpl::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
 {
   // This callback can run on threads other than the Main Thread and
   //  the Camera Thread. On Gonk, it is called from the camera
   //  library's snapshot thread.
   RwLockAutoEnterRead lock(mListenerLock);
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -48,20 +48,22 @@ public:
                                   const StartRecordingOptions* aOptions) MOZ_OVERRIDE;
   virtual nsresult StopRecording() MOZ_OVERRIDE;
 
   already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
   uint32_t GetCameraId() { return mCameraId; }
 
   virtual void Shutdown() MOZ_OVERRIDE;
 
+  // Event handlers called directly from outside this class.
   void OnShutter();
   void OnClosed();
   void OnError(CameraControlListener::CameraErrorContext aContext,
                CameraControlListener::CameraError aError);
+  void OnAutoFocusMoving(bool aIsMoving);
 
 protected:
   // Event handlers.
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
 
   bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
   void OnRecorderStateChange(CameraControlListener::RecorderState aState,
--- a/dom/camera/CameraControlListener.h
+++ b/dom/camera/CameraControlListener.h
@@ -73,16 +73,17 @@ public:
   {
   public:
     uint32_t mMaxMeteringAreas;
     uint32_t mMaxFocusAreas;
   };
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) { }
 
   virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) { }
+  virtual void OnAutoFocusMoving(bool aIsMoving) { }
   virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { }
 
   enum CameraErrorContext
   {
     kInStartCamera,
     kInStopCamera,
     kInAutoFocus,
     kInTakePicture,
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -38,17 +38,17 @@ using namespace mozilla::ipc;
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMCameraControl, DOMMediaStream)
 NS_IMPL_RELEASE_INHERITED(nsDOMCameraControl, DOMMediaStream)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_18(nsDOMCameraControl, DOMMediaStream,
+NS_IMPL_CYCLE_COLLECTION_INHERITED_19(nsDOMCameraControl, DOMMediaStream,
                                       mCapabilities,
                                       mWindow,
                                       mGetCameraOnSuccessCb,
                                       mGetCameraOnErrorCb,
                                       mAutoFocusOnSuccessCb,
                                       mAutoFocusOnErrorCb,
                                       mTakePictureOnSuccessCb,
                                       mTakePictureOnErrorCb,
@@ -56,17 +56,18 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED_18(ns
                                       mStartRecordingOnErrorCb,
                                       mReleaseOnSuccessCb,
                                       mReleaseOnErrorCb,
                                       mSetConfigurationOnSuccessCb,
                                       mSetConfigurationOnErrorCb,
                                       mOnShutterCb,
                                       mOnClosedCb,
                                       mOnRecorderStateChangeCb,
-                                      mOnPreviewStateChangeCb)
+                                      mOnPreviewStateChangeCb,
+                                      mOnAutoFocusMovingCb)
 
 class mozilla::StartRecordingHelper : public nsIDOMEventListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 
   StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl)
@@ -137,16 +138,17 @@ nsDOMCameraControl::nsDOMCameraControl(u
   , mReleaseOnSuccessCb(nullptr)
   , mReleaseOnErrorCb(nullptr)
   , mSetConfigurationOnSuccessCb(nullptr)
   , mSetConfigurationOnErrorCb(nullptr)
   , mOnShutterCb(nullptr)
   , mOnClosedCb(nullptr)
   , mOnRecorderStateChangeCb(nullptr)
   , mOnPreviewStateChangeCb(nullptr)
+  , mOnAutoFocusMovingCb(nullptr)
   , mWindow(aWindow)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mInput = new CameraPreviewMediaStream(this);
 
   SetIsDOMBinding();
 
   nsRefPtr<DOMCameraConfiguration> initialConfig =
@@ -658,16 +660,28 @@ nsDOMCameraControl::GetOnPreviewStateCha
   return cb.forget();
 }
 void
 nsDOMCameraControl::SetOnPreviewStateChange(CameraPreviewStateChange* aCb)
 {
   mOnPreviewStateChangeCb = aCb;
 }
 
+already_AddRefed<CameraAutoFocusMovingCallback>
+nsDOMCameraControl::GetOnAutoFocusMoving()
+{
+  nsRefPtr<CameraAutoFocusMovingCallback> cb = mOnAutoFocusMovingCb;
+  return cb.forget();
+}
+void
+nsDOMCameraControl::SetOnAutoFocusMoving(CameraAutoFocusMovingCallback* aCb)
+{
+  mOnAutoFocusMovingCb = aCb;
+}
+
 already_AddRefed<dom::CameraCapabilities>
 nsDOMCameraControl::Capabilities()
 {
   nsRefPtr<CameraCapabilities> caps = mCapabilities;
 
   if (!caps) {
     caps = new CameraCapabilities(mWindow);
     nsresult rv = caps->Populate(mCameraControl);
@@ -937,16 +951,17 @@ nsDOMCameraControl::Shutdown()
   mSetConfigurationOnSuccessCb = nullptr;
   mSetConfigurationOnErrorCb = nullptr;
 
   // Remove all of the unsolicited event handlers too.
   mOnShutterCb = nullptr;
   mOnClosedCb = nullptr;
   mOnRecorderStateChangeCb = nullptr;
   mOnPreviewStateChangeCb = nullptr;
+  mOnAutoFocusMovingCb = nullptr;
 
   mCameraControl->Shutdown();
 }
 
 nsresult
 nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg)
 {
   NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
@@ -1134,16 +1149,28 @@ nsDOMCameraControl::OnAutoFocusComplete(
   mAutoFocusOnErrorCb = nullptr;
   if (cb) {
     ErrorResult ignored;
     cb->Call(aAutoFocusSucceeded, ignored);
   }
 }
 
 void
+nsDOMCameraControl::OnAutoFocusMoving(bool aIsMoving)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsRefPtr<CameraAutoFocusMovingCallback> cb = mOnAutoFocusMovingCb;
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(aIsMoving, ignored);
+  }
+}
+
+void
 nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb.forget();
   mTakePictureOnErrorCb = nullptr;
   if (!cb) {
     // Warn because it shouldn't be possible to get here without
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -86,16 +86,18 @@ public:
   already_AddRefed<dom::CameraShutterCallback> GetOnShutter();
   void SetOnShutter(dom::CameraShutterCallback* aCb);
   already_AddRefed<dom::CameraClosedCallback> GetOnClosed();
   void SetOnClosed(dom::CameraClosedCallback* aCb);
   already_AddRefed<dom::CameraRecorderStateChange> GetOnRecorderStateChange();
   void SetOnRecorderStateChange(dom::CameraRecorderStateChange* aCb);
   already_AddRefed<dom::CameraPreviewStateChange> GetOnPreviewStateChange();
   void SetOnPreviewStateChange(dom::CameraPreviewStateChange* aCb);
+  already_AddRefed<dom::CameraAutoFocusMovingCallback> GetOnAutoFocusMoving();
+  void SetOnAutoFocusMoving(dom::CameraAutoFocusMovingCallback* aCb);
 
   // Methods.
   void SetConfiguration(const dom::CameraConfiguration& aConfiguration,
                         const dom::Optional<dom::OwningNonNull<dom::CameraSetConfigurationCallback> >& aOnSuccess,
                         const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
                         ErrorResult& aRv);
   void AutoFocus(dom::CameraAutoFocusCallback& aOnSuccess,
                  const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
@@ -139,16 +141,17 @@ protected:
   };
 
   friend class DOMCameraControlListener;
   friend class mozilla::StartRecordingHelper;
 
   void OnCreatedFileDescriptor(bool aSucceeded);
 
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
+  void OnAutoFocusMoving(bool aIsMoving);
   void OnTakePictureComplete(nsIDOMBlob* aPicture);
 
   void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState);
   void OnPreviewStateChange(DOMCameraControlListener::PreviewState aState);
   void OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNum);
   void OnConfigurationChange(DOMCameraConfiguration* aConfiguration);
   void OnShutter();
   void OnError(CameraControlListener::CameraErrorContext aContext, const nsAString& mError);
@@ -182,16 +185,17 @@ protected:
   nsRefPtr<dom::CameraSetConfigurationCallback> mSetConfigurationOnSuccessCb;
   nsRefPtr<dom::CameraErrorCallback>            mSetConfigurationOnErrorCb;
 
   // unsolicited event handlers
   nsRefPtr<dom::CameraShutterCallback>          mOnShutterCb;
   nsRefPtr<dom::CameraClosedCallback>           mOnClosedCb;
   nsRefPtr<dom::CameraRecorderStateChange>      mOnRecorderStateChangeCb;
   nsRefPtr<dom::CameraPreviewStateChange>       mOnPreviewStateChangeCb;
+  nsRefPtr<dom::CameraAutoFocusMovingCallback>  mOnAutoFocusMovingCb;
 
   // Camera event listener; we only need this weak reference so that
   //  we can remove the listener from the camera when we're done
   //  with it.
   DOMCameraControlListener* mListener;
 
   // our viewfinder stream
   CameraPreviewMediaStream* mInput;
--- a/dom/camera/DOMCameraControlListener.cpp
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -214,16 +214,40 @@ DOMCameraControlListener::OnConfiguratio
   protected:
     const CameraListenerConfiguration mConfiguration;
   };
 
   NS_DispatchToMainThread(new Callback(mDOMCameraControl, aConfiguration));
 }
 
 void
+DOMCameraControlListener::OnAutoFocusMoving(bool aIsMoving)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl, bool aIsMoving)
+      : DOMCallback(aDOMCameraControl)
+      , mIsMoving(aIsMoving)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnAutoFocusMoving(mIsMoving);
+    }
+
+  protected:
+    bool mIsMoving;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aIsMoving));
+}
+
+void
 DOMCameraControlListener::OnShutter()
 {
   class Callback : public DOMCallback
   {
   public:
     Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl)
       : DOMCallback(aDOMCameraControl)
     { }
--- a/dom/camera/DOMCameraControlListener.h
+++ b/dom/camera/DOMCameraControlListener.h
@@ -14,16 +14,17 @@ class nsDOMCameraControl;
 class CameraPreviewMediaStream;
 
 class DOMCameraControlListener : public CameraControlListener
 {
 public:
   DOMCameraControlListener(nsDOMCameraControl* aDOMCameraControl, CameraPreviewMediaStream* aStream);
 
   virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) MOZ_OVERRIDE;
+  virtual void OnAutoFocusMoving(bool aIsMoving) MOZ_OVERRIDE;
   virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) MOZ_OVERRIDE;
 
   virtual void OnHardwareStateChange(HardwareState aState) MOZ_OVERRIDE;
   virtual void OnPreviewStateChange(PreviewState aState) MOZ_OVERRIDE;
   virtual void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum) MOZ_OVERRIDE;
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) MOZ_OVERRIDE;
   virtual void OnShutter() MOZ_OVERRIDE;
   virtual bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) MOZ_OVERRIDE;
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -20,16 +20,17 @@ namespace mozilla {
  * definition of new camera support classes.
  */
 class FallbackCameraControl : public CameraControlImpl
 {
 public:
   FallbackCameraControl(uint32_t aCameraId) : CameraControlImpl(aCameraId) { }
 
   void OnAutoFocusComplete(bool aSuccess);
+  void OnAutoFocusMoving(bool aIsMoving) { }
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength) { }
   void OnTakePictureError() { }
   void OnNewPreviewFrame(layers::GraphicBufferLocked* aBuffer) { }
   void OnRecorderEvent(int msg, int ext1, int ext2) { }
   void OnError(CameraControlListener::CameraErrorContext aWhere,
                CameraControlListener::CameraError aError) { }
 
   virtual nsresult Set(uint32_t aKey, const nsAString& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -1466,16 +1466,22 @@ OnTakePictureError(nsGonkCameraControl* 
 
 void
 OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess)
 {
   gc->OnAutoFocusComplete(aSuccess);
 }
 
 void
+OnAutoFocusMoving(nsGonkCameraControl* gc, bool aIsMoving)
+{
+  gc->OnAutoFocusMoving(aIsMoving);
+}
+
+void
 OnNewPreviewFrame(nsGonkCameraControl* gc, layers::TextureClient* aBuffer)
 {
   gc->OnNewPreviewFrame(aBuffer);
 }
 
 void
 OnShutter(nsGonkCameraControl* gc)
 {
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -160,16 +160,17 @@ private:
   nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE;
   nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE;
 };
 
 // camera driver callbacks
 void OnTakePictureComplete(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength);
 void OnTakePictureError(nsGonkCameraControl* gc);
 void OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess);
+void OnAutoFocusMoving(nsGonkCameraControl* gc, bool aIsMoving);
 void OnNewPreviewFrame(nsGonkCameraControl* gc, layers::TextureClient* aBuffer);
 void OnShutter(nsGonkCameraControl* gc);
 void OnClosed(nsGonkCameraControl* gc);
 void OnError(nsGonkCameraControl* gc, CameraControlListener::CameraError aError,
              int32_t aArg1, int32_t aArg2);
 
 } // namespace mozilla
 
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -93,16 +93,22 @@ GonkCameraHardware::notify(int32_t aMsgT
     return;
   }
 
   switch (aMsgType) {
     case CAMERA_MSG_FOCUS:
       OnAutoFocusComplete(mTarget, !!ext1);
       break;
 
+#if ANDROID_VERSION >= 16
+    case CAMERA_MSG_FOCUS_MOVE:
+      OnAutoFocusMoving(mTarget, !!ext1);
+      break;
+#endif
+
     case CAMERA_MSG_SHUTTER:
       OnShutter(mTarget);
       break;
 
     case CAMERA_MSG_ERROR:
       OnError(mTarget, CameraControlListener::kErrorServiceFailed, ext1, ext2);
       break;
 
@@ -171,16 +177,23 @@ GonkCameraHardware::Init()
 #if ANDROID_VERSION >= 19
   mCamera->setPreviewTarget(mNativeWindow->getBufferQueue());
 #elif (ANDROID_VERSION == 17) || (ANDROID_VERSION == 18)
   mCamera->setPreviewTexture(mNativeWindow->getBufferQueue());
 #else
   mCamera->setPreviewTexture(mNativeWindow);
 #endif
 
+#if ANDROID_VERSION >= 16
+  rv = mCamera->sendCommand(CAMERA_CMD_ENABLE_FOCUS_MOVE_MSG, 1, 0);
+  if (rv != OK) {
+    NS_WARNING("Failed to send command CAMERA_CMD_ENABLE_FOCUS_MOVE_MSG");
+  }
+#endif
+
 #endif
 
   return NS_OK;
 }
 
 sp<GonkCameraHardware>
 GonkCameraHardware::Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId)
 {
--- a/dom/camera/TestGonkCameraHardware.cpp
+++ b/dom/camera/TestGonkCameraHardware.cpp
@@ -180,22 +180,66 @@ TestGonkCameraHardware::StartPreview()
   if (IsTestCase("start-preview-failure")) {
     return TestCaseError(UNKNOWN_ERROR);
   }
 
   return GonkCameraHardware::StartPreview();
 }
 
 int
+TestGonkCameraHardware::StartAutoFocusMoving(bool aIsMoving)
+{
+  class AutoFocusMoving : public nsRunnable
+  {
+  public:
+    AutoFocusMoving(nsGonkCameraControl* aTarget, bool aIsMoving)
+      : mTarget(aTarget)
+      , mIsMoving(aIsMoving)
+    { }
+
+    NS_IMETHODIMP
+    Run()
+    {
+      OnAutoFocusMoving(mTarget, mIsMoving);
+      return NS_OK;
+    }
+
+  protected:
+    nsGonkCameraControl* mTarget;
+    bool mIsMoving;
+  };
+
+  nsresult rv = NS_DispatchToCurrentThread(new AutoFocusMoving(mTarget, aIsMoving));
+  if (NS_SUCCEEDED(rv)) {
+    return OK;
+  }
+  DOM_CAMERA_LOGE("Failed to dispatch AutoFocusMoving runnable (0x%08x)\n", rv);
+  return UNKNOWN_ERROR;
+}
+
+int
 TestGonkCameraHardware::PushParameters(const GonkCameraParameters& aParams)
 {
   if (IsTestCase("push-parameters-failure")) {
     return TestCaseError(UNKNOWN_ERROR);
   }
 
+  nsString focusMode;
+  GonkCameraParameters& params = const_cast<GonkCameraParameters&>(aParams);
+  params.Get(CAMERA_PARAM_FOCUSMODE, focusMode);
+  if (focusMode.EqualsASCII("continuous-picture") ||
+      focusMode.EqualsASCII("continuous-video"))
+  {
+    if (IsTestCase("autofocus-moving-true")) {
+      return StartAutoFocusMoving(true);
+    } else if (IsTestCase("autofocus-moving-false")) {
+      return StartAutoFocusMoving(false);
+    }
+  }
+
   return GonkCameraHardware::PushParameters(aParams);
 }
 
 nsresult
 TestGonkCameraHardware::PullParameters(GonkCameraParameters& aParams)
 {
   if (IsTestCase("pull-parameters-failure")) {
     return static_cast<nsresult>(TestCaseError(UNKNOWN_ERROR));
--- a/dom/camera/TestGonkCameraHardware.h
+++ b/dom/camera/TestGonkCameraHardware.h
@@ -54,16 +54,18 @@ public:
   virtual nsresult Init() MOZ_OVERRIDE;
 
 protected:
   const nsCString TestCase();
   const nsCString GetExtraParameters();
   bool IsTestCaseInternal(const char* aTest, const char* aFile, int aLine);
   int TestCaseError(int aDefaultError);
 
+  int StartAutoFocusMoving(bool aIsMoving);
+
 private:
   TestGonkCameraHardware(const TestGonkCameraHardware&) MOZ_DELETE;
   TestGonkCameraHardware& operator=(const TestGonkCameraHardware&) MOZ_DELETE;
 };
 
 #define IsTestCase(test)  IsTestCaseInternal((test), __FILE__, __LINE__)
 
 } // namespace android
--- a/dom/camera/test/mochitest.ini
+++ b/dom/camera/test/mochitest.ini
@@ -3,8 +3,9 @@ support-files = camera_common.js
 
 [test_camera.html]
 [test_camera_2.html]
 [test_camera_3.html]
 [test_camera_hardware_init_failure.html]
 [test_camera_hardware_failures.html]
 [test_bug975472.html]
 [test_camera_fake_parameters.html]
+[test_camera_hardware_auto_focus_moving_cb.html]
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=965421
+-->
+<head>
+  <title>Bug 965421 - Test camera hardware API for Auto focus moving Callback</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="camera_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=965421">Mozilla Bug 965421</a>
+  <video id="viewfinder" width = "200" height = "200" autoplay></video>
+  <img src="#" alt="This image is going to load" id="testimage"/>
+
+<script class="testbody" type="text/javascript;version=1.7">
+
+var whichCamera = navigator.mozCameras.getListOfCameras()[0];
+var initialConfig = {
+  mode: 'picture',
+  recorderProfile: 'cif',
+  previewSize: {
+    width: 352,
+    height: 288
+  }
+};
+
+const PREF_AUTOFOCUSCALLBACK_ENABLED = "camera.control.autofocus_moving_callback.enabled";
+
+var cameraObj;
+var oldPref;
+
+// Shorthand functions
+function end() {
+  function reallyEnd() {
+    CameraTest.end();
+  }
+  if (oldPref) {
+    SpecialPowers.pushPrefEnv(
+      {'set': [[PREF_AUTOFOCUSCALLBACK_ENABLED, oldPref]]}, reallyEnd);
+  } else {
+    SpecialPowers.pushPrefEnv(
+      {'clear': [[PREF_AUTOFOCUSCALLBACK_ENABLED]]}, reallyEnd);
+  }
+}
+function next() {
+  CameraTest.next();
+}
+
+var tests = [
+  {
+    key: "autofocus-moving-true",
+    func: function testAutoFocusMovingIsTrue(camera) {
+      camera.onAutoFocusMoving = function(aIsMoving) {
+        ok(aIsMoving == true,"onAutoFocusMoving callback received true correctly");
+        camera.focusMode = 'auto';
+        next();
+      }
+      camera.focusMode = 'continuous-picture';
+    }
+  },
+  {
+    key: "autofocus-moving-false",
+    func: function testAutoFocusMovingIsFalse(camera) {
+      camera.onAutoFocusMoving = function(aIsMoving) {
+        ok(aIsMoving == false, "onAutoFocusMoving callback received false correctly");
+        camera.focusMode = 'auto';
+        end();
+      }
+      camera.focusMode = 'continuous-video';
+    }
+  },
+];
+
+var testGenerator = function() {
+  for (var i = 0; i < tests.length; ++i ) {
+    yield tests[i];
+  }
+}();
+
+window.addEventListener('beforeunload', function() {
+  document.getElementById('viewfinder').mozSrcObject = null;
+  cameraObj.release();
+  cameraObj = null;
+});
+
+// Must call CameraTest.begin() before any other async methods.
+CameraTest.begin("hardware", function(test) {
+  // If the pref doesn't exist, this get will fail; catch it and continue.
+  try {
+    oldPref = SpecialPowers.getBoolPref(PREF_AUTOFOCUSCALLBACK_ENABLED);
+  } catch(e) { }
+
+  SpecialPowers.pushPrefEnv({'set': [[PREF_AUTOFOCUSCALLBACK_ENABLED, true]]}, function() {
+    var enabled;
+    try {
+      enabled = SpecialPowers.getBoolPref(PREF_AUTOFOCUSCALLBACK_ENABLED);
+    } catch(e) { }
+    ok(enabled, PREF_AUTOFOCUSCALLBACK_ENABLED + " is " + enabled);
+
+    function onSuccess(camera, config) {
+      document.getElementById('viewfinder').mozSrcObject = camera;
+      cameraObj = camera;
+      CameraTest.next = function() {
+        try {
+          var t = testGenerator.next();
+          test.set(t.key, t.func.bind(undefined, camera));
+        } catch(e) {
+          if (e instanceof StopIteration) {
+            end();
+          } else {
+            throw e;
+          }
+        }
+      };
+      next();
+    }
+    function onError(error) {
+      ok(false, "getCamera() failed with: " + error);
+      end();
+    }
+    navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError);
+  })
+});
+
+</script>
+</body>
+
+</html>
--- a/dom/webidl/CameraControl.webidl
+++ b/dom/webidl/CameraControl.webidl
@@ -123,16 +123,17 @@ callback CameraSetConfigurationCallback 
 callback CameraAutoFocusCallback = void (boolean focused);
 callback CameraTakePictureCallback = void (Blob picture);
 callback CameraStartRecordingCallback = void ();
 callback CameraShutterCallback = void ();
 callback CameraClosedCallback = void ();
 callback CameraReleaseCallback = void ();
 callback CameraRecorderStateChange = void (DOMString newState);
 callback CameraPreviewStateChange = void (DOMString newState);
+callback CameraAutoFocusMovingCallback = void (boolean isMoving);
 
 /*
     attributes here affect the preview, any pictures taken, and/or
     any video recorded by the camera.
 */
 interface CameraControl : MediaStream
 {
   [Constant, Cached]
@@ -275,16 +276,25 @@ interface CameraControl : MediaStream
      then the preview stream needs to be rotated +90 degrees to have the
      same orientation as the real world. */
   readonly attribute long   sensorAngle;
 
   /* tell the camera to attempt to focus the image */
   [Throws]
   void autoFocus(CameraAutoFocusCallback onSuccess, optional CameraErrorCallback onError);
 
+  /* if continuous autofocus is supported and focusMode is set to enable it,
+     then this function is called whenever the camera decides to start and
+     stop moving the focus position; it can be used to update a UI element to
+     indicate that the camera is still trying to focus, or has finished. Some
+     platforms do not support this event, in which case the callback is never
+     invoked. */
+  [Pref="camera.control.autofocus_moving_callback.enabled"]
+  attribute CameraAutoFocusMovingCallback? onAutoFocusMoving;
+
   /* 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
      manually restarted (e.g. by calling .play() on it). */
   [Throws]
   void takePicture(CameraPictureOptions aOptions,
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4420,8 +4420,11 @@ pref("identity.fxaccounts.auth.uri", "ht
 // disable mozsample size for now
 pref("image.mozsamplesize.enabled", false);
 
 // Enable navigator.sendBeacon on all platforms except b2g because it doesn't
 // play nicely with Firefox OS apps yet.
 #ifndef MOZ_WIDGET_GONK
 pref("beacon.enabled", true);
 #endif
+
+// Camera prefs
+pref("camera.control.autofocus_moving_callback.enabled", false);
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -1457,11 +1457,58 @@ static NS_CYCLE_COLLECTION_INNERCLASS NS
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f13)                                       \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f14)                                       \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f15)                                       \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f16)                                       \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f17)                                       \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f18)                                       \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
+#define NS_IMPL_CYCLE_COLLECTION_INHERITED_19(_class, _base, _f1, _f2, _f3, _f4, _f5, \
+                                              _f6, _f7, _f8, _f9, _f10, _f11, _f12, _f13, _f14, \
+                                              _f15, _f16, _f17, _f18, _f19)    \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(_class)                                        \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base)                \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f1)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f2)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f3)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f4)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f5)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f6)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f7)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f8)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f9)                                          \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f10)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f11)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f12)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f13)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f14)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f15)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f16)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f17)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f18)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(_f19)                                         \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END                                           \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base)              \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f1)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f2)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f3)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f4)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f5)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f6)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f7)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f8)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f9)                                        \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f10)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f11)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f12)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f13)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f14)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f15)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f16)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f17)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f18)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f19)                                       \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
 #define NS_CYCLE_COLLECTION_NOTE_EDGE_NAME CycleCollectionNoteEdgeName
 
 #endif // nsCycleCollectionParticipant_h__