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

#include "ash/constants/ash_switches.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drivefs_test_support.h"
#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
#include "chrome/browser/ash/file_manager/mount_test_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/cast_config_controller_media_router.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "components/media_router/browser/test/mock_media_router.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_host_test_helper.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "extensions/test/result_catcher.h"
#include "google_apis/common/test_util.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "ui/shell_dialogs/select_file_dialog_factory.h"
#include "ui/shell_dialogs/select_file_policy.h"

// Tests for access to external file systems (as defined in
// storage/common/file_system/file_system_types.h) from extensions with
// fileManagerPrivate and fileBrowserHandler extension permissions.
// The tests cover following external file system types:
// - local (kFileSystemTypeLocalNative): a local file system on which files are
//   accessed using native local path.
// - restricted (kFileSystemTypeRestrictedLocalNative): a *read-only* local file
//   system which can only be accessed by extensions that have full access to
//   external file systems (i.e. extensions with fileManagerPrivate permission).
//
// The tests cover following scenarios:
// - Performing file system operations on external file systems from an
//   app with fileManagerPrivate permission (i.e. The Files app).
// - Performing read/write operations from file handler extensions. These
//   extensions need a file browser extension to give them permissions to access
//   files. This also includes file handler extensions in filesystem API.
// - Observing directory changes from a file browser extension (using
//   fileManagerPrivate API).
// - Doing searches on drive file system from file browser extension (using
//   fileManagerPrivate API).

using drive::DriveIntegrationServiceFactory;
using extensions::Extension;

namespace file_manager {
namespace {

// Root dirs for file systems expected by the test extensions.
// NOTE: Root dir for drive file system is set by Chrome's drive implementation,
// but the test will have to make sure the mount point is added before
// starting a test extension using WaitUntilDriveMountPointIsAdded().
constexpr char kLocalMountPointName[] = "local";
constexpr char kRestrictedMountPointName[] = "restricted";

// Default file content for the test files.
constexpr char kTestFileContent[] = "This is some test content.";

// User account email and directory hash for secondary account for multi-profile
// sensitive test cases.
constexpr char kSecondProfileAccount[] = "profile2@test.com";
constexpr char kSecondProfileGiaId[] = "9876543210";
constexpr char kSecondProfileHash[] = "fileBrowserApiTestProfile2";

class FakeSelectFileDialog : public ui::SelectFileDialog {
 public:
  FakeSelectFileDialog(ui::SelectFileDialog::Listener* listener,
                       std::unique_ptr<ui::SelectFilePolicy> policy,
                       const base::FilePath& drivefs_root)
      : ui::SelectFileDialog(listener, std::move(policy)),
        drivefs_root_(drivefs_root) {}

  void SelectFileImpl(Type type,
                      const std::u16string& title,
                      const base::FilePath& default_path,
                      const FileTypeInfo* file_types,
                      int file_type_index,
                      const base::FilePath::StringType& default_extension,
                      gfx::NativeWindow owning_window,
                      void* params) override {
    listener_->FileSelected(drivefs_root_.Append("root/test_dir"), 0, nullptr);
  }

  bool IsRunning(gfx::NativeWindow owning_window) const override {
    return false;
  }

  void ListenerDestroyed() override {}

  bool HasMultipleFileTypeChoicesImpl() override { return false; }

 private:
  ~FakeSelectFileDialog() override = default;

  const base::FilePath drivefs_root_;
};

class FakeSelectFileDialogFactory : public ui::SelectFileDialogFactory {
 public:
  explicit FakeSelectFileDialogFactory(const base::FilePath& drivefs_root)
      : drivefs_root_(drivefs_root) {}

 private:
  ui::SelectFileDialog* Create(
      ui::SelectFileDialog::Listener* listener,
      std::unique_ptr<ui::SelectFilePolicy> policy) override {
    return new FakeSelectFileDialog(listener, std::move(policy), drivefs_root_);
  }

  const base::FilePath drivefs_root_;
};

// Waits for a WebContents of the background page of the extension under test
// to load, then injects some javascript into it to trigger a particular test.
class JSTestStarter : public content::TestNavigationObserver {
 public:
  explicit JSTestStarter(const std::string& test_name)
      : TestNavigationObserver(GetUrlToWatch()), test_name_(test_name) {
    WatchExistingWebContents();
    StartWatchingNewWebContents();
  }

