// 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.

// MediaFileSystemRegistry unit tests.

#include <algorithm>
#include <set>

#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/media_galleries/media_file_system_context.h"
#include "chrome/browser/media_galleries/media_file_system_registry.h"
#include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
#include "chrome/browser/media_galleries/media_galleries_test_util.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/storage_monitor/removable_device_constants.h"
#include "components/storage_monitor/storage_info.h"
#include "components/storage_monitor/storage_monitor.h"
#include "components/storage_monitor/test_storage_monitor.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_factory.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_thread.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "sync/api/string_ordinal.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/users/user_manager.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/settings/device_settings_service.h"
#endif

using content::BrowserThread;
using storage_monitor::StorageInfo;
using storage_monitor::StorageMonitor;
using storage_monitor::TestStorageMonitor;

// Not anonymous so it can be friends with MediaFileSystemRegistry.
class TestMediaFileSystemContext : public MediaFileSystemContext {
 public:
  struct FSInfo {
    FSInfo() {}
    FSInfo(const std::string& device_id, const base::FilePath& path,
           const std::string& fs_name);

    bool operator<(const FSInfo& other) const;

    std::string device_id;
    base::FilePath path;
    std::string fs_name;
  };

  explicit TestMediaFileSystemContext(MediaFileSystemRegistry* registry);
  virtual ~TestMediaFileSystemContext() {}

  // MediaFileSystemContext implementation.
  virtual bool RegisterFileSystem(const std::string& device_id,
                                  const std::string& fs_name,
                                  const base::FilePath& path) OVERRIDE;

  virtual void RevokeFileSystem(const std::string& fs_name) OVERRIDE;

  virtual base::FilePath GetRegisteredPath(
      const std::string& fs_name) const OVERRIDE;

  MediaFileSystemRegistry* registry() { return registry_; }

 private:
  void AddFSEntry(const std::string& device_id,
                  const base::FilePath& path,
                  const std::string& fs_name);

  MediaFileSystemRegistry* registry_;

  // The currently allocated mock file systems.
  std::map<std::string /*fs_name*/, FSInfo> file_systems_by_name_;
};

TestMediaFileSystemContext::FSInfo::FSInfo(const std::string& device_id,
                                           const base::FilePath& path,
                                           const std::string& fs_name)
    : device_id(device_id),
      path(path),
      fs_name(fs_name) {
}

bool TestMediaFileSystemContext::FSInfo::operator<(const FSInfo& other) const {
  if (device_id != other.device_id)
    return device_id < other.device_id;
  if (path.value() != other.path.value())
    return path.value() < other.path.value();
  return fs_name < other.fs_name;
}

TestMediaFileSystemContext::TestMediaFileSystemContext(
    MediaFileSystemRegistry* registry)
    : registry_(registry) {
  registry_->file_system_context_.reset(this);
}

bool TestMediaFileSystemContext::RegisterFileSystem(
    const std::string& device_id,
    const std::string& fs_name,
    const base::FilePath& path) {
  AddFSEntry(device_id, path, fs_name);
  return true;
}

void TestMediaFileSystemContext::RevokeFileSystem(const std::string& fs_name) {
  if (!ContainsKey(file_systems_by_name_, fs_name))
    return;
  EXPECT_EQ(1U, file_systems_by_name_.erase(fs_name));
}

base::FilePath TestMediaFileSystemContext::GetRegisteredPath(
    const std::string& fs_name) const {
  std::map<std::string /*fs_name*/, FSInfo>::const_iterator it =
      file_systems_by_name_.find(fs_name);
  if (it == file_systems_by_name_.end())
    return base::FilePath();
  return it->second.path;
}

void TestMediaFileSystemContext::AddFSEntry(const std::string& device_id,
                                            const base::FilePath& path,
                                            const std::string& fs_name) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(path.IsAbsolute());
  DCHECK(!path.ReferencesParent());

  FSInfo info(device_id, path, fs_name);
  file_systems_by_name_[fs_name] = info;
}

