Bug 1037212 - [Flame] Phone is not completely erased when a ringtone is set from the Music App r=dougt,dhylands,khuey a=2.0+
authorFabrice Desré <fabrice@mozilla.com>
Thu, 17 Jul 2014 18:30:47 -0700
changeset 209152 f70dabed667eab01ef61bc4e8914d402c2a19b3e
parent 209151 4e6d0faf0d0e0ba5a2843abdabe7115bfd4cf54d
child 209153 4c002d3cbe28dc53c43066523fddad102dd4b2e4
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougt, dhylands, khuey, 2
bugs1037212
milestone32.0a2
Bug 1037212 - [Flame] Phone is not completely erased when a ringtone is set from the Music App r=dougt,dhylands,khuey a=2.0+
b2g/chrome/content/shell.js
b2g/components/ProcessGlobal.js
b2g/components/RecoveryService.js
dom/devicestorage/nsDeviceStorage.cpp
dom/power/PowerManager.cpp
dom/power/PowerManager.h
dom/system/gonk/nsIVolumeService.idl
dom/system/gonk/nsVolumeService.cpp
dom/webidl/MozPowerManager.webidl
hal/Hal.cpp
hal/Hal.h
hal/fallback/FallbackFactoryReset.cpp
hal/gonk/GonkHal.cpp
hal/gonk/nsIRecoveryService.idl
hal/sandbox/PHal.ipdl
hal/sandbox/SandboxHal.cpp
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1238,17 +1238,17 @@ window.addEventListener('ContentStart', 
   let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
                         .getService(Ci.nsIVolumeService);
 
   let stats = volumeService.createOrGetVolumeByPath(path).getStats();
 
   // We must set the size in KB, and keep a bit of free space.
   let size = Math.floor(stats.totalBytes / 1024) - 1024;
   Services.prefs.setIntPref("browser.cache.disk.capacity", size);
-}) ()
+})();
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 let SensorsListener = {
   sensorsListenerDevices: ['crespo'],
   device: libcutils.property_get("ro.product.device"),
 
   deviceNeedsWorkaround: function SensorsListener_deviceNeedsWorkaround() {
--- a/b2g/components/ProcessGlobal.js
+++ b/b2g/components/ProcessGlobal.js
@@ -28,35 +28,91 @@ Cu.import("resource://gre/modules/CSPUti
 function debug(msg) {
   log(msg);
 }
 function log(msg) {
   // This file implements console.log(), so use dump().
   //dump('ProcessGlobal: ' + msg + '\n');
 }
 
+const gFactoryResetFile = "/persist/__post_reset_cmd__";
+
 function ProcessGlobal() {}
 ProcessGlobal.prototype = {
   classID: Components.ID('{1a94c87a-5ece-4d11-91e1-d29c29f21b28}'),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
+  wipeDir: function(path) {
+    log("wipeDir " + path);
+    let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    dir.initWithPath(path);
+    if (!dir.exists() || !dir.isDirectory()) {
+      return;
+    }
+    let entries = dir.directoryEntries;
+    while (entries.hasMoreElements()) {
+      let file = entries.getNext().QueryInterface(Ci.nsIFile);
+      log("Deleting " + file.path);
+      try {
+        file.remove(true);
+      } catch(e) {}
+    }
+  },
+
+  processWipeFile: function(text) {
+    log("processWipeFile " + text);
+    let lines = text.split("\n");
+    lines.forEach((line) => {
+      log(line);
+      let params = line.split(" ");
+      if (params[0] == "wipe") {
+        this.wipeDir(params[1]);
+      }
+    });
+  },
+
+  cleanupAfterFactoryReset: function() {
+    log("cleanupAfterWipe start");
+
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    file.initWithPath(gFactoryResetFile);
+    if (!file.exists()) {
+      debug("Nothing to wipe.")
+      return;
+    }
+
+    Cu.import("resource://gre/modules/osfile.jsm");
+    let promise = OS.File.read(gFactoryResetFile);
+    promise.then(
+      (array) => {
+        file.remove(false);
+        let decoder = new TextDecoder();
+        this.processWipeFile(decoder.decode(array));
+      }
+    );
+
+    log("cleanupAfterWipe end.");
+  },
+
   observe: function pg_observe(subject, topic, data) {
     switch (topic) {
     case 'app-startup': {
       Services.obs.addObserver(this, 'console-api-log-event', false);
       let inParent = Cc["@mozilla.org/xre/app-info;1"]
                        .getService(Ci.nsIXULRuntime)
                        .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
       if (inParent) {
         let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                      .getService(Ci.nsIMessageListenerManager);
         ppmm.addMessageListener("getProfD", function(message) {
           return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
         });
+
+        this.cleanupAfterFactoryReset();
       }
       break;
     }
     case 'console-api-log-event': {
       // Pipe `console` log messages to the nsIConsoleService which
       // writes them to logcat on Gonk.
       let message = subject.wrappedJSObject;
       let prefix = ('Content JS ' + message.level.toUpperCase() +
--- a/b2g/components/RecoveryService.js
+++ b/b2g/components/RecoveryService.js
@@ -43,40 +43,71 @@ let librecovery = (function() {
 
     FotaUpdateStatus:    FotaUpdateStatus,
     getFotaUpdateStatus: library.declare("getFotaUpdateStatus",
                                          ctypes.default_abi,
                                          ctypes.int,
                                          FotaUpdateStatus.ptr)
   };
 })();
+
+const gFactoryResetFile = "/persist/__post_reset_cmd__";
+
 #endif
 
 function RecoveryService() {}
 
 RecoveryService.prototype = {
   classID: RECOVERYSERVICE_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRecoveryService]),
   classInfo: XPCOMUtils.generateCI({
     classID: RECOVERYSERVICE_CID,
     contractID: RECOVERYSERVICE_CONTRACTID,
     interfaces: [Ci.nsIRecoveryService],
     classDescription: "B2G Recovery Service"
   }),
 
-  factoryReset: function RS_factoryReset() {
+  factoryReset: function RS_factoryReset(reason) {
 #ifdef MOZ_WIDGET_GONK
-    // If this succeeds, then the device reboots and this never returns
-    if (librecovery.factoryReset() != 0) {
-      log("Error: Factory reset failed. Trying again after clearing cache.");
+    function doReset() {
+      // If this succeeds, then the device reboots and this never returns
+      if (librecovery.factoryReset() != 0) {
+        log("Error: Factory reset failed. Trying again after clearing cache.");
+      }
+      let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+                    .getService(Ci.nsICacheStorageService);
+      cache.clear();
+      if (librecovery.factoryReset() != 0) {
+        log("Error: Factory reset failed again");
+      }
     }
-    var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
-    cache.clear();
-    if (librecovery.factoryReset() != 0) {
-      log("Error: Factory reset failed again");
+
+    log("factoryReset " + reason);
+    if (reason == "wipe") {
+      let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
+                          .getService(Ci.nsIVolumeService);
+      let volNames = volumeService.getVolumeNames();
+      log("Found " + volNames.length + " volumes");
+      let text = "";
+      for (let i = 0; i < volNames.length; i++) {
+        let name = volNames.queryElementAt(i, Ci.nsISupportsString);
+        let volume = volumeService.getVolumeByName(name.data);
+        log("Got volume: " + name.data + " at " + volume.mountPoint);
+        text += "wipe " + volume.mountPoint + "\n";
+      }
+
+      Cu.import("resource://gre/modules/osfile.jsm");
+      let encoder = new TextEncoder();
+      let array = encoder.encode(text);
+      let promise = OS.File.writeAtomic(gFactoryResetFile, array,
+                                        { tmpPath: gFactoryResetFile + ".tmp" });
+
+      promise.then(doReset);
+    } else {
+      doReset();
     }
 #endif
     throw Cr.NS_ERROR_FAILURE;
   },
 
   installFotaUpdate: function RS_installFotaUpdate(updatePath) {
 #ifdef MOZ_WIDGET_GONK
     // If this succeeds, then the device reboots and this never returns
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -22,16 +22,17 @@
 #include "mozilla/dom/Promise.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/Services.h"
 
+#include "nsArrayUtils.h"
 #include "nsAutoPtr.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIFile.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIDOMFile.h"
 #include "nsDOMBlobBuilder.h"
@@ -3249,17 +3250,29 @@ nsDOMDeviceStorage::GetOrderedVolumeName
 {
   if (sVolumeNameCache && sVolumeNameCache->Length() > 0) {
     aVolumeNames.AppendElements(*sVolumeNameCache);
     return;
   }
 #ifdef MOZ_WIDGET_GONK
   nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
   if (vs) {
-    vs->GetVolumeNames(aVolumeNames);
+    nsCOMPtr<nsIArray> volNames;
+    vs->GetVolumeNames(getter_AddRefs(volNames));
+    uint32_t length = -1;
+    volNames->GetLength(&length);
+    for (uint32_t i = 0; i < length; i++) {
+      nsCOMPtr<nsISupportsString> str = do_QueryElementAt(volNames, i);
+      if (str) {
+        nsAutoString s;
+        if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
+          aVolumeNames.AppendElement(s);
+        }
+      }
+    }
 
     // If the volume sdcard exists, then we want it to be first.
 
     VolumeNameArray::index_type sdcardIndex;
     sdcardIndex = aVolumeNames.IndexOf(NS_LITERAL_STRING("sdcard"));
     if (sdcardIndex != VolumeNameArray::NoIndex && sdcardIndex > 0) {
       aVolumeNames.RemoveElementAt(sdcardIndex);
       aVolumeNames.InsertElementAt(0, NS_LITERAL_STRING("sdcard"));
--- a/dom/power/PowerManager.cpp
+++ b/dom/power/PowerManager.cpp
@@ -73,19 +73,19 @@ PowerManager::Reboot(ErrorResult& aRv)
   if (pmService) {
     pmService->Reboot();
   } else {
     aRv.Throw(NS_ERROR_UNEXPECTED);
   }
 }
 
 void
-PowerManager::FactoryReset()
+PowerManager::FactoryReset(mozilla::dom::FactoryResetReason& aReason)
 {
-  hal::FactoryReset();
+  hal::FactoryReset(aReason);
 }
 
 void
 PowerManager::PowerOff(ErrorResult& aRv)
 {
   nsCOMPtr<nsIPowerManagerService> pmService =
     do_GetService(POWERMANAGERSERVICE_CONTRACTID);
   if (pmService) {
--- a/dom/power/PowerManager.h
+++ b/dom/power/PowerManager.h
@@ -7,16 +7,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "nsIDOMWakeLockListener.h"
 #include "nsIDOMWindow.h"
 #include "nsWeakReference.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
+#include "mozilla/dom/MozPowerManagerBinding.h"
 
 class nsPIDOMWindow;
 
 namespace mozilla {
 class ErrorResult;
 
 namespace dom {
 
@@ -40,17 +41,17 @@ public:
 
   // WebIDL
   nsIDOMWindow* GetParentObject() const
   {
     return mWindow;
   }
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
   void Reboot(ErrorResult& aRv);
-  void FactoryReset();
+  void FactoryReset(mozilla::dom::FactoryResetReason& aReason);
   void PowerOff(ErrorResult& aRv);
   void AddWakeLockListener(nsIDOMMozWakeLockListener* aListener);
   void RemoveWakeLockListener(nsIDOMMozWakeLockListener* aListener);
   void GetWakeLockState(const nsAString& aTopic, nsAString& aState,
                         ErrorResult& aRv);
   bool ScreenEnabled();
   void SetScreenEnabled(bool aEnabled);
   bool KeyLightEnabled();
--- a/dom/system/gonk/nsIVolumeService.idl
+++ b/dom/system/gonk/nsIVolumeService.idl
@@ -1,34 +1,30 @@
 /* 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"
 
-%{C++
-#include "nsTArray.h"
-#include "nsString.h"
-%}
-[ref] native nsStringTArrayRef(nsTArray<nsString>);
+interface nsIArray;
 
-[scriptable, uuid(a3b110cd-74f2-43cb-84c6-2a87713f2774)]
+[scriptable, uuid(cab99ab4-542e-4387-bd40-db6ef30e4f5f)]
 interface nsIVolumeService : nsISupports
 {
     nsIVolume getVolumeByName(in DOMString volName);
     nsIVolume getVolumeByPath(in DOMString path);
     nsIVolume createOrGetVolumeByPath(in DOMString path);
 
     void BroadcastVolume(in DOMString volName);
 
     nsIVolumeMountLock createMountLock(in DOMString volName);
 
-    [noscript] void getVolumeNames(in nsStringTArrayRef aVolNames);
+    nsIArray getVolumeNames();
 
     /* for test case only to simulate sdcard insertion/removal */
     void createFakeVolume(in DOMString name, in DOMString path);
     void SetFakeVolumeState(in DOMString name, in long state);
 };
 
 %{C++
 #define NS_VOLUMESERVICE_CID \
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -7,19 +7,21 @@
 #include "Volume.h"
 #include "VolumeManager.h"
 #include "VolumeServiceIOThread.h"
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsDependentSubstring.h"
 #include "nsIDOMWakeLockListener.h"
+#include "nsIMutableArray.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIPowerManagerService.h"
+#include "nsISupportsPrimitives.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"
@@ -251,27 +253,44 @@ nsVolumeService::CreateOrGetVolumeByPath
                                          false /* isSharing */,
                                          false /* isFormatting */,
                                          true  /* isFake */);
   vol.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsVolumeService::GetVolumeNames(nsTArray<nsString>& aVolNames)
+nsVolumeService::GetVolumeNames(nsIArray** aVolNames)
 {
+  NS_ENSURE_ARG_POINTER(aVolNames);
   MonitorAutoLock autoLock(mArrayMonitor);
 
+  *aVolNames = nullptr;
+
+  nsresult rv;
+  nsCOMPtr<nsIMutableArray> volNames =
+    do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
   nsVolume::Array::index_type volIndex;
   for (volIndex = 0; volIndex < numVolumes; volIndex++) {
     nsRefPtr<nsVolume> vol = mVolumeArray[volIndex];
-    aVolNames.AppendElement(vol->Name());
+    nsCOMPtr<nsISupportsString> isupportsString =
+      do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = isupportsString->SetData(vol->Name());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = volNames->AppendElement(isupportsString, false);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  NS_ADDREF(*aVolNames = volNames);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsVolumeService::CreateMountLock(const nsAString& aVolumeName, nsIVolumeMountLock **aResult)
 {
   nsCOMPtr<nsIVolumeMountLock> mountLock = nsVolumeMountLock::Create(aVolumeName);
   if (!mountLock) {
--- a/dom/webidl/MozPowerManager.webidl
+++ b/dom/webidl/MozPowerManager.webidl
@@ -1,25 +1,35 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 interface MozWakeLockListener;
 
 /**
+  * The reason for the factory reset.
+  * "normal" : simple factory reset.
+  * "wipe"   : will also attempt to wipe all user storage areas.
+  */
+enum FactoryResetReason {
+    "normal",
+    "wipe"
+};
+
+/**
  * This interface implements navigator.mozPower
  */
 interface MozPowerManager
 {
     [Throws]
     void    powerOff();
     [Throws]
     void    reboot();
-    void    factoryReset();
+    void    factoryReset(optional FactoryResetReason reason = "normal");
 
     /**
      * The listeners are notified when a resource changes its lock state to:
      *  - unlocked
      *  - locked but not visible
      *  - locked and visible
      */
     void    addWakeLockListener(MozWakeLockListener aListener);
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -1212,20 +1212,20 @@ GetFMBandSettings(FMRadioCountry aCountr
       break;
     default:
       MOZ_ASSERT(0);
       break;
     };
     return settings;
 }
 
-void FactoryReset()
+void FactoryReset(mozilla::dom::FactoryResetReason& aReason)
 {
   AssertMainThread();
-  PROXY_IF_SANDBOXED(FactoryReset());
+  PROXY_IF_SANDBOXED(FactoryReset(aReason));
 }
 
 void
 StartDiskSpaceWatcher()
 {
   AssertMainProcess();
   AssertMainThread();
   PROXY_IF_SANDBOXED(StartDiskSpaceWatcher());
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/hal_sandbox/PHal.h"
 #include "mozilla/HalTypes.h"
 #include "base/basictypes.h"
 #include "mozilla/Observer.h"
 #include "mozilla/Types.h"
 #include "nsTArray.h"
 #include "prlog.h"
+#include "mozilla/dom/MozPowerManagerBinding.h"
 #include "mozilla/dom/battery/Types.h"
 #include "mozilla/dom/network/Types.h"
 #include "mozilla/dom/power/Types.h"
 #include "mozilla/hal_sandbox/PHal.h"
 #include "mozilla/dom/ScreenOrientation.h"
 #include "mozilla/HalScreenConfiguration.h"
 
 /*
@@ -588,17 +589,17 @@ hal::FMRadioSettings GetFMBandSettings(h
  *
  * This API is currently only allowed to be used from the main process.
  */
 void StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs);
 
 /**
  * Perform Factory Reset to wipe out all user data.
  */
-void FactoryReset();
+void FactoryReset(mozilla::dom::FactoryResetReason& aReason);
 
 /**
  * Start monitoring the status of gamepads attached to the system.
  */
 void StartMonitoringGamepadStatus();
 
 /**
  * Stop monitoring the status of gamepads attached to the system.
--- a/hal/fallback/FallbackFactoryReset.cpp
+++ b/hal/fallback/FallbackFactoryReset.cpp
@@ -6,13 +6,13 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Hal.h"
 
 namespace mozilla {
 namespace hal_impl {
 
 void
-FactoryReset()
+FactoryReset(mozilla::dom::FactoryResetReason&)
 {}
 
 } // namespace hal_impl
 } // namespace mozilla
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -100,16 +100,17 @@
 #define BATTERY_CHARGING_ARGB 0x00FF0000
 #endif
 #ifndef BATTERY_FULL_ARGB
 #define BATTERY_FULL_ARGB 0x0000FF00
 #endif
 
 using namespace mozilla;
 using namespace mozilla::hal;
+using namespace mozilla::dom;
 
 namespace mozilla {
 namespace hal_impl {
 
 namespace {
 
 /**
  * This runnable runs for the lifetime of the program, once started.  It's
@@ -1670,22 +1671,26 @@ SetCurrentThreadPriority(ThreadPriority 
     }
     default:
       LOG("Unrecognized thread priority %d; Doing nothing", aThreadPriority);
       return;
   }
 }
 
 void
-FactoryReset()
+FactoryReset(FactoryResetReason& aReason)
 {
   nsCOMPtr<nsIRecoveryService> recoveryService =
     do_GetService("@mozilla.org/recovery-service;1");
   if (!recoveryService) {
     NS_WARNING("Could not get recovery service!");
     return;
   }
 
-  recoveryService->FactoryReset();
+  if (aReason == FactoryResetReason::Wipe) {
+    recoveryService->FactoryReset("wipe");
+  } else {
+    recoveryService->FactoryReset("normal");
+  }
 }
 
 } // hal_impl
 } // mozilla
--- a/hal/gonk/nsIRecoveryService.idl
+++ b/hal/gonk/nsIRecoveryService.idl
@@ -1,32 +1,32 @@
 /* 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(acb93ff8-aa6d-4bc8-bedd-2a6a3b802a74)]
+[scriptable, uuid(bc24fb33-a0c1-49ca-aa43-05f167e02fb6)]
 interface nsIRecoveryService : nsISupports
 {
   /**
    * Possible values of fotaStatus.result. These should stay in sync with
    * librecovery/librecovery.h
    */
   const long FOTA_UPDATE_UNKNOWN = 0;
   const long FOTA_UPDATE_FAIL    = 1;
   const long FOTA_UPDATE_SUCCESS = 2;
 
   /**
    * Uses recovery to wipe the data and cache partitions. If this call is
    * successful, the device should reboot before the function call ever returns.
    *
    * @throws NS_ERROR_FAILURE when rebooting into recovery fails for some reason.
    */
-  void factoryReset();
+  void factoryReset(in string reason);
 
   /**
    * Use recovery to install an OTA update.zip. If this call is
    * successful, the device should reboot before the function call ever returns.
    *
    * @throws NS_ERROR_FAILURE when rebooting into recovery fails for some reason.
    */
   void installFotaUpdate(in string updatePath);
--- a/hal/sandbox/PHal.ipdl
+++ b/hal/sandbox/PHal.ipdl
@@ -171,17 +171,17 @@ parent:
       returns (bool allowed);
     UnlockScreenOrientation();
  
     EnableSwitchNotifications(SwitchDevice aDevice);
     DisableSwitchNotifications(SwitchDevice aDevice);
     sync GetCurrentSwitchState(SwitchDevice aDevice)
       returns (SwitchState aState);
 
-    FactoryReset();
+    FactoryReset(nsString aReason);
 
 child:
     NotifySensorChange(SensorData aSensorData);
 
 parent:
     EnableSensorNotifications(SensorType aSensor);
     DisableSensorNotifications(SensorType aSensor);
 
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -437,19 +437,23 @@ GetFMRadioSignalStrength()
 
 void
 CancelFMRadioSeek()
 {
   NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
 }
 
 void
-FactoryReset()
+FactoryReset(FactoryResetReason& aReason)
 {
-  Hal()->SendFactoryReset();
+  if (aReason == FactoryResetReason::Normal) {
+    Hal()->SendFactoryReset(NS_LITERAL_STRING("normal"));
+  } else if (aReason == FactoryResetReason::Wipe) {
+    Hal()->SendFactoryReset(NS_LITERAL_STRING("wipe"));
+  }
 }
 
 void
 StartDiskSpaceWatcher()
 {
   NS_RUNTIMEABORT("StartDiskSpaceWatcher() can't be called from sandboxed contexts.");
 }
 
@@ -870,22 +874,33 @@ public:
   }
 
   void Notify(const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo)
   {
     unused << SendNotifySystemTimezoneChange(aSystemTimezoneChangeInfo);
   }
 
   virtual bool
-  RecvFactoryReset()
+  RecvFactoryReset(const nsString& aReason) MOZ_OVERRIDE
   {
     if (!AssertAppProcessPermission(this, "power")) {
       return false;
     }
-    hal::FactoryReset();
+
+    FactoryResetReason reason = FactoryResetReason::Normal;
+    if (aReason.EqualsLiteral("normal")) {
+      reason = FactoryResetReason::Normal;
+    } else if (aReason.EqualsLiteral("wipe")) {
+      reason = FactoryResetReason::Wipe;
+    } else {
+      // Invalid factory reset reason. That should never happen.
+      return false;
+    }
+
+    hal::FactoryReset(reason);
     return true;
   }
 
   virtual mozilla::ipc::IProtocol*
   CloneProtocol(Channel* aChannel,
                 mozilla::ipc::ProtocolCloneContext* aCtx) MOZ_OVERRIDE
   {
     ContentParent* contentParent = aCtx->GetContentParent();