diff options
author | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-07 07:34:51 +0000 |
---|---|---|
committer | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-07 07:34:51 +0000 |
commit | e974ad29d8f31b1f86bb551dc99ad4e5e62b002c (patch) | |
tree | 34e9228a7f99ef48303b07d6e95bd86db9994347 | |
parent | 20ffd496a4258d22656be676286fc0dda228194d (diff) | |
download | chromium_src-e974ad29d8f31b1f86bb551dc99ad4e5e62b002c.zip chromium_src-e974ad29d8f31b1f86bb551dc99ad4e5e62b002c.tar.gz chromium_src-e974ad29d8f31b1f86bb551dc99ad4e5e62b002c.tar.bz2 |
Implement Linux media notifier.
BUG=110400
TEST=included.
Review URL: http://codereview.chromium.org/9560008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@125361 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | content/browser/browser_main_loop.cc | 14 | ||||
-rw-r--r-- | content/browser/browser_main_loop.h | 4 | ||||
-rw-r--r-- | content/browser/media_device_notifications_linux.cc | 233 | ||||
-rw-r--r-- | content/browser/media_device_notifications_linux.h | 113 | ||||
-rw-r--r-- | content/browser/media_device_notifications_linux_unittest.cc | 335 | ||||
-rw-r--r-- | content/content_browser.gypi | 2 | ||||
-rw-r--r-- | content/content_tests.gypi | 1 |
7 files changed, 701 insertions, 1 deletions
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index ed0b6320..bb3bdff 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -54,6 +54,10 @@ #include "net/base/winsock_init.h" #endif +#if defined(OS_LINUX) +#include "content/browser/media_device_notifications_linux.h" +#endif + #if defined(OS_LINUX) || defined(OS_OPENBSD) #include <glib-object.h> #endif @@ -204,7 +208,7 @@ AudioManager* BrowserMainLoop::GetAudioManager() { return g_current_browser_main_loop->audio_manager_.get(); } -// BrowserMainLoop construction / destructione ============================= +// BrowserMainLoop construction / destruction ============================= BrowserMainLoop::BrowserMainLoop(const content::MainFunctionParams& parameters) : parameters_(parameters), @@ -587,6 +591,14 @@ void BrowserMainLoop::InitializeMainThread() { void BrowserMainLoop::BrowserThreadsStarted() { // RDH needs the IO thread to be created. resource_dispatcher_host_.reset(new ResourceDispatcherHost()); + +#if defined(OS_LINUX) + // MediaDeviceNotificationsLinux needs the File Thread. + const FilePath kDefaultMtabPath("/etc/mtab"); + media_device_notifications_linux_ = + new MediaDeviceNotificationsLinux(kDefaultMtabPath); + media_device_notifications_linux_->Init(); +#endif } void BrowserMainLoop::InitializeToolkit() { diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h index d6ba7c8..18a3037 100644 --- a/content/browser/browser_main_loop.h +++ b/content/browser/browser_main_loop.h @@ -31,6 +31,7 @@ class BrowserMainParts; class BrowserShutdownImpl; class BrowserThreadImpl; struct MainFunctionParams; +class MediaDeviceNotificationsLinux; class WebKitThread; // Implements the main browser loop stages called from |BrowserMain()|. @@ -86,6 +87,9 @@ class BrowserMainLoop { scoped_ptr<AudioManager> audio_manager_; #if defined(OS_WIN) scoped_ptr<SystemMessageWindowWin> system_message_window_; +#elif defined(OS_LINUX) + scoped_refptr<MediaDeviceNotificationsLinux> + media_device_notifications_linux_; #endif // Destroy parts_ before main_message_loop_ (required) and before other diff --git a/content/browser/media_device_notifications_linux.cc b/content/browser/media_device_notifications_linux.cc new file mode 100644 index 0000000..3be3ced --- /dev/null +++ b/content/browser/media_device_notifications_linux.cc @@ -0,0 +1,233 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/media_device_notifications_linux.h" + +#include <mntent.h> +#include <stdio.h> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/string_util.h" +#include "base/system_monitor/system_monitor.h" +#include "content/public/browser/browser_thread.h" + +using base::SystemMonitor; + +namespace { + +const char* const kDCIMDirName = "DCIM"; + +// List of file systems we care about. +const char* const kKnownFileSystems[] = { + "ext2", + "ext3", + "ext4", + "fat", + "hfsplus", + "iso9660", + "msdos", + "ntfs", + "udf", + "vfat", +}; + +} // namespace + +namespace content { + +MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux( + const FilePath& path) + : initialized_(false), + mtab_path_(path), + current_device_id_(0U) { + CHECK(!path.empty()); + + // Put |kKnownFileSystems| in std::set to get O(log N) access time. + for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i) + known_file_systems_.insert(kKnownFileSystems[i]); +} + +MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() { +} + +void MediaDeviceNotificationsLinux::Init() { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&MediaDeviceNotificationsLinux::InitOnFileThread, this)); +} + +void MediaDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path) { + if (path != mtab_path_) { + NOTREACHED(); + return; + } + + UpdateMtab(); +} + +void MediaDeviceNotificationsLinux::InitOnFileThread() { + DCHECK(!initialized_); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + initialized_ = true; + + watcher_delegate_ = new WatcherDelegate(this); + if (!file_watcher_.Watch(mtab_path_, watcher_delegate_)) { + LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed"; + return; + } + + UpdateMtab(); +} + +void MediaDeviceNotificationsLinux::UpdateMtab() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + MountMap new_mtab; + ReadMtab(&new_mtab); + + // Check existing mtab entries for unaccounted mount points. + // These mount points must have been removed in the new mtab. + MountMap::iterator it = mtab_.begin(); + while (it != mtab_.end()) { + const MountPoint& mount_point(it->first); + // |mount_point| not in |new_mtab|. + if (new_mtab.find(mount_point) == new_mtab.end()) { + const SystemMonitor::DeviceIdType& device_id(it->second.second); + RemoveOldDevice(device_id); + mtab_.erase(it++); + continue; + } + // Existing mount point. Ignore and deal in the next loop. + ++it; + } + + // Check new mtab entries against existing ones. + for (MountMap::iterator newiter = new_mtab.begin(); + newiter != new_mtab.end(); + ++newiter) { + const MountPoint& mount_point(newiter->first); + const MountDeviceAndId& mount_device_and_id(newiter->second); + const MountDevice& mount_device(newiter->second.first); + SystemMonitor::DeviceIdType& id(newiter->second.second); + MountMap::iterator olditer = mtab_.find(mount_point); + // Check to see if it is a new mount point. + if (olditer == mtab_.end()) { + if (IsMediaDevice(mount_point)) { + AddNewDevice(mount_device, mount_point, &id); + mtab_[mount_point] = mount_device_and_id; + } + continue; + } + + // Existing mount point. Check to see if a new device is mounted there. + MountDeviceAndId& old_mount_device_and_id(olditer->second); + if (mount_device == old_mount_device_and_id.first) + continue; + + // New device mounted. + RemoveOldDevice(old_mount_device_and_id.second); + if (IsMediaDevice(mount_point)) { + AddNewDevice(mount_device, mount_point, &id); + olditer->second = mount_device_and_id; + } + } +} + +void MediaDeviceNotificationsLinux::ReadMtab(MountMap* mtab) { + FILE* fp = setmntent(mtab_path_.value().c_str(), "r"); + if (!fp) + return; + + MountMap& new_mtab = *mtab; + struct mntent entry; + char buf[512]; + SystemMonitor::DeviceIdType mount_position = 0; + typedef std::pair<MountPoint, SystemMonitor::DeviceIdType> MountPointAndId; + typedef std::map<MountDevice, MountPointAndId> DeviceMap; + DeviceMap device_map; + while (getmntent_r(fp, &entry, buf, sizeof(buf))) { + // We only care about real file systems. + if (known_file_systems_.find(entry.mnt_type) == known_file_systems_.end()) + continue; + // Add entries, but overwrite entries for the same mount device. Keep track + // of the entry positions in the device id field and use that below to + // resolve multiple devices mounted at the same mount point. + device_map[entry.mnt_fsname] = + std::make_pair(entry.mnt_dir, mount_position++); + } + endmntent(fp); + + for (DeviceMap::iterator device_it = device_map.begin(); + device_it != device_map.end(); + ++device_it) { + const MountDevice& device = device_it->first; + const MountPoint& mount_point = device_it->second.first; + const SystemMonitor::DeviceIdType& position = device_it->second.second; + + // No device at |mount_point|, save |device| to it. + MountMap::iterator mount_it = new_mtab.find(mount_point); + if (mount_it == new_mtab.end()) { + new_mtab[mount_point] = std::make_pair(device, position); + continue; + } + + // There is already a device mounted at |mount_point|. Check to see if + // the existing mount entry is newer than the current one. + MountDevice& existing_device = mount_it->second.first; + SystemMonitor::DeviceIdType& existing_position = mount_it->second.second; + if (existing_position > position) + continue; + + // The current entry is newer, update the mount point entry. + existing_device = device; + existing_position = position; + } +} + +bool MediaDeviceNotificationsLinux::IsMediaDevice( + const MountPoint& mount_point) { + FilePath dcim_path(mount_point); + FilePath::StringType dcim_dir(kDCIMDirName); + if (!file_util::DirectoryExists(dcim_path.Append(dcim_dir))) { + // Check for lowercase 'dcim' as well. + FilePath dcim_path_lower(dcim_path.Append(StringToLowerASCII(dcim_dir))); + if (!file_util::DirectoryExists(dcim_path_lower)) { + return false; + } + } + return true; +} + +void MediaDeviceNotificationsLinux::AddNewDevice( + const MountDevice& mount_device, + const MountPoint& mount_point, + base::SystemMonitor::DeviceIdType* device_id) { + *device_id = current_device_id_++; + base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); + system_monitor->ProcessMediaDeviceAttached(*device_id, + mount_device, + FilePath(mount_point)); +} + +void MediaDeviceNotificationsLinux::RemoveOldDevice( + const base::SystemMonitor::DeviceIdType& device_id) { + base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); + system_monitor->ProcessMediaDeviceDetached(device_id); +} + +MediaDeviceNotificationsLinux::WatcherDelegate::WatcherDelegate( + MediaDeviceNotificationsLinux* notifier) + : notifier_(notifier) { +} + +MediaDeviceNotificationsLinux::WatcherDelegate::~WatcherDelegate() { +} + +void MediaDeviceNotificationsLinux::WatcherDelegate::OnFilePathChanged( + const FilePath& path) { + notifier_->OnFilePathChanged(path); +} + +} // namespace content diff --git a/content/browser/media_device_notifications_linux.h b/content/browser/media_device_notifications_linux.h new file mode 100644 index 0000000..cd41245 --- /dev/null +++ b/content/browser/media_device_notifications_linux.h @@ -0,0 +1,113 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_ +#define CONTENT_BROWSER_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_ +#pragma once + +#include <map> +#include <set> +#include <string> +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/files/file_path_watcher.h" +#include "base/memory/ref_counted.h" +#include "base/system_monitor/system_monitor.h" +#include "content/common/content_export.h" + +namespace content { + +class CONTENT_EXPORT MediaDeviceNotificationsLinux + : public base::RefCountedThreadSafe<MediaDeviceNotificationsLinux> { + public: + explicit MediaDeviceNotificationsLinux(const FilePath& path); + + // Must be called for MediaDeviceNotificationsLinux to work. + void Init(); + + void OnFilePathChanged(const FilePath& path); + + private: + friend class base::RefCountedThreadSafe<MediaDeviceNotificationsLinux>; + + typedef std::string MountDevice; + typedef std::string MountPoint; + typedef std::pair<MountDevice, + base::SystemMonitor::DeviceIdType> MountDeviceAndId; + typedef std::map<MountPoint, MountDeviceAndId> MountMap; + + // A simple pass-through class. MediaDeviceNotificationsLinux cannot directly + // inherit from FilePathWatcher::Delegate due to multiple inheritance. + class WatcherDelegate : public base::files::FilePathWatcher::Delegate { + public: + explicit WatcherDelegate(MediaDeviceNotificationsLinux* notifier); + + // base::files::FilePathWatcher::Delegate implementation. + virtual void OnFilePathChanged(const FilePath& path) OVERRIDE; + + private: + friend class base::RefCountedThreadSafe<WatcherDelegate>; + + virtual ~WatcherDelegate(); + MediaDeviceNotificationsLinux* notifier_; + + DISALLOW_COPY_AND_ASSIGN(WatcherDelegate); + }; + + virtual ~MediaDeviceNotificationsLinux(); + + void InitOnFileThread(); + + // Parse the mtab file and find all changes. + void UpdateMtab(); + + // Read the mtab file entries into |mtab|. + void ReadMtab(MountMap* mtab); + + // For a new device mounted at |mount_point|, see if it is a media device by + // checking for the existence of a DCIM directory. + // If it is a media device, return true, otherwise return false. + // Mac OS X behaves similarly, but this is not the only heuristic it uses. + // TODO(vandebo) Try to figure out how Mac OS X decides this. + bool IsMediaDevice(const MountPoint& mount_point); + + // Add a media device with a given device and mount device. Assign it a device + // id as well. + void AddNewDevice(const MountDevice& mount_device, + const MountPoint& mount_point, + base::SystemMonitor::DeviceIdType* device_id); + + // Remove a media device with a given device id. + void RemoveOldDevice(const base::SystemMonitor::DeviceIdType& device_id); + + // Whether Init() has been called or not. + bool initialized_; + + // Mtab file that lists the mount points. + const FilePath mtab_path_; + // Watcher for |mtab_path_|. + base::files::FilePathWatcher file_watcher_; + // Delegate to receive watcher notifications. + scoped_refptr<WatcherDelegate> watcher_delegate_; + + // Mapping of relevent mount points and their corresponding mount devices. + // Keep in mind on Linux, a device can be mounted at multiple mount points, + // and multiple devices can be mounted at a mount point. + MountMap mtab_; + + // The lowest available device id number. + base::SystemMonitor::DeviceIdType current_device_id_; + + // Set of known file systems that we care about. + std::set<std::string> known_file_systems_; + + DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinux); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_ diff --git a/content/browser/media_device_notifications_linux_unittest.cc b/content/browser/media_device_notifications_linux_unittest.cc new file mode 100644 index 0000000..cd4d049 --- /dev/null +++ b/content/browser/media_device_notifications_linux_unittest.cc @@ -0,0 +1,335 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fcntl.h> +#include <mntent.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <string> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/scoped_temp_dir.h" +#include "base/system_monitor/system_monitor.h" +#include "base/test/mock_devices_changed_observer.h" +#include "content/browser/browser_thread_impl.h" +#include "content/browser/media_device_notifications_linux.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; + +namespace { + +const char* kValidFS = "vfat"; +const char* kInvalidFS = "invalidfs"; + +const char* kInvalidPath = "invalid path does not exist"; + +const char* kDevice1 = "d1"; +const char* kDevice2 = "d2"; +const char* kDevice3 = "d3"; + +const char* kMountPointA = "mnt_a"; +const char* kMountPointB = "mnt_b"; + +} // namespace + +namespace content { + +class MediaDeviceNotificationsLinuxTest : public testing::Test { + public: + struct MtabTestData { + MtabTestData(const char* mount_device, + const char* mount_point, + const char* mount_type) + : mount_device(mount_device), + mount_point(mount_point), + mount_type(mount_type) { + } + + const char* mount_device; + const char* mount_point; + const char* mount_type; + }; + + MediaDeviceNotificationsLinuxTest() + : message_loop_(MessageLoop::TYPE_IO), + file_thread_(BrowserThread::FILE, &message_loop_) { + system_monitor_.reset(new base::SystemMonitor()); + } + virtual ~MediaDeviceNotificationsLinuxTest() {} + + protected: + virtual void SetUp() { + mock_devices_changed_observer_.reset(new base::MockDevicesChangedObserver); + system_monitor_->AddDevicesChangedObserver( + mock_devices_changed_observer_.get()); + + // Create and set up a temp dir with files for the test. + ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); + FilePath test_dir = scoped_temp_dir_.path().AppendASCII("test_etc"); + ASSERT_TRUE(file_util::CreateDirectory(test_dir)); + mtab_file_ = test_dir.AppendASCII("test_mtab"); + struct MtabTestData initial_test_data[] = { + MtabTestData("dummydevice", "dummydir", kInvalidFS), + }; + WriteToMtab(initial_test_data, arraysize(initial_test_data), true); + + // Initialize the test subject. + notifications_ = new MediaDeviceNotificationsLinux(mtab_file_); + notifications_->Init(); + message_loop_.RunAllPending(); + } + + virtual void TearDown() { + message_loop_.RunAllPending(); + notifications_ = NULL; + system_monitor_->RemoveDevicesChangedObserver( + mock_devices_changed_observer_.get()); + } + + // Used to run tests. When the mtab file gets modified, the message loop + // needs to run in order to react to the file modification. + // See WriteToMtab for parameters. + void WriteToMtabAndRunLoop(struct MtabTestData* data, + size_t data_size, + bool overwrite) { + WriteToMtab(data, data_size, overwrite); + message_loop_.RunAllPending(); + } + + // Create a directory named |dir| relative to the test directory. + // Set |with_dcim_dir| to true if the created directory will have a "DCIM" + // subdirectory. + // Returns the full path to the created directory on success, or an empty + // path on failure. + FilePath CreateMountPoint(const char* dir, bool with_dcim_dir) { + FilePath return_path(scoped_temp_dir_.path()); + return_path = return_path.AppendASCII(dir); + FilePath path(return_path); + if (with_dcim_dir) + path = path.AppendASCII("DCIM"); + if (!file_util::CreateDirectory(path)) + return FilePath(); + return return_path; + } + + base::MockDevicesChangedObserver& observer() { + return *mock_devices_changed_observer_.get(); + } + + private: + // Write the test mtab data to |mtab_file_|. + // |data| is an array of mtab entries. + // |data_size| is the array size of |data|. + // |overwrite| specifies whether to overwrite |mtab_file_|. + void WriteToMtab(struct MtabTestData* data, + size_t data_size, + bool overwrite) { + FILE* file = setmntent(mtab_file_.value().c_str(), overwrite ? "w" : "a"); + ASSERT_TRUE(file); + + struct mntent entry; + entry.mnt_opts = strdup("rw"); + entry.mnt_freq = 0; + entry.mnt_passno = 0; + for (size_t i = 0; i < data_size; ++i) { + entry.mnt_fsname = strdup(data[i].mount_device); + entry.mnt_dir = strdup(data[i].mount_point); + entry.mnt_type = strdup(data[i].mount_type); + int add_result = addmntent(file, &entry); + ASSERT_EQ(0, add_result); + free(entry.mnt_fsname); + free(entry.mnt_dir); + free(entry.mnt_type); + } + free(entry.mnt_opts); + int end_result = endmntent(file); + ASSERT_EQ(1, end_result); + + // Need to ensure data reaches disk so the FilePathWatcher fires in time. + // Otherwise this will cause MediaDeviceNotificationsLinuxTest to be flaky. + int fd = open(mtab_file_.value().c_str(), O_RDONLY); + ASSERT_GE(fd, 0); + + int fsync_result = fsync(fd); + ASSERT_EQ(0, fsync_result); + + int close_result = close(fd); + ASSERT_EQ(0, close_result); + } + + // The message loop and file thread to run tests on. + MessageLoop message_loop_; + BrowserThreadImpl file_thread_; + + // SystemMonitor and DevicesChangedObserver to hook together to test. + scoped_ptr<base::SystemMonitor> system_monitor_; + scoped_ptr<base::MockDevicesChangedObserver> mock_devices_changed_observer_; + + // Temporary directory for created test data. + ScopedTempDir scoped_temp_dir_; + // Path to the test mtab file. + FilePath mtab_file_; + + scoped_refptr<MediaDeviceNotificationsLinux> notifications_; + + DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinuxTest); +}; + +TEST_F(MediaDeviceNotificationsLinuxTest, BasicAttachDetach) { + testing::Sequence mock_sequence; + FilePath test_path = CreateMountPoint(kMountPointA, true); + ASSERT_FALSE(test_path.empty()); + struct MtabTestData test_data[] = { + MtabTestData(kDevice1, kInvalidPath, kValidFS), + MtabTestData(kDevice2, test_path.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceAttached(0, kDevice2, test_path)) + .InSequence(mock_sequence); + WriteToMtabAndRunLoop(test_data, arraysize(test_data), false); + + EXPECT_CALL(observer(), OnMediaDeviceDetached(0)).InSequence(mock_sequence); + WriteToMtabAndRunLoop(NULL, 0, true); +} + +// Only mount points with DCIM directories are recognized. +TEST_F(MediaDeviceNotificationsLinuxTest, DCIM) { + testing::Sequence mock_sequence; + FilePath test_pathA = CreateMountPoint(kMountPointA, true); + ASSERT_FALSE(test_pathA.empty()); + struct MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_pathA.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceAttached(0, kDevice1, test_pathA)) + .InSequence(mock_sequence); + WriteToMtabAndRunLoop(test_data1, arraysize(test_data1), false); + + FilePath test_pathB = CreateMountPoint(kMountPointB, false); + ASSERT_FALSE(test_pathB.empty()); + struct MtabTestData test_data2[] = { + MtabTestData(kDevice2, test_pathB.value().c_str(), kValidFS), + }; + WriteToMtabAndRunLoop(test_data2, arraysize(test_data2), false); + + EXPECT_CALL(observer(), OnMediaDeviceDetached(0)).InSequence(mock_sequence); + WriteToMtabAndRunLoop(NULL, 0, true); +} + +TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesMultiMountPoints) { + FilePath test_pathA = CreateMountPoint(kMountPointA, true); + FilePath test_pathB = CreateMountPoint(kMountPointB, true); + ASSERT_FALSE(test_pathA.empty()); + ASSERT_FALSE(test_pathB.empty()); + + // Attach two devices. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + struct MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_pathA.value().c_str(), kValidFS), + MtabTestData(kDevice2, test_pathB.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); + EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); + WriteToMtabAndRunLoop(test_data1, arraysize(test_data1), false); + + // Attach |kDevice1| to |kMountPointB|. + // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been + // re-attached at |kMountPointB|, so it is 'detached' from kMountPointA. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB + struct MtabTestData test_data2[] = { + MtabTestData(kDevice1, test_pathB.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); + WriteToMtabAndRunLoop(test_data2, arraysize(test_data2), false); + + // Attach |kDevice2| to |kMountPointA|. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB + // kDevice2 -> kMountPointA + struct MtabTestData test_data3[] = { + MtabTestData(kDevice2, test_pathA.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); + WriteToMtabAndRunLoop(test_data3, arraysize(test_data3), false); + + // Detach |kDevice2| from |kMountPointA|. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB + struct MtabTestData test_data4[] = { + MtabTestData(kDevice1, test_pathA.value().c_str(), kValidFS), + MtabTestData(kDevice2, test_pathB.value().c_str(), kValidFS), + MtabTestData(kDevice1, test_pathB.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1); + WriteToMtabAndRunLoop(test_data4, arraysize(test_data4), true); + + // Detach |kDevice1| from |kMountPointB|. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); + EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1); + WriteToMtabAndRunLoop(test_data1, arraysize(test_data1), true); + + // Detach all devices. + EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); + WriteToMtabAndRunLoop(NULL, 0, true); +} + +TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesOneMountPoint) { + testing::Sequence mock_sequence; + FilePath test_pathA = CreateMountPoint(kMountPointA, true); + FilePath test_pathB = CreateMountPoint(kMountPointB, true); + ASSERT_FALSE(test_pathA.empty()); + ASSERT_FALSE(test_pathB.empty()); + + // |kDevice1| is most recently mounted at |kMountPointB|. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB + struct MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_pathA.value().c_str(), kValidFS), + MtabTestData(kDevice2, test_pathB.value().c_str(), kValidFS), + MtabTestData(kDevice1, test_pathB.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceAttached(0, kDevice1, test_pathB)) + .Times(1); + EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); + WriteToMtabAndRunLoop(test_data1, arraysize(test_data1), true); + + // Attach |kDevice3| to |kMountPointB|. + // |kDevice1| is inaccessible at its most recent mount point, so it is + // detached and unavailable, even though it is still accessible via + // |kMountPointA|. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB + // kDevice3 -> kMountPointB + struct MtabTestData test_data2[] = { + MtabTestData(kDevice3, test_pathB.value().c_str(), kValidFS), + }; + EXPECT_CALL(observer(), OnMediaDeviceDetached(0)).Times(1); + EXPECT_CALL(observer(), OnMediaDeviceAttached(1, kDevice3, test_pathB)) + .Times(1); + WriteToMtabAndRunLoop(test_data2, arraysize(test_data2), false); + + // Detach all devices. + EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnMediaDeviceDetached(1)).Times(1); + WriteToMtabAndRunLoop(NULL, 0, true); +} + +} // namespace content diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 86cfa5e..a6a81d8f 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -381,6 +381,8 @@ 'browser/load_notification_details.h', 'browser/mach_broker_mac.cc', 'browser/mach_broker_mac.h', + 'browser/media_device_notifications_linux.cc', + 'browser/media_device_notifications_linux.h', 'browser/mime_registry_message_filter.cc', 'browser/mime_registry_message_filter.h', 'browser/net/browser_online_state_observer.cc', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 2df50a9..6ad2d7b 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -224,6 +224,7 @@ 'browser/in_process_webkit/indexed_db_quota_client_unittest.cc', 'browser/in_process_webkit/webkit_thread_unittest.cc', 'browser/mach_broker_mac_unittest.cc', + 'browser/media_device_notifications_linux_unittest.cc', 'browser/notification_service_impl_unittest.cc', 'browser/plugin_loader_posix_unittest.cc', 'browser/renderer_host/accelerated_plugin_view_mac_unittest.mm', |