namespace {

typedef std::map<MediaGalleryPrefId, MediaFileSystemInfo> FSInfoMap;

void GetGalleryInfoCallback(
    FSInfoMap* results,
    const std::vector<MediaFileSystemInfo>& file_systems) {
  for (size_t i = 0; i < file_systems.size(); ++i) {
    ASSERT_FALSE(ContainsKey(*results, file_systems[i].pref_id));
    (*results)[file_systems[i].pref_id] = file_systems[i];
  }
}

void CheckGalleryInfo(const MediaFileSystemInfo& info,
                      TestMediaFileSystemContext* fs_context,
                      const base::FilePath& path,
                      bool removable,
                      bool media_device) {
  EXPECT_EQ(path, info.path);
  EXPECT_EQ(removable, info.removable);
  EXPECT_EQ(media_device, info.media_device);
  EXPECT_NE(0UL, info.pref_id);

  if (removable)
    EXPECT_NE(0UL, info.transient_device_id.size());
  else
    EXPECT_EQ(0UL, info.transient_device_id.size());

  base::FilePath fsid_path = fs_context->GetRegisteredPath(info.fsid);
  EXPECT_EQ(path, fsid_path);
}

class MockProfileSharedRenderProcessHostFactory
    : public content::RenderProcessHostFactory {
 public:
  MockProfileSharedRenderProcessHostFactory() {}
  virtual ~MockProfileSharedRenderProcessHostFactory();

  // RPH created with this factory are owned by it.  If the RPH is destroyed
  // for testing purposes, it must be removed from the factory first.
  content::MockRenderProcessHost* ReleaseRPH(
      content::BrowserContext* browser_context);

  virtual content::RenderProcessHost* CreateRenderProcessHost(
      content::BrowserContext* browser_context,
      content::SiteInstance* site_instance) const OVERRIDE;

 private:
  typedef std::map<content::BrowserContext*, content::MockRenderProcessHost*>
      ProfileRPHMap;
  mutable ProfileRPHMap rph_map_;

  DISALLOW_COPY_AND_ASSIGN(MockProfileSharedRenderProcessHostFactory);
};

class ProfileState {
 public:
  explicit ProfileState(
      MockProfileSharedRenderProcessHostFactory* rph_factory);
  ~ProfileState();

  MediaGalleriesPreferences* GetMediaGalleriesPrefs();

  void CheckGalleries(
      const std::string& test,
      const std::vector<MediaFileSystemInfo>& regular_extension_galleries,
      const std::vector<MediaFileSystemInfo>& all_extension_galleries);

  FSInfoMap GetGalleriesInfo(extensions::Extension* extension);

  extensions::Extension* all_permission_extension();
  extensions::Extension* regular_permission_extension();
  Profile* profile();

  void AddNameForReadCompare(const base::string16& name);
  void AddNameForAllCompare(const base::string16& name);

 private:
  void CompareResults(const std::string& test,
                      const std::vector<base::string16>& names,
                      const std::vector<MediaFileSystemInfo>& expected,
                      const std::vector<MediaFileSystemInfo>& actual);
  bool ContainsEntry(const MediaFileSystemInfo& info,
                     const std::vector<MediaFileSystemInfo>& container);

  int GetAndClearComparisonCount();

  int num_comparisons_;

  scoped_ptr<TestingProfile> profile_;

  scoped_refptr<extensions::Extension> all_permission_extension_;
  scoped_refptr<extensions::Extension> regular_permission_extension_;
  scoped_refptr<extensions::Extension> no_permissions_extension_;

  scoped_ptr<content::WebContents> single_web_contents_;
  scoped_ptr<content::WebContents> shared_web_contents1_;
  scoped_ptr<content::WebContents> shared_web_contents2_;

  // The RenderProcessHosts are freed when their respective WebContents /
  // RenderViewHosts go away.
  content::MockRenderProcessHost* single_rph_;
  content::MockRenderProcessHost* shared_rph_;

  std::vector<base::string16> compare_names_read_;
  std::vector<base::string16> compare_names_all_;

  DISALLOW_COPY_AND_ASSIGN(ProfileState);
};

base::string16 GetExpectedFolderName(const base::FilePath& path) {
#if defined(OS_CHROMEOS)
  return path.BaseName().LossyDisplayName();
#else
  return path.LossyDisplayName();
#endif
}

}  // namespace

class MediaFileSystemRegistryTest : public ChromeRenderViewHostTestHarness {
 public:
  void CreateProfileState(size_t profile_count);

  ProfileState* GetProfileState(size_t i);

  MediaGalleriesPreferences* GetPreferences(Profile* profile);

  base::FilePath empty_dir() {
    return empty_dir_;
  }

  base::FilePath dcim_dir() {
    return dcim_dir_;
  }

  TestMediaFileSystemContext* test_file_system_context() {
    return test_file_system_context_;
  }

  // Create a user added gallery based on the information passed and add it to
  // |profiles|. Returns the device id.
  std::string AddUserGallery(StorageInfo::Type type,
                             const std::string& unique_id,
                             const base::FilePath& path);

  // Returns the device id.
  std::string AttachDevice(StorageInfo::Type type,
                           const std::string& unique_id,
                           const base::FilePath& location);

  void DetachDevice(const std::string& device_id);

  void SetGalleryPermission(ProfileState* profile_state,
                            extensions::Extension* extension,
                            const std::string& device_id,
                            bool has_access);

  void AssertAllAutoAddedGalleries();

  void InitForGalleriesInfoTest(FSInfoMap* galleries_info);

  void CheckNewGalleryInfo(ProfileState* profile_state,
                           const FSInfoMap& galleries_info,
                           const base::FilePath& location,
                           bool removable,
                           bool media_device);

