mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
20b739aeda
MozReview-Commit-ID: BcAboKGMr3a --HG-- extra : rebase_source : 6c5050527d8dffbce4a5e867bd48254a1041e657
1498 lines
46 KiB
C++
1498 lines
46 KiB
C++
/* 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 "nsAutoPtr.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
|