Bug 1033952 - Part 2 - Implement "Removable" and "HotSwappable" APIs for device storage. r=dhylands
☠☠ backed out by 03ddb9d37b83 ☠ ☠
authorCHUANG CHENYU <echuang@mozilla.com>
Mon, 22 Dec 2014 10:33:33 +0800
changeset 221131 3263cf8aca7788e3bf4f7a4649b3e2dd4270841e
parent 221130 c78cc72ea6d2fb69dd4d2d854b50caa18f8f4a53
child 221132 756a0c4b895549a8291c81f0cc2d7e73b4475411
push id28011
push userkwierso@gmail.com
push dateWed, 24 Dec 2014 00:36:52 +0000
treeherdermozilla-central@865d06511e99 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdhylands
bugs1033952
milestone37.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 1033952 - Part 2 - Implement "Removable" and "HotSwappable" APIs for device storage. r=dhylands
dom/devicestorage/DeviceStorage.h
dom/devicestorage/nsDeviceStorage.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
dom/system/gonk/Volume.cpp
dom/system/gonk/Volume.h
dom/system/gonk/VolumeManager.cpp
dom/system/gonk/VolumeManager.h
dom/system/gonk/nsIVolume.idl
dom/system/gonk/nsVolume.cpp
dom/system/gonk/nsVolume.h
dom/system/gonk/nsVolumeService.cpp
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -267,16 +267,17 @@ public:
   already_AddRefed<DOMRequest> Format(ErrorResult& aRv);
   already_AddRefed<DOMRequest> StorageStatus(ErrorResult& aRv);
   already_AddRefed<DOMRequest> Mount(ErrorResult& aRv);
   already_AddRefed<DOMRequest> Unmount(ErrorResult& aRv);
 
   bool CanBeMounted();
   bool CanBeFormatted();
   bool CanBeShared();
+  bool IsRemovable();
   bool Default();
 
   // Uses XPCOM GetStorageName
 
   already_AddRefed<Promise>
   GetRoot(ErrorResult& aRv);
 
   static void
@@ -317,16 +318,17 @@ private:
   EnumerateInternal(const nsAString& aName,
                     const EnumerationParameters& aOptions, bool aEditable,
                     ErrorResult& aRv);
 
   nsString mStorageType;
   nsCOMPtr<nsIFile> mRootDirectory;
   nsString mStorageName;
   bool mIsShareable;
+  bool mIsRemovable;
 
   already_AddRefed<nsDOMDeviceStorage> GetStorage(const nsAString& aFullPath,
                                                   nsAString& aOutStoragePath);
   already_AddRefed<nsDOMDeviceStorage>
     GetStorageByName(const nsAString &aStorageName);
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -3332,16 +3332,17 @@ NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorag
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
 
 nsDOMDeviceStorage::nsDOMDeviceStorage(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
   , mIsShareable(false)
+  , mIsRemovable(false)
   , mIsWatchingFile(false)
   , mAllowedToWatchFile(false)
 {
 }
 
 /* virtual */ JSObject*
 nsDOMDeviceStorage::WrapObject(JSContext* aCx)
 {
@@ -3378,16 +3379,22 @@ nsDOMDeviceStorage::Init(nsPIDOMWindow* 
         return rv;
       }
       bool isFake;
       rv = vol->GetIsFake(&isFake);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       mIsShareable = !isFake;
+      bool isRemovable;
+      rv = vol->GetIsHotSwappable(&isRemovable);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      mIsRemovable = isRemovable;
     }
 #endif
   }
 
   // Grab the principal of the document
   nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
   if (!doc) {
     return NS_ERROR_FAILURE;
@@ -4221,16 +4228,22 @@ nsDOMDeviceStorage::CanBeMounted()
 }
 
 bool
 nsDOMDeviceStorage::CanBeShared()
 {
   return mIsShareable;
 }
 