  std::vector<MediaFileSystemInfo> GetAutoAddedGalleries(
      ProfileState* profile_state);

  void ProcessAttach(const std::string& id,
                     const base::string16& name,
                     const base::FilePath::StringType& location) {
    StorageInfo info(id, location, name, base::string16(), base::string16(), 0);
    StorageMonitor::GetInstance()->receiver()->ProcessAttach(info);
  }

  void ProcessDetach(const std::string& id) {
    StorageMonitor::GetInstance()->receiver()->ProcessDetach(id);
  }

  MediaFileSystemRegistry* registry() {
    return test_file_system_context_->registry();
  }

  size_t GetExtensionGalleriesHostCount(
      const MediaFileSystemRegistry* registry) const;

  int num_auto_galleries() {
    return media_directories_.num_galleries();
  }

 protected:
  virtual void SetUp() OVERRIDE;
  virtual void TearDown() OVERRIDE;

 private:
  // This makes sure that at least one default gallery exists on the file
  // system.
  EnsureMediaDirectoriesExists media_directories_;

  // Some test gallery directories.
  base::ScopedTempDir galleries_dir_;
  // An empty directory in |galleries_dir_|
  base::FilePath empty_dir_;
  // A directory in |galleries_dir_| with a DCIM directory in it.
  base::FilePath dcim_dir_;

  // MediaFileSystemRegistry owns this.
  TestMediaFileSystemContext* test_file_system_context_;

  // Needed for extension service & friends to work.

#if defined(OS_CHROMEOS)
  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
  chromeos::ScopedTestCrosSettings test_cros_settings_;
  scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
#endif

  MockProfileSharedRenderProcessHostFactory rph_factory_;

  ScopedVector<ProfileState> profile_states_;
};