  static GURL GetUrlToWatch() {
    // Use the chrome-extension:// ID corresponding to the key used in the app
    // manifests for tests in this file. An improvement to this would use the ID
    // of the extension from LoadExtensionAsComponentWithManifest(), but that's
    // potentially racy.
    return GURL(
        "chrome-extension://pkplfbidichfdicaijlchgnapepdginl/"
        "_generated_background_page.html");
  }

  // TestNavigationObserver:
  void OnDidFinishNavigation(
      content::NavigationHandle* navigation_handle) override {
    // If the background page scripts have run, the test will exist, so just run
    // it. Otherwise, schedule the test to be run at the end of the background
    // page script.
    constexpr char kScript[] = R"(
        if (self.$1) {
          chrome.test.runTests([$1])
        } else {
          self.testNameToRun = '$1';
        }
    )";
    ASSERT_TRUE(content::ExecuteScript(
        navigation_handle->GetRenderFrameHost(),
        base::ReplaceStringPlaceholders(kScript, {test_name_}, nullptr)));

    TestNavigationObserver::OnDidFinishNavigation(navigation_handle);
  }

 private:
  const std::string test_name_;
};

bool TouchFile(const base::FilePath& path,
               base::StringPiece mtime_string,
               base::StringPiece atime_string) {
  base::Time mtime, atime;
  auto result = base::Time::FromString(mtime_string.data(), &mtime) &&
                base::Time::FromString(atime_string.data(), &atime) &&
                base::TouchFile(path, atime, mtime);
  return result;
}

// Configuration of a file residing in a "test_dir" of a created volume.
// If contents is null, creates a subdirectory.
struct TestDirConfig {
  const char* mtime;
  const char* atime;
  const char* name;
  const char* contents = kTestFileContent;
};

// An arbitrary time, for tests that don't care.
constexpr const char kArbitraryTime[] = "2011-04-03T11:11:10.000Z";

// The default configuration of entries in "test_dir" used in test harnesses.
constexpr const TestDirConfig kDefaultDirConfig[] = {
    {"2011-11-02T04:00:00.000Z", "2011-11-02T04:00:00.000Z", "empty_dir",
     nullptr},
    {"2011-04-01T18:34:08.234Z", "2012-01-02T00:00:01.000Z", "subdir", nullptr},
    {"2011-12-14T00:40:47.330Z", "2012-01-02T00:00:00.000Z", "test_file.xul"},
    {"2012-01-01T10:00:30.000Z", "2012-01-01T00:00:00.000Z",
     "test_file.xul.foo"},
    {"2011-04-03T11:11:10.000Z", "2012-01-02T00:00:00.000Z", "test_file.tiff"},
    {"2011-12-14T00:40:47.330Z", "2010-01-02T00:00:00.000Z",
     "test_file.tiff.foo"},
    {"2011-12-14T00:40:47.330Z", "2011-12-14T00:40:47.330Z", "empty_file.foo",
     ""},
};

// Sets up the initial file system state for native local and restricted native
// local file systems. The hierarchy is the same as for the drive file system.
// The directory is created at unique_temp_dir/|mount_point_name| path.
bool InitializeLocalFileSystem(std::string mount_point_name,
                               base::ScopedTempDir* tmp_dir,
                               base::FilePath* mount_point_dir,
                               const std::vector<TestDirConfig>& dir_contents) {
  if (!tmp_dir->CreateUniqueTempDir())
    return false;

  *mount_point_dir = tmp_dir->GetPath().AppendASCII(mount_point_name);
  // Create the mount point.
  if (!base::CreateDirectory(*mount_point_dir))
    return false;

  constexpr TestDirConfig kTestDir = {"2012-01-02T00:00:00.000Z",
                                      "2012-01-02T00:00:01.000Z", "test_dir",
                                      nullptr};

  const base::FilePath test_dir = mount_point_dir->AppendASCII(kTestDir.name);
  if (!base::CreateDirectory(test_dir))
    return false;

  for (const auto& file : dir_contents) {
    const base::FilePath test_path = test_dir.AppendASCII(file.name);
    if (file.contents) {
      if (!google_apis::test_util::WriteStringToFile(test_path, file.contents))
        return false;
    } else {
      if (!base::CreateDirectory(test_path))
        return false;
    }
  }

  for (const auto& file : dir_contents) {
    if (!TouchFile(test_dir.Append(file.name), file.mtime, file.atime))
      return false;
  }

  // Touch the directory holding all the contents last.
  return TouchFile(test_dir, kTestDir.mtime, kTestDir.atime);
}

// Base class for FileSystemExtensionApi tests.
class FileSystemExtensionApiTestBase : public extensions::ExtensionApiTest {
 public:
  enum Flags {
    FLAGS_NONE = 0,
    FLAGS_USE_FILE_HANDLER = 1 << 1,
    FLAGS_LAZY_FILE_HANDLER = 1 << 2
  };

