Bug 785124 - Pt 1 - Add VolumeMountLock which allows SDCard to be locked. r=dougt
authorDave Hylands <dhylands@gmail.com>
Fri, 14 Dec 2012 16:01:34 -0800
changeset 118382 eea4fefd010bfeda7dcb422fe6e96051f0c4f05a
parent 118381 da9cd7ac8713d0f2d0560a90de8691420f3313b1
child 118383 82c54850f723a76881fcff1d828f5c55c4d4f9d7
push id24166
push userMs2ger@gmail.com
push dateFri, 11 Jan 2013 13:57:41 +0000
treeherdermozilla-central@63c4b0f66a0c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougt
bugs785124, 100644
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 785124 - Pt 1 - Add VolumeMountLock which allows SDCard to be locked. r=dougt From 8e39b8e5f3ab7e6344b0a8a5eeabdcf672de8fb4 Mon Sep 17 00:00:00 2001 --- dom/ipc/ContentChild.cpp | 18 +++- dom/ipc/ContentChild.h | 5 +- dom/ipc/ContentParent.cpp | 22 +++- dom/ipc/ContentParent.h | 2 + dom/ipc/PContent.ipdl | 6 +- dom/system/gonk/AutoMounter.cpp | 19 +++- dom/system/gonk/Makefile.in | 4 +- dom/system/gonk/Volume.cpp | 62 ++++++++++- dom/system/gonk/Volume.h | 11 +- dom/system/gonk/VolumeServiceIOThread.cpp | 11 +- dom/system/gonk/VolumeServiceIOThread.h | 7 +- dom/system/gonk/nsIVolume.idl | 21 +++- dom/system/gonk/nsIVolumeMountLock.idl | 12 +++ dom/system/gonk/nsIVolumeService.idl | 9 +- dom/system/gonk/nsVolume.cpp | 96 ++++++++++++++++- dom/system/gonk/nsVolume.h | 43 ++++++-- dom/system/gonk/nsVolumeMountLock.cpp | 157 +++++++++++++++++++++++++++ dom/system/gonk/nsVolumeMountLock.h | 55 ++++++++++ dom/system/gonk/nsVolumeService.cpp | 168 +++++++++++++++++++++++------ dom/system/gonk/nsVolumeService.h | 20 +++- layout/build/nsLayoutModule.cpp | 5 +- layout/build/nsLayoutStatics.cpp | 9 ++ 22 files changed, 684 insertions(+), 78 deletions(-) create mode 100644 dom/system/gonk/nsIVolumeMountLock.idl create mode 100644 dom/system/gonk/nsVolumeMountLock.cpp create mode 100644 dom/system/gonk/nsVolumeMountLock.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/system/gonk/AutoMounter.cpp
dom/system/gonk/Makefile.in
dom/system/gonk/Volume.cpp
dom/system/gonk/Volume.h
dom/system/gonk/VolumeServiceIOThread.cpp
dom/system/gonk/VolumeServiceIOThread.h
dom/system/gonk/nsIVolume.idl
dom/system/gonk/nsIVolumeMountLock.idl
dom/system/gonk/nsIVolumeService.idl
dom/system/gonk/nsVolume.cpp
dom/system/gonk/nsVolume.h
dom/system/gonk/nsVolumeMountLock.cpp
dom/system/gonk/nsVolumeMountLock.h
dom/system/gonk/nsVolumeService.cpp
dom/system/gonk/nsVolumeService.h
layout/build/nsLayoutModule.cpp
layout/build/nsLayoutStatics.cpp
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -25,16 +25,17 @@
 #include "mozilla/ipc/TestShellChild.h"
 #include "mozilla/ipc/XPCShellEnvironment.h"
 #include "mozilla/jsipc/PContextWrapperChild.h"
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/PCompositorChild.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/unused.h"
 
 #include "nsIMemoryReporter.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIObserverService.h"
 #include "nsTObserverArray.h"
 #include "nsIObserver.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsServiceManagerUtils.h"
@@ -96,16 +97,17 @@
 #include "URIUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsContentUtils.h"
 #include "nsIPrincipal.h"
 #include "nsDeviceStorage.h"
 #include "AudioChannelService.h"
 
 using namespace base;
+using namespace mozilla;
 using namespace mozilla::docshell;
 using namespace mozilla::dom::bluetooth;
 using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::sms;
 using namespace mozilla::dom::indexedDB;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
@@ -1081,22 +1083,32 @@ ContentChild::RecvFilePathUpdate(const n
     nsString reason;
     CopyASCIItoUTF16(aReason, reason);
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(dsf, "file-watcher-update", reason.get());
     return true;
 }
 
 bool
-ContentChild::RecvFileSystemUpdate(const nsString& aFsName, const nsString& aName, const int32_t &aState)
+ContentChild::RecvFileSystemUpdate(const nsString& aFsName,
+                                   const nsString& aName,
+                                   const int32_t& aState,
+                                   const int32_t& aMountGeneration)
 {
 #ifdef MOZ_WIDGET_GONK
-    nsRefPtr<nsVolume> volume = new nsVolume(aFsName, aName, aState);
+    nsRefPtr<nsVolume> volume = new nsVolume(aFsName, aName, aState,
+                                             aMountGeneration);
 
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     nsString stateStr(NS_ConvertUTF8toUTF16(volume->StateStr()));
     obs->NotifyObservers(volume, NS_VOLUME_STATE_CHANGED, stateStr.get());
+#else
+    // Remove warnings about unused arguments
+    unused << aFsName;
+    unused << aName;
+    unused << aState;
+    unused << aMountGeneration;
 #endif
     return true;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -178,17 +178,20 @@ public:
     virtual bool RecvGarbageCollect();
     virtual bool RecvCycleCollect();
 
     virtual bool RecvAppInfo(const nsCString& version, const nsCString& buildID);
 
     virtual bool RecvLastPrivateDocShellDestroyed();
 
     virtual bool RecvFilePathUpdate(const nsString& type, const nsString& path, const nsCString& reason);
-    virtual bool RecvFileSystemUpdate(const nsString& aFsName, const nsString& aName, const int32_t& aState);
+    virtual bool RecvFileSystemUpdate(const nsString& aFsName,
+                                      const nsString& aName,
+                                      const int32_t& aState,
+                                      const int32_t& aMountGeneration);
 
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
     nsString &GetIndexedDBPath();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -97,16 +97,17 @@
 
 #ifdef MOZ_WIDGET_ANDROID
 # include "AndroidBridge.h"
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
+using namespace mozilla::system;
 #endif
 
 #ifdef MOZ_B2G_BT
 #include "BluetoothParent.h"
 #include "BluetoothService.h"
 #endif
 
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
@@ -230,19 +231,19 @@ ContentParent::MaybeTakePreallocatedAppP
     nsRefPtr<ContentParent> process = sPreallocatedAppProcess.get();
     sPreallocatedAppProcess = nullptr;
     return process.forget();
 }
 
 /*static*/ void
 ContentParent::FirstIdle(void)
 {
-  // The parent has gone idle for the first time. This would be a good
-  // time to preallocate an app process.
-  ScheduleDelayedPreallocateAppProcess();
+    // The parent has gone idle for the first time. This would be a good
+    // time to preallocate an app process.
+    ScheduleDelayedPreallocateAppProcess();
 }
 
 /*static*/ void
 ContentParent::StartUp()
 {
     if (XRE_GetProcessType() != GeckoProcessType_Default) {
         return;
     }
@@ -1145,16 +1146,32 @@ ContentParent::RecvAudioChannelChangedNo
     nsRefPtr<AudioChannelService> service =
         AudioChannelService::GetAudioChannelService();
     if (service) {
        service->SendAudioChannelChangedNotification();
     }
     return true;
 }
 
