michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "AutoMounter.h" michael@0: #include "nsVolumeService.h" michael@0: #include "AutoMounterSetting.h" michael@0: #include "base/message_loop.h" michael@0: #include "mozilla/AutoRestore.h" michael@0: #include "mozilla/FileUtils.h" michael@0: #include "mozilla/Hal.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsMemory.h" michael@0: #include "nsString.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "OpenFileFinder.h" michael@0: #include "Volume.h" michael@0: #include "VolumeManager.h" michael@0: michael@0: using namespace mozilla::hal; michael@0: michael@0: /************************************************************************** michael@0: * michael@0: * The following "switch" files are available for monitoring usb michael@0: * connections: michael@0: * michael@0: * /sys/devices/virtual/switch/usb_connected/state michael@0: * /sys/devices/virtual/switch/usb_configuration/state michael@0: * michael@0: * Under gingerbread, only the usb_configuration seems to be available. michael@0: * Starting with honeycomb, usb_connected was also added. michael@0: * michael@0: * When a cable insertion/removal occurs, then a uevent similar to the michael@0: * following will be generted: michael@0: * michael@0: * change@/devices/virtual/switch/usb_configuration michael@0: * ACTION=change michael@0: * DEVPATH=/devices/virtual/switch/usb_configuration michael@0: * SUBSYSTEM=switch michael@0: * SWITCH_NAME=usb_configuration michael@0: * SWITCH_STATE=0 michael@0: * SEQNUM=5038 michael@0: * michael@0: * SWITCH_STATE will be 0 after a removal and 1 after an insertion michael@0: * michael@0: **************************************************************************/ michael@0: michael@0: #define USB_CONFIGURATION_SWITCH_NAME NS_LITERAL_STRING("usb_configuration") michael@0: michael@0: #define GB_SYS_UMS_ENABLE "/sys/devices/virtual/usb_composite/usb_mass_storage/enable" michael@0: #define GB_SYS_USB_CONFIGURED "/sys/devices/virtual/switch/usb_configuration/state" michael@0: michael@0: #define ICS_SYS_USB_FUNCTIONS "/sys/devices/virtual/android_usb/android0/functions" michael@0: #define ICS_SYS_UMS_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mass_storage" michael@0: #define ICS_SYS_USB_STATE "/sys/devices/virtual/android_usb/android0/state" michael@0: michael@0: #define USE_DEBUG 0 michael@0: michael@0: #undef LOG michael@0: #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AutoMounter", ## args) michael@0: #define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "AutoMounter", ## args) michael@0: #define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "AutoMounter", ## args) michael@0: michael@0: #if USE_DEBUG michael@0: #define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "AutoMounter" , ## args) michael@0: #else michael@0: #define DBG(args...) michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace system { michael@0: michael@0: class AutoMounter; michael@0: michael@0: static void SetAutoMounterStatus(int32_t aStatus); michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: inline const char* SwitchStateStr(const SwitchEvent& aEvent) michael@0: { michael@0: return aEvent.status() == SWITCH_STATE_ON ? "plugged" : "unplugged"; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: static bool michael@0: IsUsbCablePluggedIn() michael@0: { michael@0: #if 0 michael@0: // Use this code when bug 745078 gets fixed (or use whatever the michael@0: // appropriate method is) michael@0: return GetCurrentSwitchEvent(SWITCH_USB) == SWITCH_STATE_ON; michael@0: #else michael@0: // Until then, just go read the file directly michael@0: if (access(ICS_SYS_USB_STATE, F_OK) == 0) { michael@0: char usbState[20]; michael@0: if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) { michael@0: return strcmp(usbState, "CONFIGURED") == 0; michael@0: } michael@0: ERR("Error reading file '%s': %s", ICS_SYS_USB_STATE, strerror(errno)); michael@0: return false; michael@0: } michael@0: bool configured; michael@0: if (ReadSysFile(GB_SYS_USB_CONFIGURED, &configured)) { michael@0: return configured; michael@0: } michael@0: ERR("Error reading file '%s': %s", GB_SYS_USB_CONFIGURED, strerror(errno)); michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: // The AutoVolumeManagerStateObserver allows the AutoMounter to know when michael@0: // the volume manager changes state (i.e. it has finished initialization) michael@0: class AutoVolumeManagerStateObserver : public VolumeManager::StateObserver michael@0: { michael@0: public: michael@0: virtual void Notify(const VolumeManager::StateChangedEvent& aEvent); michael@0: }; michael@0: michael@0: // The AutoVolumeEventObserver allows the AutoMounter to know about card michael@0: // insertion and removal, as well as state changes in the volume. michael@0: class AutoVolumeEventObserver : public Volume::EventObserver michael@0: { michael@0: public: michael@0: virtual void Notify(Volume * const & aEvent); michael@0: }; michael@0: michael@0: class AutoMounterResponseCallback : public VolumeResponseCallback michael@0: { michael@0: public: michael@0: AutoMounterResponseCallback() michael@0: : mErrorCount(0) michael@0: { michael@0: } michael@0: michael@0: protected: michael@0: virtual void ResponseReceived(const VolumeCommand* aCommand); michael@0: michael@0: private: michael@0: const static int kMaxErrorCount = 3; // Max number of errors before we give up michael@0: michael@0: int mErrorCount; michael@0: }; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: class AutoMounter michael@0: { michael@0: public: michael@0: NS_INLINE_DECL_REFCOUNTING(AutoMounter) michael@0: michael@0: typedef nsTArray> VolumeArray; michael@0: michael@0: AutoMounter() michael@0: : mResponseCallback(new AutoMounterResponseCallback), michael@0: mMode(AUTOMOUNTER_DISABLE) michael@0: { michael@0: VolumeManager::RegisterStateObserver(&mVolumeManagerStateObserver); michael@0: Volume::RegisterObserver(&mVolumeEventObserver); michael@0: michael@0: VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); michael@0: VolumeManager::VolumeArray::index_type i; michael@0: for (i = 0; i < numVolumes; i++) { michael@0: RefPtr vol = VolumeManager::GetVolume(i); michael@0: if (vol) { michael@0: vol->RegisterObserver(&mVolumeEventObserver); michael@0: // We need to pick up the intial value of the michael@0: // ums.volume.NAME.enabled setting. michael@0: AutoMounterSetting::CheckVolumeSettings(vol->Name()); michael@0: } michael@0: } michael@0: michael@0: DBG("Calling UpdateState from constructor"); michael@0: UpdateState(); michael@0: } michael@0: michael@0: ~AutoMounter() michael@0: { michael@0: VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); michael@0: VolumeManager::VolumeArray::index_type volIndex; michael@0: for (volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: RefPtr vol = VolumeManager::GetVolume(volIndex); michael@0: if (vol) { michael@0: vol->UnregisterObserver(&mVolumeEventObserver); michael@0: } michael@0: } michael@0: Volume::UnregisterObserver(&mVolumeEventObserver); michael@0: VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver); michael@0: } michael@0: michael@0: void UpdateState(); michael@0: michael@0: const char* ModeStr(int32_t aMode) michael@0: { michael@0: switch (aMode) { michael@0: case AUTOMOUNTER_DISABLE: return "Disable"; michael@0: case AUTOMOUNTER_ENABLE: return "Enable"; michael@0: case AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED: return "DisableWhenUnplugged"; michael@0: } michael@0: return "??? Unknown ???"; michael@0: } michael@0: michael@0: void SetMode(int32_t aMode) michael@0: { michael@0: if ((aMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && michael@0: (mMode == AUTOMOUNTER_DISABLE)) { michael@0: // If it's already disabled, then leave it as disabled. michael@0: // AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED implies "enabled until unplugged" michael@0: aMode = AUTOMOUNTER_DISABLE; michael@0: } michael@0: michael@0: if ((aMode == AUTOMOUNTER_DISABLE) && michael@0: (mMode == AUTOMOUNTER_ENABLE) && IsUsbCablePluggedIn()) { michael@0: // On many devices (esp non-Samsung), we can't force the disable, so we michael@0: // need to defer until the USB cable is actually unplugged. michael@0: // See bug 777043. michael@0: // michael@0: // Otherwise our attempt to disable it will fail, and we'll wind up in a bad michael@0: // state where the AutoMounter thinks that Sharing has been turned off, but michael@0: // the files are actually still being Shared because the attempt to unshare michael@0: // failed. michael@0: LOG("Attempting to disable UMS. Deferring until USB cable is unplugged."); michael@0: aMode = AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED; michael@0: } michael@0: michael@0: if (aMode != mMode) { michael@0: LOG("Changing mode from '%s' to '%s'", ModeStr(mMode), ModeStr(aMode)); michael@0: mMode = aMode; michael@0: DBG("Calling UpdateState due to mode set to %d", mMode); michael@0: UpdateState(); michael@0: } michael@0: } michael@0: michael@0: void SetSharingMode(const nsACString& aVolumeName, bool aAllowSharing) michael@0: { michael@0: RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); michael@0: if (!vol) { michael@0: return; michael@0: } michael@0: if (vol->IsSharingEnabled() == aAllowSharing) { michael@0: return; michael@0: } michael@0: vol->SetUnmountRequested(false); michael@0: vol->SetMountRequested(false); michael@0: vol->SetSharingEnabled(aAllowSharing); michael@0: DBG("Calling UpdateState due to volume %s shareing set to %d", michael@0: vol->NameStr(), (int)aAllowSharing); michael@0: UpdateState(); michael@0: } michael@0: michael@0: void FormatVolume(const nsACString& aVolumeName) michael@0: { michael@0: RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); michael@0: if (!vol) { michael@0: return; michael@0: } michael@0: if (vol->IsFormatRequested()) { michael@0: return; michael@0: } michael@0: vol->SetUnmountRequested(false); michael@0: vol->SetMountRequested(false); michael@0: vol->SetFormatRequested(true); michael@0: DBG("Calling UpdateState due to volume %s formatting set to %d", michael@0: vol->NameStr(), (int)vol->IsFormatRequested()); michael@0: UpdateState(); michael@0: } michael@0: michael@0: void MountVolume(const nsACString& aVolumeName) michael@0: { michael@0: RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); michael@0: if (!vol) { michael@0: return; michael@0: } michael@0: vol->SetUnmountRequested(false); michael@0: if (vol->IsMountRequested() || vol->mState == nsIVolume::STATE_MOUNTED) { michael@0: return; michael@0: } michael@0: vol->SetMountRequested(true); michael@0: DBG("Calling UpdateState due to volume %s mounting set to %d", michael@0: vol->NameStr(), (int)vol->IsMountRequested()); michael@0: UpdateState(); michael@0: } michael@0: michael@0: void UnmountVolume(const nsACString& aVolumeName) michael@0: { michael@0: RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); michael@0: if (!vol) { michael@0: return; michael@0: } michael@0: if (vol->IsUnmountRequested()) { michael@0: return; michael@0: } michael@0: vol->SetMountRequested(false); michael@0: vol->SetUnmountRequested(true); michael@0: DBG("Calling UpdateState due to volume %s unmounting set to %d", michael@0: vol->NameStr(), (int)vol->IsUnmountRequested()); michael@0: UpdateState(); michael@0: } michael@0: michael@0: private: michael@0: michael@0: AutoVolumeEventObserver mVolumeEventObserver; michael@0: AutoVolumeManagerStateObserver mVolumeManagerStateObserver; michael@0: RefPtr mResponseCallback; michael@0: int32_t mMode; michael@0: }; michael@0: michael@0: static StaticRefPtr sAutoMounter; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: void michael@0: AutoVolumeManagerStateObserver::Notify(const VolumeManager::StateChangedEvent &) michael@0: { michael@0: LOG("VolumeManager state changed event: %s", VolumeManager::StateStr()); michael@0: michael@0: if (!sAutoMounter) { michael@0: return; michael@0: } michael@0: DBG("Calling UpdateState due to VolumeManagerStateObserver"); michael@0: sAutoMounter->UpdateState(); michael@0: } michael@0: michael@0: void michael@0: AutoVolumeEventObserver::Notify(Volume * const &) michael@0: { michael@0: if (!sAutoMounter) { michael@0: return; michael@0: } michael@0: DBG("Calling UpdateState due to VolumeEventStateObserver"); michael@0: sAutoMounter->UpdateState(); michael@0: } michael@0: michael@0: void michael@0: AutoMounterResponseCallback::ResponseReceived(const VolumeCommand* aCommand) michael@0: { michael@0: michael@0: if (WasSuccessful()) { michael@0: DBG("Calling UpdateState due to Volume::OnSuccess"); michael@0: mErrorCount = 0; michael@0: sAutoMounter->UpdateState(); michael@0: return; michael@0: } michael@0: ERR("Command '%s' failed: %d '%s'", michael@0: aCommand->CmdStr(), ResponseCode(), ResponseStr().get()); michael@0: michael@0: if (++mErrorCount < kMaxErrorCount) { michael@0: DBG("Calling UpdateState due to VolumeResponseCallback::OnError"); michael@0: sAutoMounter->UpdateState(); michael@0: } michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: void michael@0: AutoMounter::UpdateState() michael@0: { michael@0: static bool inUpdateState = false; michael@0: if (inUpdateState) { michael@0: // When UpdateState calls SetISharing, this causes a volume state michael@0: // change to occur, which would normally cause UpdateState to be called michael@0: // again. We want the volume state change to go out (so that device michael@0: // storage will see the change in sharing state), but since we're michael@0: // already in UpdateState we just want to prevent recursion from screwing michael@0: // things up. michael@0: return; michael@0: } michael@0: AutoRestore inUpdateStateDetector(inUpdateState); michael@0: inUpdateState = true; michael@0: michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: // If the following preconditions are met: michael@0: // - UMS is available (i.e. compiled into the kernel) michael@0: // - UMS is enabled michael@0: // - AutoMounter is enabled michael@0: // - USB cable is plugged in michael@0: // then we will try to unmount and share michael@0: // otherwise we will try to unshare and mount. michael@0: michael@0: if (VolumeManager::State() != VolumeManager::VOLUMES_READY) { michael@0: // The volume manager isn't in a ready state, so there michael@0: // isn't anything else that we can do. michael@0: LOG("UpdateState: VolumeManager not ready yet"); michael@0: return; michael@0: } michael@0: michael@0: if (mResponseCallback->IsPending()) { michael@0: // We only deal with one outstanding volume command at a time, michael@0: // so we need to wait for it to finish. michael@0: return; michael@0: } michael@0: michael@0: bool umsAvail = false; michael@0: bool umsEnabled = false; michael@0: michael@0: if (access(ICS_SYS_USB_FUNCTIONS, F_OK) == 0) { michael@0: umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0); michael@0: if (umsAvail) { michael@0: char functionsStr[60]; michael@0: if (ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr))) { michael@0: umsEnabled = strstr(functionsStr, "mass_storage") != nullptr; michael@0: } else { michael@0: ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno)); michael@0: umsEnabled = false; michael@0: } michael@0: } else { michael@0: umsEnabled = false; michael@0: } michael@0: } else { michael@0: umsAvail = ReadSysFile(GB_SYS_UMS_ENABLE, &umsEnabled); michael@0: } michael@0: michael@0: bool usbCablePluggedIn = IsUsbCablePluggedIn(); michael@0: bool enabled = (mMode == AUTOMOUNTER_ENABLE); michael@0: michael@0: if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) { michael@0: enabled = usbCablePluggedIn; michael@0: if (!usbCablePluggedIn) { michael@0: mMode = AUTOMOUNTER_DISABLE; michael@0: } michael@0: } michael@0: michael@0: bool tryToShare = (umsAvail && umsEnabled && enabled && usbCablePluggedIn); michael@0: LOG("UpdateState: umsAvail:%d umsEnabled:%d mode:%d usbCablePluggedIn:%d tryToShare:%d", michael@0: umsAvail, umsEnabled, mMode, usbCablePluggedIn, tryToShare); michael@0: michael@0: bool filesOpen = false; michael@0: static unsigned filesOpenDelayCount = 0; michael@0: VolumeArray::index_type volIndex; michael@0: VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); michael@0: for (volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: RefPtr vol = VolumeManager::GetVolume(volIndex); michael@0: Volume::STATE volState = vol->State(); michael@0: michael@0: if (vol->State() == nsIVolume::STATE_MOUNTED) { michael@0: LOG("UpdateState: Volume %s is %s and %s @ %s gen %d locked %d sharing %c", michael@0: vol->NameStr(), vol->StateStr(), michael@0: vol->MediaPresent() ? "inserted" : "missing", michael@0: vol->MountPoint().get(), vol->MountGeneration(), michael@0: (int)vol->IsMountLocked(), michael@0: vol->CanBeShared() ? (vol->IsSharingEnabled() ? 'y' : 'n') : 'x'); michael@0: } else { michael@0: LOG("UpdateState: Volume %s is %s and %s", vol->NameStr(), vol->StateStr(), michael@0: vol->MediaPresent() ? "inserted" : "missing"); michael@0: } michael@0: if (!vol->MediaPresent()) { michael@0: // No media - nothing we can do michael@0: continue; michael@0: } michael@0: michael@0: if ((tryToShare && vol->IsSharingEnabled()) || michael@0: vol->IsFormatRequested() || michael@0: vol->IsUnmountRequested()) { michael@0: switch (volState) { michael@0: // We're going to try to unmount the volume michael@0: case nsIVolume::STATE_MOUNTED: { michael@0: if (vol->IsMountLocked()) { michael@0: // The volume is currently locked, so leave it in the mounted michael@0: // state. michael@0: LOGW("UpdateState: Mounted volume %s is locked, not sharing or formatting", michael@0: vol->NameStr()); michael@0: break; michael@0: } michael@0: michael@0: // Mark the volume as if we've started sharing. This will cause michael@0: // apps which watch device storage notifications to see the volume michael@0: // go into the shared state, and prompt them to close any open files michael@0: // that they might have. michael@0: if (tryToShare && vol->IsSharingEnabled()) { michael@0: vol->SetIsSharing(true); michael@0: } else if (vol->IsFormatRequested()){ michael@0: vol->SetIsFormatting(true); michael@0: } michael@0: michael@0: // Check to see if there are any open files on the volume and michael@0: // don't initiate the unmount while there are open files. michael@0: OpenFileFinder::Info fileInfo; michael@0: OpenFileFinder fileFinder(vol->MountPoint()); michael@0: if (fileFinder.First(&fileInfo)) { michael@0: LOGW("The following files are open under '%s'", michael@0: vol->MountPoint().get()); michael@0: do { michael@0: LOGW(" PID: %d file: '%s' app: '%s' comm: '%s' exe: '%s'\n", michael@0: fileInfo.mPid, michael@0: fileInfo.mFileName.get(), michael@0: fileInfo.mAppName.get(), michael@0: fileInfo.mComm.get(), michael@0: fileInfo.mExe.get()); michael@0: } while (fileFinder.Next(&fileInfo)); michael@0: LOGW("UpdateState: Mounted volume %s has open files, not sharing or formatting", michael@0: vol->NameStr()); michael@0: michael@0: // Check again in a few seconds to see if the files are closed. michael@0: // Since we're trying to share the volume, this implies that we're michael@0: // plugged into the PC via USB and this in turn implies that the michael@0: // battery is charging, so we don't need to be too concerned about michael@0: // wasting battery here. michael@0: // michael@0: // If we just detected that there were files open, then we use michael@0: // a short timer. We will have told the apps that we're trying michael@0: // trying to share, and they'll be closing their files. This makes michael@0: // the sharing more responsive. If after a few seconds, the apps michael@0: // haven't closed their files, then we back off. michael@0: michael@0: int delay = 1000; michael@0: if (filesOpenDelayCount > 10) { michael@0: delay = 5000; michael@0: } michael@0: MessageLoopForIO::current()-> michael@0: PostDelayedTask(FROM_HERE, michael@0: NewRunnableMethod(this, &AutoMounter::UpdateState), michael@0: delay); michael@0: filesOpen = true; michael@0: break; michael@0: } michael@0: michael@0: // Volume is mounted, we need to unmount before michael@0: // we can share. michael@0: LOG("UpdateState: Unmounting %s", vol->NameStr()); michael@0: vol->StartUnmount(mResponseCallback); michael@0: return; // UpdateState will be called again when the Unmount command completes michael@0: } michael@0: case nsIVolume::STATE_IDLE: { michael@0: LOG("UpdateState: Volume %s is nsIVolume::STATE_IDLE", vol->NameStr()); michael@0: if (vol->IsFormatting() && !vol->IsFormatRequested()) { michael@0: vol->SetFormatRequested(false); michael@0: LOG("UpdateState: Mounting %s", vol->NameStr()); michael@0: vol->StartMount(mResponseCallback); michael@0: break; michael@0: } michael@0: if (tryToShare && vol->IsSharingEnabled()) { michael@0: // Volume is unmounted. We can go ahead and share. michael@0: LOG("UpdateState: Sharing %s", vol->NameStr()); michael@0: vol->StartShare(mResponseCallback); michael@0: } else if (vol->IsFormatRequested()){ michael@0: // Volume is unmounted. We can go ahead and format. michael@0: LOG("UpdateState: Formatting %s", vol->NameStr()); michael@0: vol->StartFormat(mResponseCallback); michael@0: } michael@0: return; // UpdateState will be called again when the Share/Format command completes michael@0: } michael@0: default: { michael@0: // Not in a state that we can do anything about. michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: // We're going to try and unshare and remount the volumes michael@0: switch (volState) { michael@0: case nsIVolume::STATE_SHARED: { michael@0: // Volume is shared. We can go ahead and unshare. michael@0: LOG("UpdateState: Unsharing %s", vol->NameStr()); michael@0: vol->StartUnshare(mResponseCallback); michael@0: return; // UpdateState will be called again when the Unshare command completes michael@0: } michael@0: case nsIVolume::STATE_IDLE: { michael@0: if (!vol->IsUnmountRequested()) { michael@0: // Volume is unmounted and mount-requested, try to mount. michael@0: michael@0: LOG("UpdateState: Mounting %s", vol->NameStr()); michael@0: vol->StartMount(mResponseCallback); michael@0: } michael@0: return; // UpdateState will be called again when Mount command completes michael@0: } michael@0: default: { michael@0: // Not in a state that we can do anything about. michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t status = AUTOMOUNTER_STATUS_DISABLED; michael@0: if (filesOpen) { michael@0: filesOpenDelayCount++; michael@0: status = AUTOMOUNTER_STATUS_FILES_OPEN; michael@0: } else if (enabled) { michael@0: filesOpenDelayCount = 0; michael@0: status = AUTOMOUNTER_STATUS_ENABLED; michael@0: } michael@0: SetAutoMounterStatus(status); michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: static void michael@0: InitAutoMounterIOThread() michael@0: { michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: MOZ_ASSERT(!sAutoMounter); michael@0: michael@0: sAutoMounter = new AutoMounter(); michael@0: } michael@0: michael@0: static void michael@0: ShutdownAutoMounterIOThread() michael@0: { michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: sAutoMounter = nullptr; michael@0: ShutdownVolumeManager(); michael@0: } michael@0: michael@0: static void michael@0: SetAutoMounterModeIOThread(const int32_t& aMode) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: MOZ_ASSERT(sAutoMounter); michael@0: michael@0: sAutoMounter->SetMode(aMode); michael@0: } michael@0: michael@0: static void michael@0: SetAutoMounterSharingModeIOThread(const nsCString& aVolumeName, const bool& aAllowSharing) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: MOZ_ASSERT(sAutoMounter); michael@0: michael@0: sAutoMounter->SetSharingMode(aVolumeName, aAllowSharing); michael@0: } michael@0: michael@0: static void michael@0: AutoMounterFormatVolumeIOThread(const nsCString& aVolumeName) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: MOZ_ASSERT(sAutoMounter); michael@0: michael@0: sAutoMounter->FormatVolume(aVolumeName); michael@0: } michael@0: michael@0: static void michael@0: AutoMounterMountVolumeIOThread(const nsCString& aVolumeName) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: MOZ_ASSERT(sAutoMounter); michael@0: michael@0: sAutoMounter->MountVolume(aVolumeName); michael@0: } michael@0: michael@0: static void michael@0: AutoMounterUnmountVolumeIOThread(const nsCString& aVolumeName) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: MOZ_ASSERT(sAutoMounter); michael@0: michael@0: sAutoMounter->UnmountVolume(aVolumeName); michael@0: } michael@0: michael@0: static void michael@0: UsbCableEventIOThread() michael@0: { michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: if (!sAutoMounter) { michael@0: return; michael@0: } michael@0: DBG("Calling UpdateState due to USBCableEvent"); michael@0: sAutoMounter->UpdateState(); michael@0: } michael@0: michael@0: /************************************************************************** michael@0: * michael@0: * Public API michael@0: * michael@0: * Since the AutoMounter runs in IO Thread context, we need to switch michael@0: * to IOThread context before we can do anything. michael@0: * michael@0: **************************************************************************/ michael@0: michael@0: class UsbCableObserver MOZ_FINAL : public SwitchObserver michael@0: { michael@0: ~UsbCableObserver() michael@0: { michael@0: UnregisterSwitchObserver(SWITCH_USB, this); michael@0: } michael@0: michael@0: public: michael@0: NS_INLINE_DECL_REFCOUNTING(UsbCableObserver) michael@0: michael@0: UsbCableObserver() michael@0: { michael@0: RegisterSwitchObserver(SWITCH_USB, this); michael@0: } michael@0: michael@0: virtual void Notify(const SwitchEvent& aEvent) michael@0: { michael@0: DBG("UsbCable switch device: %d state: %s\n", michael@0: aEvent.device(), SwitchStateStr(aEvent)); michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(UsbCableEventIOThread)); michael@0: } michael@0: }; michael@0: michael@0: static StaticRefPtr sUsbCableObserver; michael@0: static StaticRefPtr sAutoMounterSetting; michael@0: michael@0: static void michael@0: InitVolumeConfig() michael@0: { michael@0: // This function uses /system/etc/volume.cfg to add additional volumes michael@0: // to the Volume Manager. michael@0: // michael@0: // This is useful on devices like the Nexus 4, which have no physical sd card michael@0: // or dedicated partition. michael@0: // michael@0: // The format of the volume.cfg file is as follows: michael@0: // create volume-name mount-point michael@0: // Blank lines and lines starting with the hash character "#" will be ignored. michael@0: michael@0: nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE_VOID(vs); michael@0: michael@0: ScopedCloseFile fp; michael@0: int n = 0; michael@0: char line[255]; michael@0: char *command, *vol_name_cstr, *mount_point_cstr, *save_ptr; michael@0: const char *filename = "/system/etc/volume.cfg"; michael@0: if (!(fp = fopen(filename, "r"))) { michael@0: LOG("Unable to open volume configuration file '%s' - ignoring", filename); michael@0: return; michael@0: } michael@0: while(fgets(line, sizeof(line), fp)) { michael@0: const char *delim = " \t\n"; michael@0: n++; michael@0: michael@0: if (line[0] == '#') michael@0: continue; michael@0: if (!(command = strtok_r(line, delim, &save_ptr))) { michael@0: // Blank line - ignore michael@0: continue; michael@0: } michael@0: if (!strcmp(command, "create")) { michael@0: if (!(vol_name_cstr = strtok_r(nullptr, delim, &save_ptr))) { michael@0: ERR("No vol_name in %s line %d", filename, n); michael@0: continue; michael@0: } michael@0: if (!(mount_point_cstr = strtok_r(nullptr, delim, &save_ptr))) { michael@0: ERR("No mount point for volume '%s'. %s line %d", vol_name_cstr, filename, n); michael@0: continue; michael@0: } michael@0: nsString mount_point = NS_ConvertUTF8toUTF16(mount_point_cstr); michael@0: nsString vol_name = NS_ConvertUTF8toUTF16(vol_name_cstr); michael@0: nsresult rv; michael@0: rv = vs->CreateFakeVolume(vol_name, mount_point); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: rv = vs->SetFakeVolumeState(vol_name, nsIVolume::STATE_MOUNTED); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: else { michael@0: ERR("Unrecognized command: '%s'", command); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: InitAutoMounter() michael@0: { michael@0: InitVolumeConfig(); michael@0: InitVolumeManager(); michael@0: sAutoMounterSetting = new AutoMounterSetting(); michael@0: michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(InitAutoMounterIOThread)); michael@0: michael@0: // Switch Observers need to run on the main thread, so we need to michael@0: // start it here and have it send events to the AutoMounter running michael@0: // on the IO Thread. michael@0: sUsbCableObserver = new UsbCableObserver(); michael@0: } michael@0: michael@0: int32_t michael@0: GetAutoMounterStatus() michael@0: { michael@0: if (sAutoMounterSetting) { michael@0: return sAutoMounterSetting->GetStatus(); michael@0: } michael@0: return AUTOMOUNTER_STATUS_DISABLED; michael@0: } michael@0: michael@0: //static michael@0: void michael@0: SetAutoMounterStatus(int32_t aStatus) michael@0: { michael@0: if (sAutoMounterSetting) { michael@0: sAutoMounterSetting->SetStatus(aStatus); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SetAutoMounterMode(int32_t aMode) michael@0: { michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(SetAutoMounterModeIOThread, aMode)); michael@0: } michael@0: michael@0: void michael@0: SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing) michael@0: { michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(SetAutoMounterSharingModeIOThread, michael@0: aVolumeName, aAllowSharing)); michael@0: } michael@0: michael@0: void michael@0: AutoMounterFormatVolume(const nsCString& aVolumeName) michael@0: { michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(AutoMounterFormatVolumeIOThread, michael@0: aVolumeName)); michael@0: } michael@0: michael@0: void michael@0: AutoMounterMountVolume(const nsCString& aVolumeName) michael@0: { michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(AutoMounterMountVolumeIOThread, michael@0: aVolumeName)); michael@0: } michael@0: michael@0: void michael@0: AutoMounterUnmountVolume(const nsCString& aVolumeName) michael@0: { michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(AutoMounterUnmountVolumeIOThread, michael@0: aVolumeName)); michael@0: } michael@0: michael@0: void michael@0: ShutdownAutoMounter() michael@0: { michael@0: sAutoMounterSetting = nullptr; michael@0: sUsbCableObserver = nullptr; michael@0: michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(ShutdownAutoMounterIOThread)); michael@0: } michael@0: michael@0: } // system michael@0: } // mozilla