  FileSystemExtensionApiTestBase() = default;

  FileSystemExtensionApiTestBase(const FileSystemExtensionApiTestBase&) =
      delete;
  FileSystemExtensionApiTestBase& operator=(
      const FileSystemExtensionApiTestBase&) = delete;

  ~FileSystemExtensionApiTestBase() override = default;

  virtual std::vector<TestDirConfig> GetTestDirContents() {
    return {std::begin(kDefaultDirConfig), std::end(kDefaultDirConfig)};
  }

  bool SetUpUserDataDirectory() override {
    return drive::SetUpUserDataDirectoryForDriveFsTest();
  }

  void SetUp() override {
    InitTestFileSystem();
    extensions::ExtensionApiTest::SetUp();
  }

  void SetUpOnMainThread() override {
    AddTestMountPoint();

    // Mock the Media Router in extension api tests. Dispatches to the message
    // loop now try to handle mojo messages that will call back into Profile
    // creation through the media router, which then confuse the drive code.
    media_router_ = std::make_unique<media_router::MockMediaRouter>();
    ON_CALL(*media_router_, RegisterMediaSinksObserver(testing::_))
        .WillByDefault(testing::Return(true));
    CastConfigControllerMediaRouter::SetMediaRouterForTest(media_router_.get());

    extensions::ExtensionApiTest::SetUpOnMainThread();
  }

  void TearDownOnMainThread() override {
    CastConfigControllerMediaRouter::SetMediaRouterForTest(nullptr);
    extensions::ExtensionApiTest::TearDownOnMainThread();
  }

  // Runs a file system extension API test.
  // It loads test component extension at |filebrowser_path| with manifest
  // at |filebrowser_manifest|. The |filebrowser_manifest| should be a path
  // relative to |filebrowser_path|. The method waits until the test extension
  // sends test succeed or fail message. It returns true if the test succeeds.
  // If |FLAGS_USE_FILE_HANDLER| flag is set, the file handler extension at path
  // |filehandler_path| will be loaded before the file browser extension.
  // If the flag FLAGS_LAZY_FILE_HANDLER is set, the file handler extension must
  // not have persistent background page. The test will wait until the file
  // handler's background page is closed after initial load before the file
  // browser extension is loaded.
  // If |RunFileSystemExtensionApiTest| fails, |message_| will contain a failure
  // message.
  bool RunFileSystemExtensionApiTest(
      const std::string& filebrowser_path,
      const base::FilePath::CharType* filebrowser_manifest,
      const std::string& filehandler_path,
      int flags) {
    if (flags & FLAGS_USE_FILE_HANDLER) {
      if (filehandler_path.empty()) {
        message_ = "Missing file handler path.";
        return false;
      }

      if (flags & FLAGS_LAZY_FILE_HANDLER) {
        // Ensures the file handler extension's background page closes in a
        // timely manner to avoid test timeouts.
        extensions::ProcessManager::SetEventPageIdleTimeForTesting(1);
      }

      extensions::ExtensionHostTestHelper host_helper(profile());
      host_helper.RestrictToType(
          extensions::mojom::ViewType::kExtensionBackgroundPage);
      const Extension* file_handler =
          LoadExtension(test_data_dir_.AppendASCII(filehandler_path));
      if (!file_handler) {
        message_ = "Error loading file handler extension";
        return false;
      }

      if (flags & FLAGS_LAZY_FILE_HANDLER) {
        host_helper.WaitForHostDestroyed();
      } else {
        host_helper.WaitForDocumentElementAvailable();
      }
    }

    extensions::ResultCatcher catcher;

    const Extension* file_browser = LoadExtensionAsComponentWithManifest(
        test_data_dir_.AppendASCII(filebrowser_path),
        filebrowser_manifest);
    if (!file_browser) {
      message_ = "Could not create file browser";
      return false;
    }

    if (!catcher.GetNextResult()) {
      message_ = catcher.message();
      return false;
    }

    return true;
  }