+bool
+ContentParent::RecvBroadcastVolume(const nsString& aVolumeName)
+{
+#ifdef MOZ_WIDGET_GONK
+    nsresult rv;
+    nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID, &rv);
+    if (vs) {
+        vs->BroadcastVolume(aVolumeName);
+    }
+    return true;
+#else
+    NS_WARNING("ContentParent::RecvBroadcastVolume shouldn't be called when MOZ_WIDGET_GONK is not defined");
+    return false;
+#endif
+}
+
 NS_IMPL_THREADSAFE_ISUPPORTS3(ContentParent,
                               nsIObserver,
                               nsIThreadObserver,
                               nsIDOMGeoPositionCallback)
 
 NS_IMETHODIMP
 ContentParent::Observe(nsISupports* aSubject,
                        const char* aTopic,
@@ -1224,22 +1241,25 @@ ContentParent::Observe(nsISupports* aSub
         nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
         if (!vol) {
             return NS_ERROR_NOT_AVAILABLE;
         }
 
         nsString volName;
         nsString mountPoint;
         int32_t  state;
+        int32_t  mountGeneration;
 
         vol->GetName(volName);
         vol->GetMountPoint(mountPoint);
         vol->GetState(&state);
+        vol->GetMountGeneration(&mountGeneration);
 
-        unused << SendFileSystemUpdate(volName, mountPoint, state);
+        unused << SendFileSystemUpdate(volName, mountPoint, state,
+                                       mountGeneration);
     }
 #endif
 #ifdef ACCESSIBILITY
     // Make sure accessibility is running in content process when accessibility
     // gets initiated in chrome process.
     else if (aData && (*aData == '1') &&
              !strcmp(aTopic, "a11y-init-or-shutdown")) {
         unused << SendActivateA11y();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -329,16 +329,18 @@ private:
                                           bool* aValue);
 
     virtual bool RecvAudioChannelRegisterType(const AudioChannelType& aType);
     virtual bool RecvAudioChannelUnregisterType(const AudioChannelType& aType,
                                                 const bool& aElementHidden);
 
     virtual bool RecvAudioChannelChangedNotification();
 
+    virtual bool RecvBroadcastVolume(const nsString& aVolumeName);
+
     virtual void ProcessingError(Result what) MOZ_OVERRIDE;
 
     GeckoChildProcessHost* mSubprocess;
     ChildOSPrivileges mOSPrivileges;
 
     uint64_t mChildID;
     int32_t mGeolocationWatchID;
     int mRunToCompletionDepth;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -322,17 +322,18 @@ child:
 
     AppInfo(nsCString version, nsCString buildID);
 
     // Notify child that last-pb-context-exited notification was observed
     LastPrivateDocShellDestroyed();
 
     FilePathUpdate(nsString type, nsString filepath, nsCString reasons);
 
-    FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState);
+    FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
+                     int32_t mountGeneration);
 
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
@@ -441,14 +442,17 @@ parent:
     sync AudioChannelUnregisterType(AudioChannelType aType,
                                     bool aElementHidden);
 
     async AudioChannelChangedNotification();
 
     async FilePathUpdateNotify(nsString aType,
                                nsString aFilepath,
                                nsCString aReason);
+    // get nsIVolumeService to broadcast volume information
+    async BroadcastVolume(nsString volumeName);
+
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData);
 };
 
 }
 }
--- a/dom/system/gonk/AutoMounter.cpp
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -393,59 +393,74 @@ AutoMounter::UpdateState()
       umsAvail, umsEnabled, mMode, usbCablePluggedIn, tryToShare);
 
   VolumeArray::index_type volIndex;
   VolumeArray::size_type  numVolumes = mAutoVolume.Length();
   for (volIndex = 0; volIndex < numVolumes; volIndex++) {
     RefPtr<Volume>  vol = mAutoVolume[volIndex];
     Volume::STATE   volState = vol->State();
 
-    LOG("UpdateState: Volume %s is %s and %s", vol->NameStr(), vol->StateStr(),
-        vol->MediaPresent() ? "inserted" : "missing");
+    if (vol->State() == nsIVolume::STATE_MOUNTED) {
+      LOG("UpdateState: Volume %s is %s and %s @ %s gen %d locked %d",
+          vol->NameStr(), vol->StateStr(),
+          vol->MediaPresent() ? "inserted" : "missing",
+          vol->MountPoint().get(), vol->MountGeneration(),
+          (int)vol->IsMountLocked());
+    } else {
+      LOG("UpdateState: Volume %s is %s and %s", vol->NameStr(), vol->StateStr(),
+          vol->MediaPresent() ? "inserted" : "missing");
+    }
     if (!vol->MediaPresent()) {
       // No media - nothing we can do
       continue;
     }
 
     if (tryToShare) {
       // We're going to try to unmount and share the volumes
       switch (volState) {
         case nsIVolume::STATE_MOUNTED: {
+          if (vol->IsMountLocked()) {
+            // The volume is currently locked, so leave it in the mounted
+            // state.
+            DBG("UpdateState: Mounted volume %s is locked, leaving",
+                vol->NameStr());
+            break;
+          }
           // Volume is mounted, we need to unmount before
           // we can share.
           DBG("UpdateState: Unmounting %s", vol->NameStr());
           vol->StartUnmount(mResponseCallback);
-          return;
+          return; // UpdateState will be called again when the Unmount command completes
         }
         case nsIVolume::STATE_IDLE: {
           // Volume is unmounted. We can go ahead and share.
           DBG("UpdateState: Sharing %s", vol->NameStr());
           vol->StartShare(mResponseCallback);
-          return;
+          return; // UpdateState will be called again when the Share command completes
         }
         default: {
           // Not in a state that we can do anything about.
           break;
         }
       }
     } else {
       // We're going to try and unshare and remount the volumes
       switch (volState) {
         case nsIVolume::STATE_SHARED: {
           // Volume is shared. We can go ahead and unshare.
           DBG("UpdateState: Unsharing %s", vol->NameStr());
           vol->StartUnshare(mResponseCallback);
-          return;
+          return; // UpdateState will be called again when the Unshare command completes
         }
         case nsIVolume::STATE_IDLE: {
           // Volume is unmounted, try to mount.
 
           DBG("UpdateState: Mounting %s", vol->NameStr());
           vol->StartMount(mResponseCallback);
-          return;
+          return; // UpdateState will be called again when Mount command completes
         }
         default: {
           // Not in a state that we can do anything about.
           break;
         }
       }
     }
   }
--- a/dom/system/gonk/Makefile.in
+++ b/dom/system/gonk/Makefile.in
@@ -34,16 +34,17 @@ CPPSRCS = \
   SystemWorkerManager.cpp \
   $(NULL)
 
 XPIDLSRCS = \
   nsIAudioManager.idl \
   nsINetworkManager.idl \
   nsIRadioInterfaceLayer.idl \
   nsIVolume.idl \