+bool
+nsDOMDeviceStorage::IsRemovable()
+{
+  return mIsRemovable;
+}
+
 already_AddRefed<Promise>
 nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
 {
   if (!mFileSystem) {
     mFileSystem = new DeviceStorageFileSystem(mStorageType, mStorageName);
     mFileSystem->Init(this);
   }
   return mozilla::dom::Directory::GetRoot(mFileSystem, aRv);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2148,39 +2148,43 @@ bool
 ContentChild::RecvFileSystemUpdate(const nsString& aFsName,
                                    const nsString& aVolumeName,
                                    const int32_t& aState,
                                    const int32_t& aMountGeneration,
                                    const bool& aIsMediaPresent,
                                    const bool& aIsSharing,
                                    const bool& aIsFormatting,
                                    const bool& aIsFake,
-                                   const bool& aIsUnmounting)
+                                   const bool& aIsUnmounting,
+                                   const bool& aIsRemovable,
+                                   const bool& aIsHotSwappable)
 {
 #ifdef MOZ_WIDGET_GONK
     nsRefPtr<nsVolume> volume = new nsVolume(aFsName, aVolumeName, aState,
                                              aMountGeneration, aIsMediaPresent,
                                              aIsSharing, aIsFormatting, aIsFake,
-                                             aIsUnmounting);
+                                             aIsUnmounting, aIsRemovable, aIsHotSwappable);
 
     nsRefPtr<nsVolumeService> vs = nsVolumeService::GetSingleton();
     if (vs) {
         vs->UpdateVolume(volume);
     }
 #else
     // Remove warnings about unused arguments
     unused << aFsName;
     unused << aVolumeName;
     unused << aState;
     unused << aMountGeneration;
     unused << aIsMediaPresent;
     unused << aIsSharing;
     unused << aIsFormatting;
     unused << aIsFake;
     unused << aIsUnmounting;
