chromium/chrome/browser/ash/drive/drive_integration_service.h

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_DRIVE_DRIVE_INTEGRATION_SERVICE_H_
#define CHROME_BROWSER_ASH_DRIVE_DRIVE_INTEGRATION_SERVICE_H_

#include <memory>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "chromeos/ash/components/drivefs/drivefs_host.h"
#include "chromeos/ash/components/drivefs/drivefs_pinning_manager.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "chromeos/ash/components/drivefs/mojom/notifications.mojom.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_handler_observer.h"
#include "chromeos/crosapi/mojom/drive_integration_service.mojom.h"
#include "components/drive/event_logger.h"
#include "components/drive/file_errors.h"
#include "components/drive/file_system_core_util.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_change_registrar.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/auth_service_interface.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "third_party/abseil-cpp/absl/types/variant.h"

class PrefService;

namespace base {
class SequencedTaskRunner;
}  // namespace base

namespace drivefs {
class DriveFsHost;
class DriveFsSearchQuery;

namespace mojom {
class DriveFs;
}  // namespace mojom
}  // namespace drivefs

namespace user_prefs {
class PrefRegistrySyncable;
}  // namespace user_prefs

namespace drive {

namespace internal {
class ResourceMetadataStorage;
}  // namespace internal

// Mounting status. These values are persisted to logs. Entries should not be
// renumbered and numeric values should never be reused.
enum class DriveMountStatus {
  kSuccess = 0,
  kUnknownFailure = 1,
  kTemporaryUnavailable = 2,
  kInvocationFailure = 3,
  kUnexpectedDisconnect = 4,
  kTimeout = 5,
  kMaxValue = kTimeout,
};

struct QuickAccessItem {
  base::FilePath path;
  double confidence;
};

// Notifications/Errors coming from DriveFs side which we need to persist in
// the Chrome side.
struct PersistedMessage {
  // Where does the message come from in DriveFs.
  enum Source {
    kNotification = 0,
    kError = 1,
  };
  Source source;

  // DriveFs Notification/Error types which require persistence.
  using Type = absl::variant<drivefs::mojom::DriveFsNotification::Tag,
                             drivefs::mojom::MirrorSyncError::Type>;
  Type type;

  base::FilePath path;