namespace {

bool MediaFileSystemInfoComparator(const MediaFileSystemInfo& a,
                                   const MediaFileSystemInfo& b) {
  CHECK_NE(a.name, b.name);  // Name must be unique.
  return a.name < b.name;
}

///////////////////////////////////////////////
// MockProfileSharedRenderProcessHostFactory //
///////////////////////////////////////////////

MockProfileSharedRenderProcessHostFactory::
    ~MockProfileSharedRenderProcessHostFactory() {
  STLDeleteValues(&rph_map_);
}

content::MockRenderProcessHost*
MockProfileSharedRenderProcessHostFactory::ReleaseRPH(
    content::BrowserContext* browser_context) {
  ProfileRPHMap::iterator existing = rph_map_.find(browser_context);
  if (existing == rph_map_.end())
    return NULL;
  content::MockRenderProcessHost* result = existing->second;
  rph_map_.erase(existing);
  return result;
}

content::RenderProcessHost*
MockProfileSharedRenderProcessHostFactory::CreateRenderProcessHost(
    content::BrowserContext* browser_context,
    content::SiteInstance* site_instance) const {
  ProfileRPHMap::const_iterator existing = rph_map_.find(browser_context);
  if (existing != rph_map_.end())
    return existing->second;
  rph_map_[browser_context] =
      new content::MockRenderProcessHost(browser_context);
  return rph_map_[browser_context];
}

//////////////////
// ProfileState //
//////////////////

ProfileState::ProfileState(
    MockProfileSharedRenderProcessHostFactory* rph_factory)
    : num_comparisons_(0),
      profile_(new TestingProfile()) {
  extensions::TestExtensionSystem* extension_system(
      static_cast<extensions::TestExtensionSystem*>(
          extensions::ExtensionSystem::Get(profile_.get())));
  extension_system->CreateExtensionService(
      CommandLine::ForCurrentProcess(), base::FilePath(), false);

  std::vector<std::string> all_permissions;
  all_permissions.push_back("allAutoDetected");
  all_permissions.push_back("read");
  std::vector<std::string> read_permissions;
  read_permissions.push_back("read");

  all_permission_extension_ =
      AddMediaGalleriesApp("all", all_permissions, profile_.get());
  regular_permission_extension_ =
      AddMediaGalleriesApp("regular", read_permissions, profile_.get());
  no_permissions_extension_ =
      AddMediaGalleriesApp("no", read_permissions, profile_.get());

  single_web_contents_.reset(
      content::WebContentsTester::CreateTestWebContents(profile_.get(), NULL));
  single_rph_ = rph_factory->ReleaseRPH(profile_.get());

  shared_web_contents1_.reset(
      content::WebContentsTester::CreateTestWebContents(profile_.get(), NULL));
  shared_web_contents2_.reset(
      content::WebContentsTester::CreateTestWebContents(profile_.get(), NULL));
  shared_rph_ = rph_factory->ReleaseRPH(profile_.get());
}

ProfileState::~ProfileState() {
  // TestExtensionSystem uses DeleteSoon, so we need to delete the profiles
  // and then run the message queue to clean up.  But first we have to
  // delete everything that references the profile.
  single_web_contents_.reset();
  shared_web_contents1_.reset();
  shared_web_contents2_.reset();
  profile_.reset();

  base::MessageLoop::current()->RunUntilIdle();
}

MediaGalleriesPreferences* ProfileState::GetMediaGalleriesPrefs() {
  MediaGalleriesPreferences* prefs =
      MediaGalleriesPreferencesFactory::GetForProfile(profile_.get());
  base::RunLoop loop;
  prefs->EnsureInitialized(loop.QuitClosure());
  loop.Run();
  return prefs;
}

void ProfileState::CheckGalleries(
    const std::string& test,
    const std::vector<MediaFileSystemInfo>& regular_extension_galleries,
    const std::vector<MediaFileSystemInfo>& all_extension_galleries) {
  content::RenderViewHost* rvh = single_web_contents_->GetRenderViewHost();
  MediaFileSystemRegistry* registry =
      g_browser_process->media_file_system_registry();

  // No Media Galleries permissions.
  std::vector<MediaFileSystemInfo> empty_expectation;
  std::vector<base::string16> empty_names;
  registry->GetMediaFileSystemsForExtension(
      rvh, no_permissions_extension_.get(),
      base::Bind(&ProfileState::CompareResults, base::Unretained(this),
                 base::StringPrintf("%s (no permission)", test.c_str()),
                 base::ConstRef(empty_names),
                 base::ConstRef(empty_expectation)));
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(1, GetAndClearComparisonCount());

  // Read permission only.
  registry->GetMediaFileSystemsForExtension(
      rvh, regular_permission_extension_.get(),
      base::Bind(&ProfileState::CompareResults, base::Unretained(this),
                 base::StringPrintf("%s (regular permission)", test.c_str()),
                 base::ConstRef(compare_names_read_),
                 base::ConstRef(regular_extension_galleries)));
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(1, GetAndClearComparisonCount());

  // All galleries permission.
  registry->GetMediaFileSystemsForExtension(
      rvh, all_permission_extension_.get(),
      base::Bind(&ProfileState::CompareResults, base::Unretained(this),
                 base::StringPrintf("%s (all permission)", test.c_str()),
                 base::ConstRef(compare_names_all_),
                 base::ConstRef(all_extension_galleries)));
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(1, GetAndClearComparisonCount());
}

FSInfoMap ProfileState::GetGalleriesInfo(extensions::Extension* extension) {
  content::RenderViewHost* rvh = single_web_contents_->GetRenderViewHost();
  FSInfoMap results;
  MediaFileSystemRegistry* registry =
      g_browser_process->media_file_system_registry();
  registry->GetMediaFileSystemsForExtension(
      rvh, extension,
      base::Bind(&GetGalleryInfoCallback, base::Unretained(&results)));
  base::MessageLoop::current()->RunUntilIdle();
  return results;
}

extensions::Extension* ProfileState::all_permission_extension() {
  return all_permission_extension_.get();
}

extensions::Extension* ProfileState::regular_permission_extension() {
  return regular_permission_extension_.get();
}

Profile* ProfileState::profile() {
  return profile_.get();
}

void ProfileState::AddNameForReadCompare(const base::string16& name) {
  compare_names_read_.push_back(name);
}

void ProfileState::AddNameForAllCompare(const base::string16& name) {
  compare_names_all_.push_back(name);
}

bool ProfileState::ContainsEntry(
    const MediaFileSystemInfo& info,
    const std::vector<MediaFileSystemInfo>& container) {
  for (size_t i = 0; i < container.size(); ++i) {
    if (info.path.value() == container[i].path.value()) {
      EXPECT_FALSE(container[i].fsid.empty());
      if (!info.fsid.empty())
        EXPECT_EQ(info.fsid, container[i].fsid);
      return true;
    }
  }
  return false;
}

void ProfileState::CompareResults(
    const std::string& test,
    const std::vector<base::string16>& names,
    const std::vector<MediaFileSystemInfo>& expected,
    const std::vector<MediaFileSystemInfo>& actual) {
  num_comparisons_++;
  EXPECT_EQ(expected.size(), actual.size()) << test;

  // Order isn't important, so sort the results.
  std::vector<MediaFileSystemInfo> sorted(actual);
  std::sort(sorted.begin(), sorted.end(), MediaFileSystemInfoComparator);
  std::vector<MediaFileSystemInfo> expect(expected);
  std::sort(expect.begin(), expect.end(), MediaFileSystemInfoComparator);
  std::vector<base::string16> expect_names(names);
  std::sort(expect_names.begin(), expect_names.end());

  for (size_t i = 0; i < expect.size() && i < sorted.size(); ++i) {
    if (expect_names.size() > i)
      EXPECT_EQ(expect_names[i], sorted[i].name) << test;
    EXPECT_TRUE(ContainsEntry(expect[i], sorted)) << test;
  }
}

int ProfileState::GetAndClearComparisonCount() {
  int result = num_comparisons_;
  num_comparisons_ = 0;
  return result;
}

}  // namespace