+    unused << aIsRemovable;
+    unused << aIsHotSwappable;
 #endif
     return true;
 }
 
 bool
 ContentChild::RecvNotifyProcessPriorityChanged(
     const hal::ProcessPriority& aPriority)
 {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -331,17 +331,19 @@ public:
     virtual bool RecvFileSystemUpdate(const nsString& aFsName,
                                       const nsString& aVolumeName,
                                       const int32_t& aState,
                                       const int32_t& aMountGeneration,
                                       const bool& aIsMediaPresent,
                                       const bool& aIsSharing,
                                       const bool& aIsFormatting,
                                       const bool& aIsFake,
-                                      const bool& aIsUnmounting) MOZ_OVERRIDE;
+                                      const bool& aIsUnmounting,
+                                      const bool& aIsRemovable,
+                                      const bool& aIsHotSwappable) MOZ_OVERRIDE;
 
     virtual bool RecvNuwaFork() MOZ_OVERRIDE;
 
     virtual bool
     RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority) MOZ_OVERRIDE;
     virtual bool RecvMinimizeMemoryUsage() MOZ_OVERRIDE;
 
     virtual bool RecvLoadAndRegisterSheet(const URIParams& aURI,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2824,35 +2824,39 @@ ContentParent::Observe(nsISupports* aSub
         nsString mountPoint;
         int32_t  state;
         int32_t  mountGeneration;
         bool     isMediaPresent;
         bool     isSharing;
         bool     isFormatting;
         bool     isFake;
         bool     isUnmounting;
+        bool     isRemovable;
+        bool     isHotSwappable;
 
         vol->GetName(volName);
         vol->GetMountPoint(mountPoint);
         vol->GetState(&state);
         vol->GetMountGeneration(&mountGeneration);
         vol->GetIsMediaPresent(&isMediaPresent);
         vol->GetIsSharing(&isSharing);
         vol->GetIsFormatting(&isFormatting);
         vol->GetIsFake(&isFake);
         vol->GetIsUnmounting(&isUnmounting);
+        vol->GetIsRemovable(&isRemovable);
+        vol->GetIsHotSwappable(&isHotSwappable);
 
 #ifdef MOZ_NUWA_PROCESS
         if (!(IsNuwaReady() && IsNuwaProcess()))
 #endif
         {
             unused << SendFileSystemUpdate(volName, mountPoint, state,
                                            mountGeneration, isMediaPresent,
                                            isSharing, isFormatting, isFake,
-                                           isUnmounting);
+                                           isUnmounting, isRemovable, isHotSwappable);
         }
     } else if (!strcmp(aTopic, "phone-state-changed")) {
         nsString state(aData);
         unused << SendNotifyPhoneStateChange(state);
     }
 #endif
 #ifdef ACCESSIBILITY
     // Make sure accessibility is running in content process when accessibility
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -314,16 +314,18 @@ struct VolumeInfo {
   nsString mountPoint;
   int32_t volState;
   int32_t mountGeneration;
   bool isMediaPresent;
   bool isSharing;
   bool isFormatting;
   bool isFake;
   bool isUnmounting;
+  bool isRemovable;
+  bool isHotSwappable;
 };
 
 struct ClipboardCapabilities {
   bool supportsSelectionClipboard;
   bool supportsFindClipboard;
 };
 
 union MaybeFileDesc {
@@ -484,17 +486,18 @@ child:
 
     FilePathUpdate(nsString storageType, nsString storageName, nsString filepath,
                    nsCString reasons);
 
     // Note: Any changes to this structure should also be changed in
     // VolumeInfo above.
     FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
                      int32_t mountGeneration, bool isMediaPresent,
-                     bool isSharing, bool isFormatting, bool isFake, bool isUnmounting);
+                     bool isSharing, bool isFormatting, bool isFake,
+                     bool isUnmounting, bool isRemovable, bool isHotSwappable);
 
     // Ask the Nuwa process to create a new child process.
     NuwaFork();
 
     NotifyProcessPriorityChanged(ProcessPriority priority);
     MinimizeMemoryUsage();
 
     /**
--- a/dom/system/gonk/Volume.cpp
+++ b/dom/system/gonk/Volume.cpp
@@ -76,16 +76,18 @@ Volume::Volume(const nsCSubstring& aName
     mSharingEnabled(false),
     mFormatRequested(false),
     mMountRequested(false),
     mUnmountRequested(false),
     mCanBeShared(true),
     mIsSharing(false),
     mIsFormatting(false),
     mIsUnmounting(false),
+    mIsRemovable(false),
+    mIsHotSwappable(false),
     mId(sNextId++)
 {
   DBG("Volume %s: created", NameStr());
 }
 
 void
 Volume::Dump(const char* aLabel) const
 {
@@ -152,16 +154,88 @@ Volume::SetIsUnmounting(bool aIsUnmounti
   }
   mIsUnmounting = aIsUnmounting;
   LOG("Volume %s: IsUnmounting set to %d state %s",
       NameStr(), (int)mIsUnmounting, StateStr(mState));
   sEventObserverList.Broadcast(this);
 }
 
 void
+Volume::SetIsRemovable(bool aIsRemovable)
+{
+  if (aIsRemovable == mIsRemovable) {
+    return;
+  }
+  mIsRemovable = aIsRemovable;
+  if (!mIsRemovable) {
+    mIsHotSwappable = false;
+  }
+  LOG("Volume %s: IsRemovable set to %d state %s",
+      NameStr(), (int)mIsRemovable, StateStr(mState));
+  sEventObserverList.Broadcast(this);
+}
+
+void
+Volume::SetIsHotSwappable(bool aIsHotSwappable)
+{
+  if (aIsHotSwappable == mIsHotSwappable) {
+    return;
+  }
+  mIsHotSwappable = aIsHotSwappable;
+  if (mIsHotSwappable) {
+    mIsRemovable = true;
+  }
+  LOG("Volume %s: IsHotSwappable set to %d state %s",
+      NameStr(), (int)mIsHotSwappable, StateStr(mState));
+  sEventObserverList.Broadcast(this);
+}
+
+bool
+Volume::BoolConfigValue(const nsCString& aConfigValue, bool& aBoolValue)
+{
+  if (aConfigValue.EqualsLiteral("1") ||
+      aConfigValue.LowerCaseEqualsLiteral("true")) {
+    aBoolValue = true;
+    return true;
+  }
+  if (aConfigValue.EqualsLiteral("0") ||
+      aConfigValue.LowerCaseEqualsLiteral("false")) {
+    aBoolValue = false;
+    return true;
+  }
+  return false;
+}
+
+void
+Volume::SetConfig(const nsCString& aConfigName, const nsCString& aConfigValue)
+{
+  if (aConfigName.LowerCaseEqualsLiteral("removable")) {
+    bool value = false;
+    if (BoolConfigValue(aConfigValue, value)) {
+      SetIsRemovable(value);
+    } else {
+      ERR("Volume %s: invalid value '%s' for configuration '%s'",
+          NameStr(), aConfigValue.get(), aConfigName.get());
+    }
+    return;
+  }
+  if (aConfigName.LowerCaseEqualsLiteral("hotswappable")) {
+    bool value = false;
+    if (BoolConfigValue(aConfigValue, value)) {
+      SetIsHotSwappable(value);
+    } else {
+      ERR("Volume %s: invalid value '%s' for configuration '%s'",
+          NameStr(), aConfigValue.get(), aConfigName.get());
+    }
+    return;
+  }
+  ERR("Volume %s: invalid config '%s'", NameStr(), aConfigName.get());
+}
+
+void
 Volume::SetMediaPresent(bool aMediaPresent)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
   // mMediaPresent is slightly redunant to the state, however
   // when media is removed (while Idle), we get the following:
   //    631 Volume sdcard /mnt/sdcard disk removed (179:0)
--- a/dom/system/gonk/Volume.h
+++ b/dom/system/gonk/Volume.h
@@ -70,16 +70,18 @@ public:
   bool CanBeMounted() const           { return CanBeShared(); }
   bool IsSharingEnabled() const       { return mCanBeShared && mSharingEnabled; }
   bool IsFormatRequested() const      { return CanBeFormatted() && mFormatRequested; }
   bool IsMountRequested() const       { return CanBeMounted() && mMountRequested; }
   bool IsUnmountRequested() const     { return CanBeMounted() && mUnmountRequested; }
   bool IsSharing() const              { return mIsSharing; }
   bool IsFormatting() const           { return mIsFormatting; }
   bool IsUnmounting() const           { return mIsUnmounting; }
+  bool IsRemovable() const            { return mIsRemovable; }
+  bool IsHotSwappable() const         { return mIsHotSwappable; }
 
   void SetFakeVolume(const nsACString& aMountPoint);
 
   void SetSharingEnabled(bool aSharingEnabled);
   void SetFormatRequested(bool aFormatRequested);
   void SetMountRequested(bool aMountRequested);
   void SetUnmountRequested(bool aUnmountRequested);
 
@@ -102,21 +104,26 @@ private:
   void StartUnmount(VolumeResponseCallback* aCallback);
   void StartFormat(VolumeResponseCallback* aCallback);
   void StartShare(VolumeResponseCallback* aCallback);
   void StartUnshare(VolumeResponseCallback* aCallback);
 
   void SetIsSharing(bool aIsSharing);
   void SetIsFormatting(bool aIsFormatting);
   void SetIsUnmounting(bool aIsUnmounting);
+  void SetIsRemovable(bool aIsRemovable);
+  void SetIsHotSwappable(bool aIsHotSwappable);
   void SetState(STATE aNewState);
   void SetMediaPresent(bool aMediaPresent);
   void SetMountPoint(const nsCSubstring& aMountPoint);
   void StartCommand(VolumeCommand* aCommand);
 
+  bool BoolConfigValue(const nsCString& aConfigValue, bool& aBoolValue);
+  void SetConfig(const nsCString& aConfigName, const nsCString& aConfigValue);
+
   void HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer);
 
   static void UpdateMountLock(const nsACString& aVolumeName,
                               const int32_t& aMountGeneration,
                               const bool& aMountLocked);
 
   bool              mMediaPresent;
   STATE             mState;
@@ -127,16 +134,18 @@ private:
   bool              mSharingEnabled;
   bool              mFormatRequested;
   bool              mMountRequested;
   bool              mUnmountRequested;
   bool              mCanBeShared;
   bool              mIsSharing;
   bool              mIsFormatting;
   bool              mIsUnmounting;
+  bool              mIsRemovable;
+  bool              mIsHotSwappable;
   uint32_t          mId;                // Unique ID (used by MTP)
 
   static VolumeObserverList sEventObserverList;
 };
 
 } // system
 } // mozilla
 
--- a/dom/system/gonk/VolumeManager.cpp
+++ b/dom/system/gonk/VolumeManager.cpp
@@ -163,54 +163,114 @@ void VolumeManager::InitConfig()
   // This function uses /system/etc/volume.cfg to add additional volumes
   // to the Volume Manager.
   //
   // This is useful on devices like the Nexus 4, which have no physical sd card
   // or dedicated partition.
   //
   // The format of the volume.cfg file is as follows:
   // create volume-name mount-point
+  // configure volume-name preference preference-value
   // Blank lines and lines starting with the hash character "#" will be ignored.
 
   ScopedCloseFile fp;
   int n = 0;
   char line[255];
-  char *command, *volNamePtr, *mountPointPtr, *save_ptr;
   const char *filename = "/system/etc/volume.cfg";
   if (!(fp = fopen(filename, "r"))) {
     LOG("Unable to open volume configuration file '%s' - ignoring", filename);
     return;
   }
   while(fgets(line, sizeof(line), fp)) {
     const char *delim = " \t\n";
     n++;
 
     if (line[0] == '#')
       continue;
-    if (!(command = strtok_r(line, delim, &save_ptr))) {
+
+    nsCString commandline(line);
+    nsCWhitespaceTokenizer tokenizer(commandline);
+    if (!tokenizer.hasMoreTokens()) {
       // Blank line - ignore
       continue;
     }
-    if (!strcmp(command, "create")) {
-      if (!(volNamePtr = strtok_r(nullptr, delim, &save_ptr))) {
+
+    nsCString command(tokenizer.nextToken());
+    if (command.EqualsLiteral("create")) {
+      if (!tokenizer.hasMoreTokens()) {
         ERR("No vol_name in %s line %d",  filename, n);
         continue;
       }
-      if (!(mountPointPtr = strtok_r(nullptr, delim, &save_ptr))) {
-        ERR("No mount point for volume '%s'. %s line %d", volNamePtr, filename, n);
+      nsCString volName(tokenizer.nextToken());
+      if (!tokenizer.hasMoreTokens()) {
+        ERR("No mount point for volume '%s'. %s line %d",
+             volName.get(), filename, n);
+        continue;
+      }
+      nsCString mountPoint(tokenizer.nextToken());
+      RefPtr<Volume> vol = FindAddVolumeByName(volName);
+      vol->SetFakeVolume(mountPoint);
+      continue;
+    }
+    if (command.EqualsLiteral("configure")) {
+      if (!tokenizer.hasMoreTokens()) {
+        ERR("No vol_name in %s line %d", filename, n);
+        continue;
+      }
+      nsCString volName(tokenizer.nextToken());
+      if (!tokenizer.hasMoreTokens()) {
+        ERR("No configuration name specified for volume '%s'. %s line %d",
+             volName.get(), filename, n);
+        continue;
+      }
+      nsCString configName(tokenizer.nextToken());
+      if (!tokenizer.hasMoreTokens()) {
+        ERR("No value for configuration name '%s'. %s line %d",
+            configName.get(), filename, n);
         continue;
       }
-      nsCString mountPoint(mountPointPtr);
-      nsCString volName(volNamePtr);
+      nsCString configValue(tokenizer.nextToken());
+      RefPtr<Volume> vol = FindVolumeByName(volName);
+      if (vol) {
+        vol->SetConfig(configName, configValue);
+      } else {
+        ERR("Invalid volume name '%s'.", volName.get());
+      }
+      continue;
+    }
+    ERR("Unrecognized command: '%s'", command.get());
+  }
+}
+
+void
+VolumeManager::DefaultConfig()
+{
 
-      RefPtr<Volume> vol = FindAddVolumeByName(volName);
-      vol->SetFakeVolume(mountPoint);
-    }
-    else {
-      ERR("Unrecognized command: '%s'", command);
+  VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+  if (numVolumes == 0) {
+    return;
+  }
+  if (numVolumes == 1) {
+    // This is to cover early shipping phones like the Buri,
+    // which had no internal storage, and only external sdcard.
+    //
+    // Phones line the nexus-4 which only have an internal
+    // storage area will need to have a volume.cfg file with
+    // removable set to false.
+    RefPtr<Volume> vol = VolumeManager::GetVolume(0);
+    vol->SetIsRemovable(true);
+    vol->SetIsHotSwappable(true);
+    return;
+  }
+  VolumeManager::VolumeArray::index_type volIndex;
+  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+    RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+    if (!vol->Name().EqualsLiteral("sdcard")) {
+      vol->SetIsRemovable(true);
+      vol->SetIsHotSwappable(true);
     }
   }
 }
 
 class VolumeListCallback : public VolumeResponseCallback
 {
   virtual void ResponseReceived(const VolumeCommand* aCommand)
   {
@@ -228,16 +288,17 @@ class VolumeListCallback : public Volume
         vol->HandleVoldResponse(ResponseCode(), tokenizer);
         break;
       }
 
       case ::ResponseCode::CommandOkay: {
         // We've received the list of volumes. Now read the Volume.cfg
         // file to perform customizations, and then tell everybody
         // that we're ready for business.
+        VolumeManager::DefaultConfig();
         VolumeManager::InitConfig();
         VolumeManager::Dump("READY");
         VolumeManager::SetState(VolumeManager::VOLUMES_READY);
         break;
       }
     }
   }
 };
--- a/dom/system/gonk/VolumeManager.h
+++ b/dom/system/gonk/VolumeManager.h
@@ -133,16 +133,18 @@ public:
   static void       PostCommand(VolumeCommand* aCommand);
 
 protected:
 
   virtual void OnLineRead(int aFd, nsDependentCSubstring& aMessage);
   virtual void OnFileCanWriteWithoutBlocking(int aFd);
   virtual void OnError();
 
+  static void DefaultConfig();
+
 private:
   bool OpenSocket();
 
   friend class VolumeListCallback; // Calls SetState
 
   static void SetState(STATE aNewState);
 
   void Restart();
--- a/dom/system/gonk/nsIVolume.idl
+++ b/dom/system/gonk/nsIVolume.idl
@@ -1,16 +1,16 @@
 /* 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(9B5E27F4-601C-11E4-B541-3E2D1D5D46B0)]
+[scriptable, uuid(946B5334-6EC9-11E4-8689-F3061E5D46B0)]
 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;
@@ -81,16 +81,23 @@ interface nsIVolume : nsISupports
   void mount();
 
   // Unmounts the volume in IO thread, if the volume is already mounted.
   // Automounter will unmount it. Otherwise Automounter will skip this.
   void unmount();
 
   // Whether this is a fake volume.
   readonly attribute boolean isFake;
+
+  // Whether this is a removable volume
+  readonly attribute boolean isRemovable;
+
+  // Whether this is a hot-swappable volume
+  readonly attribute boolean isHotSwappable;
+
 };
 
 %{C++
 // For use with the ObserverService
 #define NS_VOLUME_STATE_CHANGED  "volume-state-changed"
 
 namespace mozilla {
 namespace system {
--- a/dom/system/gonk/nsVolume.cpp
+++ b/dom/system/gonk/nsVolume.cpp
@@ -54,17 +54,19 @@ nsVolume::nsVolume(const Volume* aVolume
     mMountPoint(NS_ConvertUTF8toUTF16(aVolume->MountPoint())),
     mState(aVolume->State()),
     mMountGeneration(aVolume->MountGeneration()),
     mMountLocked(aVolume->IsMountLocked()),
     mIsFake(!aVolume->CanBeShared()),
     mIsMediaPresent(aVolume->MediaPresent()),
     mIsSharing(aVolume->IsSharing()),
     mIsFormatting(aVolume->IsFormatting()),
-    mIsUnmounting(aVolume->IsUnmounting())
+    mIsUnmounting(aVolume->IsUnmounting()),
+    mIsRemovable(aVolume->IsRemovable()),
+    mIsHotSwappable(aVolume->IsHotSwappable())
 {
 }
 
 bool nsVolume::Equals(nsIVolume* aVolume)
 {
   nsString volName;
   aVolume->GetName(volName);
   if (!mName.Equals(volName)) {
@@ -114,16 +116,28 @@ bool nsVolume::Equals(nsIVolume* aVolume
   }
 
   bool isUnmounting;
   aVolume->GetIsUnmounting(&isUnmounting);
   if (mIsUnmounting != isUnmounting) {
     return false;
   }
 
+  bool isRemovable;
+  aVolume->GetIsRemovable(&isRemovable);
+  if (mIsRemovable != isRemovable) {
+    return false;
+  }
+
+  bool isHotSwappable;
+  aVolume->GetIsHotSwappable(&isHotSwappable);
+  if (mIsHotSwappable != isHotSwappable) {
+    return false;
+  }
+
   return true;
 }
 
 NS_IMETHODIMP nsVolume::GetIsMediaPresent(bool* aIsMediaPresent)
 {
   *aIsMediaPresent = mIsMediaPresent;
   return NS_OK;
 }
@@ -195,16 +209,28 @@ NS_IMETHODIMP nsVolume::GetStats(nsIVolu
 }
 
 NS_IMETHODIMP nsVolume::GetIsFake(bool *aIsFake)
 {
   *aIsFake = mIsFake;
   return NS_OK;
 }
 
+NS_IMETHODIMP nsVolume::GetIsRemovable(bool *aIsRemovable)
+{
+  *aIsRemovable = mIsRemovable;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsHotSwappable(bool *aIsHotSwappable)
+{
+  *aIsHotSwappable = mIsHotSwappable;
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsVolume::Format()
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   XRE_GetIOMessageLoop()->PostTask(
       FROM_HERE,
       NewRunnableFunction(FormatVolumeIOThread, NameStr()));
 
@@ -272,21 +298,22 @@ void nsVolume::UnmountVolumeIOThread(con
   AutoMounterUnmountVolume(aVolume);
 }
 
 void
 nsVolume::LogState() const
 {
   if (mState == nsIVolume::STATE_MOUNTED) {
     LOG("nsVolume: %s state %s @ '%s' gen %d locked %d fake %d "
-        "media %d sharing %d formatting %d unmounting %d",
+        "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
         NameStr().get(), StateStr(), MountPointStr().get(),
         MountGeneration(), (int)IsMountLocked(), (int)IsFake(),
         (int)IsMediaPresent(), (int)IsSharing(),
-        (int)IsFormatting(), (int)IsUnmounting());
+        (int)IsFormatting(), (int)IsUnmounting(),
+        (int)IsRemovable(), (int)IsHotSwappable());
     return;
   }
 
   LOG("nsVolume: %s state %s", NameStr().get(), StateStr());
 }
 
 void nsVolume::Set(nsIVolume* aVolume)
 {
@@ -295,16 +322,18 @@ void nsVolume::Set(nsIVolume* aVolume)
   aVolume->GetName(mName);
   aVolume->GetMountPoint(mMountPoint);
   aVolume->GetState(&mState);
   aVolume->GetIsFake(&mIsFake);
   aVolume->GetIsMediaPresent(&mIsMediaPresent);
   aVolume->GetIsSharing(&mIsSharing);
   aVolume->GetIsFormatting(&mIsFormatting);
   aVolume->GetIsUnmounting(&mIsUnmounting);
+  aVolume->GetIsRemovable(&mIsRemovable);
+  aVolume->GetIsHotSwappable(&mIsHotSwappable);
 
   int32_t volMountGeneration;
   aVolume->GetMountGeneration(&volMountGeneration);
 
   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;
@@ -373,16 +402,34 @@ nsVolume::SetIsFake(bool aIsFake)
   if (mIsFake) {
     // The media is always present for fake volumes.
     mIsMediaPresent = true;
     MOZ_ASSERT(!mIsSharing);
   }
 }
 
 void
+nsVolume::SetIsRemovable(bool aIsRemovable)
+{
+  mIsRemovable = aIsRemovable;
+  if (!mIsRemovable) {
+    mIsHotSwappable = false;
+  }
+}
+
+void
+nsVolume::SetIsHotSwappable(bool aIsHotSwappable)
+{
+  mIsHotSwappable = aIsHotSwappable;
+  if (mIsHotSwappable) {
+    mIsRemovable = true;
+  }
+}
+
+void
 nsVolume::SetState(int32_t aState)
 {
   static int32_t sMountGeneration = 0;
 
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(IsFake());
 
--- a/dom/system/gonk/nsVolume.h
+++ b/dom/system/gonk/nsVolume.h
@@ -26,42 +26,47 @@ public:
   nsVolume(const Volume* aVolume);
 
   // This constructor is used by ContentChild::RecvFileSystemUpdate which is
   // used to update the volume cache maintained in the child process.
   nsVolume(const nsAString& aName, const nsAString& aMountPoint,
            const int32_t& aState, const int32_t& aMountGeneration,
            const bool& aIsMediaPresent, const bool& aIsSharing,
            const bool& aIsFormatting, const bool& aIsFake,
-           const bool& aIsUnmounting)
+           const bool& aIsUnmounting, const bool& aIsRemovable,
+           const bool& aIsHotSwappable)
     : mName(aName),
       mMountPoint(aMountPoint),
       mState(aState),
       mMountGeneration(aMountGeneration),
       mMountLocked(false),
       mIsFake(aIsFake),
       mIsMediaPresent(aIsMediaPresent),
       mIsSharing(aIsSharing),
       mIsFormatting(aIsFormatting),
-      mIsUnmounting(aIsUnmounting)
+      mIsUnmounting(aIsUnmounting),
+      mIsRemovable(aIsRemovable),
+      mIsHotSwappable(aIsHotSwappable)
   {
   }
 
   // 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),
       mMountGeneration(-1),
       mMountLocked(true),  // Needs to agree with Volume::Volume
       mIsFake(false),
       mIsMediaPresent(false),
       mIsSharing(false),
       mIsFormatting(false),
-      mIsUnmounting(false)
+      mIsUnmounting(false),
+      mIsRemovable(false),
+      mIsHotSwappable(false)
   {
   }
 
   bool Equals(nsIVolume* aVolume);
   void Set(nsIVolume* aVolume);
 
   void LogState() const;
 
@@ -77,40 +82,46 @@ public:
   int32_t State() const               { return mState; }
   const char* StateStr() const        { return NS_VolumeStateStr(mState); }
 
   bool IsFake() const                 { return mIsFake; }
   bool IsMediaPresent() const         { return mIsMediaPresent; }
   bool IsSharing() const              { return mIsSharing; }
   bool IsFormatting() const           { return mIsFormatting; }
   bool IsUnmounting() const           { return mIsUnmounting; }
+  bool IsRemovable() const            { return mIsRemovable; }
+  bool IsHotSwappable() const         { return mIsHotSwappable; }
 
   typedef nsTArray<nsRefPtr<nsVolume> > Array;
 
 private:
   virtual ~nsVolume() {}  // MozExternalRefCountType complains if this is non-virtual
 
   friend class nsVolumeService; // Calls the following XxxMountLock functions
   void UpdateMountLock(const nsAString& aMountLockState);
   void UpdateMountLock(bool aMountLocked);
 
   void SetIsFake(bool aIsFake);
+  void SetIsRemovable(bool aIsRemovable);
+  void SetIsHotSwappable(bool aIsHotSwappble);
   void SetState(int32_t aState);
   static void FormatVolumeIOThread(const nsCString& aVolume);
   static void MountVolumeIOThread(const nsCString& aVolume);
   static void UnmountVolumeIOThread(const nsCString& aVolume);
 
   nsString mName;
   nsString mMountPoint;
   int32_t  mState;
   int32_t  mMountGeneration;
   bool     mMountLocked;
   bool     mIsFake;
   bool     mIsMediaPresent;
   bool     mIsSharing;
   bool     mIsFormatting;
   bool     mIsUnmounting;
+  bool     mIsRemovable;
+  bool     mIsHotSwappable;
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_nsvolume_h__
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -205,17 +205,19 @@ nsVolumeService::CreateOrGetVolumeByPath
   // from the pathname, so that the caller can determine the volume size.
   nsCOMPtr<nsIVolume> vol = new nsVolume(NS_LITERAL_STRING("fake"),
                                          aPath, nsIVolume::STATE_MOUNTED,
                                          -1    /* generation */,
                                          true  /* isMediaPresent*/,
                                          false /* isSharing */,
                                          false /* isFormatting */,
                                          true  /* isFake */,
