summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/AutoMounter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/gonk/AutoMounter.cpp')
-rw-r--r--dom/system/gonk/AutoMounter.cpp1496
1 files changed, 1496 insertions, 0 deletions
diff --git a/dom/system/gonk/AutoMounter.cpp b/dom/system/gonk/AutoMounter.cpp
new file mode 100644
index 000000000..52c4554fb
--- /dev/null
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -0,0 +1,1496 @@
+/* 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 <sys/statfs.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 <cutils/properties.h>
+
+#include "AutoMounter.h"
+#include "nsVolumeService.h"
+#include "AutoMounterSetting.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Hal.h"
+#include "mozilla/StaticPtr.h"
+#include "MozMtpServer.h"
+#include "MozMtpStorage.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsMemory.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "OpenFileFinder.h"
+#include "Volume.h"
+#include "VolumeManager.h"
+#include "nsIStatusReporter.h"
+
+USING_MTP_NAMESPACE
+
+/**************************************************************************
+*
+* 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_MTP_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mtp"
+#define ICS_SYS_USB_STATE "/sys/devices/virtual/android_usb/android0/state"
+
+#undef USE_DEBUG // MozMtpDatabase.h also defines USE_DEBUG
+#define USE_DEBUG 0
+
+#undef LOG
+#undef LOGW
+#undef ERR
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AutoMounter", ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "AutoMounter", ## args)
+#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "AutoMounter", ## args)
+
+#undef DBG
+#if USE_DEBUG
+#define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "AutoMounter" , ## args)
+#else
+#define DBG(args...)
+#endif
+
+namespace mozilla {
+namespace system {
+
+#define SYS_USB_CONFIG "sys.usb.config"
+#define PERSIST_SYS_USB_CONFIG "persist.sys.usb.config"
+
+#define USB_FUNC_ADB "adb"
+#define USB_FUNC_MTP "mtp"
+#define USB_FUNC_NONE "none"
+#define USB_FUNC_RNDIS "rndis"
+#define USB_FUNC_UMS "mass_storage"
+#define USB_FUNC_DEFAULT "default"
+
+class AutoMounter;
+
+static void SetAutoMounterStatus(int32_t aStatus);
+
+/***************************************************************************/
+
+inline const char* SwitchStateStr(const hal::SwitchEvent& aEvent)
+{
+ return aEvent.status() == hal::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) == hal::SWITCH_STATE_ON;
+#else
+ // Until then, just go read the file directly
+ if (access(ICS_SYS_USB_STATE, F_OK) == 0) {
+ char usbState[20];
+ if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) {
+ DBG("IsUsbCablePluggedIn: state = '%s'", usbState);
+ return strcmp(usbState, "CONFIGURED") == 0 ||
+ strcmp(usbState, "CONNECTED") == 0;
+ }
+ ERR("Error reading file '%s': %s", ICS_SYS_USB_STATE, strerror(errno));
+ return false;
+ }
+ bool configured;
+ if (ReadSysFile(GB_SYS_USB_CONFIGURED, &configured)) {
+ return configured;
+ }
+ ERR("Error reading file '%s': %s", GB_SYS_USB_CONFIGURED, strerror(errno));
+ return false;
+#endif
+}
+
+static bool
+IsUsbConfigured()
+{
+ if (access(ICS_SYS_USB_STATE, F_OK) == 0) {
+ char usbState[20];
+ if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) {
+ DBG("IsUsbConfigured: state = '%s'", usbState);
+ return strcmp(usbState, "CONFIGURED") == 0;
+ }
+ ERR("Error reading file '%s': %s", ICS_SYS_USB_STATE, strerror(errno));
+ return false;
+ }
+ bool configured;
+ if (ReadSysFile(GB_SYS_USB_CONFIGURED, &configured)) {
+ return configured;
+ }
+ ERR("Error reading file '%s': %s", GB_SYS_USB_CONFIGURED, strerror(errno));
+ return false;
+}
+
+/***************************************************************************/
+
+// The AutoVolumeManagerStateObserver allows the AutoMounter to know when
+// the volume manager changes state (i.e. it has finished initialization)
+class AutoVolumeManagerStateObserver : public VolumeManager::StateObserver
+{
+public:
+ virtual void Notify(const VolumeManager::StateChangedEvent& aEvent);
+};
+
+// The AutoVolumeEventObserver allows the AutoMounter to know about card
+// insertion and removal, as well as state changes in the volume.
+class AutoVolumeEventObserver : public Volume::EventObserver
+{
+public:
+ virtual void Notify(Volume* const& 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:
+ NS_INLINE_DECL_REFCOUNTING(AutoMounter)
+
+ typedef nsTArray<RefPtr<Volume>> VolumeArray;
+
+ AutoMounter()
+ : mState(STATE_IDLE),
+ mResponseCallback(new AutoMounterResponseCallback),
+ mMode(AUTOMOUNTER_DISABLE)
+ {
+ VolumeManager::RegisterStateObserver(&mVolumeManagerStateObserver);
+ Volume::RegisterVolumeObserver(&mVolumeEventObserver, "AutoMounter");
+
+ // It's possible that the VolumeManager is already in the READY state,
+ // so we call CheckVolumeSettings here to cover that case. Otherwise,
+ // we'll pick it up when the VolumeManage state changes to VOLUMES_READY.
+ CheckVolumeSettings();
+
+ DBG("Calling UpdateState from constructor");
+ UpdateState();
+ }
+
+ void CheckVolumeSettings()
+ {
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ DBG("CheckVolumeSettings: VolumeManager is NOT READY yet");
+ return;
+ }
+ DBG("CheckVolumeSettings: VolumeManager is READY");
+
+ // The VolumeManager knows about all of the volumes from vold. We now
+ // know the names of all of the volumes, so we can find out what the
+ // initial sharing settings are set to.
+
+ VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ VolumeManager::VolumeArray::index_type i;
+ for (i = 0; i < numVolumes; i++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(i);
+ if (vol) {
+ // We need to pick up the intial value of the
+ // ums.volume.NAME.enabled setting.
+ AutoMounterSetting::CheckVolumeSettings(vol->Name());
+
+ // Note: eventually CheckVolumeSettings will call
+ // AutoMounter::SetSharingMode, which will in turn call
+ // UpdateState if needed.
+ }
+ }
+ }
+
+ void UpdateState();
+ void GetStatus(bool& umsAvail, bool& umsConfigured, bool& umsEnabled, bool& mtpAvail,
+ bool& mtpConfigured, bool& mtpEnabled, bool& rndisConfigured);
+
+ nsresult Dump(nsACString& desc);
+
+ void ConfigureUsbFunction(const char* aUsbFunc);
+
+ bool StartMtpServer();
+ void StopMtpServer();
+
+ void StartUmsSharing();
+ void StopUmsSharing();
+
+
+ const char* ModeStr(int32_t aMode)
+ {
+ switch (aMode) {
+ case AUTOMOUNTER_DISABLE: return "Disable";
+ case AUTOMOUNTER_ENABLE_UMS: return "Enable-UMS";
+ case AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED: return "DisableWhenUnplugged";
+ case AUTOMOUNTER_ENABLE_MTP: return "Enable-MTP";
+ }
+ return "??? Unknown ???";
+ }
+
+ bool IsModeEnabled(int32_t aMode)
+ {
+ return aMode == AUTOMOUNTER_ENABLE_MTP ||
+ aMode == AUTOMOUNTER_ENABLE_UMS;
+ }
+
+ 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;
+ }
+
+ if (aMode == AUTOMOUNTER_DISABLE &&
+ mMode == AUTOMOUNTER_ENABLE_UMS && IsUsbCablePluggedIn()) {
+ // On many devices (esp non-Samsung), we can't force the disable, so we
+ // need to defer until the USB cable is actually unplugged.
+ // See bug 777043.
+ //
+ // Otherwise our attempt to disable it will fail, and we'll wind up in a bad
+ // state where the AutoMounter thinks that Sharing has been turned off, but
+ // the files are actually still being Shared because the attempt to unshare
+ // failed.
+ LOG("Attempting to disable UMS. Deferring until USB cable is unplugged.");
+ aMode = AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED;
+ }
+
+ if (aMode != mMode) {
+ LOG("Changing mode from '%s' to '%s'", ModeStr(mMode), ModeStr(aMode));
+ mMode = aMode;
+ DBG("Calling UpdateState due to mode set to %d", mMode);
+ UpdateState();
+ }
+ }
+
+ void SetSharingMode(const nsACString& aVolumeName, bool aAllowSharing)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ if (vol->IsSharingEnabled() == aAllowSharing) {
+ return;
+ }
+ vol->SetUnmountRequested(false);
+ vol->SetMountRequested(false);
+ vol->SetSharingEnabled(aAllowSharing);
+ DBG("Calling UpdateState due to volume %s sharing set to %d",
+ vol->NameStr(), (int)aAllowSharing);
+ UpdateState();
+ }
+
+ void FormatVolume(const nsACString& aVolumeName)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ if (vol->IsFormatRequested()) {
+ return;
+ }
+ vol->SetUnmountRequested(false);
+ vol->SetMountRequested(false);
+ vol->SetFormatRequested(true);
+ DBG("Calling UpdateState due to volume %s formatting set to %d",
+ vol->NameStr(), (int)vol->IsFormatRequested());
+ UpdateState();
+ }
+
+ void MountVolume(const nsACString& aVolumeName)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ vol->SetUnmountRequested(false);
+ if (vol->IsMountRequested() || vol->mState == nsIVolume::STATE_MOUNTED) {
+ return;
+ }
+ vol->SetMountRequested(true);
+ DBG("Calling UpdateState due to volume %s mounting set to %d",
+ vol->NameStr(), (int)vol->IsMountRequested());
+ UpdateState();
+ }
+
+ void UnmountVolume(const nsACString& aVolumeName)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ if (vol->IsUnmountRequested()) {
+ return;
+ }
+ vol->SetMountRequested(false);
+ vol->SetUnmountRequested(true);
+ DBG("Calling UpdateState due to volume %s unmounting set to %d",
+ vol->NameStr(), (int)vol->IsUnmountRequested());
+ UpdateState();
+ }
+
+protected:
+ ~AutoMounter()
+ {
+ Volume::UnregisterVolumeObserver(&mVolumeEventObserver, "AutoMounter");
+ VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver);
+ }
+
+private:
+
+ enum STATE
+ {
+ // IDLE - Nothing is being shared
+ STATE_IDLE,
+
+ // We've detected that conditions are right to enable mtp. So we've
+ // set sys.usb.config to include mtp, and we're waiting for the USB
+ // subsystem to be "configured". Once mtp shows up in
+ // then we know
+ // that its been configured and we can open /dev/mtp_usb
+ STATE_MTP_CONFIGURING,
+
+ // mtp has been configured (i.e. mtp now shows up in
+ // /sys/devices/virtual/android_usb/android0/functions so we can start
+ // the mtp server.
+ STATE_MTP_STARTED,
+
+ // The mtp server has reported sessionStarted. We'll leave this state
+ // when we receive sessionEnded.
+ STATE_MTP_CONNECTED,
+
+ // We've added mass_storage (aka UMS) to sys.usb.config and we're waiting for
+ // mass_storage to appear in /sys/devices/virtual/android_usb/android0/functions
+ STATE_UMS_CONFIGURING,
+
+ // mass_storage has been configured and we can start sharing once the user
+ // enables it.
+ STATE_UMS_CONFIGURED,
+
+ // USB Tethering is enabled
+ STATE_RNDIS_CONFIGURED,
+ };
+
+ const char *StateStr(STATE aState)
+ {
+ switch (aState) {
+ case STATE_IDLE: return "IDLE";
+ case STATE_MTP_CONFIGURING: return "MTP_CONFIGURING";
+ case STATE_MTP_CONNECTED: return "MTP_CONNECTED";
+ case STATE_MTP_STARTED: return "MTP_STARTED";
+ case STATE_UMS_CONFIGURING: return "UMS_CONFIGURING";
+ case STATE_UMS_CONFIGURED: return "UMS_CONFIGURED";
+ case STATE_RNDIS_CONFIGURED: return "RNDIS_CONFIGURED";
+ }
+ return "STATE_???";
+ }
+
+ void SetState(STATE aState)
+ {
+ const char *oldStateStr = StateStr(mState);
+ mState = aState;
+ const char *newStateStr = StateStr(mState);
+ LOG("AutoMounter state changed from %s to %s", oldStateStr, newStateStr);
+ }
+
+ STATE mState;
+
+ AutoVolumeEventObserver mVolumeEventObserver;
+ AutoVolumeManagerStateObserver mVolumeManagerStateObserver;
+ RefPtr<VolumeResponseCallback> mResponseCallback;
+ int32_t mMode;
+ MozMtpStorage::Array mMozMtpStorage;
+};
+
+static StaticRefPtr<AutoMounter> sAutoMounter;
+static StaticRefPtr<MozMtpServer> sMozMtpServer;
+
+// The following is for status reporter
+enum STATE_REPORTER_STATE
+{
+ REPORTER_UNREGISTERED,
+ REPORTER_REGISTERED
+};
+
+static int status_reporter_progress = REPORTER_UNREGISTERED;
+nsresult getState(nsACString &desc)
+{
+ sAutoMounter->Dump(desc);
+ return NS_OK;
+}
+NS_STATUS_REPORTER_IMPLEMENT(AutoMounter, "AutoMounter", getState);
+
+/***************************************************************************/
+
+void
+AutoVolumeManagerStateObserver::Notify(const VolumeManager::StateChangedEvent &)
+{
+ LOG("VolumeManager state changed event: %s", VolumeManager::StateStr());
+
+ if (!sAutoMounter) {
+ return;
+ }
+
+ // In the event that the VolumeManager just entered the VOLUMES_READY state,
+ // we call CheckVolumeSettings here (it's possible that this method never
+ // gets called if the VolumeManager was already in the VOLUMES_READY state
+ // by the time the AutoMounter was constructed).
+ sAutoMounter->CheckVolumeSettings();
+
+ DBG("Calling UpdateState due to VolumeManagerStateObserver");
+ sAutoMounter->UpdateState();
+}
+
+void
+AutoVolumeEventObserver::Notify(Volume * const &)
+{
+ if (!sAutoMounter) {
+ return;
+ }
+ DBG("Calling UpdateState due to VolumeEventStateObserver");
+ 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();
+ }
+}
+
+static bool
+IsUsbFunctionEnabled(const char* aConfig, const char* aUsbFunc)
+{
+ nsAutoCString config(aConfig);
+ nsCCharSeparatedTokenizer tokenizer(config, ',');
+
+ while (tokenizer.hasMoreTokens()) {
+ nsAutoCString token(tokenizer.nextToken());
+ if (token.Equals(aUsbFunc)) {
+ DBG("IsUsbFunctionEnabled('%s', '%s'): returning true", aConfig, aUsbFunc);
+ return true;
+ }
+ }
+ DBG("IsUsbFunctionEnabled('%s', '%s'): returning false", aConfig, aUsbFunc);
+ return false;
+}
+
+static void
+SetUsbFunction(const char* aUsbFunc)
+{
+ char oldSysUsbConfig[PROPERTY_VALUE_MAX];
+ property_get(SYS_USB_CONFIG, oldSysUsbConfig, "");
+
+ if (strcmp(oldSysUsbConfig, USB_FUNC_NONE) == 0) {
+ // It's quite possible that sys.usb.config may have the value "none". We
+ // convert that to an empty string here, and at the end we convert the
+ // empty string back to "none".
+ oldSysUsbConfig[0] = '\0';
+ }
+
+ if (IsUsbFunctionEnabled(oldSysUsbConfig, aUsbFunc)) {
+ // The function is already configured. Nothing else to do.
+ DBG("SetUsbFunction('%s') - already set - nothing to do", aUsbFunc);
+ return;
+ }
+
+ char newSysUsbConfig[PROPERTY_VALUE_MAX];
+
+ if (strcmp(aUsbFunc, USB_FUNC_MTP) == 0) {
+ // We're enabling MTP. For this we'll wind up using mtp, or mtp,adb
+ strlcpy(newSysUsbConfig, USB_FUNC_MTP, sizeof(newSysUsbConfig));
+ } else if (strcmp(aUsbFunc, USB_FUNC_UMS) == 0) {
+ // We're enabling UMS. For this we make the assumption that the persisted
+ // property has mass_storage enabled.
+ property_get(PERSIST_SYS_USB_CONFIG, newSysUsbConfig, "");
+ } else if (strcmp(aUsbFunc, USB_FUNC_DEFAULT) == 0) {
+ // Set the property as PERSIST_SYS_USB_CONFIG
+ property_get(PERSIST_SYS_USB_CONFIG, newSysUsbConfig, "");
+ } else {
+ printf_stderr("AutoMounter::SetUsbFunction Unrecognized aUsbFunc '%s'\n", aUsbFunc);
+ MOZ_ASSERT(0);
+ return;
+ }
+
+ // Make sure the new value that we write into sys.usb.config keeps the adb
+ // (or non-adb) of the current string.
+
+ if (IsUsbFunctionEnabled(oldSysUsbConfig, USB_FUNC_ADB)) {
+ // ADB was turned on - keep it on.
+ if (!IsUsbFunctionEnabled(newSysUsbConfig, USB_FUNC_ADB)) {
+ // Add adb to the new string
+ strlcat(newSysUsbConfig, ",", sizeof(newSysUsbConfig));
+ strlcat(newSysUsbConfig, USB_FUNC_ADB, sizeof(newSysUsbConfig));
+ }
+ } else {
+ // ADB was turned off - keep it off
+ if (IsUsbFunctionEnabled(newSysUsbConfig, USB_FUNC_ADB)) {
+ // Remove ADB from the new string.
+ if (strcmp(newSysUsbConfig, USB_FUNC_ADB) == 0) {
+ newSysUsbConfig[0] = '\0';
+ } else {
+ nsAutoCString withoutAdb(newSysUsbConfig);
+ withoutAdb.ReplaceSubstring( "," USB_FUNC_ADB, "");
+ strlcpy(newSysUsbConfig, withoutAdb.get(), sizeof(newSysUsbConfig));
+ }
+ }
+ }
+
+ // If the persisted function didn't have mass_storage (this happens on
+ // the nexus 4/5, then we can get to here and have oldSysUsbConfig equal
+ // to newSysUsbConfig. So we need to check for that.
+
+ if (strcmp(oldSysUsbConfig, newSysUsbConfig) == 0) {
+ DBG("SetUsbFunction('%s') %s is already set to '%s' - nothing to do",
+ aUsbFunc, SYS_USB_CONFIG, newSysUsbConfig);
+ return;
+ }
+
+ if (newSysUsbConfig[0] == '\0') {
+ // Convert the empty string back to the string "none"
+ strlcpy(newSysUsbConfig, USB_FUNC_NONE, sizeof(newSysUsbConfig));
+ }
+ LOG("SetUsbFunction(%s) %s from '%s' to '%s'", aUsbFunc, SYS_USB_CONFIG,
+ oldSysUsbConfig, newSysUsbConfig);
+ property_set(SYS_USB_CONFIG, newSysUsbConfig);
+}
+
+bool
+AutoMounter::StartMtpServer()
+{
+ if (sMozMtpServer) {
+ // Mtp Server is already running - nothing to do
+ return true;
+ }
+ LOG("Starting MtpServer");
+
+ // For debugging, Change the #if 0 to #if 1, and then attach gdb during
+ // the 5 second interval below. Otherwise, configuring MTP will cause adb
+ // (and thus gdb) to get bounced.
+#if 0
+ LOG("Sleeping");
+ PRTime now = PR_Now();
+ PRTime stopTime = now + 5000000;
+ while (PR_Now() < stopTime) {
+ LOG("Sleeping...");
+ sleep(1);
+ }
+ LOG("Sleep done");
+#endif
+
+ sMozMtpServer = new MozMtpServer();
+ if (!sMozMtpServer->Init()) {
+ sMozMtpServer = nullptr;
+ return false;
+ }
+
+ VolumeArray::index_type volIndex;
+ VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+ RefPtr<MozMtpStorage> storage = new MozMtpStorage(vol, sMozMtpServer);
+ mMozMtpStorage.AppendElement(storage);
+ }
+
+ sMozMtpServer->Run();
+ return true;
+}
+
+void
+AutoMounter::StopMtpServer()
+{
+ LOG("Stopping MtpServer");
+
+ mMozMtpStorage.Clear();
+ sMozMtpServer = nullptr;
+}
+
+/***************************************************************************/
+
+void
+AutoMounter::UpdateState()
+{
+ static bool inUpdateState = false;
+ if (inUpdateState) {
+ // When UpdateState calls SetISharing, this causes a volume state
+ // change to occur, which would normally cause UpdateState to be called
+ // again. We want the volume state change to go out (so that device
+ // storage will see the change in sharing state), but since we're
+ // already in UpdateState we just want to prevent recursion from screwing
+ // things up.
+ return;
+ }
+ AutoRestore<bool> inUpdateStateDetector(inUpdateState);
+ inUpdateState = true;
+
+ 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;
+ }
+
+ // Calling setprop sys.usb.config mtp,adb (or adding mass_storage) will
+ // cause /sys/devices/virtual/android_usb/android0/state to go:
+ // CONFIGURED -> DISCONNECTED -> CONNECTED -> CONFIGURED
+ //
+ // Since IsUsbCablePluggedIn returns state == CONFIGURED, it will look
+ // like a cable pull and replugin.
+ bool umsAvail, umsConfigured, umsEnabled;
+ bool mtpAvail, mtpConfigured, mtpEnabled;
+ bool rndisConfigured;
+ bool usbCablePluggedIn = IsUsbCablePluggedIn();
+ GetStatus(umsAvail, umsConfigured, umsEnabled, mtpAvail,
+ mtpConfigured, mtpEnabled, rndisConfigured);
+ bool enabled = mtpEnabled || umsEnabled;
+
+ if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) {
+ // DISABLE_WHEN_UNPLUGGED implies already enabled.
+ enabled = usbCablePluggedIn;
+ if (!usbCablePluggedIn) {
+ mMode = AUTOMOUNTER_DISABLE;
+ mtpEnabled = false;
+ umsEnabled = false;
+ }
+ }
+
+ DBG("UpdateState: ums:A%dC%dE%d mtp:A%dC%dE%d rndis:%d mode:%d usb:%d mState:%s",
+ umsAvail, umsConfigured, umsEnabled,
+ mtpAvail, mtpConfigured, mtpEnabled, rndisConfigured,
+ mMode, usbCablePluggedIn, StateStr(mState));
+
+ switch (mState) {
+
+ case STATE_IDLE:
+ if (!usbCablePluggedIn) {
+ // Stay in the IDLE state. We'll get a CONNECTED or CONFIGURED
+ // UEvent when the usb cable is plugged in.
+ break;
+ }
+ if (rndisConfigured) {
+ // USB Tethering uses RNDIS. We'll just wait until its turned off.
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ if (mtpEnabled) {
+ if (mtpConfigured) {
+ // The USB layer has already been configured. Now we can go ahead
+ // and start the MTP server. This particular codepath will not
+ // normally be taken, but it could happen if you stop and restart
+ // b2g while sys.usb.config is set to enable mtp.
+ if (StartMtpServer()) {
+ SetState(STATE_MTP_STARTED);
+ } else {
+ if (umsAvail) {
+ // Unable to start MTP. Go back to UMS.
+ LOG("UpdateState: StartMtpServer failed, switch to UMS");
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ } else {
+ LOG("UpdateState: StartMtpServer failed, keep idle state");
+ SetUsbFunction(USB_FUNC_DEFAULT);
+ }
+ }
+ } else {
+ // We need to configure USB to use mtp. Wait for it to be configured
+ // before we start the MTP server.
+ SetUsbFunction(USB_FUNC_MTP);
+ SetState(STATE_MTP_CONFIGURING);
+ }
+ } else if (umsConfigured) {
+ // UMS is already configured.
+ SetState(STATE_UMS_CONFIGURED);
+ } else if (umsAvail) {
+ // We do this whether or not UMS is enabled. With UMS, it's the
+ // sharing of the volume which is significant. What is important
+ // is that we don't leave it in MTP mode when MTP isn't enabled.
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ }
+ break;
+
+ case STATE_MTP_CONFIGURING:
+ // While configuring, the USB configuration state will change from
+ // CONFIGURED -> CONNECTED -> DISCONNECTED -> CONNECTED -> CONFIGURED
+ // so we don't check for cable unplugged here.
+ if (mtpEnabled && mtpConfigured) {
+ // The USB layer has been configured. Now we can go ahead and start
+ // the MTP server.
+ if (StartMtpServer()) {
+ SetState(STATE_MTP_STARTED);
+ } else {
+ // Unable to start MTP. Go back to UMS.
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ }
+ break;
+ }
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ break;
+
+ case STATE_MTP_STARTED:
+ if (usbCablePluggedIn && mtpConfigured && mtpEnabled) {
+ // Everything is still good. Leave the MTP server running
+ break;
+ }
+ DBG("STATE_MTP_STARTED: About to StopMtpServer "
+ "mtpConfigured = %d mtpEnabled = %d usbCablePluggedIn: %d",
+ mtpConfigured, mtpEnabled, usbCablePluggedIn);
+ StopMtpServer();
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ if (umsAvail) {
+ // Switch back to UMS
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ break;
+ }
+
+ // if ums/rndis is not available and mtp is disable,
+ // restore the usb function as PERSIST_SYS_USB_CONFIG.
+ SetUsbFunction(USB_FUNC_DEFAULT);
+ SetState(STATE_IDLE);
+ break;
+
+ case STATE_UMS_CONFIGURING:
+ if (mtpEnabled) {
+ // MTP was enabled. Start reconfiguring.
+ SetState(STATE_MTP_CONFIGURING);
+ SetUsbFunction(USB_FUNC_MTP);
+ break;
+ }
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ // While configuring, the USB configuration state will change from
+ // CONFIGURED -> CONNECTED -> DISCONNECTED -> CONNECTED -> CONFIGURED
+ // so we don't check for cable unplugged here. However, having said
+ // that, we'll often sit in this state while the cable is unplugged,
+ // since we might not get any events until the cable gets plugged back
+ // in. This is why we need to check for mtpEnabled once we get the
+ // configured event.
+ if (umsConfigured) {
+ SetState(STATE_UMS_CONFIGURED);
+ }
+ break;
+
+ case STATE_UMS_CONFIGURED:
+ if (usbCablePluggedIn) {
+ if (mtpEnabled) {
+ // MTP was enabled. Start reconfiguring.
+ SetState(STATE_MTP_CONFIGURING);
+ SetUsbFunction(USB_FUNC_MTP);
+ break;
+ }
+ if (umsConfigured && umsEnabled) {
+ // This is the normal state when UMS is enabled.
+ break;
+ }
+ }
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ SetState(STATE_IDLE);
+ break;
+
+ case STATE_RNDIS_CONFIGURED:
+ if (usbCablePluggedIn && rndisConfigured) {
+ // Normal state when RNDIS is enabled.
+ break;
+ }
+ SetState(STATE_IDLE);
+ break;
+
+ default:
+ SetState(STATE_IDLE);
+ break;
+ }
+
+ bool tryToShare = umsEnabled && usbCablePluggedIn;
+ LOG("UpdateState: ums:A%dC%dE%d mtp:A%dC%dE%d mode:%d usb:%d tryToShare:%d state:%s",
+ umsAvail, umsConfigured, umsEnabled,
+ mtpAvail, mtpConfigured, mtpEnabled,
+ mMode, usbCablePluggedIn, tryToShare, StateStr(mState));
+
+ bool filesOpen = false;
+ static unsigned filesOpenDelayCount = 0;
+ VolumeArray::index_type volIndex;
+ VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+ Volume::STATE volState = vol->State();
+
+ if (vol->State() == nsIVolume::STATE_MOUNTED) {
+ LOG("UpdateState: Volume %s is %s and %s @ %s gen %d locked %d sharing %s",
+ vol->NameStr(), vol->StateStr(),
+ vol->MediaPresent() ? "inserted" : "missing",
+ vol->MountPoint().get(), vol->MountGeneration(),
+ (int)vol->IsMountLocked(),
+ vol->CanBeShared() ? (vol->IsSharingEnabled() ?
+ (vol->IsSharing() ? "en-y" : "en-n") : "dis") : "x");
+ if (vol->IsSharing() && !usbCablePluggedIn) {
+ // We call SetIsSharing(true) below to indicate intent to share. This
+ // causes a state change which notifys apps, and they'll close any
+ // open files, which will initiate the change away from the mounted
+ // state and into the sharing state. Normally, when the volume
+ // transitions back to the mounted state, then vol->mIsSharing gets set
+ // to false. However, if the user pulls the USB cable before we
+ // actually start sharing, then the volume never actually leaves
+ // the mounted state (and hence never transitions from
+ // sharing -> mounted), and mIsSharing never gets set back to false.
+ // So we clear mIsSharing here.
+ vol->SetIsSharing(false);
+ }
+ } else {
+ LOG("UpdateState: Volume %s is %s and %s", vol->NameStr(), vol->StateStr(),
+ vol->MediaPresent() ? "inserted" : "missing");
+ }
+ if (!vol->MediaPresent()) {
+ // No media - nothing we can do
+ continue;
+ }
+
+ if (vol->State() == nsIVolume::STATE_CHECKMNT) {
+ // vold reports the volume is "Mounted". Need to check if the volume is
+ // accessible by statfs(). Once it can be accessed, set the volume as
+ // STATE_MOUNTED, otherwise, post a delay task of UpdateState to check it
+ // again.
+ struct statfs fsbuf;
+ int rc = MOZ_TEMP_FAILURE_RETRY(statfs(vol->MountPoint().get(), &fsbuf));
+ if (rc == -1) {
+ // statfs() failed. Stay in STATE_CHECKMNT. Any failures here
+ // are probably non-recoverable, so we need to wait until
+ // something else changes the state back to IDLE/UNMOUNTED, etc.
+ ERR("statfs failed for '%s': errno = %d (%s)", vol->NameStr(), errno, strerror(errno));
+ continue;
+ }
+ static int delay = 250;
+ if (fsbuf.f_blocks == 0) {
+ if (delay <= 4000) {
+ LOG("UpdateState: Volume '%s' is inaccessible, checking again in %d msec", vol->NameStr(), delay);
+ MessageLoopForIO::current()->
+ PostDelayedTask(NewRunnableMethod(this, &AutoMounter::UpdateState),
+ delay);
+ delay *= 2;
+ } else {
+ LOG("UpdateState: Volume '%s' is inaccessible, giving up", vol->NameStr());
+ }
+ continue;
+ } else {
+ delay = 250;
+ vol->SetState(nsIVolume::STATE_MOUNTED);
+ }
+ }
+
+ if ((tryToShare && vol->IsSharingEnabled()) ||
+ vol->IsFormatRequested() ||
+ vol->IsUnmountRequested()) {
+ switch (volState) {
+ // We're going to try to unmount the volume
+ case nsIVolume::STATE_MOUNTED: {
+ if (vol->IsMountLocked()) {
+ // The volume is currently locked, so leave it in the mounted
+ // state.
+ LOGW("UpdateState: Mounted volume %s is locked, not sharing or formatting",
+ vol->NameStr());
+ break;
+ }
+
+ // Mark the volume as if we've started sharing/formatting/unmmounting.
+ // This will cause apps which watch device storage notifications to see
+ // the volume go into the different state, and prompt them to close any
+ // open files that they might have.
+ if (tryToShare && vol->IsSharingEnabled()) {
+ vol->SetIsSharing(true);
+ } else if (vol->IsFormatRequested()){
+ vol->SetIsFormatting(true);
+ } else if (vol->IsUnmountRequested()){
+ vol->SetIsUnmounting(true);
+ }
+
+ // Check to see if there are any open files on the volume and
+ // don't initiate the unmount while there are open files.
+ OpenFileFinder::Info fileInfo;
+ OpenFileFinder fileFinder(vol->MountPoint());
+ if (fileFinder.First(&fileInfo)) {
+ LOGW("The following files are open under '%s'",
+ vol->MountPoint().get());
+ do {
+ LOGW(" PID: %d file: '%s' app: '%s' comm: '%s' exe: '%s'\n",
+ fileInfo.mPid,
+ fileInfo.mFileName.get(),
+ fileInfo.mAppName.get(),
+ fileInfo.mComm.get(),
+ fileInfo.mExe.get());
+ } while (fileFinder.Next(&fileInfo));
+ LOGW("UpdateState: Mounted volume %s has open files, not sharing or formatting",
+ vol->NameStr());
+
+ // Check again in a few seconds to see if the files are closed.
+ // Since we're trying to share the volume, this implies that we're
+ // plugged into the PC via USB and this in turn implies that the
+ // battery is charging, so we don't need to be too concerned about
+ // wasting battery here.
+ //
+ // If we just detected that there were files open, then we use
+ // a short timer. We will have told the apps that we're trying
+ // trying to share, and they'll be closing their files. This makes
+ // the sharing more responsive. If after a few seconds, the apps
+ // haven't closed their files, then we back off.
+
+ int delay = 1000;
+ if (filesOpenDelayCount > 10) {
+ delay = 5000;
+ }
+ MessageLoopForIO::current()->
+ PostDelayedTask(NewRunnableMethod(this, &AutoMounter::UpdateState),
+ delay);
+ filesOpen = true;
+ break;
+ }
+
+ // Volume is mounted, we need to unmount before
+ // we can share.
+ LOG("UpdateState: Unmounting %s", vol->NameStr());
+ vol->StartUnmount(mResponseCallback);
+ return; // UpdateState will be called again when the Unmount command completes
+ }
+ case nsIVolume::STATE_IDLE:
+ case nsIVolume::STATE_MOUNT_FAIL: {
+ LOG("UpdateState: Volume %s is %s", vol->NameStr(), vol->StateStr());
+ if (vol->IsFormatting() && !vol->IsFormatRequested()) {
+ vol->SetFormatRequested(false);
+ if (!(tryToShare && vol->IsSharingEnabled()) && volState == nsIVolume::STATE_IDLE) {
+ LOG("UpdateState: Mounting %s", vol->NameStr());
+ vol->StartMount(mResponseCallback);
+ break;
+ }
+ }
+
+ // If there are format and share requests in the same time,
+ // we should do format first then share.
+ if (vol->IsFormatRequested()) {
+ // Volume is unmounted. We can go ahead and format.
+ LOG("UpdateState: Formatting %s", vol->NameStr());
+ vol->StartFormat(mResponseCallback);
+ } else if (tryToShare && vol->IsSharingEnabled() && volState == nsIVolume::STATE_IDLE) {
+ // Volume is unmounted. We can go ahead and share.
+ LOG("UpdateState: Sharing %s", vol->NameStr());
+ vol->StartShare(mResponseCallback);
+ }
+ return; // UpdateState will be called again when the Share/Format command completes
+ }
+ default: {
+ // Not in a state that we can do anything about.
+ break;
+ }
+ }
+ } else {
+ // We're going to try and unshare and remount the volumes
+ switch (volState) {
+ case nsIVolume::STATE_SHARED: {
+ // Volume is shared. We can go ahead and unshare.
+ LOG("UpdateState: Unsharing %s", vol->NameStr());
+ vol->StartUnshare(mResponseCallback);
+ return; // UpdateState will be called again when the Unshare command completes
+ }
+ case nsIVolume::STATE_IDLE: {
+ if (!vol->IsUnmountRequested()) {
+ // Volume is unmounted and mount-requested, try to mount.
+
+ LOG("UpdateState: Mounting %s", vol->NameStr());
+ vol->StartMount(mResponseCallback);
+ }
+ return; // UpdateState will be called again when Mount command completes
+ }
+ default: {
+ // Not in a state that we can do anything about.
+ break;
+ }
+ }
+ }
+ }
+
+ int32_t status = AUTOMOUNTER_STATUS_DISABLED;
+ if (filesOpen) {
+ filesOpenDelayCount++;
+ status = AUTOMOUNTER_STATUS_FILES_OPEN;
+ } else if (enabled) {
+ filesOpenDelayCount = 0;
+ status = AUTOMOUNTER_STATUS_ENABLED;
+ }
+ SetAutoMounterStatus(status);
+}
+
+/***************************************************************************/
+
+void AutoMounter::GetStatus(bool& umsAvail, bool& umsConfigured, bool& umsEnabled,
+ bool& mtpAvail, bool& mtpConfigured, bool& mtpEnabled,
+ bool& rndisConfigured)
+{
+ umsAvail = false;
+ umsConfigured = false;
+ umsEnabled = false;
+ mtpAvail = false;
+ mtpConfigured = false;
+ mtpEnabled = false;
+ rndisConfigured = false;
+
+ if (access(ICS_SYS_USB_FUNCTIONS, F_OK) != 0) {
+ return;
+ }
+
+ char functionsStr[60];
+ if (!ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr))) {
+ ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno));
+ functionsStr[0] = '\0';
+ }
+ DBG("GetStatus: USB functions: '%s'", functionsStr);
+
+ bool usbConfigured = IsUsbConfigured();
+
+ // On the Nexus 4/5, it advertises that the UMS usb function is available,
+ // but we have a further requirement that mass_storage be in the
+ // persist.sys.usb.config property.
+ char persistSysUsbConfig[PROPERTY_VALUE_MAX];
+ property_get(PERSIST_SYS_USB_CONFIG, persistSysUsbConfig, "");
+ if (IsUsbFunctionEnabled(persistSysUsbConfig, USB_FUNC_UMS)) {
+ umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
+ }
+ if (umsAvail) {
+ umsConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_UMS) != nullptr;
+ umsEnabled = (mMode == AUTOMOUNTER_ENABLE_UMS) ||
+ ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && umsConfigured);
+ } else {
+ umsConfigured = false;
+ umsEnabled = false;
+ }
+
+ mtpAvail = (access(ICS_SYS_MTP_DIRECTORY, F_OK) == 0);
+ if (mtpAvail) {
+ mtpConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_MTP) != nullptr;
+ mtpEnabled = (mMode == AUTOMOUNTER_ENABLE_MTP) ||
+ ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && mtpConfigured);
+ } else {
+ mtpConfigured = false;
+ mtpEnabled = false;
+ }
+
+ rndisConfigured = strstr(functionsStr, USB_FUNC_RNDIS) != nullptr;
+}
+
+
+nsresult AutoMounter::Dump(nsACString& desc)
+{
+ DBG("GetState!");
+ bool umsAvail, umsConfigured, umsEnabled;
+ bool mtpAvail, mtpConfigured, mtpEnabled;
+ bool rndisConfigured;
+ GetStatus(umsAvail, umsConfigured, umsEnabled, mtpAvail,
+ mtpConfigured, mtpEnabled, rndisConfigured);
+ if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) {
+ // DISABLE_WHEN_UNPLUGGED implies already enabled.
+ if (!IsUsbCablePluggedIn()) {
+ mMode = AUTOMOUNTER_DISABLE;
+ mtpEnabled = false;
+ umsEnabled = false;
+ }
+ }
+
+ // Automounter information
+ desc += "Current Mode:";
+ desc += ModeStr(mMode);
+ desc += "|";
+
+
+ desc += "Current State:";
+ desc += StateStr(mState);
+ desc += "|";
+
+ desc += "UMS Status:";
+ if (umsAvail) {
+ desc += "Available";
+ } else {
+ desc += "UnAvailable";
+ }
+ desc += ",";
+ if (umsConfigured) {
+ desc += "Configured";
+ } else {
+ desc += "Un-Configured";
+ }
+ desc += ",";
+ if (umsEnabled) {
+ desc += "Enabled";
+ } else {
+ desc += "Disabled";
+ }
+ desc += "|";
+
+
+ desc += "MTP Status:";
+ if (mtpAvail) {
+ desc += "Available";
+ } else {
+ desc += "UnAvailable";
+ }
+ desc += ",";
+ if (mtpConfigured) {
+ desc += "Configured";
+ } else {
+ desc += "Un-Configured";
+ }
+ desc += ",";
+ if (mtpEnabled) {
+ desc += "Enabled";
+ } else {
+ desc += "Disabled";
+ }
+
+
+ // Volume information
+ VolumeArray::index_type volIndex;
+ VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+
+ desc += "|";
+ desc += vol->NameStr();
+ desc += ":";
+ desc += vol->StateStr();
+ desc += "@";
+ desc += vol->MountPoint().get();
+
+ if (!vol->MediaPresent()) {
+ continue;
+ }
+
+ if (vol->CanBeShared()) {
+ desc += ",CanBeShared";
+ }
+ if (vol->CanBeFormatted()) {
+ desc += ",CanBeFormatted";
+ }
+ if (vol->CanBeMounted()) {
+ desc += ",CanBeMounted";
+ }
+ if (vol->IsRemovable()) {
+ desc += ",Removable";
+ }
+ if (vol->IsHotSwappable()) {
+ desc += ",HotSwappable";
+ }
+ }
+
+ return NS_OK;
+}
+
+
+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 = nullptr;
+ ShutdownVolumeManager();
+}
+
+static void
+SetAutoMounterModeIOThread(const int32_t& aMode)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->SetMode(aMode);
+}
+
+static void
+SetAutoMounterSharingModeIOThread(const nsCString& aVolumeName, const bool& aAllowSharing)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->SetSharingMode(aVolumeName, aAllowSharing);
+}
+
+static void
+AutoMounterFormatVolumeIOThread(const nsCString& aVolumeName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->FormatVolume(aVolumeName);
+}
+
+static void
+AutoMounterMountVolumeIOThread(const nsCString& aVolumeName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->MountVolume(aVolumeName);
+}
+
+static void
+AutoMounterUnmountVolumeIOThread(const nsCString& aVolumeName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->UnmountVolume(aVolumeName);
+}
+
+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 final : public hal::SwitchObserver
+{
+ ~UsbCableObserver()
+ {
+ hal::UnregisterSwitchObserver(hal::SWITCH_USB, this);
+ }
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(UsbCableObserver)
+
+ UsbCableObserver()
+ {
+ hal::RegisterSwitchObserver(hal::SWITCH_USB, this);
+ }
+
+ virtual void Notify(const hal::SwitchEvent& aEvent)
+ {
+ DBG("UsbCable switch device: %d state: %s\n",
+ aEvent.device(), SwitchStateStr(aEvent));
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(UsbCableEventIOThread));
+ }
+};
+
+static StaticRefPtr<UsbCableObserver> sUsbCableObserver;
+static StaticRefPtr<AutoMounterSetting> sAutoMounterSetting;
+
+void
+InitAutoMounter()
+{
+ InitVolumeManager();
+ sAutoMounterSetting = new AutoMounterSetting();
+
+ XRE_GetIOMessageLoop()->PostTask(
+ 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();
+
+ // Register status reporter into reporter manager
+ if(status_reporter_progress == REPORTER_UNREGISTERED) {
+ NS_RegisterStatusReporter(new NS_STATUS_REPORTER_NAME(AutoMounter));
+ }
+ status_reporter_progress = REPORTER_REGISTERED;
+}
+
+int32_t
+GetAutoMounterStatus()
+{
+ if (sAutoMounterSetting) {
+ return sAutoMounterSetting->GetStatus();
+ }
+ return AUTOMOUNTER_STATUS_DISABLED;
+}
+
+//static
+void
+SetAutoMounterStatus(int32_t aStatus)
+{
+ if (sAutoMounterSetting) {
+ sAutoMounterSetting->SetStatus(aStatus);
+ }
+}
+
+void
+SetAutoMounterMode(int32_t aMode)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(SetAutoMounterModeIOThread, aMode));
+}
+
+void
+SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(SetAutoMounterSharingModeIOThread,
+ aVolumeName, aAllowSharing));
+}
+
+void
+AutoMounterFormatVolume(const nsCString& aVolumeName)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(AutoMounterFormatVolumeIOThread,
+ aVolumeName));
+}
+
+void
+AutoMounterMountVolume(const nsCString& aVolumeName)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(AutoMounterMountVolumeIOThread,
+ aVolumeName));
+}
+
+void
+AutoMounterUnmountVolume(const nsCString& aVolumeName)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(AutoMounterUnmountVolumeIOThread,
+ aVolumeName));
+}
+
+void
+ShutdownAutoMounter()
+{
+ if (sAutoMounter) {
+ DBG("ShutdownAutoMounter: About to StopMtpServer");
+ sAutoMounter->StopMtpServer();
+ // Unregister status reporter into reporter manager
+ if(status_reporter_progress == REPORTER_REGISTERED) {
+ NS_UnregisterStatusReporter(new NS_STATUS_REPORTER_NAME(AutoMounter));
+ }
+ status_reporter_progress = REPORTER_UNREGISTERED;
+ }
+ sAutoMounterSetting = nullptr;
+ sUsbCableObserver = nullptr;
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(ShutdownAutoMounterIOThread));
+}
+
+} // system
+} // mozilla