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 97199 815d53fb27e0cd0d18638d59338d710dc9f988ac
parent 97198 0773c8788f78702a52db3c36b700eb0a6e608121
child 97200 cff8079f23256006ab0a53e071f83170aeea78b4
push id1439
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 20:19:22 +0000
treeherdermozilla-aurora@ea74834dccd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones, qDot
bugs737153
milestone15.0a1
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) {