  // Starts the app in |app_folder| then triggers the |test_name| on the
  // background page using JSTestStarter. Does not configure a file handler app.
  bool RunBackgroundPageTestCase(const std::string& app_folder,
                                 const std::string& test_name) {
    JSTestStarter starter(test_name);
    return RunFileSystemExtensionApiTest("file_browser/" + app_folder,
                                         FILE_PATH_LITERAL("manifest.json"),
                                         std::string(), FLAGS_NONE);
  }

 protected:
  // Sets up initial test file system hierarchy.
  virtual void InitTestFileSystem() = 0;
  // Registers mount point used in the test.
  virtual void AddTestMountPoint() = 0;

 private:
  std::unique_ptr<media_router::MockMediaRouter> media_router_;
};

// Tests for a native local file system.
class LocalFileSystemExtensionApiTest : public FileSystemExtensionApiTestBase {
 public:
  LocalFileSystemExtensionApiTest() = default;
  ~LocalFileSystemExtensionApiTest() override = default;

  // FileSystemExtensionApiTestBase override.
  void InitTestFileSystem() override {
    ASSERT_TRUE(InitializeLocalFileSystem(kLocalMountPointName, &tmp_dir_,
                                          &mount_point_dir_,
                                          GetTestDirContents()))
        << "Failed to initialize file system.";
  }

  // FileSystemExtensionApiTestBase override.
  void AddTestMountPoint() override {
    EXPECT_TRUE(profile()->GetMountPoints()->RegisterFileSystem(
        kLocalMountPointName, storage::kFileSystemTypeLocal,
        storage::FileSystemMountOption(), mount_point_dir_));
    VolumeManager::Get(profile())->AddVolumeForTesting(
        mount_point_dir_, VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN,
        false /* read_only */);
  }

 private:
  base::ScopedTempDir tmp_dir_;
  base::FilePath mount_point_dir_;
};

// Tests for restricted native local file systems.
class RestrictedFileSystemExtensionApiTest
    : public FileSystemExtensionApiTestBase {
 public:
  RestrictedFileSystemExtensionApiTest() = default;
  ~RestrictedFileSystemExtensionApiTest() override = default;

  // FileSystemExtensionApiTestBase override.
  void InitTestFileSystem() override {
    ASSERT_TRUE(InitializeLocalFileSystem(kRestrictedMountPointName, &tmp_dir_,
                                          &mount_point_dir_,
                                          GetTestDirContents()))
        << "Failed to initialize file system.";
  }

  // FileSystemExtensionApiTestBase override.
  void AddTestMountPoint() override {
    EXPECT_TRUE(profile()->GetMountPoints()->RegisterFileSystem(
        kRestrictedMountPointName, storage::kFileSystemTypeRestrictedLocal,
        storage::FileSystemMountOption(), mount_point_dir_));
    VolumeManager::Get(profile())->AddVolumeForTesting(
        mount_point_dir_, VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN,
        true /* read_only */);
  }

 private:
  base::ScopedTempDir tmp_dir_;
  base::FilePath mount_point_dir_;
};

// Tests for a drive file system.
class DriveFileSystemExtensionApiTest : public FileSystemExtensionApiTestBase {
 public:
  DriveFileSystemExtensionApiTest() = default;
  ~DriveFileSystemExtensionApiTest() override = default;

  // FileSystemExtensionApiTestBase override.
  void InitTestFileSystem() override {
    // Set up cache root to be used by DriveIntegrationService. This has to be
    // done before the browser is created because the service instance is
    // initialized by EventRouter.
    ASSERT_TRUE(test_cache_root_.CreateUniqueTempDir());

    // This callback will get called during Profile creation.
    create_drive_integration_service_ = base::BindRepeating(
        &DriveFileSystemExtensionApiTest::CreateDriveIntegrationService,
        base::Unretained(this));
    service_factory_for_test_ =
        std::make_unique<DriveIntegrationServiceFactory::ScopedFactoryForTest>(
            &create_drive_integration_service_);
  }