+  nsIVolumeMountLock.idl \
   nsIVolumeService.idl \
   nsIVolumeStat.idl \
   nsIWorkerHolder.idl \
   nsIAudioChannelManager.idl \
   nsINavigatorAudioChannelManager.idl \
   $(NULL)
 
 LOCAL_INCLUDES = \
@@ -58,30 +59,31 @@ LOCAL_INCLUDES = \
 ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   AudioManager.cpp \
   AutoMounter.cpp \
   AutoMounterSetting.cpp \
   GonkGPSGeolocationProvider.cpp \
   AudioChannelManager.cpp \
   nsVolume.cpp \
+  nsVolumeMountLock.cpp \
   nsVolumeService.cpp \
   nsVolumeStat.cpp \
   TimeZoneSettingObserver.cpp \
   Volume.cpp \
   VolumeCommand.cpp \
   VolumeManager.cpp \
   VolumeServiceIOThread.cpp \
   VolumeServiceTest.cpp \
   $(NULL)
 # for our local copy of AudioSystem.h
 LOCAL_INCLUDES += -I$(topsrcdir)/media/libsydneyaudio/src
 EXPORTS = \
+  GonkGPSGeolocationProvider.h \
   nsVolume.h \
-  GonkGPSGeolocationProvider.h \
   $(NULL)
 endif
 
 EXTRA_COMPONENTS = \
   NetworkManager.manifest \
   NetworkManager.js \
   RadioInterfaceLayer.manifest \
   RadioInterfaceLayer.js \