  int64_t stable_id;
};

// DriveIntegrationService is used to integrate Drive to Chrome. This class
// exposes the file system representation built on top of Drive and some
// other Drive related objects to the file manager, and some other sub
// systems.
//
// The class is essentially a container that manages lifetime of the objects
// that are used to integrate Drive to Chrome. The object of this class is
// created per-profile.
class DriveIntegrationService : public KeyedService,
                                public drivefs::DriveFsHost::MountObserver,
                                drivefs::pinning::PinningManager::Observer,
                                ash::NetworkStateHandler::Observer {
 public:
  using DriveFsMojoListenerFactory = base::RepeatingCallback<
      std::unique_ptr<drivefs::DriveFsBootstrapListener>()>;
  using GetQuickAccessItemsCallback =
      base::OnceCallback<void(FileError, std::vector<QuickAccessItem>)>;
  using SearchDriveByFileNameCallback =
      drivefs::mojom::SearchQuery::GetNextPageCallback;
  using GetThumbnailCallback =
      base::OnceCallback<void(const std::optional<std::vector<uint8_t>>&)>;
  using GetReadOnlyAuthenticationTokenCallback =
      base::OnceCallback<void(google_apis::ApiErrorCode code,
                              const std::string& access_token)>;

  // test_mount_point_name, test_cache_root and
  // test_drivefs_mojo_listener_factory are used by tests to inject customized
  // instances.
  // Pass NULL or the empty value when not interested.
  DriveIntegrationService(
      Profile* profile,
      const std::string& test_mount_point_name,
      const base::FilePath& test_cache_root,
      DriveFsMojoListenerFactory test_drivefs_mojo_listener_factory = {});

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

  ~DriveIntegrationService() override;

  // KeyedService override.
  void Shutdown() override;

  void SetEnabled(bool enabled);
  bool is_enabled() const { return enabled_; }

  bool IsOnline() const { return is_online_; }

  bool IsMounted() const;

  bool mount_failed() const { return mount_failed_; }

  // Returns the path of the mount point for drive. It is only valid to call if
  // |IsMounted()|.
  base::FilePath GetMountPointPath() const;

  // Returns the path of DriveFS log if enabled or empty path.
  base::FilePath GetDriveFsLogPath() const;

  // Returns the path of the DriveFs content cache.
  base::FilePath GetDriveFsContentCachePath() const;

  // Returns true if |local_path| resides inside |GetMountPointPath()|.
  // In this case |drive_path| will contain 'drive' path of this file, e.g.
  // reparented to the mount point.
  // It is only valid to call if |IsMounted()|.
  bool GetRelativeDrivePath(const base::FilePath& local_path,
                            base::FilePath* drive_path) const;

  bool IsSharedDrive(const base::FilePath& local_path) const;

  // Base class for classes that need to observe events from
  // DriveIntegrationService. All events are notified on the UI thread.
  class Observer : public base::CheckedObserver {
   public:
    ~Observer() override;

    // Triggered when the `DriveIntegrationService` is being destroyed.
    virtual void OnDriveIntegrationServiceDestroyed() {}

    // Triggered when the file system is mounted.
    virtual void OnFileSystemMounted() {}

    // Triggered when the file system is being unmounted.
    virtual void OnFileSystemBeingUnmounted() {}

    // Triggered when mounting the filesystem has failed in a fashion that will
    // not be automatically retried.
    virtual void OnFileSystemMountFailed() {}

    // Triggered when the mirroring functionality is enabled.
    virtual void OnMirroringEnabled() {}

    // Triggered when the mirroring functionality is disabled.
    virtual void OnMirroringDisabled() {}

    // Triggered when the bulk pinning manager reports progress.
    virtual void OnBulkPinProgress(const drivefs::pinning::Progress& progress) {
    }

    // Triggered when the bulk-pinning manager is fully initialized.
    virtual void OnBulkPinInitialized() {}

    // Triggered when the network connection to Drive could have changed.
    virtual void OnDriveConnectionStatusChanged(util::ConnectionStatus status) {
    }

    // Starts observing the given service.
    void Observe(DriveIntegrationService* service);

    // Stops observing the service.
    void Reset();

    // Gets a pointer to the service being observed.
    DriveIntegrationService* GetService() const { return service_; }

   private:
    // The service being observed.
    raw_ptr<DriveIntegrationService> service_ = nullptr;
  };

  // MountObserver implementation.
  void OnMounted(const base::FilePath& mount_path) override;
  void OnUnmounted(std::optional<base::TimeDelta> remount_delay) override;
  void OnMountFailed(MountFailure failure,
                     std::optional<base::TimeDelta> remount_delay) override;

  // PinningManager::Observer implementation
  using Progress = drivefs::pinning::Progress;
  void OnProgress(const Progress& progress) override;

  EventLogger* GetLogger() { return &logger_; }

  // Clears all the local cache folder and remounts the file system. |callback|
  // is called with true when this operation is done successfully. Otherwise,
  // |callback| is called with false. |callback| must not be null.
  void ClearCacheAndRemountFileSystem(base::OnceCallback<void(bool)> callback);

  // Returns the DriveFsHost if it is enabled.
  drivefs::DriveFsHost* GetDriveFsHost() const;

  // Returns the PinningManager if DriveFS is mounted and bulk-pinning is
  // enabled.
  using PinningManager = drivefs::pinning::PinningManager;
  PinningManager* GetPinningManager() const;

  // Returns the mojo interface to the DriveFs daemon if it is enabled and
  // connected.
  drivefs::mojom::DriveFs* GetDriveFsInterface() const;

  void GetQuickAccessItems(int max_number,
                           GetQuickAccessItemsCallback callback);

  void SearchDriveByFileName(
      std::string query,
      int max_results,
      drivefs::mojom::QueryParameters::SortField sort_field,
      drivefs::mojom::QueryParameters::SortDirection sort_direction,
      drivefs::mojom::QueryParameters::QuerySource query_source,
      SearchDriveByFileNameCallback callback) const;
  // Returns nullptr if DriveFS is not mounted.
  std::unique_ptr<drivefs::DriveFsSearchQuery> CreateSearchQueryByFileName(
      std::string query,
      int max_results,
      drivefs::mojom::QueryParameters::SortField sort_field,
      drivefs::mojom::QueryParameters::SortDirection sort_direction,
      drivefs::mojom::QueryParameters::QuerySource query_source) const;

  // Returns the metadata for Drive file at |local_path|.
  void GetMetadata(const base::FilePath& local_path,
                   drivefs::mojom::DriveFs::GetMetadataCallback callback);

  // Locates files or dirs by their server-side ID. Paths are relative to the
  // mount point.
  void LocateFilesByItemIds(
      const std::vector<std::string>& item_ids,
      drivefs::mojom::DriveFs::LocateFilesByItemIdsCallback callback);

  // Returns the total and free space available in the user's Drive.
  void GetQuotaUsage(drivefs::mojom::DriveFs::GetQuotaUsageCallback callback);

  // Returns the total and free space available in the user's Drive.
  // Additionally, if the user belongs to an organization, whether the
  // organization quota is full or not, and the name of the organization.
  void GetPooledQuotaUsage(
      drivefs::mojom::DriveFs::GetPooledQuotaUsageCallback callback);

  void RestartDrive();

  // Sets the arguments to be parsed by DriveFS on startup. Should only be
  // called in developer mode.
  void SetStartupArguments(std::string arguments,
                           base::OnceCallback<void(bool)> callback);

  // Gets the currently set arguments parsed by DriveFS on startup. Should only
  // be called in developer mode.
  void GetStartupArguments(
      base::OnceCallback<void(const std::string&)> callback);

  // Enables or disables performance tracing, which logs to
  // |data_dir_path|/Logs/drive_fs_trace.
  void SetTracingEnabled(bool enabled);

  // Enables or disables networking for testing. Should only be called in
  // developer mode.
  void SetNetworkingEnabled(bool enabled);

  // Overrides syncing to be paused if enabled. Should only be called in
  // developer mode.
  void ForcePauseSyncing(bool enabled);

  // Dumps account settings (including feature flags) to
  // |data_dir_path/account_settings. Should only be called in developer mode.
  void DumpAccountSettings();

  // Loads account settings (including feature flags) from
  // |data_dir_path/account_settings. Should only be called in developer mode.
  void LoadAccountSettings();

  // Returns a PNG containing a thumbnail for |path|. If |crop_to_square|, a
  // 360x360 thumbnail, cropped to fit a square is returned; otherwise a
  // thumbnail up to 500x500, maintaining aspect ration, is returned. If |path|
  // does not exist or does not have a thumbnail, |thumbnail| will be null.
  void GetThumbnail(const base::FilePath& path,
                    bool crop_to_square,
                    GetThumbnailCallback callback);

  // Toggle mirroring on or off defined by |enabled|.
  void ToggleMirroring(
      bool enabled,
      drivefs::mojom::DriveFs::ToggleMirroringCallback callback);

  // Toggle syncing for a specific path. Should only be called once mirroring
  // has been enabled via |ToggleMirroring|.
  void ToggleSyncForPath(
      const base::FilePath& path,
      drivefs::mojom::MirrorPathStatus status,
      drivefs::mojom::DriveFs::ToggleSyncForPathCallback callback);

  // Retrieves a list of paths being synced.
  void GetSyncingPaths(
      drivefs::mojom::DriveFs::GetSyncingPathsCallback callback);

  // Tells DriveFS to update its cached pin states of hosted files (once).
  void PollHostedFilePinStates();

  // Returns whether mirroring is enabled.
  bool IsMirroringEnabled();

  // Requests Drive to resync the office file at |local_path| from the cloud.
  void ForceReSyncFile(const base::FilePath& local_path,
                       base::OnceClosure callback);

  // Gets a read-only OAuth token that allows downloading files from the user's
  // Drive. If an error occurs or the user does not have access to download
  // files from Drive, `access_token` will be an empty string.
  void GetReadOnlyAuthenticationToken(
      GetReadOnlyAuthenticationTokenCallback callback);

  // Returns via callback the amount of storage taken by all currently pinned
  // files.
  void GetTotalPinnedSize(base::OnceCallback<void(int64_t)> callback);

  void ClearOfflineFiles(base::OnceCallback<void(FileError)> callback);

  // Tells Drive to immediately start uploading the file at |path|, which is a
  // relative path in Drive. This avoids queuing delays for newly created files,
  // when we are sure that there are no more subsequent operations on the file
  // that we should wait for.
  void ImmediatelyUpload(
      const base::FilePath& path,
      drivefs::mojom::DriveFs::ImmediatelyUploadCallback callback);

  // Called by lacros to register a bridge that this service can call into when
  // DriveFS wants to initiate a connection to an extension in lacros.
  void RegisterDriveFsNativeMessageHostBridge(
      mojo::PendingRemote<crosapi::mojom::DriveFsNativeMessageHostBridge>
          bridge);

  // Gets counts of files in docs offline extension.
  void GetDocsOfflineStats(
      drivefs::mojom::DriveFs::GetDocsOfflineStatsCallback callback);

  // Gets the mirror sync status for a specific file.
  void GetMirrorSyncStatusForFile(
      const base::FilePath& path,
      drivefs::mojom::DriveFs::GetMirrorSyncStatusForFileCallback callback);

  // Gets the mirror sync status for a specific directory.
  void GetMirrorSyncStatusForDirectory(
      const base::FilePath& path,
      drivefs::mojom::DriveFs::GetMirrorSyncStatusForDirectoryCallback
          callback);

  void OnNetworkChanged();

  // Register the drive related profile prefs.
  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* prefs);