/////////////////////////////////
// MediaFileSystemRegistryTest //
/////////////////////////////////

void MediaFileSystemRegistryTest::CreateProfileState(size_t profile_count) {
  for (size_t i = 0; i < profile_count; ++i) {
    ProfileState* state = new ProfileState(&rph_factory_);
    profile_states_.push_back(state);
  }
}

ProfileState* MediaFileSystemRegistryTest::GetProfileState(size_t i) {
  return profile_states_[i];
}

MediaGalleriesPreferences* MediaFileSystemRegistryTest::GetPreferences(
    Profile* profile) {
  MediaGalleriesPreferences* prefs = registry()->GetPreferences(profile);
  base::RunLoop loop;
  prefs->EnsureInitialized(loop.QuitClosure());
  loop.Run();
  return prefs;
}

std::string MediaFileSystemRegistryTest::AddUserGallery(
    StorageInfo::Type type,
    const std::string& unique_id,
    const base::FilePath& path) {
  std::string device_id = StorageInfo::MakeDeviceId(type, unique_id);
  DCHECK(!StorageInfo::IsMediaDevice(device_id));

  for (size_t i = 0; i < profile_states_.size(); ++i) {
    profile_states_[i]->GetMediaGalleriesPrefs()->AddGallery(
        device_id, base::FilePath(), MediaGalleryPrefInfo::kUserAdded,
        base::string16(), base::string16(), base::string16(), 0,
        base::Time::Now(), 0, 0, 0);
  }
  return device_id;
}

std::string MediaFileSystemRegistryTest::AttachDevice(
    StorageInfo::Type type,
    const std::string& unique_id,
    const base::FilePath& location) {
  std::string device_id = StorageInfo::MakeDeviceId(type, unique_id);
  DCHECK(StorageInfo::IsRemovableDevice(device_id));
  base::string16 label = location.BaseName().LossyDisplayName();
  ProcessAttach(device_id, label, location.value());
  base::MessageLoop::current()->RunUntilIdle();
  return device_id;
}

void MediaFileSystemRegistryTest::DetachDevice(const std::string& device_id) {
  DCHECK(StorageInfo::IsRemovableDevice(device_id));
  ProcessDetach(device_id);
  base::MessageLoop::current()->RunUntilIdle();
}

void MediaFileSystemRegistryTest::SetGalleryPermission(
    ProfileState* profile_state, extensions::Extension* extension,
    const std::string& device_id, bool has_access) {
  MediaGalleriesPreferences* preferences =
      profile_state->GetMediaGalleriesPrefs();
  MediaGalleryPrefIdSet pref_id =
      preferences->LookUpGalleriesByDeviceId(device_id);
  ASSERT_EQ(1U, pref_id.size());
  preferences->SetGalleryPermissionForExtension(*extension, *pref_id.begin(),
                                                has_access);
}

void MediaFileSystemRegistryTest::AssertAllAutoAddedGalleries() {
  for (size_t i = 0; i < profile_states_.size(); ++i) {
    MediaGalleriesPreferences* prefs =
        profile_states_[0]->GetMediaGalleriesPrefs();

    // Make sure that we have at least one gallery and that they are all
    // auto added galleries.
    const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
    ASSERT_GT(galleries.size(), 0U);
#endif
    for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
         it != galleries.end();
         ++it) {
      ASSERT_EQ(MediaGalleryPrefInfo::kAutoDetected, it->second.type);
    }
  }
}

void MediaFileSystemRegistryTest::InitForGalleriesInfoTest(
    FSInfoMap* galleries_info) {
  CreateProfileState(1);
  AssertAllAutoAddedGalleries();

  // Get all existing gallery names.
  ProfileState* profile_state = GetProfileState(0U);
  *galleries_info = profile_state->GetGalleriesInfo(
      profile_state->all_permission_extension());
#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
  ASSERT_EQ(3U, galleries_info->size());
#else
  ASSERT_EQ(0U, galleries_info->size());
#endif
}

void MediaFileSystemRegistryTest::CheckNewGalleryInfo(
    ProfileState* profile_state,
    const FSInfoMap& galleries_info,
    const base::FilePath& location,
    bool removable,
    bool media_device) {
  // Get new galleries.
  FSInfoMap new_galleries_info = profile_state->GetGalleriesInfo(
      profile_state->all_permission_extension());
  ASSERT_EQ(galleries_info.size() + 1U, new_galleries_info.size());

  bool found_new = false;
  for (FSInfoMap::const_iterator it = new_galleries_info.begin();
       it != new_galleries_info.end();
       ++it) {
    if (ContainsKey(galleries_info, it->first))
      continue;

    ASSERT_FALSE(found_new);
    CheckGalleryInfo(it->second, test_file_system_context_, location,
                     removable, media_device);
    found_new = true;
  }
  ASSERT_TRUE(found_new);
}