--- a/dom/system/gonk/Volume.cpp
+++ b/dom/system/gonk/Volume.cpp
@@ -11,22 +11,58 @@
 
 #include <vold/ResponseCode.h>
 
 namespace mozilla {
 namespace system {
 
 Volume::EventObserverList Volume::mEventObserverList;
 
+// We have a feature where volumes can be locked when mounted. This
+// is used to prevent a volume from being shared with the PC while
+// it is actively being used (say for storing an update image)
+//
+// We use WakeLocks (a poor choice of name, but it does what we want)
+// from the PowerManagerService to determine when we're locked.
+// In particular we'll create a wakelock called volume-NAME-GENERATION
+// (where NAME is the volume name, and GENERATION is its generation
+// number), and if this wakelock is locked, then we'll prevent a volume
+// from being shared.
+//
+// Implementation Details:
+//
+// Since the AutoMounter can only control when something gets mounted
+// and not when it gets unmounted (for example: a user pulls the SDCard)
+// and because Volume and nsVolume data structures are maintained on
+// separate threads, we have the potential for some race conditions.
+// We eliminate the race conditions by introducing the concept of a
+// generation number. Every time a volume transitions to the Mounted
+// state, it gets assigned a new generation number. Whenever the state
+// of a Volume changes, we send the updated state and current generation
+// number to the main thread where it gets updated in the nsVolume.
+//
+// Since WakeLocks can only be queried from the main-thread, the
+// nsVolumeService looks for WakeLock status changes, and forwards
+// the results to the IOThread.
+//
+// If the Volume (IOThread) recieves a volume update where the generation
+// number mismatches, then the update is simply ignored.
+//
+// When a Volume (IOThread) initially becomes mounted, we assume it to
+// be locked until we get our first update from nsVolume (MainThread).
+static int32_t sMountGeneration = 0;
+
 // We don't get media inserted/removed events at startup. So we
 // assume it's present, and we'll be told that it's missing.
 Volume::Volume(const nsCSubstring &aName)
   : mMediaPresent(true),
     mState(nsIVolume::STATE_INIT),
-    mName(aName)
+    mName(aName),
+    mMountGeneration(-1),
+    mMountLocked(true)  // Needs to agree with nsVolume::nsVolume
 {
   DBG("Volume %s: created", NameStr());
 }
 
 void
 Volume::SetMediaPresent(bool aMediaPresent)
 {
   // mMediaPresent is slightly redunant to the state, however
@@ -63,19 +99,22 @@ Volume::SetMediaPresent(bool aMediaPrese
 
 void
 Volume::SetState(Volume::STATE aNewState)
 {
   if (aNewState == mState) {
     return;
   }
   if (aNewState == nsIVolume::STATE_MOUNTED) {
-    LOG("Volume %s: changing state from %s to %s @ '%s' (%d observers)",
+    mMountGeneration = ++sMountGeneration;
+    LOG("Volume %s: changing state from %s to %s @ '%s' (%d observers) "
+        "mountGeneration = %d, locked = %d",
         NameStr(), StateStr(mState),
-        StateStr(aNewState), mMountPoint.get(), mEventObserverList.Length());
+        StateStr(aNewState), mMountPoint.get(), mEventObserverList.Length(),
+        mMountGeneration, (int)mMountLocked);
   } else {
     LOG("Volume %s: changing state from %s to %s (%d observers)",
         NameStr(), StateStr(mState),
         StateStr(aNewState), mEventObserverList.Length());
   }
 
   if (aNewState == nsIVolume::STATE_NOMEDIA) {
     // Cover the startup case where we don't get insertion/removal events
@@ -144,16 +183,33 @@ Volume::RegisterObserver(Volume::EventOb
 void
 Volume::UnregisterObserver(Volume::EventObserver *aObserver)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
   mEventObserverList.RemoveObserver(aObserver);
 }
 
+//static
+void
+Volume::UpdateMountLock(const nsACString &aVolumeName,
+                        const int32_t &aMountGeneration,
+                        const bool &aMountLocked)
+{
+  RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+  if (!vol || (vol->mMountGeneration != aMountGeneration)) {
+    return;
+  }
+  if (vol->mMountLocked != aMountLocked) {
+    vol->mMountLocked = aMountLocked;
+    DBG("Volume::UpdateMountLock for '%s' to %d\n", vol->NameStr(), (int)aMountLocked);
+    mEventObserverList.Broadcast(vol);
+  }
+}
+
 void
 Volume::HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer &aTokenizer)
 {
   // The volume name will have already been parsed, and the tokenizer will point
   // to the token after the volume name
   switch (aResponseCode) {
     case ResponseCode::VolumeListResult: {
       // Each line will look something like:
--- a/dom/system/gonk/Volume.h
+++ b/dom/system/gonk/Volume.h
@@ -37,27 +37,30 @@ public:
 
   const nsCString &Name() const { return mName; }
   const char *NameStr() const   { return mName.get(); }
 
   // The mount point is the name of the directory where the volume is mounted.
   // (i.e. path that leads to the files stored on the volume).
   const nsCString &MountPoint() const { return mMountPoint; }
 
-  bool MediaPresent() const     { return mMediaPresent; }
+  int32_t MountGeneration() const     { return mMountGeneration; }
+  bool IsMountLocked() const          { return mMountLocked; }
+  bool MediaPresent() const           { return mMediaPresent; }
 
   typedef mozilla::Observer<Volume *>     EventObserver;
   typedef mozilla::ObserverList<Volume *> EventObserverList;
 
   // NOTE: that observers must live in the IOThread.
   static void RegisterObserver(EventObserver *aObserver);
   static void UnregisterObserver(EventObserver *aObserver);
 
 private:
   friend class AutoMounter;         // Calls StartXxx
+  friend class nsVolume;            // Calls UpdateMountLock
   friend class VolumeManager;       // Calls HandleVoldResponse
   friend class VolumeListCallback;  // Calls SetMountPoint, SetState
 
   // The StartXxx functions will queue up a command to the VolumeManager.
   // You can queue up as many commands as you like, and aCallback will
   // be called as each one completes.
   void StartMount(VolumeResponseCallback *aCallback);
   void StartUnmount(VolumeResponseCallback *aCallback);
@@ -66,20 +69,26 @@ private:
 
   void SetState(STATE aNewState);
   void SetMediaPresent(bool aMediaPresent);
   void SetMountPoint(const nsCSubstring &aMountPoint);
   void StartCommand(VolumeCommand *aCommand);
 
   void HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer &aTokenizer);
 
+  static void UpdateMountLock(const nsACString &aVolumeName,
+                              const int32_t &aMountGeneration,
+                              const bool &aMountLocked);
+
   bool              mMediaPresent;
   STATE             mState;
   const nsCString   mName;
   nsCString         mMountPoint;
+  int32_t           mMountGeneration;
+  bool              mMountLocked;
 
   static EventObserverList mEventObserverList;
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_volumemanager_h__
--- a/dom/system/gonk/VolumeServiceIOThread.cpp
+++ b/dom/system/gonk/VolumeServiceIOThread.cpp
@@ -7,17 +7,18 @@
 #include "nsVolumeService.h"
 #include "nsXULAppAPI.h"
 #include "Volume.h"
 #include "VolumeManager.h"
 
 namespace mozilla {
 namespace system {
 
-VolumeServiceIOThread::VolumeServiceIOThread()
+VolumeServiceIOThread::VolumeServiceIOThread(nsVolumeService *aVolumeService)
+  : mVolumeService(aVolumeService)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
   VolumeManager::RegisterStateObserver(this);
   Volume::RegisterObserver(this);
   UpdateAllVolumes();
 }
 
@@ -30,17 +31,17 @@ VolumeServiceIOThread::~VolumeServiceIOT
 
 void
 VolumeServiceIOThread::Notify(Volume * const &aVolume)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
     return;
   }
-  nsVolumeService::UpdateVolumeIOThread(aVolume);
+  mVolumeService->UpdateVolumeIOThread(aVolume);
 }
 
 void
 VolumeServiceIOThread::Notify(const VolumeManager::StateChangedEvent &aEvent)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   UpdateAllVolumes();
 }
@@ -52,27 +53,27 @@ VolumeServiceIOThread::UpdateAllVolumes(
   if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
     return;
   }
   VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
   VolumeManager::VolumeArray::index_type volIndex;
 
   for (volIndex = 0; volIndex < numVolumes; volIndex++) {
     RefPtr<Volume>  vol = VolumeManager::GetVolume(volIndex);
-    nsVolumeService::UpdateVolumeIOThread(vol);
+    mVolumeService->UpdateVolumeIOThread(vol);
   }
 }
 
-static RefPtr<VolumeServiceIOThread> sVolumeServiceIOThread;
+static StaticRefPtr<VolumeServiceIOThread> sVolumeServiceIOThread;
 
 void
-InitVolumeServiceIOThread()
+InitVolumeServiceIOThread(nsVolumeService * const &aVolumeService)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
-  sVolumeServiceIOThread = new VolumeServiceIOThread();
+  sVolumeServiceIOThread = new VolumeServiceIOThread(aVolumeService);
 }
 
 void
 ShutdownVolumeServiceIOThread()
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   sVolumeServiceIOThread = NULL;
 }
--- a/dom/system/gonk/VolumeServiceIOThread.h
+++ b/dom/system/gonk/VolumeServiceIOThread.h
@@ -7,35 +7,38 @@
 
 #include "Volume.h"
 #include "VolumeManager.h"
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
 namespace system {
 
+class nsVolumeService;
+
 /***************************************************************************
 * The nsVolumeServiceIOThread is a companion class to the nsVolumeService
 * class, but whose methods are called from IOThread.
 */
 class VolumeServiceIOThread : public VolumeManager::StateObserver,
                               public Volume::EventObserver,
                               public RefCounted<VolumeServiceIOThread>
 {
 public:
-  VolumeServiceIOThread();
+  VolumeServiceIOThread(nsVolumeService *aVolumeService);
   ~VolumeServiceIOThread();
 
 private:
   void  UpdateAllVolumes();
 
   virtual void Notify(const VolumeManager::StateChangedEvent &aEvent);
   virtual void Notify(Volume * const &aVolume);
 
+  RefPtr<nsVolumeService>   mVolumeService;
 };
 
-void InitVolumeServiceIOThread();
+void InitVolumeServiceIOThread(nsVolumeService * const &aVolumeService);
 void ShutdownVolumeServiceIOThread();
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_volumeserviceiothread_h__
--- a/dom/system/gonk/nsIVolume.idl
+++ b/dom/system/gonk/nsIVolume.idl
@@ -1,36 +1,55 @@
 /* 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 "nsISupports.idl"
 #include "nsIVolumeStat.idl"
 
-[scriptable, uuid(3c9cae8d-9da2-4aa1-b8bf-4db8c8620808)]
+[scriptable, uuid(1134f267-7b81-42f2-b64a-6edb91286576)]
 interface nsIVolume : nsISupports
 {
   // These MUST match the states from android's system/vold/Volume.h header
   const long STATE_INIT        = -1;
   const long STATE_NOMEDIA     = 0;
   const long STATE_IDLE        = 1;
   const long STATE_PENDING     = 2;
   const long STATE_CHECKING    = 3;
   const long STATE_MOUNTED     = 4;
   const long STATE_UNMOUNTING  = 5;
   const long STATE_FORMATTING  = 6;
   const long STATE_SHARED      = 7;
   const long STATE_SHAREDMNT   = 8;
 
+  // The name of the volume. Often there is only one volume, called sdcard.
+  // But some phones support multiple volumes.
   readonly attribute DOMString name;
 
+  // The mount point is the path on the system where the volume is mounted
+  // and is only valid when state == STATE_MOUNTED.
   readonly attribute DOMString mountPoint;
 
+  // Reflects the current state of the volume, using STATE_xxx constants
+  // from above.
   readonly attribute long state;
 
+  // mountGeneration is a unique number which is used distinguish between
+  // periods of time that a volume is in the mounted state. Each time a
+  // volume transitions to the mounted state, the mountGeneration will
+  // be different from the last time it transitioned to the mounted state.
+  readonly attribute long mountGeneration;
+
+  // While a volume is mounted, it can be locked, preventing it from being
+  // shared with the PC. To lock a volume, acquire an nsIDOMMozWakeLock
+  // using the name of this attribute. Note that mountLockName changes
+  // every time the mountGeneration changes, so you'll need to reacquire
+  // the wakelock every time the volume becomes mounted.
+  readonly attribute DOMString mountLockName;
+
   nsIVolumeStat getStats();
 };
 
 %{C++
 // For use with the ObserverService
 #define NS_VOLUME_STATE_CHANGED  "volume-state-changed"
 
 namespace mozilla {
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/nsIVolumeMountLock.idl
@@ -0,0 +1,12 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(44449f34-5ca1-4aff-bce6-22c79263de24)]
+interface nsIVolumeMountLock : nsISupports
+{
+    void unlock();
+};
+
--- a/dom/system/gonk/nsIVolumeService.idl
+++ b/dom/system/gonk/nsIVolumeService.idl
@@ -1,19 +1,24 @@
 /* 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 "nsISupports.idl"
 #include "nsIVolume.idl"
+#include "nsIVolumeMountLock.idl"
 
-[scriptable, uuid(b31bd379-4bd2-4189-a85b-c0927a266a85)]
+[scriptable, uuid(597403c6-5ba4-4e7b-b3f4-ed3f05f775d8)]
 interface nsIVolumeService : nsISupports
 {
     nsIVolume getVolumeByName(in DOMString volName);
     nsIVolume getVolumeByPath(in DOMString path);
+
+    void BroadcastVolume(in DOMString volName);
+
+    nsIVolumeMountLock createMountLock(in DOMString volName);
 };
 
 %{C++
 #define NS_VOLUMESERVICE_CID \
-  {0xb31bd379, 0x4bd2, 0x4189, {0xa8, 0x5b, 0xc0, 0x92, 0x7a, 0x26, 0x6a, 0x85}}
+  {0x597403c6, 0x5ba4, 0x4e7b, {0xb3, 0xf4, 0xed, 0x3f, 0x05, 0xf7, 0x75, 0xd8}}
 #define NS_VOLUMESERVICE_CONTRACTID "@mozilla.org/telephony/volume-service;1"
 %}
--- a/dom/system/gonk/nsVolume.cpp
+++ b/dom/system/gonk/nsVolume.cpp
@@ -1,18 +1,27 @@
 /* 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 "nsVolume.h"
+
+#include "base/message_loop.h"
+#include "nsIPowerManagerService.h"
 #include "nsISupportsUtils.h"
 #include "nsIVolume.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
 #include "nsVolumeStat.h"
+#include "nsXULAppAPI.h"
 #include "Volume.h"
 
+#define VOLUME_MANAGER_LOG_TAG  "nsVolume"
+#include "VolumeManagerLog.h"
+
 namespace mozilla {
 namespace system {
 
 const char *
 NS_VolumeStateStr(int32_t aState)
 {
   switch (aState) {
     case nsIVolume::STATE_INIT:       return "Init";
@@ -29,26 +38,42 @@ NS_VolumeStateStr(int32_t aState)
   return "???";
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsVolume, nsIVolume)
 
 nsVolume::nsVolume(const Volume *aVolume)
   : mName(NS_ConvertUTF8toUTF16(aVolume->Name())),
     mMountPoint(NS_ConvertUTF8toUTF16(aVolume->MountPoint())),
-    mState(aVolume->State())
+    mState(aVolume->State()),
+    mMountGeneration(aVolume->MountGeneration()),
+    mMountLocked(aVolume->IsMountLocked())
 {
 }
 
 NS_IMETHODIMP nsVolume::GetName(nsAString &aName)
 {
   aName = mName;
   return NS_OK;
 }
 
+NS_IMETHODIMP nsVolume::GetMountGeneration(int32_t *aMountGeneration)
+{
+  *aMountGeneration = mMountGeneration;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetMountLockName(nsAString &aMountLockName)
+{
+  aMountLockName = NS_LITERAL_STRING("volume-") + Name();
+  aMountLockName.AppendPrintf("-%d", mMountGeneration);
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsVolume::GetMountPoint(nsAString &aMountPoint)
 {
   aMountPoint = mMountPoint;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsVolume::GetState(int32_t *aState)
 {
@@ -61,10 +86,79 @@ NS_IMETHODIMP nsVolume::GetStats(nsIVolu
   if (mState != STATE_MOUNTED) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   NS_IF_ADDREF(*aResult = new nsVolumeStat(mMountPoint));
   return NS_OK;
 }
 
+void
+nsVolume::LogState() const
+{
+  if (mState == nsIVolume::STATE_MOUNTED) {
+    LOG("nsVolume: %s state %s @ '%s' gen %d locked %d",
+        NameStr(), StateStr(), MountPointStr(),
+        MountGeneration(), (int)IsMountLocked());
+    return;
+  }
+
+  LOG("nsVolume: %s state %s", NameStr(), StateStr());
+}
+
+void nsVolume::Set(const nsVolume *aVolume)
+{
+  mName = aVolume->mName;
+  mMountPoint = aVolume->mMountPoint;
+  mState = aVolume->mState;
+
+  if (mState != nsIVolume::STATE_MOUNTED) {
+    // Since we're not in the mounted state, we need to
+    // forgot whatever mount generation we may have had.
+    mMountGeneration = -1;
+    return;
+  }
+  if (mMountGeneration == aVolume->mMountGeneration) {
+    // No change in mount generation, nothing else to do
+    return;
+  }
+
+  mMountGeneration = aVolume->mMountGeneration;
+
+  // Notify the Volume on IOThread whether the volume is locked or not.
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  if (!pmService) {
+    return;
+  }
+  nsString mountLockName;
+  GetMountLockName(mountLockName);
+  nsString mountLockState;
+  pmService->GetWakeLockState(mountLockName, mountLockState);
+  UpdateMountLock(mountLockState);
+}
+
+void
+nsVolume::UpdateMountLock(const nsAString &aMountLockState)
+{
+  // There are 3 states, unlocked, locked-background, and locked-foreground
+  // I figured it was easier to use negtive logic and compare for unlocked.
+  UpdateMountLock(!aMountLockState.EqualsLiteral("unlocked"));
+}
+
+void
+nsVolume::UpdateMountLock(bool aMountLocked)
+{
+  if (aMountLocked == mMountLocked) {
+    return;
+  }
+  // The locked/unlocked state changed. Tell IOThread about it.
+  mMountLocked = aMountLocked;
+  LogState();
+  XRE_GetIOMessageLoop()->PostTask(
+     FROM_HERE,
+     NewRunnableFunction(Volume::UpdateMountLock,
+                         NS_LossyConvertUTF16toASCII(Name()),
+                         MountGeneration(), aMountLocked));
+}
+
 } // system
 } // mozilla
--- a/dom/system/gonk/nsVolume.h
+++ b/dom/system/gonk/nsVolume.h
@@ -1,72 +1,93 @@
 /* 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/. */
 
 #ifndef mozilla_system_nsvolume_h__
 #define mozilla_system_nsvolume_h__
 
+#include "nsCOMPtr.h"
 #include "nsIVolume.h"
 #include "nsString.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 namespace system {
 
 class Volume;
+class VolumeMountLock;
 
 class nsVolume : public nsIVolume
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIVOLUME
 
+  // This constructor is used by the UpdateVolumeRunnable constructor
   nsVolume(const Volume *aVolume);
 
-  nsVolume(const nsAString &aName, const nsAString &aMountPoint, const int32_t &aState)
-    : mName(aName), mMountPoint(aMountPoint), mState(aState)
+  // This constructor is used by ContentChild::RecvFileSystemUpdate
+  nsVolume(const nsAString &aName, const nsAString &aMountPoint,
+           const int32_t &aState, const int32_t &aMountGeneration)
+    : mName(aName),
+      mMountPoint(aMountPoint),
+      mState(aState),
+      mMountGeneration(aMountGeneration),
+      mMountLocked(false)
   {
   }
 
+  // This constructor is used by nsVolumeService::FindAddVolumeByName, and
+  // will be followed shortly by a Set call.
   nsVolume(const nsAString &aName)
     : mName(aName),
-      mState(STATE_INIT)
+      mState(STATE_INIT),
+      mMountGeneration(-1),
+      mMountLocked(true)  // Needs to agree with Volume::Volume
   {
   }
 
   bool Equals(const nsVolume *aVolume)
   {
     return mName.Equals(aVolume->mName)
         && mMountPoint.Equals(aVolume->mMountPoint)
-        && (mState == aVolume->mState);
+        && (mState == aVolume->mState)
+        && (mMountGeneration == aVolume->mMountGeneration)
+        && (mMountLocked == aVolume->mMountLocked);
   }
 
-  void Set(const nsVolume *aVolume)
-  {
-    mName = aVolume->mName;
-    mMountPoint = aVolume->mMountPoint;
-    mState = aVolume->mState;
-  }
+  void Set(const nsVolume *aVolume);
+
+  void LogState() const;
 
   const nsString &Name() const        { return mName; }
   const char *NameStr() const         { return NS_LossyConvertUTF16toASCII(mName).get(); }
 
+  int32_t MountGeneration() const     { return mMountGeneration; }
+  bool IsMountLocked() const          { return mMountLocked; }
+
   const nsString &MountPoint() const  { return mMountPoint; }
   const char *MountPointStr() const   { return NS_LossyConvertUTF16toASCII(mMountPoint).get(); }
 
   int32_t State() const               { return mState; }
   const char *StateStr() const        { return NS_VolumeStateStr(mState); }
 
   typedef nsTArray<nsRefPtr<nsVolume> > Array;
 
 private:
   ~nsVolume() {}
 
-protected:
+  friend class nsVolumeService; // Calls the following XxxMountLock functions
+  void UpdateMountLock(const nsAString &aMountLockState);
+  void UpdateMountLock(bool aMountLocked);
+
   nsString mName;
   nsString mMountPoint;
   int32_t  mState;
+  int32_t  mMountGeneration;
+  bool     mMountLocked;
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_nsvolume_h__
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/nsVolumeMountLock.cpp
@@ -0,0 +1,157 @@
+/* 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 "nsVolumeMountLock.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Services.h"
+
+#include "nsIObserverService.h"
+#include "nsIPowerManagerService.h"
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#include "nsString.h"
+
+#define VOLUME_MANAGER_LOG_TAG  "nsVolumeMountLock"
+#include "VolumeManagerLog.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::services;
+
+namespace mozilla {
+namespace system {
+
+NS_IMPL_ISUPPORTS3(nsVolumeMountLock, nsIVolumeMountLock,
+                   nsIObserver, nsISupportsWeakReference)
+
+// static
+already_AddRefed<nsVolumeMountLock>
+nsVolumeMountLock::Create(const nsAString &aVolumeName)
+{
+  DBG("nsVolumeMountLock::Create called");
+
+  nsRefPtr<nsVolumeMountLock> mountLock = new nsVolumeMountLock(aVolumeName);
+  nsresult rv = mountLock->Init();
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  return mountLock.forget();
+}
+
+nsVolumeMountLock::nsVolumeMountLock(const nsAString &aVolumeName)
+  : mVolumeName(aVolumeName),
+    mVolumeGeneration(-1),
+    mUnlocked(false)
+{
+}
+
+//virtual
+nsVolumeMountLock::~nsVolumeMountLock()
+{
+  Unlock();
+}
+
+nsresult nsVolumeMountLock::Init()
+{
+  LOG("nsVolumeMountLock created for '%s'",
+      NS_LossyConvertUTF16toASCII(mVolumeName).get());
+
+  // Add ourselves as an Observer. It's important that we use a weak
+  // reference here. If we used a strong reference, then that reference
+  // would prevent this object from being destructed.
+  nsCOMPtr<nsIObserverService> obs = GetObserverService();
+  obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, true /*weak*/);
+
+  // Request the sdcard info, so we know the state/generation without having
+  // to wait for a state change.
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    ContentChild::GetSingleton()->SendBroadcastVolume(mVolumeName);
+    return NS_OK;
+  }
+  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(vs, NS_ERROR_FAILURE);
+
+  vs->BroadcastVolume(mVolumeName);
+
+  return NS_OK;
+}
+
+/* void unlock (); */
+NS_IMETHODIMP nsVolumeMountLock::Unlock()
+{
+  LOG("nsVolumeMountLock released for '%s'",
+      NS_LossyConvertUTF16toASCII(mVolumeName).get());
+
+  mUnlocked = true;
+  mWakeLock = nullptr;
+
+  // While we don't really need to remove weak observers, we do so anyways
+  // since it will reduce the number of times Observe gets called.
+  nsCOMPtr<nsIObserverService> obs = GetObserverService();
+  obs->RemoveObserver(this, NS_VOLUME_STATE_CHANGED);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsVolumeMountLock::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
+{
+  if (strcmp(aTopic, NS_VOLUME_STATE_CHANGED) != 0) {
+    return NS_OK;
+  }
+  if (mUnlocked) {
+    // We're not locked anymore, so we don't need to look at the notifications.
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
+  if (!vol) {
+    return NS_OK;
+  }
+  nsString volName;
+  vol->GetName(volName);
+  if (!volName.Equals(mVolumeName)) {
+    return NS_OK;
+  }
+  int32_t state;
+  nsresult rv = vol->GetState(&state);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (state != nsIVolume::STATE_MOUNTED) {
+    mWakeLock = nullptr;
+    mVolumeGeneration = -1;
+    return NS_OK;
+  }
+
+  int32_t   mountGeneration;
+  rv = vol->GetMountGeneration(&mountGeneration);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  DBG("nsVolumeMountLock::Observe mountGeneration = %d mVolumeGeneration = %d",
+       mountGeneration, mVolumeGeneration);
+
+  if (mVolumeGeneration == mountGeneration) {
+    return NS_OK;
+  }
+
+  // The generation changed, which means that any wakelock we may have
+  // been holding is now invalid. Grab a new wakelock for the new generation
+  // number.
+
+  mWakeLock = nullptr;
+  mVolumeGeneration = mountGeneration;
+
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(pmService, NS_ERROR_FAILURE);
+
+  nsString mountLockName;
+  vol->GetMountLockName(mountLockName);
+  rv = pmService->NewWakeLock(mountLockName, nullptr, getter_AddRefs(mWakeLock));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  LOG("nsVolumeMountLock acquired for '%s' gen %d",
+      NS_LossyConvertUTF16toASCII(mVolumeName).get(), mVolumeGeneration);
+  return NS_OK;
+}
+
+} // namespace system
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/nsVolumeMountLock.h
@@ -0,0 +1,53 @@
+/* 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/. */
+
+#ifndef mozilla_system_nsvolumemountlock_h__
+#define mozilla_system_nsvolumemountlock_h__
+
+#include "nsIVolumeMountLock.h"
+
+#include "nsIDOMWakeLock.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace system {
+
+/* The VolumeMountLock is designed so that it can be used in the Child or
+ * Parent process. While the VolumeMountLock object exists, then the
+ * VolumeManager/AutoMounter will prevent a mounted volume from being
+ * shared with the PC.
+ */
+
+class nsVolumeMountLock MOZ_FINAL : public nsIVolumeMountLock,
+                                    public nsIObserver,
+                                    public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIVOLUMEMOUNTLOCK
+
+  static already_AddRefed<nsVolumeMountLock> Create(const nsAString &volumeName);
+
+  const nsString &VolumeName() const  { return mVolumeName; }
+
+private:
+  nsVolumeMountLock(const nsAString &aVolumeName);
+  ~nsVolumeMountLock();
+
+  nsresult Init();
+
+  nsString                    mVolumeName;
+  int32_t                     mVolumeGeneration;
+  nsCOMPtr<nsIDOMMozWakeLock> mWakeLock;
+  bool                        mUnlocked;
+};
+
+} // namespace system
+} // namespace mozilla
+
+#endif  // mozilla_system_nsvolumemountlock_h__
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -6,73 +6,152 @@
 
 #include "Volume.h"
 #include "VolumeManager.h"
 #include "VolumeServiceIOThread.h"
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsDependentSubstring.h"
+#include "nsIDOMWakeLockListener.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
+#include "nsIPowerManagerService.h"
 #include "nsISupportsUtils.h"
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
 #include "nsLocalFile.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