-                                         false /* isUnmounting*/);
+                                         false /* isUnmounting */,
+                                         false /* isRemovable */,
+                                         false /* isHotSwappable*/);
   vol.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsVolumeService::GetVolumeNames(nsIArray** aVolNames)
 {
   GetVolumesFromParent();
@@ -295,17 +297,19 @@ nsVolumeService::GetVolumesFromParent()
     nsRefPtr<nsVolume> vol = new nsVolume(volInfo.name(),
                                           volInfo.mountPoint(),
                                           volInfo.volState(),
                                           volInfo.mountGeneration(),
                                           volInfo.isMediaPresent(),
                                           volInfo.isSharing(),
                                           volInfo.isFormatting(),
                                           volInfo.isFake(),
-                                          volInfo.isUnmounting());
+                                          volInfo.isUnmounting(),
+                                          volInfo.isRemovable(),
+                                          volInfo.isHotSwappable());
     UpdateVolume(vol, false);
   }
 }
 
 NS_IMETHODIMP
 nsVolumeService::CreateMountLock(const nsAString& aVolumeName, nsIVolumeMountLock **aResult)
 {
   nsCOMPtr<nsIVolumeMountLock> mountLock = nsVolumeMountLock::Create(aVolumeName);
@@ -420,17 +424,19 @@ nsVolumeService::CreateFakeVolume(const 
 {
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     nsRefPtr<nsVolume> vol = new nsVolume(name, path, nsIVolume::STATE_INIT,
                                           -1    /* mountGeneration */,
                                           true  /* isMediaPresent */,
                                           false /* isSharing */,
                                           false /* isFormatting */,
                                           true  /* isFake */,
-                                          false /* isUnmounting */);
+                                          false /* isUnmounting */,
+                                          false /* isRemovable */,
+                                          false /* isHotSwappable */);
     vol->LogState();
     UpdateVolume(vol.get());
     return NS_OK;
   }
 
   ContentChild::GetSingleton()->SendCreateFakeVolume(nsString(name), nsString(path));
   return NS_OK;
 }