 private:
  enum class State {
    kNone,
    kInitializing,
    kInitialized,
    kRemounting,
  };

  class DriveFsHolder;

  PrefService* GetPrefs() const { return profile_->GetPrefs(); }

  // Returns true if Drive is enabled.
  // Must be called on UI thread.
  bool IsDriveEnabled();

  enum class DirResult { kError, kExisting, kCreated };
  static DirResult EnsureDirectoryExists(const base::FilePath& data_dir);

  // Registers remote file system for drive mount point. If DriveFS is enabled,
  // but not yet mounted, this will start it mounting and wait for it to
  // complete before adding the mount point.
  void AddDriveMountPoint();

  // Mounts Drive if the directory exists.
  void MaybeMountDrive(const base::FilePath& data_dir,
                       DirResult data_dir_result);

  // Registers remote file system for drive mount point.
  bool AddDriveMountPointAfterMounted();

  // Unregisters drive mount point from File API.
  void RemoveDriveMountPoint();

  // Adds back the drive mount point.
  // Used to implement ClearCacheAndRemountFileSystem().
  void AddBackDriveMountPoint(base::OnceCallback<void(bool)> callback,
                              FileError error);

  // Unregisters drive mount point, and if |remount_delay| is specified
  // then tries to add it back after that delay. If |remount_delay| isn't
  // specified, |failed_to_mount| is true and the user is offline, schedules a
  // retry when the user is online.
  void MaybeRemountFileSystem(std::optional<base::TimeDelta> remount_delay,
                              bool failed_to_mount);