std::vector<MediaFileSystemInfo>
MediaFileSystemRegistryTest::GetAutoAddedGalleries(
    ProfileState* profile_state) {
  const MediaGalleriesPrefInfoMap& galleries =
      profile_state->GetMediaGalleriesPrefs()->known_galleries();
  std::vector<MediaFileSystemInfo> result;
  for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
       it != galleries.end();
       ++it) {
    if (it->second.type == MediaGalleryPrefInfo::kAutoDetected) {
      base::FilePath path = it->second.AbsolutePath();
      MediaFileSystemInfo info(path.BaseName().LossyDisplayName(), path,
                               std::string(), 0, std::string(), false, false);
      result.push_back(info);
    }
  }
  std::sort(result.begin(), result.end(), MediaFileSystemInfoComparator);
  return result;
}

size_t MediaFileSystemRegistryTest::GetExtensionGalleriesHostCount(
    const MediaFileSystemRegistry* registry) const {
  size_t extension_galleries_host_count = 0;
  for (MediaFileSystemRegistry::ExtensionGalleriesHostMap::const_iterator it =
           registry->extension_hosts_map_.begin();
       it != registry->extension_hosts_map_.end();
       ++it) {
    extension_galleries_host_count += it->second.size();
  }
  return extension_galleries_host_count;
}


void MediaFileSystemRegistryTest::SetUp() {
  ChromeRenderViewHostTestHarness::SetUp();
  ASSERT_TRUE(TestStorageMonitor::CreateAndInstall());

  DeleteContents();
  SetRenderProcessHostFactory(&rph_factory_);

  test_file_system_context_ = new TestMediaFileSystemContext(
      g_browser_process->media_file_system_registry());

#if defined(OS_CHROMEOS)
  test_user_manager_.reset(new chromeos::ScopedTestUserManager());
#endif

  ASSERT_TRUE(galleries_dir_.CreateUniqueTempDir());
  empty_dir_ = galleries_dir_.path().AppendASCII("empty");
  ASSERT_TRUE(base::CreateDirectory(empty_dir_));
  dcim_dir_ = galleries_dir_.path().AppendASCII("with_dcim");
  ASSERT_TRUE(base::CreateDirectory(dcim_dir_));
  ASSERT_TRUE(base::CreateDirectory(
      dcim_dir_.Append(storage_monitor::kDCIMDirectoryName)));
}

void MediaFileSystemRegistryTest::TearDown() {
  profile_states_.clear();
  MediaFileSystemRegistry* registry =
      g_browser_process->media_file_system_registry();
  EXPECT_EQ(0U, GetExtensionGalleriesHostCount(registry));
  TestStorageMonitor::Destroy();
#if defined(OS_CHROMEOS)
  test_user_manager_.reset();
#endif

  ChromeRenderViewHostTestHarness::TearDown();
}

///////////
// Tests //
///////////

TEST_F(MediaFileSystemRegistryTest, Basic) {
  CreateProfileState(1);
  AssertAllAutoAddedGalleries();

  ProfileState* profile_state = GetProfileState(0);
  std::vector<MediaFileSystemInfo> auto_galleries =
      GetAutoAddedGalleries(profile_state);
  std::vector<MediaFileSystemInfo> empty_expectation;
  profile_state->CheckGalleries("basic", empty_expectation, auto_galleries);
}

TEST_F(MediaFileSystemRegistryTest, UserAddedGallery) {
  CreateProfileState(1);
  AssertAllAutoAddedGalleries();
  ProfileState* profile_state = GetProfileState(0);
  std::vector<MediaFileSystemInfo> auto_galleries =
      GetAutoAddedGalleries(profile_state);
  std::vector<MediaFileSystemInfo> added_galleries;
  profile_state->CheckGalleries("user added init", added_galleries,
                                auto_galleries);

  // Add a user gallery to the regular permission extension.
  std::string device_id = AddUserGallery(StorageInfo::FIXED_MASS_STORAGE,
                                         empty_dir().AsUTF8Unsafe(),
                                         empty_dir());
  SetGalleryPermission(profile_state,
                       profile_state->regular_permission_extension(),
                       device_id,
                       true /*has access*/);
  MediaFileSystemInfo added_info(empty_dir().LossyDisplayName(), empty_dir(),
                                 std::string(), 0, std::string(), false, false);
  added_galleries.push_back(added_info);
  profile_state->CheckGalleries("user added regular", added_galleries,
                                auto_galleries);

  // Add it to the all galleries extension.
  SetGalleryPermission(profile_state,
                       profile_state->all_permission_extension(),
                       device_id,
                       true /*has access*/);
  auto_galleries.push_back(added_info);
  profile_state->CheckGalleries("user added all", added_galleries,
                                auto_galleries);
}