  // FileSystemExtensionApiTestBase override.
  void AddTestMountPoint() override {
    test_util::WaitUntilDriveMountPointIsAdded(profile());
  }

  // FileSystemExtensionApiTestBase override.
  void TearDown() override {
    FileSystemExtensionApiTestBase::TearDown();
    ui::SelectFileDialog::SetFactory(nullptr);
  }

 protected:
  // DriveIntegrationService factory function for this test.
  drive::DriveIntegrationService* CreateDriveIntegrationService(
      Profile* profile) {
    // Ignore signin and lock screen apps profile.
    if (profile->GetPath() == chromeos::ProfileHelper::GetSigninProfileDir() ||
        profile->GetPath() ==
            chromeos::ProfileHelper::GetLockScreenAppProfilePath()) {
      return nullptr;
    }

    // DriveFileSystemExtensionApiTest doesn't expect that several user profiles
    // could exist simultaneously.
    base::FilePath drivefs_mount_point;
    InitializeLocalFileSystem("drive-user/root", &drivefs_root_,
                              &drivefs_mount_point, GetTestDirContents());
    fake_drivefs_helper_ = std::make_unique<drive::FakeDriveFsHelper>(
        profile, drivefs_mount_point.DirName());
    return new drive::DriveIntegrationService(
        profile, "", test_cache_root_.GetPath(),
        fake_drivefs_helper_->CreateFakeDriveFsListenerFactory());
  }

  base::ScopedTempDir test_cache_root_;
  base::ScopedTempDir drivefs_root_;
  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
  DriveIntegrationServiceFactory::FactoryCallback
      create_drive_integration_service_;
  std::unique_ptr<DriveIntegrationServiceFactory::ScopedFactoryForTest>
      service_factory_for_test_;
};

// Tests for Drive file systems in multi-profile setting.
class MultiProfileDriveFileSystemExtensionApiTest :
    public FileSystemExtensionApiTestBase {
 public:
  MultiProfileDriveFileSystemExtensionApiTest() = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    FileSystemExtensionApiTestBase::SetUpCommandLine(command_line);
    // Don't require policy for our sessions - this is required because
    // this test creates a secondary profile synchronously, so we need to
    // let the policy code know not to expect cached policy.
    command_line->AppendSwitchASCII(chromeos::switches::kProfileRequiresPolicy,
                                    "false");
  }

  void SetUpOnMainThread() override {
    base::FilePath user_data_directory;
    base::PathService::Get(chrome::DIR_USER_DATA, &user_data_directory);
    session_manager::SessionManager::Get()->CreateSession(
        AccountId::FromUserEmailGaiaId(kSecondProfileAccount,
                                       kSecondProfileGiaId),
        kSecondProfileHash, false);
    // Set up the secondary profile.
    base::FilePath profile_dir =
        user_data_directory.Append(
            chromeos::ProfileHelper::GetUserProfileDir(
                kSecondProfileHash).BaseName());
    second_profile_ =
        g_browser_process->profile_manager()->GetProfile(profile_dir);

    FileSystemExtensionApiTestBase::SetUpOnMainThread();
  }

  void InitTestFileSystem() override {
    ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());