  // Helper function for ClearCacheAndRemountFileSystem() that deletes the cache
  // folder.
  void ClearCacheAndRemountFileSystemAfterDelay(
      base::OnceCallback<void(bool)> callback);

  // Helper function for ClearCacheAndRemountFileSystem() that remounts Drive if
  // necessary.
  void MaybeRemountFileSystemAfterClearCache(
      base::OnceCallback<void(bool)> callback,
      bool success);

  // Initializes the object. This function should be called before any
  // other functions.
  void Initialize();

  // Called when metadata initialization is done. Continues initialization if
  // the metadata initialization is successful.
  void InitializeAfterMetadataInitialized(FileError error);

  // Change the download directory to the local "Downloads" if the download
  // destination is set under Drive. This must be called when disabling Drive.
  void AvoidDriveAsDownloadDirectoryPreference();

  bool DownloadDirectoryPreferenceIsInDrive();

  // Migrate pinned files from the old Drive integration to DriveFS.
  void MigratePinnedFiles();

  // Pin all the files in |files_to_pin| with DriveFS.
  void PinFiles(const std::vector<base::FilePath>& files_to_pin);

  // Called when the "drivefs.bulk_pinning_enabled" pref changes value.
  // Starts or stops DriveFS bulk pinning accordingly.
  // Does nothing if there is no bulk-pinning manager.
  void StartOrStopBulkPinning();

