Bug 737153 - Enable mounting storage through USB from a host machine - Add the AutoMounter, sr=cjones r=qDot
authorDave Hylands <dhylands@mozilla.com>
Thu, 24 May 2012 21:03:34 -0700
changeset 94917 815d53fb27e0cd0d18638d59338d710dc9f988ac
parent 94916 0773c8788f78702a52db3c36b700eb0a6e608121
child 94918 cff8079f23256006ab0a53e071f83170aeea78b4
push id9867
push userkmachulis@mozilla.com
push dateFri, 25 May 2012 04:04:08 +0000
treeherdermozilla-inbound@cff8079f2325 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones, qDot
bugs737153
milestone15.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 737153 - Enable mounting storage through USB from a host machine - Add the AutoMounter, sr=cjones r=qDot
dom/system/gonk/AutoMounter.cpp
dom/system/gonk/AutoMounter.h
dom/system/gonk/AutoMounterSetting.cpp
dom/system/gonk/AutoMounterSetting.h
hal/HalTypes.h
hal/gonk/GonkSwitch.cpp
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -0,0 +1,490 @@
+/* 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 <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <android/log.h>
+
+#include "AutoMounter.h"
+#include "AutoMounterSetting.h"
+#include "base/message_loop.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Hal.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "Volume.h"
+#include "VolumeManager.h"
+
+using namespace mozilla::hal;
+
+/**************************************************************************
+*
+* The following "switch" files are available for monitoring usb
+* connections:
+*
+*   /sys/devices/virtual/switch/usb_connected/state
+*   /sys/devices/virtual/switch/usb_configuration/state
+*
+*   Under gingerbread, only the usb_configuration seems to be available.
+*   Starting with honeycomb, usb_connected was also added.
+*
+*   When a cable insertion/removal occurs, then a uevent similar to the
+*   following will be generted:
+*
+*    change@/devices/virtual/switch/usb_configuration
+*      ACTION=change
+*      DEVPATH=/devices/virtual/switch/usb_configuration
+*      SUBSYSTEM=switch
+*      SWITCH_NAME=usb_configuration
+*      SWITCH_STATE=0
+*      SEQNUM=5038
+*
+*    SWITCH_STATE will be 0 after a removal and 1 after an insertion
+*
+**************************************************************************/
+
+#define USB_CONFIGURATION_SWITCH_NAME   NS_LITERAL_STRING("usb_configuration")
+
+#define GB_SYS_UMS_ENABLE     "/sys/devices/virtual/usb_composite/usb_mass_storage/enable"
+#define GB_SYS_USB_CONFIGURED "/sys/devices/virtual/switch/usb_configuration/state"
+
+#define ICS_SYS_USB_FUNCTIONS "/sys/devices/virtual/android_usb/android0/functions"
+#define ICS_SYS_UMS_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mass_storage"
+#define ICS_SYS_USB_STATE     "/sys/devices/virtual/android_usb/android0/state"
+
+#define USE_DEBUG 0
+
+#undef LOG
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AutoMounter" , ## args)
+#define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "AutoMounter" , ## args)
+
+#if USE_DEBUG
+#define DBG(args...)  __android_log_print(ANDROID_LOG_DEBUG, "AutoMounter" , ## args)
+#else
+#define DBG(args...)
+#endif
+
+namespace mozilla {
+namespace system {
+
+class AutoMounter;
+
+/**************************************************************************
+*
+*   Some helper functions for reading/writing files in /sys
+*
+**************************************************************************/
+
+static bool
+ReadSysFile(const char *aFilename, char *aBuf, size_t aBufSize)
+{
+  int fd = open(aFilename, O_RDONLY);
+  if (fd < 0) {
+    ERR("Unable to open file '%s' for reading", aFilename);
+    return false;
+  }
+  ScopedClose autoClose(fd);
+  ssize_t bytesRead = read(fd, aBuf, aBufSize - 1);
+  if (bytesRead < 0) {
+    ERR("Unable to read from file '%s'", aFilename);
+    return false;
+  }
+  if (aBuf[bytesRead - 1] == '\n') {
+    bytesRead--;
+  }
+  aBuf[bytesRead] = '\0';
+  return true;
+}
+
+static bool
+ReadSysFile(const char *aFilename, bool *aVal)
+{
+  char valBuf[20];
+  if (!ReadSysFile(aFilename, valBuf, sizeof(valBuf))) {
+    return false;
+  }
+  int intVal;
+  if (sscanf(valBuf, "%d", &intVal) != 1) {
+    return false;
+  }
+  *aVal = (intVal != 0);
+  return true;
+}
+
+/***************************************************************************/
+
+inline const char *SwitchStateStr(const SwitchEvent &aEvent)
+{
+  return aEvent.status() == SWITCH_STATE_ON ? "plugged" : "unplugged";
+}
+
+/***************************************************************************/
+
+static bool
+IsUsbCablePluggedIn()
+{
+#if 0
+  // Use this code when bug 745078 gets fixed (or use whatever the
+  // appropriate method is)
+  return GetCurrentSwitchEvent(SWITCH_USB) == SWITCH_STATE_ON;
+#else
+  // Until then, just go read the file directly
+  if (access(ICS_SYS_USB_STATE, F_OK) == 0) {
+    char usbState[20];
+    return ReadSysFile(ICS_SYS_USB_STATE,
+                       usbState, sizeof(usbState)) &&
+           (strcmp(usbState, "CONFIGURED") == 0);
+  }
+  bool configured;
+  return ReadSysFile(GB_SYS_USB_CONFIGURED, &configured) &&
+         configured;
+#endif
+}
+
+/***************************************************************************/
+
+class VolumeManagerStateObserver : public VolumeManager::StateObserver
+{
+public:
+  virtual void Notify(const VolumeManager::StateChangedEvent &aEvent);
+};
+
+class AutoMounterResponseCallback : public VolumeResponseCallback
+{
+public:
+  AutoMounterResponseCallback()
+    : mErrorCount(0)
+  {
+  }
+
+protected:
+  virtual void ResponseReceived(const VolumeCommand *aCommand);
+
+private:
+    const static int kMaxErrorCount = 3; // Max number of errors before we give up
+
+    int   mErrorCount;
+};
+
+/***************************************************************************/
+
+class AutoMounter : public RefCounted<AutoMounter>
+{
+public:
+  AutoMounter()
+    : mResponseCallback(new AutoMounterResponseCallback),
+      mMode(AUTOMOUNTER_DISABLE)
+  {
+    VolumeManager::RegisterStateObserver(&mVolumeManagerStateObserver);
+    DBG("Calling UpdateState from constructor");
+    UpdateState();
+  }
+
+  ~AutoMounter()
+  {
+    VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver);
+  }
+
+  void UpdateState();
+
+  void SetMode(int32_t aMode)
+  {
+    if ((aMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) &&
+        (mMode == AUTOMOUNTER_DISABLE)) {
+      // If it's already disabled, then leave it as disabled.
+      // AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED implies "enabled until unplugged"
+      aMode = AUTOMOUNTER_DISABLE;
+    }
+    mMode = aMode;
+    DBG("Calling UpdateState due to mode set to %d", mMode);
+    UpdateState();
+  }
+
+private:
+
+  VolumeManagerStateObserver      mVolumeManagerStateObserver;
+  RefPtr<VolumeResponseCallback>  mResponseCallback;
+  int32_t                         mMode;
+};
+
+static RefPtr<AutoMounter> sAutoMounter;
+
+/***************************************************************************/
+
+void
+VolumeManagerStateObserver::Notify(const VolumeManager::StateChangedEvent &)
+{
+  LOG("VolumeManager state changed event: %s", VolumeManager::StateStr());
+
+  if (!sAutoMounter) {
+    return;
+  }
+  DBG("Calling UpdateState due to VolumeManagerStateObserver");
+  sAutoMounter->UpdateState();
+}
+
+void
+AutoMounterResponseCallback::ResponseReceived(const VolumeCommand *aCommand)
+{
+
+  if (WasSuccessful()) {
+    DBG("Calling UpdateState due to Volume::OnSuccess");
+    mErrorCount = 0;
+    sAutoMounter->UpdateState();
+    return;
+  }
+  ERR("Command '%s' failed: %d '%s'",
+      aCommand->CmdStr(), ResponseCode(), ResponseStr().get());
+
+  if (++mErrorCount < kMaxErrorCount) {
+    DBG("Calling UpdateState due to VolumeResponseCallback::OnError");
+    sAutoMounter->UpdateState();
+  }
+}
+
+/***************************************************************************/
+
+void
+AutoMounter::UpdateState()
+{
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+  // If the following preconditions are met:
+  //    - UMS is available (i.e. compiled into the kernel)
+  //    - UMS is enabled
+  //    - AutoMounter is enabled
+  //    - USB cable is plugged in
+  //  then we will try to unmount and share
+  //  otherwise we will try to unshare and mount.
+
+  if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+    // The volume manager isn't in a ready state, so there
+    // isn't anything else that we can do.
+    LOG("UpdateState: VolumeManager not ready yet");
+    return;
+  }
+
+  if (mResponseCallback->IsPending()) {
+    // We only deal with one outstanding volume command at a time,
+    // so we need to wait for it to finish.
+    return;
+  }
+
+  std::vector<RefPtr<Volume> > volumeArray;
+  RefPtr<Volume> vol = VolumeManager::FindVolumeByName(NS_LITERAL_CSTRING("sdcard"));
+  if (vol != NULL) {
+    volumeArray.push_back(vol);
+  }
+  if (volumeArray.size() == 0) {
+    // No volumes of interest, nothing to do
+    LOG("UpdateState: No volumes found");
+    return;
+  }
+
+  bool  umsAvail = false;
+  bool  umsEnabled = false;
+
+  if (access(ICS_SYS_USB_FUNCTIONS, F_OK) == 0) {
+    umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
+    char functionsStr[60];
+    umsEnabled = umsAvail &&
+                 ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr)) &&
+                 !!strstr(functionsStr, "mass_storage");
+  } else {
+    umsAvail = ReadSysFile(GB_SYS_UMS_ENABLE, &umsEnabled);
+  }
+
+  bool usbCablePluggedIn = IsUsbCablePluggedIn();
+  bool enabled = (mMode == AUTOMOUNTER_ENABLE);
+
+  if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) {
+    enabled = usbCablePluggedIn;
+    if (!usbCablePluggedIn) {
+      mMode = AUTOMOUNTER_DISABLE;
+    }
+  }
+
+  bool tryToShare = (umsAvail && umsEnabled && enabled && usbCablePluggedIn);
+  LOG("UpdateState: umsAvail:%d umsEnabled:%d mode:%d usbCablePluggedIn:%d tryToShare:%d",
+      umsAvail, umsEnabled, mMode, usbCablePluggedIn, tryToShare);
+
+  VolumeManager::VolumeArray::iterator volIter;
+  for (volIter = volumeArray.begin(); volIter != volumeArray.end(); volIter++) {
+    RefPtr<Volume>  vol = *volIter;
+    Volume::STATE   volState = vol->State();
+
+    LOG("UpdateState: Volume %s is %s", vol->NameStr(), vol->StateStr());
+    if (tryToShare) {
+      // We're going to try to unmount and share the volumes
+      switch (volState) {
+        case Volume::STATE_MOUNTED: {
+          // Volume is mounted, we need to unmount before
+          // we can share.
+          DBG("UpdateState: Unmounting %s", vol->NameStr());
+          vol->StartUnmount(mResponseCallback);
+          return;
+        }
+        case Volume::STATE_IDLE: {
+          // Volume is unmounted. We can go ahead and share.
+          DBG("UpdateState: Sharing %s", vol->NameStr());
+          vol->StartShare(mResponseCallback);
+          return;
+        }
+        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 Volume::STATE_SHARED: {
+          // Volume is shared. We can go ahead and unshare.
+          DBG("UpdateState: Unsharing %s", vol->NameStr());
+          vol->StartUnshare(mResponseCallback);
+          return;
+        }
+        case Volume::STATE_IDLE: {
+          // Volume is unmounted, try to mount.
+
+          DBG("UpdateState: Mounting %s", vol->NameStr());
+          vol->StartMount(mResponseCallback);
+          return;
+        }
+        default: {
+          // Not in a state that we can do anything about.
+          break;
+        }
+      }
+    }
+  }
+}
+
+/***************************************************************************/
+
+static void
+InitAutoMounterIOThread()
+{
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+  MOZ_ASSERT(!sAutoMounter);
+
+  sAutoMounter = new AutoMounter();
+}
+
+static void
+ShutdownAutoMounterIOThread()
+{
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+  sAutoMounter = NULL;
+  ShutdownVolumeManager();
+}
+
+static void
+SetAutoMounterModeIOThread(const int32_t &aMode)
+{
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+  MOZ_ASSERT(sAutoMounter);
+
+  sAutoMounter->SetMode(aMode);
+}
+
+static void
+UsbCableEventIOThread()
+{
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+  if (!sAutoMounter) {
+    return;
+  }
+  DBG("Calling UpdateState due to USBCableEvent");
+  sAutoMounter->UpdateState();
+}
+
+/**************************************************************************
+*
+*   Public API
+*
+*   Since the AutoMounter runs in IO Thread context, we need to switch
+*   to IOThread context before we can do anything.
+*
+**************************************************************************/
+
+class UsbCableObserver : public SwitchObserver,
+                         public RefCounted<UsbCableObserver>
+{
+public:
+  UsbCableObserver()
+  {
+    RegisterSwitchObserver(SWITCH_USB, this);
+  }
+
+  ~UsbCableObserver()
+  {
+    UnregisterSwitchObserver(SWITCH_USB, this);
+  }
+
+  virtual void Notify(const SwitchEvent &aEvent)
+  {
+    DBG("UsbCable switch device: %d state: %s\n",
+        aEvent.device(), SwitchStateStr(aEvent));
+    XRE_GetIOMessageLoop()->PostTask(
+        FROM_HERE,
+        NewRunnableFunction(UsbCableEventIOThread));
+  }
+};
+
+static RefPtr<UsbCableObserver> sUsbCableObserver;
+static RefPtr<AutoMounterSetting> sAutoMounterSetting;
+
+void
+InitAutoMounter()
+{
+  InitVolumeManager();
+  sAutoMounterSetting = new AutoMounterSetting();
+
+  XRE_GetIOMessageLoop()->PostTask(
+      FROM_HERE,
+      NewRunnableFunction(InitAutoMounterIOThread));
+
+  // Switch Observers need to run on the main thread, so we need to
+  // start it here and have it send events to the AutoMounter running
+  // on the IO Thread.
+  sUsbCableObserver = new UsbCableObserver();
+}
+
+void
+SetAutoMounterMode(int32_t aMode)
+{
+  XRE_GetIOMessageLoop()->PostTask(
+      FROM_HERE,
+      NewRunnableFunction(SetAutoMounterModeIOThread, aMode));
+}
+
+void
+ShutdownAutoMounter()
+{
+  sAutoMounterSetting = NULL;
+  sUsbCableObserver = NULL;
+
+  XRE_GetIOMessageLoop()->PostTask(
+      FROM_HERE,
+      NewRunnableFunction(ShutdownAutoMounterIOThread));
+}
+
+} // system
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/AutoMounter.h
@@ -0,0 +1,47 @@
+/* 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_automounter_h__
+#define mozilla_system_automounter_h__
+
+#include "mozilla/StandardInteger.h"
+
+namespace mozilla {
+namespace system {
+
+// AutoMounter modes
+#define AUTOMOUNTER_DISABLE                 0
+#define AUTOMOUNTER_ENABLE                  1
+#define AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED  2
+
+/**
+ * Initialize the automounter. This causes some of the phone's
+ * directories to show up on the host when the phone is plugged
+ * into the host via USB.
+ *
+ * When the AutoMounter starts, it will poll the current state
+ * of affairs (usb cable plugged in, automounter enabled, etc)
+ * and try to make the state of the volumes match.
+ */
+void InitAutoMounter();
+
+/**
+ * Sets the enabled state of the automounter.
+ *
+ * This will in turn cause the automounter to re-evaluate
+ * whether it should mount/unmount/share/unshare volumes.
+ */
+void SetAutoMounterMode(int32_t aMode);
+
+/**
+ * Shuts down the automounter.
+ *
+ * This leaves the volumes in whatever state they're in.
+ */
+void ShutdownAutoMounter();
+
+} // system
+} // mozilla
+
+#endif  // mozilla_system_automounter_h__
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/AutoMounterSetting.cpp
@@ -0,0 +1,150 @@
+/* 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 "AutoMounter.h"
+#include "AutoMounterSetting.h"
+
+#include "base/message_loop.h"
+#include "jsapi.h"
+#include "mozilla/Services.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsIObserverService.h"
+#include "nsIJSContextStack.h"
+#include "nsISettingsService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "xpcpublic.h"
+
+#undef LOG
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AutoMounterSetting" , ## args)
+#define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "AutoMounterSetting" , ## args)
+
+#define UMS_MODE                "ums.mode"
+#define MOZSETTINGS_CHANGED     "mozsettings-changed"
+
+namespace mozilla {
+namespace system {
+
+class SettingsServiceCallback : public nsISettingsServiceCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  SettingsServiceCallback() {}
+
+  NS_IMETHOD Handle(const nsAString &aName, const JS::Value &aResult, JSContext *aContext) {
+    if (JSVAL_IS_INT(aResult)) {
+      int32_t mode = JSVAL_TO_INT(aResult);
+      SetAutoMounterMode(mode);
+    }
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleError(const nsAString &aName, JSContext *aContext) {
+    ERR("SettingsCallback::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
+    return NS_OK;
+  }
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(SettingsServiceCallback, nsISettingsServiceCallback)
+
+AutoMounterSetting::AutoMounterSetting()
+{
+  // Setup an observer to watch changes to the setting
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (!observerService) {
+    ERR("GetObserverService failed");
+    return;
+  }
+  nsresult rv;
+  rv = observerService->AddObserver(this, MOZSETTINGS_CHANGED, false);
+  if (NS_FAILED(rv)) {
+    ERR("AddObserver failed");
+    return;
+  }
+
+  // Get the initial value of the setting.
+  nsCOMPtr<nsISettingsService> settingsService =
+    do_GetService("@mozilla.org/settingsService;1");
+  if (!settingsService) {
+    ERR("Failed to get settingsLock service!");
+    return;
+  }
+  nsCOMPtr<nsISettingsServiceLock> lock;
+  settingsService->GetLock(getter_AddRefs(lock));
+  nsCOMPtr<nsISettingsServiceCallback> callback = new SettingsServiceCallback();
+  lock->Get(UMS_MODE, callback);
+}
+
+AutoMounterSetting::~AutoMounterSetting()
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService) {
+    observerService->RemoveObserver(this, MOZSETTINGS_CHANGED);
+  }
+}
+
+NS_IMPL_ISUPPORTS1(AutoMounterSetting, nsIObserver)
+
+NS_IMETHODIMP
+AutoMounterSetting::Observe(nsISupports *aSubject,
+                            const char *aTopic,
+                            const PRUnichar *aData)
+{
+  if (strcmp(aTopic, MOZSETTINGS_CHANGED) != 0) {
+    return NS_OK;
+  }
+  LOG("%s: detected %s data = '%s'", __FUNCTION__, aTopic,
+      NS_LossyConvertUTF16toASCII(aData).get());
+
+  // Note that this function gets called for any and all settings changes,
+  // so we need to carefully check if we have the one we're interested in.
+  //
+  // The string that we're interested in will be a JSON string that looks like:
+  //  {"key":"ums.autoMount","value":true}
+
+  nsCOMPtr<nsIThreadJSContextStack> stack =
+    do_GetService("@mozilla.org/js/xpc/ContextStack;1");
+  if (!stack) {
+    ERR("Failed to get JSContextStack");
+    return NS_OK;
+  }
+  JSContext *cx = stack->GetSafeJSContext();
+  if (!cx) {
+    ERR("Failed to GetSafeJSContext");
+    return NS_OK;
+  }
+  nsDependentString dataStr(aData);
+  JS::Value val;
+  if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
+      !val.isObject()) {
+    return NS_OK;
+  }
+  JSObject &obj(val.toObject());
+  JS::Value key;
+  if (!JS_GetProperty(cx, &obj, "key", &key) ||
+      !key.isString()) {
+    return NS_OK;
+  }
+  JSBool match;
+  if (!JS_StringEqualsAscii(cx, key.toString(), UMS_MODE, &match) ||
+      (match != JS_TRUE)) {
+    return NS_OK;
+  }
+  JS::Value value;
+  if (!JS_GetProperty(cx, &obj, "value", &value) ||
+      !value.isInt32()) {
+    return NS_OK;
+  }
+  int32_t mode = value.toInt32();
+  SetAutoMounterMode(mode);
+
+  return NS_OK;
+}
+
+}   // namespace system
+}   // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/AutoMounterSetting.h
@@ -0,0 +1,29 @@
+/* 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_automountersetting_h__
+#define mozilla_system_automountersetting_h__
+
+#include "nsIObserver.h"
+
+namespace mozilla {
+namespace system {
+
+class ResultListener;
+
+class AutoMounterSetting : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  AutoMounterSetting();
+  virtual ~AutoMounterSetting();
+};
+
+}   // namespace system
+}   // namespace mozilla
+
+#endif  // mozilla_system_automountersetting_h__
+
--- a/hal/HalTypes.h
+++ b/hal/HalTypes.h
@@ -37,16 +37,17 @@ enum FlashMode {
     eHalLightFlash_Hardware = 2   // hardware assisted flashing
 };
 
 class SwitchEvent;
 
 enum SwitchDevice {
   SWITCH_DEVICE_UNKNOWN = -1,
   SWITCH_HEADPHONES,
+  SWITCH_USB,
   NUM_SWITCH_DEVICE
 };
 
 enum SwitchState {
   SWITCH_STATE_UNKNOWN = -1,
   SWITCH_STATE_ON,
   SWITCH_STATE_OFF,
   NUM_SWITCH_STATE
--- a/hal/gonk/GonkSwitch.cpp
+++ b/hal/gonk/GonkSwitch.cpp
@@ -17,16 +17,17 @@ using namespace mozilla::hal;
 
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "GonkSwitch" , ## args) 
 
 namespace mozilla {
 namespace hal_impl {
 
 struct {const char* name; SwitchDevice device; } kSwitchNameMap[] = {
   { "h2w", SWITCH_HEADPHONES },
+  { "usb_configuration", SWITCH_USB },
   { NULL, SWITCH_DEVICE_UNKNOWN },
 };
 
 static SwitchDevice
 NameToDevice(const char* name) {
   for (int i = 0; kSwitchNameMap[i].device != SWITCH_DEVICE_UNKNOWN; i++) {
     if (strcmp(name, kSwitchNameMap[i].name) == 0) {
       return kSwitchNameMap[i].device;
@@ -107,17 +108,35 @@ private:
       mEventInfo[i].mEvent.status() = SWITCH_STATE_UNKNOWN;
     }
   }
 
   bool GetEventInfo(const NetlinkEvent& event, const char** name, const char** state) {
     //working around the android code not being const-correct
     NetlinkEvent *e = const_cast<NetlinkEvent*>(&event);
     const char* subsystem = e->getSubsystem();
- 
+
+    if (subsystem && (strcmp(subsystem, "android_usb") == 0)) {
+      // Under GB, usb cable plugin was done using a virtual switch
+      // (usb_configuration). Under ICS, they changed to using the
+      // android_usb device, so we emulate the usb_configuration switch.
+
+      *name = "usb_configuration";
+      const char *usb_state = e->findParam("USB_STATE");
+      if (!usb_state) {
+        return false;
+      }
+      if (strcmp(usb_state, "CONFIGURED") == 0) {
+        *state = "1";
+        return true;
+      }
+      *state = "0";
+      return true;
+    }
+
     if (!subsystem || strcmp(subsystem, "switch")) {
       return false;
     }
 
     *name = e->findParam("SWITCH_NAME");
     *state = e->findParam("SWITCH_STATE");
 
     if (!*name || !*state) {