    // This callback will get called during Profile creation.
    create_drive_integration_service_ =
        base::BindRepeating(&MultiProfileDriveFileSystemExtensionApiTest::
                                CreateDriveIntegrationService,
                            base::Unretained(this));
    service_factory_for_test_ =
        std::make_unique<DriveIntegrationServiceFactory::ScopedFactoryForTest>(
            &create_drive_integration_service_);
  }

  void AddTestMountPoint() override {
    test_util::WaitUntilDriveMountPointIsAdded(profile());
    test_util::WaitUntilDriveMountPointIsAdded(second_profile_);
  }

 protected:
  // DriveIntegrationService factory function for this test.
  drive::DriveIntegrationService* CreateDriveIntegrationService(
      Profile* profile) {
    // Ignore signin and lock screen apps profile.
    if (profile->GetPath() == chromeos::ProfileHelper::GetSigninProfileDir() ||
        profile->GetPath() ==
            chromeos::ProfileHelper::GetLockScreenAppProfilePath()) {
      return nullptr;
    }

    base::FilePath cache_dir;
    base::CreateTemporaryDirInDir(tmp_dir_.GetPath(),
                                  base::FilePath::StringType(), &cache_dir);

    base::FilePath drivefs_dir;
    base::CreateTemporaryDirInDir(tmp_dir_.GetPath(),
                                  base::FilePath::StringType(), &drivefs_dir);
    auto profile_name_storage = profile->GetBaseName().value();
    base::StringPiece profile_name = profile_name_storage;
    if (base::StartsWith(profile_name, "u-")) {
      profile_name = profile_name.substr(2);
    }
    drivefs_dir = drivefs_dir.Append(base::StrCat({"drive-", profile_name}));
    auto test_dir = drivefs_dir.Append("root/test_dir");
    CHECK(base::CreateDirectory(test_dir));
    CHECK(google_apis::test_util::WriteStringToFile(
        test_dir.AppendASCII("test_file.tiff"), kTestFileContent));
    CHECK(google_apis::test_util::WriteStringToFile(
        test_dir.AppendASCII("hosted_doc.gdoc"), kTestFileContent));

    const auto& drivefs_helper = fake_drivefs_helpers_[profile] =
        std::make_unique<drive::FakeDriveFsHelper>(profile, drivefs_dir);
    return new drive::DriveIntegrationService(
        profile, std::string(), cache_dir,
        drivefs_helper->CreateFakeDriveFsListenerFactory());
  }

  base::ScopedTempDir tmp_dir_;
  DriveIntegrationServiceFactory::FactoryCallback
      create_drive_integration_service_;
  std::unique_ptr<DriveIntegrationServiceFactory::ScopedFactoryForTest>
      service_factory_for_test_;
  Profile* second_profile_ = nullptr;
  std::unordered_map<Profile*, std::unique_ptr<drive::FakeDriveFsHelper>>
      fake_drivefs_helpers_;
};

class LocalAndDriveFileSystemExtensionApiTest
    : public FileSystemExtensionApiTestBase {
 public:
  LocalAndDriveFileSystemExtensionApiTest() = default;
  ~LocalAndDriveFileSystemExtensionApiTest() override = default;

  // FileSystemExtensionApiTestBase override.
  void InitTestFileSystem() override {
    ASSERT_TRUE(InitializeLocalFileSystem(kLocalMountPointName, &local_tmp_dir_,
                                          &local_mount_point_dir_,
                                          GetTestDirContents()))
        << "Failed to initialize file system.";

    // Set up cache root to be used by DriveIntegrationService. This has to be
    // done before the browser is created because the service instance is
    // initialized by EventRouter.
    ASSERT_TRUE(test_cache_root_.CreateUniqueTempDir());

    // This callback will get called during Profile creation.
    create_drive_integration_service_ = base::BindRepeating(
        &LocalAndDriveFileSystemExtensionApiTest::CreateDriveIntegrationService,
        base::Unretained(this));
    service_factory_for_test_ =
        std::make_unique<DriveIntegrationServiceFactory::ScopedFactoryForTest>(
            &create_drive_integration_service_);
  }

  // FileSystemExtensionApiTestBase override.
  void AddTestMountPoint() override {
    EXPECT_TRUE(profile()->GetMountPoints()->RegisterFileSystem(
        kLocalMountPointName, storage::kFileSystemTypeLocal,
        storage::FileSystemMountOption(), local_mount_point_dir_));
    VolumeManager::Get(profile())->AddVolumeForTesting(
        local_mount_point_dir_, VOLUME_TYPE_TESTING,
        chromeos::DEVICE_TYPE_UNKNOWN, false /* read_only */);
    test_util::WaitUntilDriveMountPointIsAdded(profile());
  }

 protected:
  // DriveIntegrationService factory function for this test.
  drive::DriveIntegrationService* CreateDriveIntegrationService(
      Profile* profile) {
    // Ignore signin and lock screen apps profile.
    if (profile->GetPath() == chromeos::ProfileHelper::GetSigninProfileDir() ||
        profile->GetPath() ==
            chromeos::ProfileHelper::GetLockScreenAppProfilePath()) {
      return nullptr;
    }

    // LocalAndDriveFileSystemExtensionApiTest doesn't expect that several user
    // profiles could exist simultaneously.
    base::FilePath drivefs_mount_point;
    InitializeLocalFileSystem("drive-user/root", &drivefs_root_,
                              &drivefs_mount_point, GetTestDirContents());
    fake_drivefs_helper_ = std::make_unique<drive::FakeDriveFsHelper>(
        profile, drivefs_mount_point.DirName());
    return new drive::DriveIntegrationService(
        profile, "", test_cache_root_.GetPath(),
        fake_drivefs_helper_->CreateFakeDriveFsListenerFactory());
  }

 private:
  // For local volume.
  base::ScopedTempDir local_tmp_dir_;
  base::FilePath local_mount_point_dir_;

  // For drive volume.
  base::ScopedTempDir test_cache_root_;
  base::ScopedTempDir drivefs_root_;
  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
  DriveIntegrationServiceFactory::FactoryCallback
      create_drive_integration_service_;
  std::unique_ptr<DriveIntegrationServiceFactory::ScopedFactoryForTest>
      service_factory_for_test_;
};