+#include "nsVolumeMountLock.h"
 #include "nsXULAppAPI.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/Services.h"
 
 #define VOLUME_MANAGER_LOG_TAG  "nsVolumeService"
 #include "VolumeManagerLog.h"
 
 #include <stdlib.h>
 
+using namespace mozilla::dom;
 using namespace mozilla::services;
 
 namespace mozilla {
 namespace system {
 
-NS_IMPL_ISUPPORTS1(nsVolumeService, nsIVolumeService)
+NS_IMPL_ISUPPORTS2(nsVolumeService, nsIVolumeService, nsIDOMMozWakeLockListener)
+
+StaticRefPtr<nsVolumeService> nsVolumeService::sSingleton;
+
+// static
+already_AddRefed<nsVolumeService>
+nsVolumeService::GetSingleton()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sSingleton) {
+    sSingleton = new nsVolumeService();
+  }
+  NS_ADDREF(sSingleton.get());
+  return sSingleton.get();
+}
+
+// static
+void
+nsVolumeService::Shutdown()
+{
+  if (!sSingleton || (XRE_GetProcessType() != GeckoProcessType_Default)) {
+    return;
+  }
+
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  if (pmService) {
+    pmService->RemoveWakeLockListener(sSingleton.get());
+  }
+
+  XRE_GetIOMessageLoop()->PostTask(
+      FROM_HERE,
+      NewRunnableFunction(ShutdownVolumeServiceIOThread));
+
+  sSingleton = nullptr;
+}
 
 nsVolumeService::nsVolumeService()
 {
+  sSingleton = this;
+
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    // We don't support the nsIVolumeService in the child processes,
+    // but we get constructed due to the way we're registered with
+    // nsLayoutModule.cpp. So we exit early to reduce our memory
+    // impact, and so that we don't start unnecessary IOThread stuff.
+    return;
+  }
+
   // Startup the IOThread side of things. The actual volume changes
   // are captured by the IOThread and forwarded to main thread.
   XRE_GetIOMessageLoop()->PostTask(
       FROM_HERE,
-      NewRunnableFunction(InitVolumeServiceIOThread));
+      NewRunnableFunction(InitVolumeServiceIOThread, this));
+
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  if (!pmService) {
+    return;
+  }
+  pmService->AddWakeLockListener(this);
 }
 
 nsVolumeService::~nsVolumeService()
 {
-  XRE_GetIOMessageLoop()->PostTask(
-      FROM_HERE,
-      NewRunnableFunction(ShutdownVolumeServiceIOThread));
+}
+
+// Callback for nsIDOMMozWakeLockListener
+NS_IMETHODIMP nsVolumeService::Callback(const nsAString &aTopic, const nsAString &aState)
+{
+  CheckMountLock(aTopic, aState);
+  return NS_OK;
 }
 