// Regression test to make sure erasing galleries does not result a crash.
TEST_F(MediaFileSystemRegistryTest, EraseGalleries) {
  CreateProfileState(1);
  AssertAllAutoAddedGalleries();

  ProfileState* profile_state = GetProfileState(0);
  std::vector<MediaFileSystemInfo> auto_galleries =
      GetAutoAddedGalleries(profile_state);
  std::vector<MediaFileSystemInfo> empty_expectation;
  profile_state->CheckGalleries("erase", empty_expectation, auto_galleries);

  MediaGalleriesPreferences* prefs = profile_state->GetMediaGalleriesPrefs();
  MediaGalleriesPrefInfoMap galleries = prefs->known_galleries();
  for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
       it != galleries.end(); ++it) {
    prefs->ForgetGalleryById(it->first);
  }
}

// Regression test to make sure calling GetPreferences() does not re-insert
// galleries on auto-detected removable devices that were blacklisted.
TEST_F(MediaFileSystemRegistryTest,
       GetPreferencesDoesNotReinsertBlacklistedGalleries) {
  CreateProfileState(1);
  AssertAllAutoAddedGalleries();

  ProfileState* profile_state = GetProfileState(0);
  const size_t gallery_count = GetAutoAddedGalleries(profile_state).size();

  // Attach a device.
  const std::string device_id = AttachDevice(
      StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
      "removable_dcim_fake_id",
      dcim_dir());
  EXPECT_EQ(gallery_count + 1, GetAutoAddedGalleries(profile_state).size());

  // Forget the device.
  bool forget_gallery = false;
  MediaGalleriesPreferences* prefs = GetPreferences(profile_state->profile());
  const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
  for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
       it != galleries.end(); ++it) {
    if (it->second.device_id == device_id) {
      prefs->ForgetGalleryById(it->first);
      forget_gallery = true;
      break;
    }
  }
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_TRUE(forget_gallery);
  EXPECT_EQ(gallery_count, GetAutoAddedGalleries(profile_state).size());

  // Call GetPreferences() and the gallery count should not change.
  prefs = GetPreferences(profile_state->profile());
  EXPECT_EQ(gallery_count, GetAutoAddedGalleries(profile_state).size());
}

TEST_F(MediaFileSystemRegistryTest, GalleryNameDefault) {
  FSInfoMap galleries_info;
  InitForGalleriesInfoTest(&galleries_info);

  for (FSInfoMap::const_iterator it = galleries_info.begin();
       it != galleries_info.end();
       ++it) {
    CheckGalleryInfo(it->second, test_file_system_context(),
                     it->second.path, false, false);
  }
}

// TODO(gbillock): Move the remaining test into the linux directory.
#if !defined(OS_MACOSX) && !defined(OS_WIN)
TEST_F(MediaFileSystemRegistryTest, GalleryMTP) {
  FSInfoMap galleries_info;
  InitForGalleriesInfoTest(&galleries_info);

  base::FilePath location(FILE_PATH_LITERAL("/mtp_bogus"));
  AttachDevice(StorageInfo::MTP_OR_PTP, "mtp_fake_id", location);
  CheckNewGalleryInfo(GetProfileState(0U), galleries_info, location,
                      true /*removable*/, true /* media device */);
}
#endif

TEST_F(MediaFileSystemRegistryTest, GalleryDCIM) {
  FSInfoMap galleries_info;
  InitForGalleriesInfoTest(&galleries_info);

  AttachDevice(StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
               "removable_dcim_fake_id",
               dcim_dir());
  CheckNewGalleryInfo(GetProfileState(0U), galleries_info, dcim_dir(),
                      true /*removable*/, true /* media device */);
}

TEST_F(MediaFileSystemRegistryTest, GalleryNoDCIM) {
  FSInfoMap galleries_info;
  InitForGalleriesInfoTest(&galleries_info);

  std::string device_id =
      AttachDevice(StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM,
                   empty_dir().AsUTF8Unsafe(),
                   empty_dir());
  std::string device_id2 =
      AddUserGallery(StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM,
                     empty_dir().AsUTF8Unsafe(),
                     empty_dir());
  ASSERT_EQ(device_id, device_id2);
  // Add permission for new non-default gallery.
  ProfileState* profile_state = GetProfileState(0U);
  SetGalleryPermission(profile_state,
                       profile_state->all_permission_extension(),
                       device_id,
                       true /*has access*/);
  CheckNewGalleryInfo(profile_state, galleries_info, empty_dir(),
                      true /*removable*/, false /* media device */);
}