// Mixin for starting one of the FileSystem test fixures with a specific app
// configuration, which may include default-installed apps. Currently set up
// to run with the chrome://media-app.
class FileSystemExtensionApiTestWithApps
    : public LocalFileSystemExtensionApiTest {
 public:
  FileSystemExtensionApiTestWithApps() {}

  // FileManagerPrivateApiTest:
  void SetUpOnMainThread() override {
    Profile* profile = browser()->profile();
    file_manager::test::AddDefaultComponentExtensionsOnMainThread(profile);
    web_app::WebAppProvider::GetForTest(profile)
        ->system_web_app_manager()
        .InstallSystemAppsForTesting();
    LocalFileSystemExtensionApiTest::SetUpOnMainThread();
  }

  std::vector<TestDirConfig> GetTestDirContents() override {
    return {{kArbitraryTime, kArbitraryTime, "test_file.png"},
            {kArbitraryTime, kArbitraryTime, "test_file.arw"}};
  }
};

namespace {

// Constants from app_service_metrics.cc.
constexpr int kGalleryUmaBucket = 13;
constexpr int kMediaAppUmaBucket = 19;

// Metric recorded as the result of the call to apps::RecordAppLaunch().
constexpr char kAppLaunchMetric[] = "Apps.DefaultAppLaunch.FromFileManager";

}  // namespace

// Check the interception of ExecuteTask calls to replace Gallery for PNGs. The
// Media App should always be used in this case.
IN_PROC_BROWSER_TEST_F(FileSystemExtensionApiTestWithApps, OpenGalleryForPng) {
  base::HistogramTester histogram_tester;
  EXPECT_TRUE(RunBackgroundPageTestCase("open_gallery",
                                        "testPngOpensGalleryReturnsOpened"))
      << message_;
  histogram_tester.ExpectBucketCount(kAppLaunchMetric, kGalleryUmaBucket, 0);
  histogram_tester.ExpectBucketCount(kAppLaunchMetric, kMediaAppUmaBucket, 1);
}

//
// LocalFileSystemExtensionApiTests.
//

IN_PROC_BROWSER_TEST_F(LocalFileSystemExtensionApiTest,
                       DISABLED_FileSystemOperations) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/filesystem_operations_test",
      FILE_PATH_LITERAL("manifest.json"),
      "",
      FLAGS_NONE)) << message_;
}

IN_PROC_BROWSER_TEST_F(LocalFileSystemExtensionApiTest, FileWatch) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/file_watcher_test",
      FILE_PATH_LITERAL("manifest.json"),
      "",
      FLAGS_NONE)) << message_;
}

IN_PROC_BROWSER_TEST_F(LocalFileSystemExtensionApiTest, FileBrowserHandlers) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/handler_test_runner",
      FILE_PATH_LITERAL("manifest.json"),
      "file_browser/file_browser_handler",
      FLAGS_USE_FILE_HANDLER)) << message_;
}