  // Called when the "drivefs.bulk_pinning.visible" pref changes value.
  // Creates or deletes the DriveFS bulk-pinning manager accordingly.
  void CreateOrDeleteBulkPinningManager();

  // Regularly samples the bulk-pinning preference and stores the result in a
  // UMA histogram.
  void SampleBulkPinningPref();

  void OnGetOfflineItemsPage(
      int64_t total_size,
      mojo::Remote<drivefs::mojom::SearchQuery> search_query,
      base::OnceCallback<void(int64_t)> callback,
      FileError error,
      std::optional<std::vector<drivefs::mojom::QueryItemPtr>> results);

  void OnGetQuickAccessItems(
      GetQuickAccessItemsCallback callback,
      FileError error,
      std::optional<std::vector<drivefs::mojom::QueryItemPtr>> items);

  void OnSearchDriveByFileName(
      SearchDriveByFileNameCallback callback,
      FileError error,
      std::optional<std::vector<drivefs::mojom::QueryItemPtr>> items);

  void OnEnableMirroringStatusUpdate(drivefs::mojom::MirrorSyncStatus status);
  void OnMyFilesSyncPathAdded(drive::FileError status);

  void OnDisableMirroringStatusUpdate(drivefs::mojom::MirrorSyncStatus status);

  // Before adding a new root, get all existing roots first to see if it exists
  // or not. If it exists, do nothing.
  void OnGetSyncPathsForAddingPath(
      const base::FilePath& path_to_add,
      drivefs::mojom::DriveFs::ToggleSyncForPathCallback callback,
      drive::FileError status,
      const std::vector<base::FilePath>& paths);

  // Toggle syncing for |path| if the the directory exists.
  void ToggleSyncForPathIfDirectoryExists(
      const base::FilePath& path,
      drivefs::mojom::DriveFs::ToggleSyncForPathCallback callback,
      bool exists);

  void OnUpdateFromPairedDocComplete(const base::FilePath& drive_path,
                                     base::OnceClosure callback,
                                     FileError error);

  void OnGetOfflineFilesSpaceUsage(base::OnceCallback<void(int64_t)> callback,
                                   FileError error,
                                   int64_t total_size);

  void RegisterPrefs();
  void OnDrivePrefChanged();
  void OnMirroringPrefChanged();

  // NetworkStateHandler::Observer implementation.
  void PortalStateChanged(const ash::NetworkState*,
                          ash::NetworkState::PortalState portal_state) override;
  void DefaultNetworkChanged(const ash::NetworkState*) override;
  void OnShuttingDown() override;