-/* nsIVolume getVolumeByName (in DOMString volName); */
+NS_IMETHODIMP nsVolumeService::BroadcastVolume(const nsAString &aVolName)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+  nsRefPtr<nsVolume> vol = FindVolumeByName(aVolName);
+  if (!vol) {
+    ERR("BroadcastVolume: Unable to locate volume '%s'",
+        NS_LossyConvertUTF16toASCII(aVolName).get());
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsIObserverService> obs = GetObserverService();
+  NS_ENSURE_TRUE(obs, NS_NOINTERFACE);
+
+  DBG("nsVolumeService::BroadcastVolume for '%s'", vol->NameStr());
+  nsString stateStr(NS_ConvertUTF8toUTF16(vol->StateStr()));
+  obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get());
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString &aVolName, nsIVolume **aResult)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   nsRefPtr<nsVolume> vol = FindVolumeByName(aVolName);
   if (!vol) {
     ERR("GetVolumeByName: Unable to locate volume '%s'",
         NS_LossyConvertUTF16toASCII(aVolName).get());
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   NS_ADDREF(*aResult = vol);
   return NS_OK;
 }
 
-/* nsIVolume getVolumeByPath (in DOMString path); */
 NS_IMETHODIMP nsVolumeService::GetVolumeByPath(const nsAString &aPath, nsIVolume **aResult)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   nsCString utf8Path = NS_ConvertUTF16toUTF8(aPath);
   char realPathBuf[PATH_MAX];
 
   if (!realpath(utf8Path.get(), realPathBuf)) {
     ERR("GetVolumeByPath: realpath on '%s' failed: %d", utf8Path.get(), errno);
@@ -100,37 +179,68 @@ NS_IMETHODIMP nsVolumeService::GetVolume
       NS_ADDREF(*aResult = vol);
       return NS_OK;
     }
   }
 
   // In order to support queries by DeviceStorage and the updater, we will fabricate
   // a volume from the pathname, so that the caller can determine the volume size
   nsRefPtr<nsVolume> vol = new nsVolume(NS_LITERAL_STRING("fake"),
-  aPath, nsIVolume::STATE_MOUNTED);
+                                        aPath, nsIVolume::STATE_MOUNTED,
+                                        -1 /*generation*/);
   NS_ADDREF(*aResult = vol);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsVolumeService::CreateMountLock(const nsAString &aVolumeName, nsIVolumeMountLock **aResult)