TEST_F(MediaFileSystemRegistryTest, GalleryUserAddedPath) {
  FSInfoMap galleries_info;
  InitForGalleriesInfoTest(&galleries_info);

  std::string device_id = AddUserGallery(StorageInfo::FIXED_MASS_STORAGE,
                                         empty_dir().AsUTF8Unsafe(),
                                         empty_dir());
  // Add permission for new non-default gallery.
  ProfileState* profile_state = GetProfileState(0U);
  SetGalleryPermission(profile_state,
                       profile_state->all_permission_extension(),
                       device_id,
                       true /*has access*/);
  CheckNewGalleryInfo(profile_state, galleries_info, empty_dir(),
                      false /*removable*/, false /* media device */);
}

TEST_F(MediaFileSystemRegistryTest, DetachedDeviceGalleryPath) {
  const std::string device_id = AttachDevice(
      StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
      "removable_dcim_fake_id",
      dcim_dir());

  MediaGalleryPrefInfo pref_info;
  pref_info.device_id = device_id;
  EXPECT_EQ(dcim_dir().value(), pref_info.AbsolutePath().value());

  MediaGalleryPrefInfo pref_info_with_relpath;
  pref_info_with_relpath.path =
      base::FilePath(FILE_PATH_LITERAL("test_relpath"));
  pref_info_with_relpath.device_id = device_id;
  EXPECT_EQ(dcim_dir().Append(pref_info_with_relpath.path).value(),
            pref_info_with_relpath.AbsolutePath().value());

  DetachDevice(device_id);
  EXPECT_TRUE(pref_info.AbsolutePath().empty());
  EXPECT_TRUE(pref_info_with_relpath.AbsolutePath().empty());
}

TEST_F(MediaFileSystemRegistryTest, TestNameConstruction) {
  CreateProfileState(1);
  AssertAllAutoAddedGalleries();

  ProfileState* profile_state = GetProfileState(0);

  std::string user_gallery = AddUserGallery(StorageInfo::FIXED_MASS_STORAGE,
                                            empty_dir().AsUTF8Unsafe(),
                                            empty_dir());
  SetGalleryPermission(profile_state,
                       profile_state->regular_permission_extension(),
                       user_gallery,
                       true /*has access*/);
  SetGalleryPermission(profile_state,
                       profile_state->all_permission_extension(),
                       user_gallery,
                       true /*has access*/);

  std::vector<MediaFileSystemInfo> auto_galleries =
      GetAutoAddedGalleries(profile_state);
  MediaFileSystemInfo added_info(empty_dir().BaseName().LossyDisplayName(),
                                 empty_dir(), std::string(), 0, std::string(),
                                 false, false);
  auto_galleries.push_back(added_info);
  std::vector<MediaFileSystemInfo> one_expectation;
  one_expectation.push_back(added_info);

  base::string16 empty_dir_name = GetExpectedFolderName(empty_dir());
  profile_state->AddNameForReadCompare(empty_dir_name);
  profile_state->AddNameForAllCompare(empty_dir_name);

  // This part of the test is conditional on default directories existing
  // on the test platform. In ChromeOS, these directories do not exist.
  base::FilePath path;
  if (num_auto_galleries() > 0) {
    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_MUSIC, &path));
    profile_state->AddNameForAllCompare(GetExpectedFolderName(path));
    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_PICTURES, &path));
    profile_state->AddNameForAllCompare(GetExpectedFolderName(path));
    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_VIDEOS, &path));
    profile_state->AddNameForAllCompare(GetExpectedFolderName(path));

    profile_state->CheckGalleries("names-dir", one_expectation, auto_galleries);
  } else {
    profile_state->CheckGalleries("names", one_expectation, one_expectation);
  }
}

TEST_F(MediaFileSystemRegistryTest, PreferenceListener) {
  CreateProfileState(1);
  AssertAllAutoAddedGalleries();

  // Add a user gallery to the regular permission extension.
  std::string device_id = AddUserGallery(StorageInfo::FIXED_MASS_STORAGE,
                                         empty_dir().AsUTF8Unsafe(),
                                         empty_dir());
  ProfileState* profile_state = GetProfileState(0);
  SetGalleryPermission(profile_state,
                       profile_state->regular_permission_extension(),
                       device_id,
                       true /*has access*/);

  FSInfoMap fs_info = profile_state->GetGalleriesInfo(
      profile_state->regular_permission_extension());
  ASSERT_EQ(1U, fs_info.size());
  EXPECT_FALSE(test_file_system_context()->GetRegisteredPath(
      fs_info.begin()->second.fsid).empty());

  // Revoke permission and ensure that the file system is revoked.
  SetGalleryPermission(profile_state,
                       profile_state->regular_permission_extension(),
                       device_id,
                       false /*has access*/);
  EXPECT_TRUE(test_file_system_context()->GetRegisteredPath(
      fs_info.begin()->second.fsid).empty());
}