IN_PROC_BROWSER_TEST_F(LocalFileSystemExtensionApiTest,
                       FileBrowserHandlersLazy) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/handler_test_runner",
      FILE_PATH_LITERAL("manifest.json"),
      "file_browser/file_browser_handler_lazy",
      FLAGS_USE_FILE_HANDLER | FLAGS_LAZY_FILE_HANDLER)) << message_;
}

IN_PROC_BROWSER_TEST_F(LocalFileSystemExtensionApiTest, AppFileHandler) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/handler_test_runner",
      FILE_PATH_LITERAL("manifest.json"),
      "file_browser/app_file_handler",
      FLAGS_USE_FILE_HANDLER)) << message_;
}

IN_PROC_BROWSER_TEST_F(LocalFileSystemExtensionApiTest, DefaultFileHandler) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest("file_browser/default_file_handler",
                                            FILE_PATH_LITERAL("manifest.json"),
                                            "", FLAGS_NONE))
      << message_;
}

//
// RestrictedFileSystemExtensionApiTests.
//
IN_PROC_BROWSER_TEST_F(RestrictedFileSystemExtensionApiTest,
                       FileSystemOperations) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/filesystem_operations_test",
      FILE_PATH_LITERAL("manifest.json"),
      "",
      FLAGS_NONE)) << message_;
}

//
// DriveFileSystemExtensionApiTests.
//
// This test is flaky. See https://crbug.com/1008880.
IN_PROC_BROWSER_TEST_F(DriveFileSystemExtensionApiTest,
                       DISABLED_FileSystemOperations) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/filesystem_operations_test",
      FILE_PATH_LITERAL("manifest.json"),
      "",
      FLAGS_NONE)) << message_;
}

IN_PROC_BROWSER_TEST_F(DriveFileSystemExtensionApiTest, FileWatch) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/file_watcher_test",
      FILE_PATH_LITERAL("manifest.json"),
      "",
      FLAGS_NONE)) << message_;
}

IN_PROC_BROWSER_TEST_F(DriveFileSystemExtensionApiTest, FileBrowserHandlers) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/handler_test_runner",
      FILE_PATH_LITERAL("manifest.json"),
      "file_browser/file_browser_handler",
      FLAGS_USE_FILE_HANDLER)) << message_;
}

IN_PROC_BROWSER_TEST_F(DriveFileSystemExtensionApiTest, Search) {
  // Configure the drive service to return only one search result at a time
  // to simulate paginated searches.
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/drive_search_test",
      FILE_PATH_LITERAL("manifest.json"),
      "",
      FLAGS_NONE)) << message_;
}

IN_PROC_BROWSER_TEST_F(DriveFileSystemExtensionApiTest, AppFileHandler) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/handler_test_runner",
      FILE_PATH_LITERAL("manifest.json"),
      "file_browser/app_file_handler",
      FLAGS_USE_FILE_HANDLER)) << message_;
}

IN_PROC_BROWSER_TEST_F(DriveFileSystemExtensionApiTest, RetainEntry) {
  ui::SelectFileDialog::SetFactory(new FakeSelectFileDialogFactory(
      drivefs_root_.GetPath().Append("drive-user")));
  EXPECT_TRUE(RunFileSystemExtensionApiTest("file_browser/retain_entry",
                                            FILE_PATH_LITERAL("manifest.json"),
                                            "",
                                            FLAGS_NONE))
      << message_;
}

IN_PROC_BROWSER_TEST_F(MultiProfileDriveFileSystemExtensionApiTest,
                       CrossProfileCopy) {
  EXPECT_TRUE(RunFileSystemExtensionApiTest(
      "file_browser/multi_profile_copy",
      FILE_PATH_LITERAL("manifest.json"),
      "",
      FLAGS_NONE)) << message_;
}

//
// LocalAndDriveFileSystemExtensionApiTests.
IN_PROC_BROWSER_TEST_F(LocalAndDriveFileSystemExtensionApiTest,
                       AppFileHandlerMulti) {
  EXPECT_TRUE(
      RunFileSystemExtensionApiTest("file_browser/app_file_handler_multi",
                                    FILE_PATH_LITERAL("manifest.json"),
                                    "",
                                    FLAGS_NONE))
      << message_;
}
}  // namespace
}  // namespace file_manager