+{
+  nsRefPtr<nsVolumeMountLock> mountLock = nsVolumeMountLock::Create(aVolumeName);
+  if (!mountLock) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  NS_ADDREF(*aResult = mountLock);
+  return NS_OK;
+}
+
+void nsVolumeService::CheckMountLock(const nsAString &aMountLockName,
+                                     const nsAString &aMountLockState)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsVolume::Array::size_type  numVolumes = mVolumeArray.Length();
+  nsVolume::Array::index_type volIndex;
+  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+    nsRefPtr<nsVolume> vol = mVolumeArray[volIndex];
+    nsString mountLockName;
+    vol->GetMountLockName(mountLockName);
+    if (mountLockName.Equals(aMountLockName)) {
+      vol->UpdateMountLock(aMountLockState);
+      return;
+    }
+  }
+}
+
 already_AddRefed<nsVolume> nsVolumeService::FindVolumeByName(const nsAString &aName)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsVolume::Array::size_type  numVolumes = mVolumeArray.Length();
   nsVolume::Array::index_type volIndex;
   for (volIndex = 0; volIndex < numVolumes; volIndex++) {
     nsRefPtr<nsVolume> vol = mVolumeArray[volIndex];
     if (vol->Name().Equals(aName)) {
       return vol.forget();
     }
   }
   return NULL;
 }
 