  friend class DriveIntegrationServiceFactory;

  const raw_ptr<Profile> profile_;

  State state_ = State::kNone;
  bool enabled_ = false;
  bool mount_failed_ = false;
  bool in_clear_cache_ = false;

  // Is the bulk-pinning preference sampling task currently scheduled?
  bool bulk_pinning_pref_sampling_ = false;
  bool mirroring_enabled_ = false;
  bool is_online_ = true;
  bool remount_when_online_ = false;

  // Custom mount point name that can be injected for testing in constructor.
  std::string mount_point_name_;
  base::FilePath cache_root_directory_;
  EventLogger logger_;
  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
  std::unique_ptr<internal::ResourceMetadataStorage, util::DestroyHelper>
      metadata_storage_;

  base::ObserverList<Observer, true> observers_;

  std::unique_ptr<DriveFsHolder> drivefs_holder_;

  std::unique_ptr<PinningManager> pinning_manager_;

  int drivefs_total_failures_count_ = 0;
  int drivefs_consecutive_failures_count_ = 0;

  // Used to fetch authentication and refresh tokens from Drive.
  std::unique_ptr<google_apis::AuthServiceInterface> auth_service_;

  base::TimeTicks mount_start_;

  base::Time last_offline_storage_size_time_;
  int64_t last_offline_storage_size_result_;

  PrefChangeRegistrar registrar_;

  base::ScopedObservation<ash::NetworkStateHandler,
                          ash::NetworkStateHandler::Observer>
      network_state_handler_{this};

  // Note: This should remain the last member so it'll be destroyed and
  // invalidate its weak pointers before any other members are destroyed.
  base::WeakPtrFactory<DriveIntegrationService> weak_ptr_factory_{this};

  FRIEND_TEST_ALL_PREFIXES(DriveIntegrationServiceTest, EnsureDirectoryExists);
};

// Singleton that owns all instances of DriveIntegrationService and
// associates them with Profiles.
class DriveIntegrationServiceFactory : public ProfileKeyedServiceFactory {
 public:
  // Factory function used by tests.
  using FactoryCallback =
      base::RepeatingCallback<DriveIntegrationService*(Profile* profile)>;

  // Sets and resets a factory function for tests. See below for why we can't
  // use BrowserContextKeyedServiceFactory::SetTestingFactory().
  class ScopedFactoryForTest {
   public:
    explicit ScopedFactoryForTest(FactoryCallback* factory_for_test);
    ~ScopedFactoryForTest();
  };

  // Returns the DriveIntegrationService for |profile|, creating it if it is
  // not yet created.
  static DriveIntegrationService* GetForProfile(Profile* profile);

  // Returns the DriveIntegrationService that is already associated with
  // |profile|, if it is not yet created it will return NULL.
  static DriveIntegrationService* FindForProfile(Profile* profile);

  // Returns the DriveIntegrationServiceFactory instance.
  static DriveIntegrationServiceFactory* GetInstance();

 private:
  friend struct base::DefaultSingletonTraits<DriveIntegrationServiceFactory>;

  DriveIntegrationServiceFactory();
  ~DriveIntegrationServiceFactory() override;

  // BrowserContextKeyedServiceFactory overrides.
  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
      content::BrowserContext* context) const override;

  // This is static so it can be set without instantiating the factory. This
  // allows factory creation to be delayed until it normally happens (on profile
  // creation) rather than when tests are set up. DriveIntegrationServiceFactory
  // transitively depends on ExtensionSystemFactory which crashes if created too
  // soon (i.e. before the BrowserProcess exists).
  static FactoryCallback* factory_for_test_;
};

}  // namespace drive

#endif  // CHROME_BROWSER_ASH_DRIVE_DRIVE_INTEGRATION_SERVICE_H_