summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorthestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-07 07:34:51 +0000
committerthestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-07 07:34:51 +0000
commite974ad29d8f31b1f86bb551dc99ad4e5e62b002c (patch)
tree34e9228a7f99ef48303b07d6e95bd86db9994347
parent20ffd496a4258d22656be676286fc0dda228194d (diff)
downloadchromium_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.cc14
-rw-r--r--content/browser/browser_main_loop.h4
-rw-r--r--content/browser/media_device_notifications_linux.cc233
-rw-r--r--content/browser/media_device_notifications_linux.h113
-rw-r--r--content/browser/media_device_notifications_linux_unittest.cc335
-rw-r--r--content/content_browser.gypi2
-rw-r--r--content/content_tests.gypi1
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',