+//static
 already_AddRefed<nsVolume> nsVolumeService::FindAddVolumeByName(const nsAString &aName)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<nsVolume> vol;
   vol = FindVolumeByName(aName);
   if (vol) {
@@ -152,66 +262,55 @@ void nsVolumeService::UpdateVolume(const
     // Nothing has really changed. Don't bother telling anybody.
     return;
   }
   vol->Set(aVolume);
   nsCOMPtr<nsIObserverService> obs = GetObserverService();
   if (!obs) {
     return;
   }
-  if (aVolume->State() == nsIVolume::STATE_MOUNTED) {
-    LOG("UpdateVolume: '%s' state %s @ '%s'",
-        aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPointStr());
-  } else {
-    LOG("UpdateVolume: '%s' state %s",
-        aVolume->NameStr(), aVolume->StateStr());
-  }
   nsString stateStr(NS_ConvertUTF8toUTF16(vol->StateStr()));
   obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get());
 }
 
 /***************************************************************************
 * The UpdateVolumeRunnable creates an nsVolume and updates the main thread
 * data structure while running on the main thread.
 */
 class UpdateVolumeRunnable : public nsRunnable
 {
 public:
-  UpdateVolumeRunnable(const Volume *aVolume)
-    : mVolume(new nsVolume(aVolume))
+  UpdateVolumeRunnable(nsVolumeService *aVolumeService, const Volume *aVolume)
+    : mVolumeService(aVolumeService),
+      mVolume(new nsVolume(aVolume))
   {
     MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    DBG("UpdateVolumeRunnable::Run '%s' state %s",
-        mVolume->NameStr(), mVolume->StateStr());
+    DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d",
+        mVolume->NameStr(), mVolume->StateStr(), mVolume->MountGeneration(),
+        (int)mVolume->IsMountLocked());
 
-    nsCOMPtr<nsIVolumeService> ivs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-    if (!ivs) {
-      return NS_OK;
-    }
-    nsCOMPtr<nsVolumeService> vs(do_QueryInterface(ivs));
-    if (!vs) {
-      return NS_OK;
-    }
-    vs->UpdateVolume(mVolume);
+    mVolumeService->UpdateVolume(mVolume);
+    mVolumeService = NULL;
     mVolume = NULL;
     return NS_OK;
   }
 
 private:
-  nsRefPtr<nsVolume>  mVolume;
+  nsRefPtr<nsVolumeService> mVolumeService;
+  nsRefPtr<nsVolume>        mVolume;
 };
 
-//static
 void nsVolumeService::UpdateVolumeIOThread(const Volume *aVolume)
 {
-  DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s'",
-      aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get());
+  DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d",
+      aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(),
+      aVolume->MountGeneration(), (int)aVolume->IsMountLocked());
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
-  NS_DispatchToMainThread(new UpdateVolumeRunnable(aVolume));
+  NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume));
 }
 
-} // system
-} // mozilla
+} // namespace system
+} // namespace mozilla
--- a/dom/system/gonk/nsVolumeService.h
+++ b/dom/system/gonk/nsVolumeService.h
@@ -1,45 +1,60 @@
 /* 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/. */
 
 #ifndef mozilla_system_nsvolumeservice_h__
 #define mozilla_system_nsvolumeservice_h__
 
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
 #include "nsCOMPtr.h"
+#include "nsIDOMWakeLockListener.h"
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
 #include "nsVolume.h"
 #include "Volume.h"
 
 namespace mozilla {
 namespace system {
 
+class WakeLockCallback;
+
 /***************************************************************************
 * The nsVolumeData class encapsulates the data that is updated/maintained
 * on the main thread in order to support the nsIVolume and nsIVolumeService
 * classes.
 */
 
-class nsVolumeService : public nsIVolumeService
+class nsVolumeService MOZ_FINAL : public nsIVolumeService,
+                                  public nsIDOMMozWakeLockListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIVOLUMESERVICE
+  NS_DECL_NSIDOMMOZWAKELOCKLISTENER
 
   nsVolumeService();
 
+  static already_AddRefed<nsVolumeService> GetSingleton();
+  //static nsVolumeService *GetSingleton();
+  static void Shutdown();
+
+  void CheckMountLock(const nsAString &aMountLockName,
+                      const nsAString &aMountLockState);
   already_AddRefed<nsVolume> FindVolumeByName(const nsAString &aName);
   already_AddRefed<nsVolume> FindAddVolumeByName(const nsAString &aName);
   void UpdateVolume(const nsVolume *aVolume);
-  static void UpdateVolumeIOThread(const Volume *aVolume);
+  void UpdateVolumeIOThread(const Volume *aVolume);
 
 private:
   ~nsVolumeService();
 
-  nsVolume::Array  mVolumeArray;
+  nsVolume::Array mVolumeArray;
+
+  static StaticRefPtr<nsVolumeService> sSingleton;
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_nsvolumeservice_h__
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -291,17 +291,16 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 #endif
 #ifdef MOZ_B2G_BT
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(BluetoothService,
                                          BluetoothService::FactoryCreate)
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 NS_GENERIC_FACTORY_CONSTRUCTOR(AudioManager)
-NS_GENERIC_FACTORY_CONSTRUCTOR(nsVolumeService)
 #endif
 
 #ifdef MOZ_B2G_FM
 NS_GENERIC_FACTORY_CONSTRUCTOR(FMRadio)
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(AudioChannelAgent)
 
@@ -320,16 +319,20 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIAlarmHalService,
                                          AlarmHalService::GetInstance)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITimeService,
                                          TimeService::GetInstance)
 
 #ifdef MOZ_WIDGET_GONK
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIGeolocationProvider,
                                          GonkGPSGeolocationProvider::GetSingleton)
+// Since the nsVolumeService constructor calls into nsIPowerManagerService,
+// we need it to be constructed sometime after nsIPowerManagerService.
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsVolumeService,
+                                         nsVolumeService::GetSingleton)
 #endif
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIMediaManagerService,
                                          MediaManager::GetInstance)
 
 //-----------------------------------------------------------------------------
 
 // Per bug 209804, it is necessary to observe the "xpcom-shutdown" event and
 // perform shutdown of the layout modules at that time instead of waiting for
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -82,16 +82,21 @@
 #ifdef MOZ_WMF
 #include "WMFDecoder.h"
 #endif
 
 #ifdef MOZ_SYDNEYAUDIO
 #include "AudioStream.h"
 #endif
 
+#ifdef MOZ_WIDGET_GONK
+#include "nsVolumeService.h"
+using namespace mozilla::system;
+#endif
+
 #include "nsError.h"
 
 #include "nsCycleCollector.h"
 #include "nsJSEnvironment.h"
 #include "nsContentSink.h"
 #include "nsFrameMessageManager.h"
 #include "nsRefreshDriver.h"
 #include "nsDOMMutationObserver.h"
@@ -344,16 +349,20 @@ nsLayoutStatics::Shutdown()
 #ifdef MOZ_SYDNEYAUDIO
   AudioStream::ShutdownLibrary();
 #endif
 
 #ifdef MOZ_WMF
   WMFDecoder::UnloadDLLs();
 #endif
 
+#ifdef MOZ_WIDGET_GONK
+  nsVolumeService::Shutdown();
+#endif
+
   nsCORSListenerProxy::Shutdown();
 
   nsIPresShell::ReleaseStatics();
 
   nsTreeSanitizer::ReleaseStatics();
 
   nsHtml5Module::ReleaseStatics();