@@ -470,40 +476,42 @@ public:
   {
     MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
     DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d "
-        "media %d sharing %d formatting %d unmounting %d",
+        "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
         mVolume->NameStr().get(), mVolume->StateStr(),
         mVolume->MountGeneration(), (int)mVolume->IsMountLocked(),
         (int)mVolume->IsMediaPresent(), mVolume->IsSharing(),
-        mVolume->IsFormatting(), mVolume->IsUnmounting());
+        mVolume->IsFormatting(), mVolume->IsUnmounting(),
+        (int)mVolume->IsRemovable(), (int)mVolume->IsHotSwappable());
 
     mVolumeService->UpdateVolume(mVolume);
     mVolumeService = nullptr;
     mVolume = nullptr;
     return NS_OK;
   }
 
 private:
   nsRefPtr<nsVolumeService> mVolumeService;
   nsRefPtr<nsVolume>        mVolume;
 };
 
 void
 nsVolumeService::UpdateVolumeIOThread(const Volume* aVolume)
 {
   DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d "
-      "media %d sharing %d formatting %d unmounting %d",
+      "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
       aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(),
       aVolume->MountGeneration(), (int)aVolume->IsMountLocked(),
       (int)aVolume->MediaPresent(), (int)aVolume->IsSharing(),
-      (int)aVolume->IsFormatting(), (int)mVolume->IsUnmounting());
+      (int)aVolume->IsFormatting(), (int)mVolume->IsUnmounting(),
+      (int)aVolume->IsRemovable(), (int)mVolume->IsHotSwappable());
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume));
 }
 
 } // namespace system
 } // namespace mozilla