chromium/chrome/browser/ash/remote_apps/remote_apps_manager.h

// Copyright 2020 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_REMOTE_APPS_REMOTE_APPS_MANAGER_H_
#define CHROME_BROWSER_ASH_REMOTE_APPS_REMOTE_APPS_MANAGER_H_

#include <map>
#include <optional>
#include <string>
#include <vector>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "chrome/browser/apps/app_service/publishers/remote_apps.h"
#include "chrome/browser/ash/app_list/app_list_model_updater_observer.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/chrome_app_list_model_updater.h"
#include "chrome/browser/ash/remote_apps/remote_apps_impl.h"
#include "chrome/browser/ash/remote_apps/remote_apps_model.h"
#include "chrome/browser/ash/remote_apps/remote_apps_types.h"
#include "chromeos/components/remote_apps/mojom/remote_apps.mojom.h"
#include "components/keyed_service/core/keyed_service.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"

class AppListModelUpdater;
class ChromeAppListItem;
class Profile;

namespace apps {
struct MenuItems;
}  // namespace apps

namespace gfx {
class ImageSkia;
}  // namespace gfx

namespace extensions {
class EventRouter;
}  // namespace extensions

namespace ash {

class RemoteAppsImpl;

// KeyedService which manages the logic for |AppType::kRemote| in AppService.
// This service is created for Managed Guest Sessions and Regular User Sessions.
// The IDs of the added apps and folders are GUIDs generated using
// |base::Uuid::GenerateRandomV4().AsLowercaseString()|.
// See crbug.com/1101208 for more details on Remote Apps.
class RemoteAppsManager
    : public KeyedService,
      public apps::RemoteApps::Delegate,
      public app_list::AppListSyncableService::Observer,
      public AppListModelUpdaterObserver,
      public chromeos::remote_apps::mojom::RemoteAppsFactory,
      public chromeos::remote_apps::mojom::RemoteAppsLacrosBridge {
 public:
  class Observer : public base::CheckedObserver {
   public:
    ~Observer() override = default;

    // Invoked when an app is launched. |id| is the ID of the app.
    virtual void OnAppLaunched(const std::string& id) {}
  };

  class ImageDownloader {
   public:
    virtual ~ImageDownloader() = default;

    using DownloadCallback = base::OnceCallback<void(const gfx::ImageSkia&)>;
    virtual void Download(const GURL& url, DownloadCallback callback) = 0;
  };

  explicit RemoteAppsManager(Profile* profile);
  RemoteAppsManager(const RemoteAppsManager&) = delete;
  RemoteAppsManager& operator=(const RemoteAppsManager&) = delete;
  ~RemoteAppsManager() override;

  bool is_initialized() const { return is_initialized_; }

  void BindFactoryInterface(
      mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteAppsFactory>
          pending_remote_apps_factory);

  void BindLacrosBridgeInterface(
      mojo::PendingReceiver<
          chromeos::remote_apps::mojom::RemoteAppsLacrosBridge>
          pending_remote_apps_lacros_bridge);

  using AddAppCallback =
      base::OnceCallback<void(const std::string& id, RemoteAppsError error)>;

  // Adds a app with the given `name`. If `folder_id` is non-empty, the app is
  // added to the folder with the given ID. The icon of the app is an image
  // retrieved from `icon_url` and is retrieved asynchronously. If the icon has
  // not been downloaded, or there is an error in downloading the icon, a
  // placeholder icon will be used. If `add_to_front` is true and the app has
  // no parent folder, the app will be added to the front of the app item list.
  // `source_id` is a string used to identify the caller of this method. This
  // identifier is typically an extension or app ID.
  // The callback will be run with the ID of the added app, or an error if
  // there is one.
  // Adding to a non-existent folder will result in an error.
  // Adding an app before the manager is initialized will result in an error.
  void AddApp(const std::string& source_id,
              const std::string& name,
              const std::string& folder_id,
              const GURL& icon_url,
              bool add_to_front,
              AddAppCallback callback);

  // Adds a folder if the specified folder is missing in `model_updater_`.
  void MaybeAddFolder(const std::string& folder_id);

  // Returns a const pointer to the info of the specified app. If the app does
  // not exist, returns a nullptr.
  const RemoteAppsModel::AppInfo* GetAppInfo(const std::string& app_id) const;

  // Deletes the app with id |id|.
  // Deleting a non-existent app will result in an error.
  RemoteAppsError DeleteApp(const std::string& id);

  // Sorts the launcher items with the custom kAlphabeticalEphemeralAppFirst
  // sort order which moves the remote apps to the front of the launcher.
  void SortLauncherWithRemoteAppsFirst();

  // Sets the list of apps to be pinned on the shelf. If `app_ids` are empty
  // it should unpin all currently pinned apps.
  RemoteAppsError SetPinnedApps(const std::vector<std::string>& app_ids);

  // Adds a folder with |folder_name|. Note that empty folders are not shown in
  // the launcher. Returns the ID for the added folder. If |add_to_front| is
  // true, the folder will be added to the front of the app item list.
  std::string AddFolder(const std::string& folder_name, bool add_to_front);

  // Deletes the folder with id |folder_id|. All items in the folder are moved
  // to the top-level in the launcher.
  // Deleting a non-existent folder will result in an error.
  RemoteAppsError DeleteFolder(const std::string& folder_id);

  // Returns true if the app or folder with |id| should be added to the front
  // of the app item list.
  bool ShouldAddToFront(const std::string& id) const;

  // KeyedService:
  void Shutdown() override;

  // chromeos::remote_apps::mojom::RemoteAppsFactory:
  void BindRemoteAppsAndAppLaunchObserver(
      const std::string& source_id,
      mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteApps>
          pending_remote_apps,
      mojo::PendingRemote<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
          pending_observer) override;

  // chromeos::remote_apps::mojom::RemoteAppsLacrosBridge:
  void BindRemoteAppsAndAppLaunchObserverForLacros(
      mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteApps>
          pending_remote_apps,
      mojo::PendingRemote<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
          pending_observer) override;

  // apps::RemoteApps::Delegate:
  const std::map<std::string, RemoteAppsModel::AppInfo>& GetApps() override;
  void LaunchApp(const std::string& app_id) override;
  gfx::ImageSkia GetIcon(const std::string& id) override;
  gfx::ImageSkia GetPlaceholderIcon(const std::string& id,
                                    int32_t size_hint_in_dip) override;
  apps::MenuItems GetMenuModel(const std::string& id) override;

  // app_list::AppListSyncableService::Observer:
  void OnSyncModelUpdated() override;

  // AppListModelUpdaterObserver:
  void OnAppListItemAdded(ChromeAppListItem* item) override;

  void SetImageDownloaderForTesting(
      std::unique_ptr<ImageDownloader> image_downloader);

  RemoteAppsModel* GetModelForTesting();

  RemoteAppsImpl& GetRemoteAppsImpl() { return remote_apps_impl_; }

  void SetIsInitializedForTesting(bool is_initialized);

 private:
  void Initialize();

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

  void HandleOnFolderCreated(const std::string& folder_id);

  void StartIconDownload(const std::string& id, const GURL& icon_url);

  void OnIconDownloaded(const std::string& id, const gfx::ImageSkia& icon);

  raw_ptr<Profile> profile_ = nullptr;
  bool is_initialized_ = false;
  raw_ptr<app_list::AppListSyncableService> app_list_syncable_service_ =
      nullptr;
  raw_ptr<AppListModelUpdater> model_updater_ = nullptr;
  raw_ptr<extensions::EventRouter> event_router_ = nullptr;
  std::unique_ptr<apps::RemoteApps> remote_apps_;
  RemoteAppsImpl remote_apps_impl_{this};
  std::unique_ptr<RemoteAppsModel> model_;
  std::unique_ptr<ImageDownloader> image_downloader_;
  base::ObserverList<Observer> observer_list_;
  mojo::ReceiverSet<chromeos::remote_apps::mojom::RemoteAppsFactory>
      factory_receivers_;
  mojo::ReceiverSet<chromeos::remote_apps::mojom::RemoteAppsLacrosBridge>
      bridge_receivers_;
  // Map from id to callback. The callback is run after |OnAppUpdate| for the
  // app has been observed.
  std::map<std::string, AddAppCallback> add_app_callback_map_;
  std::map<std::string, std::string> app_id_to_source_id_map_;
  base::ScopedObservation<app_list::AppListSyncableService,
                          app_list::AppListSyncableService::Observer>
      app_list_syncable_service_observation_{this};
  base::ScopedObservation<AppListModelUpdater, AppListModelUpdaterObserver>
      app_list_model_updater_observation_{this};
  base::WeakPtrFactory<RemoteAppsManager> weak_factory_{this};
};

}  // namespace ash

#endif  // CHROME_BROWSER_ASH_REMOTE_APPS_REMOTE_APPS_MANAGER_H_