chromium/chrome/browser/ash/floating_workspace/floating_workspace_service.h

// Copyright 2022 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_FLOATING_WORKSPACE_FLOATING_WORKSPACE_SERVICE_H_
#define CHROME_BROWSER_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_SERVICE_H_

#include <memory>
#include <string>

#include "ash/public/cpp/desk_template.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/system/tray/system_tray_observer.h"
#include "base/callback_list.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/uuid.h"
#include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
#include "chrome/browser/ui/ash/desks/desks_client.h"
#include "chromeos/ash/components/network/network_state_handler_observer.h"
#include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/desks_storage/core/desk_model.h"
#include "components/desks_storage/core/desk_sync_bridge.h"
#include "components/desks_storage/core/desk_sync_service.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_service_observer.h"
#include "components/sync_device_info/device_info_sync_service.h"
#include "components/sync_device_info/device_info_tracker.h"
#include "ui/message_center/public/cpp/notification.h"

class Profile;

namespace sync_sessions {
class OpenTabsUIDelegate;
class SessionSyncService;
struct SyncedSession;
}  // namespace sync_sessions

namespace ash {

extern const char kNotificationForNoNetworkConnection[];
extern const char kNotificationForSyncErrorOrTimeOut[];
extern const char kNotificationForRestoreAfterError[];
extern const char kNotificationForProgressStatus[];

// The restore from error notification button index.
enum class RestoreFromErrorNotificationButtonIndex {
  kRestore = 0,
  kCancel,
};

// The notification type for floating workspace service.
enum class FloatingWorkspaceServiceNotificationType {
  kUnknown = 0,
  kNoNetworkConnection,
  kSyncErrorOrTimeOut,
  kRestoreAfterError,
  kProgressStatus,
  kSafeMode
};

// A keyed service to support floating workspace. Note that a periodical
// task `CaptureAndUploadActiveDesk` will be dispatched during service
// initialization.
class FloatingWorkspaceService : public KeyedService,
                                 public message_center::NotificationObserver,
                                 public syncer::SyncServiceObserver,
                                 public apps::AppRegistryCache::Observer,
                                 public apps::AppRegistryCacheWrapper::Observer,
                                 public ash::SessionObserver,
                                 public NetworkStateHandlerObserver,
                                 public ash::SystemTrayObserver,
                                 public chromeos::PowerManagerClient::Observer,
                                 public syncer::DeviceInfoTracker::Observer {
 public:
  static FloatingWorkspaceService* GetForProfile(Profile* profile);

  explicit FloatingWorkspaceService(
      Profile* profile,
      floating_workspace_util::FloatingWorkspaceVersion version);

  ~FloatingWorkspaceService() override;

  // Used in constructor for initializations
  void Init(syncer::SyncService* sync_service,
            desks_storage::DeskSyncService* desk_sync_service,
            syncer::DeviceInfoSyncService* device_info_sync_service);

  // Add subscription to foreign session changes.
  void SubscribeToForeignSessionUpdates();

  // Get and restore most recently used device browser session
  // remote or local.
  void RestoreBrowserWindowsFromMostRecentlyUsedDevice();

  void TryRestoreMostRecentlyUsedSession();

  void CaptureAndUploadActiveDeskForTest(
      std::unique_ptr<DeskTemplate> desk_template);

  // Get latest Floating Workspace Template from DeskSyncBridge.
  const DeskTemplate* GetLatestFloatingWorkspaceTemplate();

  // syncer::SyncServiceObserver overrides:
  void OnStateChanged(syncer::SyncService* sync) override;
  void OnSyncShutdown(syncer::SyncService* sync) override;

  // message_center::NotificationObserver overrides:
  void Click(const std::optional<int>& button_index,
             const std::optional<std::u16string>& reply) override;

  // ash::SessionObserver overrides:
  void OnActiveUserSessionChanged(const AccountId& account_id) override;
  void OnLockStateChanged(bool locked) override;

  // NetworkStateHandlerObserver:
  void OnShuttingDown() override;
  void NetworkConnectionStateChanged(const NetworkState* network) override;
  void DefaultNetworkChanged(const NetworkState* network) override;

  // ash::SystemTrayObserver overrides:
  void OnFocusLeavingSystemTray(bool reverse) override;
  void OnSystemTrayBubbleShown() override;

  // chromeos::PowerManagerClient::Observer overrides.
  void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
  void SuspendDone(base::TimeDelta sleep_duration) override;

  // syncer::DeviceInfoTracker::Observer:
  void OnDeviceInfoChange() override;
  void OnDeviceInfoShutdown() override;

  void MaybeCloseNotification();

  std::vector<const ash::DeskTemplate*> GetFloatingWorkspaceTemplateEntries();

  // Setups the convenience pointers to the dependent services and observers.
  // This will be called when the service is first initialized and when the
  // active user session is changed back to the first logged in user.
  void SetUpServiceAndObservers(
      syncer::SyncService* sync_service,
      desks_storage::DeskSyncService* desk_sync_service,
      syncer::DeviceInfoSyncService* device_info_sync_service);

  // Shuts down the observers and dependent services.
  // This will be called when the user session changes to a different user or
  // on service shutdown.
  void ShutDownServicesAndObservers();

  // Capture the current active desk task, running every ~30(TBD) seconds.
  // Upload captured desk to chrome sync and record the randomly generated
  // UUID key to `floating_workspace_template_uuid_`.
  void CaptureAndUploadActiveDesk();

 protected:
  std::unique_ptr<DeskTemplate> previously_captured_desk_template_;

 private:
  // AppRegistryCache::Observer
  void OnAppRegistryCacheWillBeDestroyed(
      apps::AppRegistryCache* cache) override;
  void OnAppTypeInitialized(apps::AppType app_type) override;

  // AppRegistryCacheWrapper::Observer
  void OnAppRegistryCacheAdded(const AccountId& account_id) override;

  void InitForV1();
  void InitForV2(syncer::SyncService* sync_service,
                 desks_storage::DeskSyncService* desk_sync_service,
                 syncer::DeviceInfoSyncService* device_info_sync_service);

  const sync_sessions::SyncedSession* GetMostRecentlyUsedRemoteSession();

  const sync_sessions::SyncedSession* GetLocalSession();

  // Virtual for testing.
  virtual void RestoreForeignSessionWindows(
      const sync_sessions::SyncedSession* session);

  // Virtual for testing.
  virtual void RestoreLocalSessionWindows();

  // Virtual for testing.
  virtual sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate();

  // Start and Stop capturing and uploading the active desks.
  void StartCaptureAndUploadActiveDesk();
  void StopCaptureAndUploadActiveDesk();

  // Start and stop the progress bar notification.
  void MaybeStartProgressBarNotification();
  void StopProgressBarNotification();

  // Handles the updating of progress bar notification.
  void HandleProgressBarStatus();

  // Stops the progress bar and resumes the latest floating workspace. This is
  // called when the app cache is ready and we have received `kUpToDate` from
  // sync service.
  void StopProgressBarAndRestoreFloatingWorkspace();

  // Restore last saved floating workspace desk for current user with
  // floating_workspace_template_uuid_.
  void RestoreFloatingWorkspaceTemplate(const DeskTemplate* desk_template);

  // Launch downloaded floating workspace desk when all conditions are met.
  // Virtual for testing.
  virtual void LaunchFloatingWorkspaceTemplate(
      const DeskTemplate* desk_template);

  // Handles the recording of the error for template launch.
  void HandleTemplateLaunchErrors(DesksClient::DeskActionError error);

  // Callback function that is run after a floating workspace template
  // is downloaded and launched.
  void OnTemplateLaunched(std::optional<DesksClient::DeskActionError> error,
                          const base::Uuid& desk_uuid);

  // Return the desk client to be used, in test it will return a mocked one.
  virtual DesksClient* GetDesksClient();

  // Compare currently captured and previous floating workspace desk.
  // Called by CaptureAndUploadActiveDesk before upload.
  // If no difference is recorded no upload job will be triggered.
  bool IsCurrentDeskSameAsPrevious(DeskTemplate* current_desk_template) const;

  // Handles the recording of the error for template capture.
  void HandleTemplateCaptureErrors(DesksClient::DeskActionError error);

  // Callback function that is run after a floating workspace template is
  // captured by `desks_storage::DeskSyncBridge`.
  void OnTemplateCaptured(std::optional<DesksClient::DeskActionError> error,
                          std::unique_ptr<DeskTemplate> desk_template);

  // Upload floating workspace desk template after detecting that it's a
  // different template. Virtual for testing.
  virtual void UploadFloatingWorkspaceTemplateToDeskModel(
      std::unique_ptr<DeskTemplate> desk_template);

  void OnTemplateUploaded(
      desks_storage::DeskModel::AddOrUpdateEntryStatus status,
      std::unique_ptr<DeskTemplate> new_entry);

  // Get the associated floating workspace uuid for the current device. Return
  // an std::nullopt if there is no floating workspace uuid that is associated
  // with the current device.
  std::optional<base::Uuid> GetFloatingWorkspaceUuidForCurrentDevice();
  // When sync passes an error status to floating workspace service,
  // floating workspace service should send notification to user asking
  // whether to restore the most recent FWS desk from local storage.
  void HandleSyncError();

  // When floating workspace service waited long enough but no desk is
  // restored floating workspace service should send notification to user
  // asking whether to restore the most recent FWS desk from local storage.
  void MaybeHandleDownloadTimeOut();

  void SendNotification(const std::string& id);

  // Performs garbage collection of stale floating workspace templates. A
  // floating workspace template is considered stale if it's older than 30
  // days. The only exception is if it's the only floating workspace
  // template associated with the current user, which we want to keep.
  void DoGarbageCollection(const DeskTemplate* exclude_template);

  // Close desks that were already open on current device.
  void RemoveAllPreviousDesksExceptActiveDesk(
      const base::Uuid& exclude_desk_uuid);

  // Sign out of the current user session when we detect another active
  // session after this service was started.
  void MaybeSignOutOfCurrentSession();

  // Updates the `is_cache_ready_` status if all the required app types are
  // initialized.
  bool AreRequiredAppTypesInitialized();

  // Once network state or sync feature active state changes have been detected,
  // handle the internet connectivity notification appropriately based on
  // connection.
  void OnNetworkStateOrSyncServiceStateChanged();

  // Initial task start. This involves checking the network connectivity upon
  // log in and sending a notification if no network is connected or start
  // posting a task for waiting for sync server downloads to complete.
  void InitiateSigninTask();

  // Returns true if we should exclude the `floating_workspace_template` from
  // consideration for either sign out or restore.
  bool ShouldExcludeTemplate(const DeskTemplate* floating_workspace_template);

  // Called by local_device_info_provider when it is ready.
  void OnLocalDeviceInfoProviderReady();

  // Updates the local device info with the new floating workspace recent signin
  // time.
  void UpdateLocalDeviceInfo();

  const raw_ptr<Profile> profile_;

  const floating_workspace_util::FloatingWorkspaceVersion version_;

  raw_ptr<sync_sessions::SessionSyncService> session_sync_service_;

  base::CallbackListSubscription foreign_session_updated_subscription_;

  // Flag to determine if we should run the restore.
  bool should_run_restore_ = true;

  // Tells us whether or not the apps cache is ready.
  bool is_cache_ready_ = false;

  // Flag to tell us if we should launch on cache is ready.
  bool should_launch_on_ready_ = false;

  // Flag to tell us if we should restore when we wake up from sleep.
  bool restore_upon_wake_ = false;

  // Flag to tell us if we should launch the floating workspace template onto a
  // new desk.
  bool launch_on_new_desk_ = false;

  // Time when the service is initialized.
  base::TimeTicks initialization_timeticks_;

  // Time when service is initialized in base::Time format for comparison with
  // desk template time.
  base::Time initialization_time_;

  // Time when we first received `kUpToDate` status from `sync_service_`
  std::optional<base::TimeTicks> first_uptodate_download_timeticks_;

  // Time when the last template was uploaded.
  base::TimeTicks last_uploaded_timeticks_;

  // The in memory cache of the latest floating workspace template. This is
  // populated when we first capture a floating workspace template and every
  // time we receive a new floating workspace template from sync. This is used
  // to detect stale entries when we rerun floating workspace flow from sleep
  // mode.
  std::optional<base::Time> timestamp_before_suspend_;

  // The in memory cache of the latest workspace desk datatype download status.
  std::optional<syncer::SyncService::DataTypeDownloadStatus>
      download_status_cache_;

  // Timer used for periodic capturing and uploading.
  base::RepeatingTimer timer_;

  // Timer used to wait for internet connection after service initialization.
  base::OneShotTimer connection_timer_;

  // Timer used to periodically update the progress status bar based on time
  // from the 2 seconds after login to 15 seconds max wait time.
  base::RepeatingTimer progress_timer_;

  // Convenience pointer to desks_storage::DeskSyncService. Guaranteed to be
  // not null for the duration of `this`.
  raw_ptr<desks_storage::DeskSyncService> desk_sync_service_ = nullptr;

  raw_ptr<syncer::SyncService> sync_service_ = nullptr;

  raw_ptr<syncer::DeviceInfoSyncService> device_info_sync_service_ = nullptr;

  base::CallbackListSubscription local_device_info_ready_subscription_;

  // The uuid associated with this device's floating workspace template. This is
  // populated when we first capture a floating workspace template.
  std::optional<base::Uuid> floating_workspace_uuid_;

  std::unique_ptr<message_center::Notification> notification_;
  std::string progress_notification_id_;

  // The in memory cache of the floating workspace that should be restored
  // after downloading latest updates. Saved in case the user delays resuming
  // the session and a captured template was uploaded.
  std::unique_ptr<DeskTemplate> floating_workspace_template_to_restore_ =
      nullptr;

  // scoped Observations
  base::ScopedObservation<apps::AppRegistryCache,
                          apps::AppRegistryCache::Observer>
      app_cache_obs_{this};
  base::ScopedObservation<apps::AppRegistryCacheWrapper,
                          apps::AppRegistryCacheWrapper::Observer>
      app_cache_wrapper_obs_{this};
  // Weak pointer factory used to provide references to this service.
  base::WeakPtrFactory<FloatingWorkspaceService> weak_pointer_factory_{this};
};

}  // namespace ash

#endif  // CHROME_BROWSER_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_SERVICE_H_