diff --git a/dom/system/gonk/AutoMounter.cpp b/dom/system/gonk/AutoMounter.cpp index 4d7e7023bdc..2419fb67a60 100644 --- a/dom/system/gonk/AutoMounter.cpp +++ b/dom/system/gonk/AutoMounter.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "AutoMounter.h" #include "nsVolumeService.h" @@ -26,7 +27,9 @@ #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" @@ -90,6 +93,13 @@ USING_MTP_NAMESPACE 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_UMS "mass_storage" + class AutoMounter; static void SetAutoMounterStatus(int32_t aStatus); @@ -115,7 +125,9 @@ IsUsbCablePluggedIn() if (access(ICS_SYS_USB_STATE, F_OK) == 0) { char usbState[20]; if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) { - return strcmp(usbState, "CONFIGURED") == 0; + 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; @@ -129,6 +141,26 @@ IsUsbCablePluggedIn() #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 @@ -144,7 +176,7 @@ public: class AutoVolumeEventObserver : public Volume::EventObserver { public: - virtual void Notify(Volume * const & aEvent); + virtual void Notify(Volume* const& aEvent); }; class AutoMounterResponseCallback : public VolumeResponseCallback @@ -174,7 +206,8 @@ public: typedef nsTArray> VolumeArray; AutoMounter() - : mResponseCallback(new AutoMounterResponseCallback), + : mState(STATE_IDLE), + mResponseCallback(new AutoMounterResponseCallback), mMode(AUTOMOUNTER_DISABLE) { VolumeManager::RegisterStateObserver(&mVolumeManagerStateObserver); @@ -232,21 +265,34 @@ public: } } + void UpdateState(); + + void ConfigureUsbFunction(const char* aUsbFunc); + void StartMtpServer(); void StopMtpServer(); - void UpdateState(); + void StartUmsSharing(); + void StopUmsSharing(); + const char* ModeStr(int32_t aMode) { switch (aMode) { case AUTOMOUNTER_DISABLE: return "Disable"; - case AUTOMOUNTER_ENABLE: return "Enable"; + 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) && @@ -256,8 +302,8 @@ public: aMode = AUTOMOUNTER_DISABLE; } - if ((aMode == AUTOMOUNTER_DISABLE) && - (mMode == AUTOMOUNTER_ENABLE) && IsUsbCablePluggedIn()) { + 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. @@ -346,10 +392,64 @@ public: 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, + }; + + 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"; + } + 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 mResponseCallback; int32_t mMode; + MozMtpStorage::Array mMozMtpStorage; }; static StaticRefPtr sAutoMounter; @@ -405,6 +505,78 @@ AutoMounterResponseCallback::ResponseReceived(const VolumeCommand* aCommand) } } +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 (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 { + 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)); + } + } + } + + LOG("SetUsbFunction(%s) %s to '%s'", aUsbFunc, SYS_USB_CONFIG, newSysUsbConfig); + property_set(SYS_USB_CONFIG, newSysUsbConfig); +} + void AutoMounter::StartMtpServer() { @@ -415,12 +587,22 @@ AutoMounter::StartMtpServer() LOG("Starting MtpServer"); sMozMtpServer = new MozMtpServer(); sMozMtpServer->Run(); + + VolumeArray::index_type volIndex; + VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); + for (volIndex = 0; volIndex < numVolumes; volIndex++) { + RefPtr vol = VolumeManager::GetVolume(volIndex); + nsRefPtr storage = new MozMtpStorage(vol, sMozMtpServer); + mMozMtpStorage.AppendElement(storage); + } } void AutoMounter::StopMtpServer() { LOG("Stopping MtpServer"); + + mMozMtpStorage.Clear(); sMozMtpServer = nullptr; } @@ -465,10 +647,20 @@ AutoMounter::UpdateState() return; } - bool umsAvail = false; - bool umsEnabled = false; - bool mtpAvail = false; - bool mtpEnabled = false; + // 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 = false; + bool umsConfigured = false; + bool umsEnabled = false; + bool mtpAvail = false; + bool mtpConfigured = false; + bool mtpEnabled = false; + bool usbCablePluggedIn = IsUsbCablePluggedIn(); if (access(ICS_SYS_USB_FUNCTIONS, F_OK) == 0) { char functionsStr[60]; @@ -476,40 +668,149 @@ AutoMounter::UpdateState() ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno)); functionsStr[0] = '\0'; } + DBG("UpdateState: USB functions: '%s'", functionsStr); + + bool usbConfigured = IsUsbConfigured(); umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0); if (umsAvail) { - umsEnabled = strstr(functionsStr, "mass_storage") != nullptr; + 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) { - mtpEnabled = strstr(functionsStr, "mtp") != nullptr; + mtpConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_MTP) != nullptr; + mtpEnabled = (mMode == AUTOMOUNTER_ENABLE_MTP) || + (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && mtpConfigured; + } else { + mtpConfigured = false; + mtpEnabled = false; } } - bool usbCablePluggedIn = IsUsbCablePluggedIn(); - bool enabled = (mMode == AUTOMOUNTER_ENABLE); + 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; } } - bool tryToShare = (((umsAvail && umsEnabled) || (mtpAvail && mtpEnabled)) - && enabled && usbCablePluggedIn); - LOG("UpdateState: ums:%d%d mtp:%d%d mode:%d usbCablePluggedIn:%d tryToShare:%d", - umsAvail, umsEnabled, mtpAvail, mtpEnabled, mMode, usbCablePluggedIn, tryToShare); + DBG("UpdateState: ums:A%dC%dE%d mtp:A%dC%dE%d mode:%d usb:%d mState:%s", + umsAvail, umsConfigured, umsEnabled, + mtpAvail, mtpConfigured, mtpEnabled, + mMode, usbCablePluggedIn, StateStr(mState)); - if (mtpAvail && mtpEnabled) { - if (enabled && usbCablePluggedIn) { - StartMtpServer(); - } else { - StopMtpServer(); - } - return; + 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 (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. + StartMtpServer(); + SetState(STATE_MTP_STARTED); + } else { + // The MTP USB layer is configuring. Wait for it to finish + // 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. + StartMtpServer(); + SetState(STATE_MTP_STARTED); + } + break; + + case STATE_MTP_STARTED: + if (usbCablePluggedIn) { + if (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 (umsAvail) { + // Switch back to UMS + SetUsbFunction(USB_FUNC_UMS); + SetState(STATE_UMS_CONFIGURING); + break; + } + } + SetState(STATE_IDLE); + break; + + case STATE_UMS_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 (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; + } + } + 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; @@ -524,7 +825,8 @@ AutoMounter::UpdateState() vol->MediaPresent() ? "inserted" : "missing", vol->MountPoint().get(), vol->MountGeneration(), (int)vol->IsMountLocked(), - vol->CanBeShared() ? (vol->IsSharingEnabled() ? (vol->IsSharing() ? "en-y" : "en-n") : "dis") : "x"); + 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 @@ -940,6 +1242,7 @@ void ShutdownAutoMounter() { if (sAutoMounter) { + DBG("ShutdownAutoMounter: About to StopMtpServer"); sAutoMounter->StopMtpServer(); } sAutoMounterSetting = nullptr; diff --git a/dom/system/gonk/AutoMounter.h b/dom/system/gonk/AutoMounter.h index 680d276e163..ea98cadf1d0 100644 --- a/dom/system/gonk/AutoMounter.h +++ b/dom/system/gonk/AutoMounter.h @@ -14,8 +14,9 @@ namespace system { // AutoMounter modes #define AUTOMOUNTER_DISABLE 0 -#define AUTOMOUNTER_ENABLE 1 +#define AUTOMOUNTER_ENABLE_UMS 1 #define AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED 2 +#define AUTOMOUNTER_ENABLE_MTP 3 // Automounter statuses #define AUTOMOUNTER_STATUS_DISABLED 0 diff --git a/dom/system/gonk/MozMtpDatabase.cpp b/dom/system/gonk/MozMtpDatabase.cpp index b2634e0184b..e888a999f66 100644 --- a/dom/system/gonk/MozMtpDatabase.cpp +++ b/dom/system/gonk/MozMtpDatabase.cpp @@ -40,9 +40,9 @@ ObjectPropertyAsStr(MtpObjectProperty aProperty) return "MTP_PROPERTY_???"; } -MozMtpDatabase::MozMtpDatabase(const char *aDir) +MozMtpDatabase::MozMtpDatabase() { - MTP_LOG(""); + MTP_LOG("constructed"); // We use the index into the array as the handle. Since zero isn't a valid // index, we stick a dummy entry there. @@ -50,14 +50,12 @@ MozMtpDatabase::MozMtpDatabase(const char *aDir) RefPtr dummy; mDb.AppendElement(dummy); - - ReadVolume("sdcard", aDir); } //virtual MozMtpDatabase::~MozMtpDatabase() { - MTP_LOG(""); + MTP_LOG("destructed"); } void @@ -67,7 +65,7 @@ MozMtpDatabase::AddEntry(DbEntry *entry) MOZ_ASSERT(mDb.Length() == entry->mHandle); mDb.AppendElement(entry); - MTP_LOG("AddEntry: Handle: 0x%08x Parent: 0x%08x Path:'%s'", + MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path:'%s'", entry->mHandle, entry->mParent, entry->mPath.get()); } @@ -120,18 +118,20 @@ GetPathWithoutFileName(const nsCString& aFullPath) } void -MozMtpDatabase::ParseDirectory(const char *aDir, MtpObjectHandle aParent) +MozMtpDatabase::AddDirectory(MtpStorageID aStorageID, + const char* aPath, + MtpObjectHandle aParent) { ScopedCloseDir dir; - if (!(dir = PR_OpenDir(aDir))) { - MTP_ERR("Unable to open directory '%s'", aDir); + if (!(dir = PR_OpenDir(aPath))) { + MTP_ERR("Unable to open directory '%s'", aPath); return; } PRDirEntry* dirEntry; while ((dirEntry = PR_ReadDir(dir, PR_SKIP_BOTH))) { - nsPrintfCString filename("%s/%s", aDir, dirEntry->name); + nsPrintfCString filename("%s/%s", aPath, dirEntry->name); PRFileInfo64 fileInfo; if (PR_GetFileInfo64(filename.get(), &fileInfo) != PR_SUCCESS) { MTP_ERR("Unable to retrieve file information for '%s'", filename.get()); @@ -140,7 +140,7 @@ MozMtpDatabase::ParseDirectory(const char *aDir, MtpObjectHandle aParent) RefPtr entry = new DbEntry; - entry->mStorageID = MTP_STORAGE_FIXED_RAM; + entry->mStorageID = aStorageID; entry->mParent = aParent; entry->mObjectName = dirEntry->name; entry->mDisplayName = dirEntry->name; @@ -157,40 +157,59 @@ MozMtpDatabase::ParseDirectory(const char *aDir, MtpObjectHandle aParent) entry->mObjectFormat = MTP_FORMAT_ASSOCIATION; entry->mObjectSize = 0; AddEntry(entry); - ParseDirectory(filename.get(), entry->mHandle); + AddDirectory(aStorageID, filename.get(), entry->mHandle); } } } void -MozMtpDatabase::ReadVolume(const char *volumeName, const char *aDir) +MozMtpDatabase::AddStorage(MtpStorageID aStorageID, + const char* aPath, + const char* aName) { //TODO: Add an assert re thread being run on PRFileInfo fileInfo; - if (PR_GetFileInfo(aDir, &fileInfo) != PR_SUCCESS) { - MTP_ERR("'%s' doesn't exist", aDir); + if (PR_GetFileInfo(aPath, &fileInfo) != PR_SUCCESS) { + MTP_ERR("'%s' doesn't exist", aPath); return; } if (fileInfo.type != PR_FILE_DIRECTORY) { - MTP_ERR("'%s' isn't a directory", aDir); + MTP_ERR("'%s' isn't a directory", aPath); return; } +#if 0 RefPtr entry = new DbEntry; - entry->mStorageID = MTP_STORAGE_FIXED_RAM; + entry->mStorageID = aStorageID; entry->mParent = MTP_PARENT_ROOT; - entry->mObjectName = volumeName; - entry->mDisplayName = volumeName; - entry->mPath = aDir; + entry->mObjectName = aName; + entry->mDisplayName = aName; + entry->mPath = aPath; entry->mObjectFormat = MTP_FORMAT_ASSOCIATION; entry->mObjectSize = 0; AddEntry(entry); - ParseDirectory(aDir, entry->mHandle); + AddDirectory(aStorageID, aPath, entry->mHandle); +#else + AddDirectory(aStorageID, aPath, MTP_PARENT_ROOT); +#endif +} + +void +MozMtpDatabase::RemoveStorage(MtpStorageID aStorageID) +{ + DbArray::size_type numEntries = mDb.Length(); + DbArray::index_type entryIndex; + for (entryIndex = 1; entryIndex < numEntries; entryIndex++) { + RefPtr entry = mDb[entryIndex]; + if (entry && entry->mStorageID == aStorageID) { + mDb[entryIndex] = nullptr; + } + } } // called from SendObjectInfo to reserve a database entry for the incoming file @@ -232,12 +251,12 @@ MozMtpDatabase::beginSendObject(const char* aPath, //virtual void MozMtpDatabase::endSendObject(const char* aPath, - MtpObjectHandle aHandle, - MtpObjectFormat aFormat, - bool succeeded) + MtpObjectHandle aHandle, + MtpObjectFormat aFormat, + bool aSucceeded) { MTP_LOG("Handle: 0x%08x Path: '%s'", aHandle, aPath); - if (!succeeded) { + if (!aSucceeded) { RemoveEntry(aHandle); } } @@ -245,8 +264,8 @@ MozMtpDatabase::endSendObject(const char* aPath, //virtual MtpObjectHandleList* MozMtpDatabase::getObjectList(MtpStorageID aStorageID, - MtpObjectFormat aFormat, - MtpObjectHandle aParent) + MtpObjectFormat aFormat, + MtpObjectHandle aParent) { MTP_LOG("StorageID: 0x%08x Format: 0x%04x Parent: 0x%08x", aStorageID, aFormat, aParent); @@ -257,11 +276,14 @@ MozMtpDatabase::getObjectList(MtpStorageID aStorageID, list = new MtpObjectHandleList(); + // Note: objects in the topmost directory of each storage area have a parent + // of MTP_PARENT_ROOT. So we need to filter on storage ID as well. + DbArray::size_type numEntries = mDb.Length(); DbArray::index_type entryIndex; for (entryIndex = 1; entryIndex < numEntries; entryIndex++) { RefPtr entry = mDb[entryIndex]; - if (entry && entry->mParent == aParent) { + if (entry && entry->mStorageID == aStorageID && entry->mParent == aParent) { list->push(entry->mHandle); } } @@ -271,12 +293,23 @@ MozMtpDatabase::getObjectList(MtpStorageID aStorageID, //virtual int MozMtpDatabase::getNumObjects(MtpStorageID aStorageID, - MtpObjectFormat aFormat, - MtpObjectHandle aParent) + MtpObjectFormat aFormat, + MtpObjectHandle aParent) { MTP_LOG(""); - return mDb.Length() - 1; + int count = 0; + + DbArray::size_type numEntries = mDb.Length(); + DbArray::index_type entryIndex; + for (entryIndex = 1; entryIndex < numEntries; entryIndex++) { + RefPtr entry = mDb[entryIndex]; + if (entry && entry->mStorageID == aStorageID) { + count++; + } + } + + return count; } //virtual @@ -344,8 +377,8 @@ MozMtpDatabase::getSupportedDeviceProperties() //virtual MtpResponseCode MozMtpDatabase::getObjectPropertyValue(MtpObjectHandle aHandle, - MtpObjectProperty aProperty, - MtpDataPacket& aPacket) + MtpObjectProperty aProperty, + MtpDataPacket& aPacket) { RefPtr entry = GetEntry(aHandle); if (!entry) { @@ -417,8 +450,8 @@ GetTypeOfObjectProp(MtpObjectProperty aProperty) //virtual MtpResponseCode MozMtpDatabase::setObjectPropertyValue(MtpObjectHandle aHandle, - MtpObjectProperty aProperty, - MtpDataPacket& aPacket) + MtpObjectProperty aProperty, + MtpDataPacket& aPacket) { MTP_LOG("Handle: 0x%08x Property: 0x%08x", aHandle, aProperty); @@ -463,7 +496,7 @@ MozMtpDatabase::setObjectPropertyValue(MtpObjectHandle aHandle, //virtual MtpResponseCode MozMtpDatabase::getDevicePropertyValue(MtpDeviceProperty aProperty, - MtpDataPacket& aPacket) + MtpDataPacket& aPacket) { MTP_LOG("(GENERAL ERROR)"); return MTP_RESPONSE_GENERAL_ERROR; @@ -472,7 +505,7 @@ MozMtpDatabase::getDevicePropertyValue(MtpDeviceProperty aProperty, //virtual MtpResponseCode MozMtpDatabase::setDevicePropertyValue(MtpDeviceProperty aProperty, - MtpDataPacket& aPacket) + MtpDataPacket& aPacket) { MTP_LOG("(NOT SUPPORTED)"); return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; @@ -567,13 +600,13 @@ MozMtpDatabase::QueryEntries(MozMtpDatabase::MatchType aMatchType, //virtual MtpResponseCode MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle, - uint32_t aFormat, - uint32_t aProperty, - int aGroupCode, - int aDepth, - MtpDataPacket& aPacket) + uint32_t aFormat, + uint32_t aProperty, + int aGroupCode, + int aDepth, + MtpDataPacket& aPacket) { - MTP_LOG("Handle: 0x%08x Format: 0x%08x aProperty: 0x%08x aGroupCode: %d aDepth %d (NOT SUPPORTED)", + MTP_LOG("Handle: 0x%08x Format: 0x%08x aProperty: 0x%08x aGroupCode: %d aDepth %d", aHandle, aFormat, aProperty, aGroupCode, aDepth); if (aDepth > 1) { @@ -726,7 +759,7 @@ MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle, //virtual MtpResponseCode MozMtpDatabase::getObjectInfo(MtpObjectHandle aHandle, - MtpObjectInfo& aInfo) + MtpObjectInfo& aInfo) { RefPtr entry = GetEntry(aHandle); if (!entry) { @@ -780,9 +813,9 @@ MozMtpDatabase::getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize) //virtual MtpResponseCode MozMtpDatabase::getObjectFilePath(MtpObjectHandle aHandle, - MtpString& aOutFilePath, - int64_t& aOutFileLength, - MtpObjectFormat& aOutFormat) + MtpString& aOutFilePath, + int64_t& aOutFileLength, + MtpObjectFormat& aOutFormat) { RefPtr entry = GetEntry(aHandle); if (!entry) { @@ -811,6 +844,10 @@ MozMtpDatabase::deleteFile(MtpObjectHandle aHandle) MTP_LOG("Handle: 0x%08x '%s'", aHandle, entry->mPath.get()); + //TODO: MtpServer::doDeleteObject calls us, and then calls a private + // method (deletePath) which recursively deletes the path. + // We need to tell device storage that these files are gone + // File deletion will happen in lower level implementation. // The only thing we need to do is removing the entry from the db. RemoveEntry(aHandle); @@ -854,7 +891,7 @@ MozMtpDatabase::getObjectReferences(MtpObjectHandle aHandle) //virtual MtpResponseCode MozMtpDatabase::setObjectReferences(MtpObjectHandle aHandle, - MtpObjectHandleList* aReferences) + MtpObjectHandleList* aReferences) { MTP_LOG("Handle: 0x%08x (NOT SUPPORTED)", aHandle); return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; diff --git a/dom/system/gonk/MozMtpDatabase.h b/dom/system/gonk/MozMtpDatabase.h index 7edd3ca5c5b..09fdffb415d 100644 --- a/dom/system/gonk/MozMtpDatabase.h +++ b/dom/system/gonk/MozMtpDatabase.h @@ -22,7 +22,9 @@ using namespace android; class MozMtpDatabase : public MtpDatabase { public: - MozMtpDatabase(const char *aDir); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpDatabase) + + MozMtpDatabase(); virtual ~MozMtpDatabase(); // called from SendObjectInfo to reserve a database entry for the incoming file @@ -106,11 +108,14 @@ public: virtual void sessionEnded(); + void AddStorage(MtpStorageID aStorageID, const char* aPath, const char *aName); + void RemoveStorage(MtpStorageID aStorageID); + private: struct DbEntry { - NS_INLINE_DECL_REFCOUNTING(DbEntry) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DbEntry) MtpObjectHandle mHandle; // uint32_t MtpStorageID mStorageID; // uint32_t @@ -152,8 +157,7 @@ private: return mDb.Length(); } - void ParseDirectory(const char *aDir, MtpObjectHandle aParent); - void ReadVolume(const char *aVolumeName, const char *aDir); + void AddDirectory(MtpStorageID aStorageID, const char *aPath, MtpObjectHandle aParent); }; END_MTP_NAMESPACE diff --git a/dom/system/gonk/MozMtpServer.cpp b/dom/system/gonk/MozMtpServer.cpp index 43df913dcc9..47447c8f8d7 100644 --- a/dom/system/gonk/MozMtpServer.cpp +++ b/dom/system/gonk/MozMtpServer.cpp @@ -16,46 +16,34 @@ #include #include +#include +#include "base/message_loop.h" #include "mozilla/FileUtils.h" #include "mozilla/Scoped.h" +#include "mozilla/StaticPtr.h" +#include "nsAutoPtr.h" #include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "Volume.h" using namespace android; using namespace mozilla; -USING_MTP_NAMESPACE +BEGIN_MTP_NAMESPACE class MtpServerRunnable : public nsRunnable { public: + MtpServerRunnable(int aMtpUsbFd, MozMtpServer* aMozMtpServer) + : mMozMtpServer(aMozMtpServer), + mMtpUsbFd(aMtpUsbFd) + { + } + nsresult Run() { - const char *mtpUsbFilename = "/dev/mtp_usb"; - const char *productName = "FirefoxOS"; - const char *storageDir = "/storage/sdcard"; - - mFd = open(mtpUsbFilename, O_RDWR); - if (mFd.get() < 0) { - MTP_LOG("open of '%s' failed", mtpUsbFilename); - return NS_OK; - } - - MTP_LOG("MozMtpServer open done, fd: %d. Start reading.", mFd.get()); - - ScopedDeletePtr database; - ScopedDeletePtr server; - ScopedDeletePtr storage; - - database = new MozMtpDatabase(storageDir); - server = new MtpServer(mFd.get(), database, false, 1023, 0664, 0775); - storage = new MtpStorage(MTP_STORAGE_FIXED_RAM, // id - storageDir, // filePath - productName, // description - 100uLL * 1024uLL * 1024uLL, // reserveSpace - false, // removable - 2uLL * 1024uLL * 1024uLL * 1024uLL); // maxFileSize - - server->addStorage(storage); + nsRefPtr server = mMozMtpServer->GetMtpServer(); MTP_LOG("MozMtpServer started"); server->run(); @@ -65,16 +53,51 @@ public: } private: - ScopedClose mFd; + nsRefPtr mMozMtpServer; + ScopedClose mMtpUsbFd; // We want to hold this open while the server runs }; +already_AddRefed +MozMtpServer::GetMtpServer() +{ + nsRefPtr server = mMtpServer; + return server.forget(); +} + +already_AddRefed +MozMtpServer::GetMozMtpDatabase() +{ + nsRefPtr db = mMozMtpDatabase; + return db.forget(); +} + void MozMtpServer::Run() { + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + const char *mtpUsbFilename = "/dev/mtp_usb"; + ScopedClose mtpUsbFd(open(mtpUsbFilename, O_RDWR)); + if (mtpUsbFd.get() < 0) { + MTP_ERR("open of '%s' failed", mtpUsbFilename); + return; + } + MTP_LOG("Opened '%s' fd %d", mtpUsbFilename, mtpUsbFd.get()); + + mMozMtpDatabase = new MozMtpDatabase(); + mMtpServer = new RefCountedMtpServer(mtpUsbFd.get(), // fd + mMozMtpDatabase.get(), // MtpDatabase + false, // ptp? + AID_MEDIA_RW, // file group + 0664, // file permissions + 0775); // dir permissions + nsresult rv = NS_NewNamedThread("MtpServer", getter_AddRefs(mServerThread)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } MOZ_ASSERT(mServerThread); - mServerThread->Dispatch(new MtpServerRunnable(), 0); + mServerThread->Dispatch(new MtpServerRunnable(mtpUsbFd.forget(), this), NS_DISPATCH_NORMAL); } + +END_MTP_NAMESPACE diff --git a/dom/system/gonk/MozMtpServer.h b/dom/system/gonk/MozMtpServer.h index c7c6a6f8b2c..ca47566733b 100644 --- a/dom/system/gonk/MozMtpServer.h +++ b/dom/system/gonk/MozMtpServer.h @@ -8,20 +8,48 @@ #define mozilla_system_mozmtpserver_h__ #include "MozMtpCommon.h" +#include "MozMtpDatabase.h" +#include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsIThread.h" +namespace mozilla { +namespace system { + class Volume; +} +} + BEGIN_MTP_NAMESPACE +using namespace android; + +class RefCountedMtpServer : public MtpServer +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMtpServer) + + RefCountedMtpServer(int aFd, MtpDatabase* aDatabase, bool aPtp, + int aFileGroup, int aFilePerm, int aDirectoryPerm) + : MtpServer(aFd, aDatabase, aPtp, aFileGroup, aFilePerm, aDirectoryPerm) + { + } +}; class MozMtpServer { public: - NS_INLINE_DECL_REFCOUNTING(MozMtpServer) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpServer) void Run(); +// void UpdateStorage(android::MtpStorageID id, Volume *vol); + + already_AddRefed GetMtpServer(); + already_AddRefed GetMozMtpDatabase(); + private: + nsRefPtr mMtpServer; + nsRefPtr mMozMtpDatabase; nsCOMPtr mServerThread; }; diff --git a/dom/system/gonk/MozMtpStorage.cpp b/dom/system/gonk/MozMtpStorage.cpp new file mode 100644 index 00000000000..b88e2f29a97 --- /dev/null +++ b/dom/system/gonk/MozMtpStorage.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "MozMtpStorage.h" +#include "MozMtpDatabase.h" +#include "MozMtpServer.h" + +#include "base/message_loop.h" +#include "nsXULAppAPI.h" + +BEGIN_MTP_NAMESPACE +using namespace android; + +MozMtpStorage::MozMtpStorage(Volume* aVolume, MozMtpServer* aMozMtpServer) + : mMozMtpServer(aMozMtpServer), + mVolume(aVolume) +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + // The MtpStorageID has the physical volume in the top 16 bits, and the + // logical volumein the lower 16 bits. We treat each volume as a separate + // phsyical storage; + mStorageID = mVolume->Id() << 16 | 1; + + MTP_LOG("Storage constructed for Volume %s mStorageID 0x%08x", + aVolume->NameStr(), mStorageID); + + Volume::RegisterObserver(this); + + // Get things in sync + Notify(mVolume); +} + +MozMtpStorage::~MozMtpStorage() +{ + MTP_LOG("Storage destructed for Volume %s mStorageID 0x%08x", + mVolume->NameStr(), mStorageID); + + Volume::UnregisterObserver(this); + if (mMtpStorage) { + StorageUnavailable(); + } +} + +// virtual +void +MozMtpStorage::Notify(Volume* const& aVolume) +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + if (aVolume != mVolume) { + // Not our volume + return; + } + Volume::STATE volState = aVolume->State(); + + MTP_LOG("Volume %s mStorageID 0x%08x state changed to %s SharingEnabled: %d", + aVolume->NameStr(), mStorageID, aVolume->StateStr(), + aVolume->IsSharingEnabled()); + + if (mMtpStorage) { + if (volState != nsIVolume::STATE_MOUNTED || !aVolume->IsSharingEnabled()) { + // The volume is no longer accessible. We need to remove this storage + // from the MTP server + StorageUnavailable(); + } + } else { + if (volState == nsIVolume::STATE_MOUNTED && aVolume->IsSharingEnabled()) { + // The volume is accessible. Tell the MTP server. + StorageAvailable(); + } + } +} + +void +MozMtpStorage::StorageAvailable() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + nsCString mountPoint = mVolume->MountPoint(); + + MTP_LOG("Adding Volume %s mStorageID 0x%08x mountPoint %s to MozMtpDatabase", + mVolume->NameStr(), mStorageID, mountPoint.get()); + + nsRefPtr db = mMozMtpServer->GetMozMtpDatabase(); + db->AddStorage(mStorageID, mountPoint.get(), mVolume->NameStr()); + + MOZ_ASSERT(!mMtpStorage); + + //TODO: For now we assume that the storage removable unless we're sure it's + // not. Bug 1033952 will add an isRemovable attribute to the Volume + // and then we'll know properly. + + //TODO: Figure out what to do about maxFileSize. + + mMtpStorage.reset(new MtpStorage(mStorageID, // id + mountPoint.get(), // filePath + mVolume->NameStr(), // description + 1024uLL * 1024uLL, // reserveSpace + true, // removable + 2uLL * 1024uLL * 1024uLL * 1024uLL)); // maxFileSize + nsRefPtr server = mMozMtpServer->GetMtpServer(); + + MTP_LOG("Adding Volume %s mStorageID 0x%08x mountPoint %s to MtpServer", + mVolume->NameStr(), mStorageID, mountPoint.get()); + server->addStorage(mMtpStorage.get()); +} + +void +MozMtpStorage::StorageUnavailable() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + MOZ_ASSERT(mMtpStorage); + + MTP_LOG("Removing mStorageID 0x%08x from MtpServer", mStorageID); + + nsRefPtr server = mMozMtpServer->GetMtpServer(); + server->removeStorage(mMtpStorage.get()); + + MTP_LOG("Removing mStorageID 0x%08x from MozMtpDatabse", mStorageID); + + nsRefPtr db = mMozMtpServer->GetMozMtpDatabase(); + db->RemoveStorage(mStorageID); + + mMtpStorage = nullptr; +} + +END_MTP_NAMESPACE + + diff --git a/dom/system/gonk/MozMtpStorage.h b/dom/system/gonk/MozMtpStorage.h new file mode 100644 index 00000000000..cbc3438eda5 --- /dev/null +++ b/dom/system/gonk/MozMtpStorage.h @@ -0,0 +1,48 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_system_mozmtpstorage_h__ +#define mozilla_system_mozmtpstorage_h__ + +#include "MozMtpCommon.h" + +#include "nsAutoPtr.h" +#include "mozilla/UniquePtr.h" + +#include "Volume.h" + +BEGIN_MTP_NAMESPACE +using namespace android; + +class MozMtpServer; + +class MozMtpStorage : public Volume::EventObserver +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpStorage) + + MozMtpStorage(Volume* aVolume, MozMtpServer* aMozMtpServer); + + typedef nsTArray > Array; + +private: + virtual ~MozMtpStorage(); + virtual void Notify(Volume* const& aEvent); + + void StorageAvailable(); + void StorageUnavailable(); + + nsRefPtr mMozMtpServer; + UniquePtr mMtpStorage; + RefPtr mVolume; + MtpStorageID mStorageID; +}; + +END_MTP_NAMESPACE + +#endif // mozilla_system_mozmtpstorage_h__ + + diff --git a/dom/system/gonk/Volume.cpp b/dom/system/gonk/Volume.cpp index 74b6ef813c7..57fa2076fae 100644 --- a/dom/system/gonk/Volume.cpp +++ b/dom/system/gonk/Volume.cpp @@ -50,6 +50,8 @@ Volume::EventObserverList Volume::mEventObserverList; // be locked until we get our first update from nsVolume (MainThread). static int32_t sMountGeneration = 0; +static uint32_t sNextId = 1; + // We don't get media inserted/removed events at startup. So we // assume it's present, and we'll be told that it's missing. Volume::Volume(const nsCSubstring& aName) @@ -64,7 +66,8 @@ Volume::Volume(const nsCSubstring& aName) mUnmountRequested(false), mCanBeShared(true), mIsSharing(false), - mIsFormatting(false) + mIsFormatting(false), + mId(sNextId++) { DBG("Volume %s: created", NameStr()); } @@ -140,6 +143,7 @@ Volume::SetSharingEnabled(bool aSharingEnabled) LOG("SetSharingMode for volume %s to %d canBeShared = %d", NameStr(), (int)mSharingEnabled, (int)mCanBeShared); + mEventObserverList.Broadcast(this); } void @@ -179,14 +183,14 @@ Volume::SetState(Volume::STATE aNewState) } if (aNewState == nsIVolume::STATE_MOUNTED) { mMountGeneration = ++sMountGeneration; - LOG("Volume %s: changing state from %s to %s @ '%s' (%d observers) " + LOG("Volume %s (%u): changing state from %s to %s @ '%s' (%d observers) " "mountGeneration = %d, locked = %d", - NameStr(), StateStr(mState), + NameStr(), mId, StateStr(mState), StateStr(aNewState), mMountPoint.get(), mEventObserverList.Length(), mMountGeneration, (int)mMountLocked); } else { - LOG("Volume %s: changing state from %s to %s (%d observers)", - NameStr(), StateStr(mState), + LOG("Volume %s (%u): changing state from %s to %s (%d observers)", + NameStr(), mId, StateStr(mState), StateStr(aNewState), mEventObserverList.Length()); } diff --git a/dom/system/gonk/Volume.h b/dom/system/gonk/Volume.h index 7bb3d32bfe4..556626a99ef 100644 --- a/dom/system/gonk/Volume.h +++ b/dom/system/gonk/Volume.h @@ -44,6 +44,8 @@ public: // (i.e. path that leads to the files stored on the volume). const nsCString& MountPoint() const { return mMountPoint; } + uint32_t Id() const { return mId; } + int32_t MountGeneration() const { return mMountGeneration; } bool IsMountLocked() const { return mMountLocked; } bool MediaPresent() const { return mMediaPresent; } @@ -110,6 +112,7 @@ private: bool mCanBeShared; bool mIsSharing; bool mIsFormatting; + uint32_t mId; // Unique ID (used by MTP) static EventObserverList mEventObserverList; }; diff --git a/dom/system/gonk/moz.build b/dom/system/gonk/moz.build index bd4a638e705..2badf69c3e4 100644 --- a/dom/system/gonk/moz.build +++ b/dom/system/gonk/moz.build @@ -43,6 +43,7 @@ SOURCES += [ 'GonkGPSGeolocationProvider.cpp', 'MozMtpDatabase.cpp', 'MozMtpServer.cpp', + 'MozMtpStorage.cpp', 'NetworkUtils.cpp', 'NetworkWorker.cpp', 'nsVolume.cpp',