Commit cefea6a2 authored by Jit Yao Yap's avatar Jit Yao Yap Committed by Commit Bot

Add RemoteApps

This CL implements the RemoteApps PublisherBase and RemoteAppsManager
which are used to manage Remote Apps. Remote Apps are items which are
added to the launcher but are not backed by an app/extension/web app.
Instead, the item is managed by an extension via an API. The extension
is able to add multiple items and folders to the launcher and observe
when the items are launched. The extension can then decide on how to
handle the launch event. Currently RemoteApps are only available to
Managed Guest Sessions.

RemoteAppsManager is the KeyedService which handles the adding and
deleting of Remote Apps and folders. RemoteAppsManager also implements
RemoteApps::Delegate and is used by RemoteApps to handle the LaunchApp(),
GetIcon() and GetMenuModel() methods.

RemoteAppsManager is not initialized as a KeyedService so this CL does
not change any existing behavior.

Bug: 1101208
Change-Id: I664469fa0284c6e65288e59ba7a5d1271b081a04
DD: go/cros-remote-apps
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2299226
Commit-Queue: Jit Yao Yap <jityao@google.com>
Reviewed-by: default avatarNancy Wang <nancylingwang@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791788}
parent 34eb6ecb
...@@ -3830,6 +3830,8 @@ static_library("browser") { ...@@ -3830,6 +3830,8 @@ static_library("browser") {
"apps/app_service/paused_apps.h", "apps/app_service/paused_apps.h",
"apps/app_service/plugin_vm_apps.cc", "apps/app_service/plugin_vm_apps.cc",
"apps/app_service/plugin_vm_apps.h", "apps/app_service/plugin_vm_apps.h",
"apps/app_service/remote_apps.cc",
"apps/app_service/remote_apps.h",
"apps/app_service/uninstall_dialog.cc", "apps/app_service/uninstall_dialog.cc",
"apps/app_service/uninstall_dialog.h", "apps/app_service/uninstall_dialog.h",
"apps/app_service/web_apps_chromeos.cc", "apps/app_service/web_apps_chromeos.cc",
......
// Copyright 2020 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 "chrome/browser/apps/app_service/remote_apps.h"
#include <utility>
#include "base/callback.h"
#include "chrome/browser/apps/app_service/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "ui/gfx/image/image_skia.h"
namespace apps {
RemoteApps::RemoteApps(Profile* profile, Delegate* delegate)
: profile_(profile), delegate_(delegate) {
DCHECK(delegate);
AppServiceProxy* proxy = AppServiceProxyFactory::GetForProfile(profile_);
if (!proxy) {
return;
}
mojo::Remote<mojom::AppService>& app_service = proxy->AppService();
if (!app_service.is_bound()) {
return;
}
Initialize(app_service, mojom::AppType::kRemote);
}
RemoteApps::~RemoteApps() = default;
void RemoteApps::AddApp(const chromeos::RemoteAppsModel::AppInfo& info) {
mojom::AppPtr app = Convert(info);
Publish(std::move(app), subscribers_);
}
void RemoteApps::UpdateAppIcon(const std::string& app_id) {
mojom::AppPtr app = mojom::App::New();
app->app_type = mojom::AppType::kRemote;
app->app_id = app_id;
app->icon_key = icon_key_factory_.MakeIconKey(IconEffects::kNone);
Publish(std::move(app), subscribers_);
}
void RemoteApps::DeleteApp(const std::string& app_id) {
mojom::AppPtr app = mojom::App::New();
app->app_type = mojom::AppType::kRemote;
app->app_id = app_id;
app->readiness = mojom::Readiness::kUninstalledByUser;
Publish(std::move(app), subscribers_);
}
apps::mojom::AppPtr RemoteApps::Convert(
const chromeos::RemoteAppsModel::AppInfo& info) {
apps::mojom::AppPtr app = PublisherBase::MakeApp(
mojom::AppType::kRemote, info.id, mojom::Readiness::kReady, info.name,
mojom::InstallSource::kUser);
app->show_in_launcher = mojom::OptionalBool::kTrue;
app->show_in_management = mojom::OptionalBool::kFalse;
app->show_in_search = mojom::OptionalBool::kTrue;
app->show_in_shelf = mojom::OptionalBool::kFalse;
app->icon_key = icon_key_factory_.MakeIconKey(IconEffects::kNone);
return app;
}
void RemoteApps::Connect(
mojo::PendingRemote<mojom::Subscriber> subscriber_remote,
mojom::ConnectOptionsPtr opts) {
mojo::Remote<mojom::Subscriber> subscriber(std::move(subscriber_remote));
std::vector<mojom::AppPtr> apps;
for (const auto& entry : delegate_->GetApps()) {
apps.push_back(Convert(entry.second));
}
subscriber->OnApps(std::move(apps));
subscribers_.Add(std::move(subscriber));
}
void RemoteApps::LoadIcon(const std::string& app_id,
mojom::IconKeyPtr icon_key,
mojom::IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
LoadIconCallback callback) {
// TODO(crbug.com/1083331): Handle IconType::kStandard.
DCHECK(icon_type != mojom::IconType::kCompressed)
<< "Remote app should not be shown in management";
mojom::IconValuePtr icon = mojom::IconValue::New();
gfx::ImageSkia icon_image = delegate_->GetIcon(app_id);
if (!icon_image.isNull()) {
icon->icon_type = mojom::IconType::kUncompressed;
icon->uncompressed = icon_image;
apps::ApplyIconEffects(apps::IconEffects::kResizeAndPad, size_hint_in_dip,
&icon->uncompressed);
}
std::move(callback).Run(std::move(icon));
}
void RemoteApps::Launch(const std::string& app_id,
int32_t event_flags,
mojom::LaunchSource launch_source,
int64_t display_id) {
delegate_->LaunchApp(app_id);
}
void RemoteApps::GetMenuModel(const std::string& app_id,
mojom::MenuType menu_type,
int64_t display_id,
GetMenuModelCallback callback) {
std::move(callback).Run(delegate_->GetMenuModel(app_id));
}
} // namespace apps
// Copyright 2020 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.
#ifndef CHROME_BROWSER_APPS_APP_SERVICE_REMOTE_APPS_H_
#define CHROME_BROWSER_APPS_APP_SERVICE_REMOTE_APPS_H_
#include <map>
#include <string>
#include <vector>
#include "chrome/browser/apps/app_service/icon_key_util.h"
#include "chrome/browser/chromeos/remote_apps/remote_apps_model.h"
#include "components/services/app_service/public/cpp/publisher_base.h"
#include "components/services/app_service/public/mojom/app_service.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote_set.h"
class Profile;
namespace gfx {
class ImageSkia;
} // namespace gfx
namespace apps {
// An app publisher (in the App Service sense) of Remote apps.
//
// See components/services/app_service/README.md.
class RemoteApps : public apps::PublisherBase {
public:
// Delegate which handles calls to get the properties of the app and also
// handles launching of the app.
class Delegate {
public:
virtual ~Delegate() = default;
virtual const std::map<std::string, chromeos::RemoteAppsModel::AppInfo>&
GetApps() = 0;
virtual gfx::ImageSkia GetIcon(const std::string& id) = 0;
virtual void LaunchApp(const std::string& id) = 0;
virtual apps::mojom::MenuItemsPtr GetMenuModel(const std::string& id) = 0;
};
RemoteApps(Profile* profile, Delegate* delegate);
RemoteApps(const RemoteApps&) = delete;
RemoteApps& operator=(const RemoteApps&) = delete;
~RemoteApps() override;
void AddApp(const chromeos::RemoteAppsModel::AppInfo& info);
void UpdateAppIcon(const std::string& app_id);
void DeleteApp(const std::string& app_id);
private:
apps::mojom::AppPtr Convert(const chromeos::RemoteAppsModel::AppInfo& info);
// apps::PublisherBase:
void Connect(mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
apps::mojom::ConnectOptionsPtr opts) override;
void LoadIcon(const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
LoadIconCallback callback) override;
void Launch(const std::string& app_id,
int32_t event_flags,
apps::mojom::LaunchSource launch_source,
int64_t display_id) override;
void GetMenuModel(const std::string& app_id,
apps::mojom::MenuType menu_type,
int64_t display_id,
GetMenuModelCallback callback) override;
Profile* const profile_;
Delegate* const delegate_;
mojo::RemoteSet<apps::mojom::Subscriber> subscribers_;
apps_util::IncrementingIconKeyFactory icon_key_factory_;
};
} // namespace apps
#endif // CHROME_BROWSER_APPS_APP_SERVICE_REMOTE_APPS_H_
...@@ -2362,6 +2362,14 @@ source_set("chromeos") { ...@@ -2362,6 +2362,14 @@ source_set("chromeos") {
"release_notes/release_notes_notification.h", "release_notes/release_notes_notification.h",
"release_notes/release_notes_storage.cc", "release_notes/release_notes_storage.cc",
"release_notes/release_notes_storage.h", "release_notes/release_notes_storage.h",
"remote_apps/id_generator.cc",
"remote_apps/id_generator.h",
"remote_apps/remote_apps_manager.cc",
"remote_apps/remote_apps_manager.h",
"remote_apps/remote_apps_manager_factory.cc",
"remote_apps/remote_apps_manager_factory.h",
"remote_apps/remote_apps_model.cc",
"remote_apps/remote_apps_model.h",
"reset/metrics.h", "reset/metrics.h",
"scheduler_configuration_manager.cc", "scheduler_configuration_manager.cc",
"scheduler_configuration_manager.h", "scheduler_configuration_manager.h",
...@@ -3380,6 +3388,7 @@ source_set("unit_tests") { ...@@ -3380,6 +3388,7 @@ source_set("unit_tests") {
"proxy_config_service_impl_unittest.cc", "proxy_config_service_impl_unittest.cc",
"release_notes/release_notes_notification_unittest.cc", "release_notes/release_notes_notification_unittest.cc",
"release_notes/release_notes_storage_unittest.cc", "release_notes/release_notes_storage_unittest.cc",
"remote_apps/remote_apps_model_unittest.cc",
"scheduler_configuration_manager_unittest.cc", "scheduler_configuration_manager_unittest.cc",
"session_length_limiter_unittest.cc", "session_length_limiter_unittest.cc",
"settings/cros_settings_unittest.cc", "settings/cros_settings_unittest.cc",
......
specific_include_rules = {
"remote_apps_manager_browsertest\.cc": [
"+ash/app_list",
"+ash/shell.h",
],
}
// Copyright 2020 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 "chrome/browser/chromeos/remote_apps/id_generator.h"
#include "base/guid.h"
namespace chromeos {
std::string GuidIdGenerator::GenerateId() {
return base::GenerateGUID();
}
FakeIdGenerator::FakeIdGenerator(const std::vector<std::string>& ids)
: ids_(ids) {}
FakeIdGenerator::~FakeIdGenerator() = default;
std::string FakeIdGenerator::GenerateId() {
std::string id = ids_[index_];
++index_;
return id;
}
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROME_BROWSER_CHROMEOS_REMOTE_APPS_ID_GENERATOR_H_
#define CHROME_BROWSER_CHROMEOS_REMOTE_APPS_ID_GENERATOR_H_
#include <string>
#include <vector>
namespace chromeos {
// A class to generate IDs.
class IdGenerator {
public:
virtual ~IdGenerator() = default;
virtual std::string GenerateId() = 0;
};
// Generates IDs using |base::GenerateGUID()|.
class GuidIdGenerator : public IdGenerator {
public:
GuidIdGenerator() = default;
GuidIdGenerator(const GuidIdGenerator&) = delete;
GuidIdGenerator& operator=(const GuidIdGenerator&) = delete;
~GuidIdGenerator() override = default;
// IdGenerator:
std::string GenerateId() override;
};
class FakeIdGenerator : public IdGenerator {
public:
explicit FakeIdGenerator(const std::vector<std::string>& ids);
FakeIdGenerator(const FakeIdGenerator&) = delete;
FakeIdGenerator& operator=(const FakeIdGenerator&) = delete;
~FakeIdGenerator() override;
// IdGenerator:
std::string GenerateId() override;
private:
std::vector<std::string> ids_;
int index_ = 0;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_REMOTE_APPS_ID_GENERATOR_H_
// Copyright 2020 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 "chrome/browser/chromeos/remote_apps/remote_apps_manager.h"
#include <utility>
#include "ash/public/cpp/app_menu_constants.h"
#include "ash/public/cpp/image_downloader.h"
#include "base/bind.h"
#include "chrome/browser/apps/app_service/menu_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_list_model_updater.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ui/app_list/chrome_app_list_item.h"
#include "chrome/grit/generated_resources.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "ui/gfx/image/image_skia.h"
namespace chromeos {
namespace {
class ImageDownloaderImpl : public RemoteAppsManager::ImageDownloader {
public:
ImageDownloaderImpl() = default;
ImageDownloaderImpl(const ImageDownloaderImpl&) = delete;
ImageDownloaderImpl& operator=(const ImageDownloaderImpl&) = delete;
~ImageDownloaderImpl() override = default;
void Download(const GURL& url, DownloadCallback callback) override {
ash::ImageDownloader* image_downloader = ash::ImageDownloader::Get();
DCHECK(image_downloader);
// TODO(jityao): Set traffic annotation.
image_downloader->Download(url, NO_TRAFFIC_ANNOTATION_YET,
std::move(callback));
}
};
} // namespace
RemoteAppsManager::RemoteAppsManager(Profile* profile)
: profile_(profile),
remote_apps_(std::make_unique<apps::RemoteApps>(profile_, this)),
model_(std::make_unique<RemoteAppsModel>()),
image_downloader_(std::make_unique<ImageDownloaderImpl>()) {
app_list_syncable_service_ =
app_list::AppListSyncableServiceFactory::GetForProfile(profile_);
model_updater_ = app_list_syncable_service_->GetModelUpdater();
app_list_model_updater_observer_.Add(model_updater_);
// |AppListSyncableService| manages the Chrome side AppList and has to be
// initialized before apps can be added.
if (app_list_syncable_service_->IsInitialized()) {
Initialize();
} else {
app_list_syncable_service_observer_.Add(app_list_syncable_service_);
}
}
RemoteAppsManager::~RemoteAppsManager() = default;
void RemoteAppsManager::Initialize() {
DCHECK(app_list_syncable_service_->IsInitialized());
is_initialized_ = true;
}
void RemoteAppsManager::AddApp(const std::string& name,
const std::string& folder_id,
const GURL& icon_url,
AddAppCallback callback) {
if (!is_initialized_) {
std::move(callback).Run(std::string(), Error::kNotReady);
return;
}
if (!folder_id.empty() && !model_->HasFolder(folder_id)) {
std::move(callback).Run(std::string(), Error::kFolderIdDoesNotExist);
return;
}
const RemoteAppsModel::AppInfo& info =
model_->AddApp(name, icon_url, folder_id);
add_app_callback_map_.insert({info.id, std::move(callback)});
remote_apps_->AddApp(info);
}
RemoteAppsManager::Error RemoteAppsManager::DeleteApp(const std::string& id) {
// Check if app was added but |HandleOnAppAdded| has not been called.
if (!model_->HasApp(id) ||
add_app_callback_map_.find(id) != add_app_callback_map_.end())
return Error::kAppIdDoesNotExist;
model_->DeleteApp(id);
remote_apps_->DeleteApp(id);
return Error::kNone;
}
std::string RemoteAppsManager::AddFolder(const std::string& folder_name) {
const RemoteAppsModel::FolderInfo& folder_info =
model_->AddFolder(folder_name);
return folder_info.id;
}
RemoteAppsManager::Error RemoteAppsManager::DeleteFolder(
const std::string& folder_id) {
if (!model_->HasFolder(folder_id))
return Error::kFolderIdDoesNotExist;
// Move all items out of the folder. Empty folders are automatically deleted.
RemoteAppsModel::FolderInfo& folder_info = model_->GetFolderInfo(folder_id);
for (const auto& app : folder_info.items)
model_updater_->MoveItemToFolder(app, std::string());
model_->DeleteFolder(folder_id);
return Error::kNone;
}
void RemoteAppsManager::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void RemoteAppsManager::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void RemoteAppsManager::Shutdown() {}
const std::map<std::string, RemoteAppsModel::AppInfo>&
RemoteAppsManager::GetApps() {
return model_->GetAllAppInfo();
}
void RemoteAppsManager::LaunchApp(const std::string& id) {
for (Observer& observer : observer_list_)
observer.OnAppLaunched(id);
}
gfx::ImageSkia RemoteAppsManager::GetIcon(const std::string& id) {
if (!model_->HasApp(id))
return gfx::ImageSkia();
return model_->GetAppInfo(id).icon;
}
apps::mojom::MenuItemsPtr RemoteAppsManager::GetMenuModel(
const std::string& id) {
apps::mojom::MenuItemsPtr menu_items = apps::mojom::MenuItems::New();
// TODO(jityao): Temporary string for menu item.
apps::AddCommandItem(ash::LAUNCH_NEW, IDS_APP_CONTEXT_MENU_ACTIVATE_ARC,
&menu_items);
return menu_items;
}
void RemoteAppsManager::OnSyncModelUpdated() {
DCHECK(!is_initialized_);
Initialize();
app_list_syncable_service_observer_.RemoveAll();
}
void RemoteAppsManager::OnAppListItemAdded(ChromeAppListItem* item) {
if (item->is_folder() || item->is_page_break())
return;
// Make a copy of id as item->metadata can be invalidated.
HandleOnAppAdded(std::string(item->id()));
}
void RemoteAppsManager::SetRemoteAppsForTesting(
std::unique_ptr<apps::RemoteApps> remote_apps) {
remote_apps_ = std::move(remote_apps);
}
void RemoteAppsManager::SetImageDownloaderForTesting(
std::unique_ptr<ImageDownloader> image_downloader) {
image_downloader_ = std::move(image_downloader);
}
RemoteAppsModel* RemoteAppsManager::GetModelForTesting() {
return model_.get();
}
void RemoteAppsManager::SetIsInitializedForTesting(bool is_initialized) {
is_initialized_ = is_initialized;
}
void RemoteAppsManager::HandleOnAppAdded(const std::string& id) {
if (!model_->HasApp(id))
return;
RemoteAppsModel::AppInfo& app_info = model_->GetAppInfo(id);
const std::string& folder_id = app_info.folder_id;
// If folder was deleted, |folder_id| would be set to empty by the model, so
// we don't have to check if it was deleted.
if (!folder_id.empty()) {
bool folder_already_exists = model_updater_->FindFolderItem(folder_id);
model_updater_->MoveItemToFolder(id, folder_id);
RemoteAppsModel::FolderInfo& folder_info = model_->GetFolderInfo(folder_id);
if (!folder_already_exists) {
// Update metadata for newly created folder.
ChromeAppListItem* item = model_updater_->FindFolderItem(folder_id);
DCHECK(item) << "Missing folder item for folder_id: " << folder_id;
item->SetName(folder_info.name);
item->SetIsPersistent(true);
item->SetPosition(model_updater_->GetFirstAvailablePosition());
}
}
StartIconDownload(id, app_info.icon_url);
auto it = add_app_callback_map_.find(id);
DCHECK(it != add_app_callback_map_.end())
<< "Missing callback for id: " << id;
std::move(it->second).Run(id, Error::kNone);
add_app_callback_map_.erase(it);
}
void RemoteAppsManager::StartIconDownload(const std::string& id,
const GURL& icon_url) {
image_downloader_->Download(
icon_url, base::BindOnce(&RemoteAppsManager::OnIconDownloaded,
weak_factory_.GetWeakPtr(), id));
}
void RemoteAppsManager::OnIconDownloaded(const std::string& id,
const gfx::ImageSkia& icon) {
// App may have been deleted.
if (!model_->HasApp(id))
return;
RemoteAppsModel::AppInfo& app_info = model_->GetAppInfo(id);
app_info.icon = icon;
remote_apps_->UpdateAppIcon(id);
}
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MANAGER_H_
#define CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MANAGER_H_
#include <map>
#include <vector>
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observer.h"
#include "chrome/browser/apps/app_service/remote_apps.h"
#include "chrome/browser/chromeos/remote_apps/remote_apps_model.h"
#include "chrome/browser/ui/app_list/app_list_model_updater_observer.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
#include "chrome/browser/ui/app_list/chrome_app_list_model_updater.h"
#include "components/keyed_service/core/keyed_service.h"
class AppListModelUpdater;
class ChromeAppListItem;
class Profile;
namespace apps {
class AppUpdate;
} // namespace apps
namespace gfx {
class ImageSkia;
} // namespace gfx
namespace chromeos {
// KeyedService which manages the logic for |AppType::kRemote| in AppService.
// This service is only created for Managed Guest Sessions.
// The IDs of the added apps and folders are GUIDs generated using
// |base::GenerateGUID()|.
class RemoteAppsManager : public KeyedService,
public apps::RemoteApps::Delegate,
public app_list::AppListSyncableService::Observer,
public AppListModelUpdaterObserver {
public:
enum class Error {
kNone = 0,
kAppIdDoesNotExist,
kFolderIdDoesNotExist,
// Manager has not been initialized.
kNotReady,
};
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_; }
using AddAppCallback =
base::OnceCallback<void(const std::string& id, Error 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.
// 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& name,
const std::string& folder_id,
const GURL& icon_url,
AddAppCallback callback);
// Deletes the app with id |id|.
// Deleting a non-existent app will result in an error.
Error DeleteApp(const std::string& id);
// Adds a folder with |folder_name|. Note that empty folders are not
// shown in the launcher. Returns the ID for the added folder.
std::string AddFolder(const std::string& folder_name);
// 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.
Error DeleteFolder(const std::string& folder_id);
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// KeyedService:
void Shutdown() override;
// apps::RemoteApps::Delegate:
const std::map<std::string, RemoteAppsModel::AppInfo>& GetApps() override;
void LaunchApp(const std::string& id) override;
gfx::ImageSkia GetIcon(const std::string& id) override;
apps::mojom::MenuItemsPtr GetMenuModel(const std::string& id) override;
// app_list::AppListSyncableService::Observer:
void OnSyncModelUpdated() override;
// AppListModelUpdaterObserver:
void OnAppListItemAdded(ChromeAppListItem* item) override;
void SetRemoteAppsForTesting(std::unique_ptr<apps::RemoteApps> remote_apps);
void SetImageDownloaderForTesting(
std::unique_ptr<ImageDownloader> image_downloader);
RemoteAppsModel* GetModelForTesting();
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);
Profile* profile_ = nullptr;
bool is_initialized_ = false;
app_list::AppListSyncableService* app_list_syncable_service_ = nullptr;
AppListModelUpdater* model_updater_ = nullptr;
std::unique_ptr<apps::RemoteApps> remote_apps_;
std::unique_ptr<RemoteAppsModel> model_;
std::unique_ptr<ImageDownloader> image_downloader_;
base::ObserverList<Observer> observer_list_;
// 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_;
ScopedObserver<app_list::AppListSyncableService,
app_list::AppListSyncableService::Observer,
&app_list::AppListSyncableService::AddObserverAndStart>
app_list_syncable_service_observer_{this};
ScopedObserver<AppListModelUpdater, AppListModelUpdaterObserver>
app_list_model_updater_observer_{this};
base::WeakPtrFactory<RemoteAppsManager> weak_factory_{this};
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MANAGER_H_
// Copyright 2020 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 "chrome/browser/chromeos/remote_apps/remote_apps_manager_factory.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_type.h"
#include "content/public/browser/browser_context.h"
namespace chromeos {
// static
RemoteAppsManager* RemoteAppsManagerFactory::GetForProfile(Profile* profile) {
return static_cast<RemoteAppsManager*>(
RemoteAppsManagerFactory::GetInstance()->GetServiceForBrowserContext(
profile, /*create=*/true));
}
// static
RemoteAppsManagerFactory* RemoteAppsManagerFactory::GetInstance() {
static base::NoDestructor<RemoteAppsManagerFactory> instance;
return instance.get();
}
RemoteAppsManagerFactory::RemoteAppsManagerFactory()
: BrowserContextKeyedServiceFactory(
"RemoteAppsManager",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(app_list::AppListSyncableServiceFactory::GetInstance());
DependsOn(apps::AppServiceProxyFactory::GetInstance());
}
RemoteAppsManagerFactory::~RemoteAppsManagerFactory() = default;
KeyedService* RemoteAppsManagerFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
chromeos::ProfileHelper* profile_helper = chromeos::ProfileHelper::Get();
if (!profile_helper)
return nullptr;
Profile* profile = Profile::FromBrowserContext(context);
user_manager::User* user = profile_helper->GetUserByProfile(profile);
if (!user || user->GetType() != user_manager::USER_TYPE_PUBLIC_ACCOUNT)
return nullptr;
return new RemoteAppsManager(profile);
}
content::BrowserContext* RemoteAppsManagerFactory::GetBrowserContextToUse(
content::BrowserContext* context) const {
Profile* profile = Profile::FromBrowserContext(context);
if (!profile || profile->IsSystemProfile() ||
!chromeos::ProfileHelper::IsRegularProfile(profile)) {
return nullptr;
}
return BrowserContextKeyedServiceFactory::GetBrowserContextToUse(context);
}
bool RemoteAppsManagerFactory::ServiceIsCreatedWithBrowserContext() const {
return true;
}
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MANAGER_FACTORY_H_
#define CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MANAGER_FACTORY_H_
#include "base/no_destructor.h"
#include "chrome/browser/chromeos/remote_apps/remote_apps_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class KeyedService;
class Profile;
namespace content {
class BrowserContext;
} // namespace content
namespace chromeos {
// Singleton that creates |RemoteAppsManager|s and associates them with a
// |Profile|.
class RemoteAppsManagerFactory : public BrowserContextKeyedServiceFactory {
public:
static RemoteAppsManager* GetForProfile(Profile* profile);
static RemoteAppsManagerFactory* GetInstance();
private:
friend base::NoDestructor<RemoteAppsManagerFactory>;
RemoteAppsManagerFactory();
RemoteAppsManagerFactory(const RemoteAppsManagerFactory&) = delete;
RemoteAppsManagerFactory& operator=(const RemoteAppsManagerFactory&) = delete;
~RemoteAppsManagerFactory() override;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override;
bool ServiceIsCreatedWithBrowserContext() const override;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MANAGER_FACTORY_H_
// Copyright 2020 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 "chrome/browser/chromeos/remote_apps/remote_apps_model.h"
namespace chromeos {
RemoteAppsModel::AppInfo::AppInfo(const std::string& id,
const std::string& name,
const GURL& icon_url,
std::string folder_id)
: id(id),
name(name),
icon_url(icon_url),
folder_id(folder_id),
icon(gfx::ImageSkia()) {}
RemoteAppsModel::AppInfo::AppInfo(const AppInfo& other) = default;
RemoteAppsModel::AppInfo::~AppInfo() = default;
RemoteAppsModel::FolderInfo::FolderInfo(const std::string& id,
const std::string& name)
: id(id), name(name) {}
RemoteAppsModel::FolderInfo::FolderInfo(const FolderInfo& other) = default;
RemoteAppsModel::FolderInfo::~FolderInfo() = default;
RemoteAppsModel::RemoteAppsModel()
: id_generator_(std::make_unique<GuidIdGenerator>()) {}
RemoteAppsModel::~RemoteAppsModel() = default;
RemoteAppsModel::AppInfo& RemoteAppsModel::AddApp(
const std::string& name,
const GURL& icon_url,
const std::string& folder_id) {
std::string id = id_generator_->GenerateId();
app_map_.insert({id, AppInfo(id, name, icon_url, folder_id)});
if (!folder_id.empty()) {
DCHECK(folder_map_.find(folder_id) != folder_map_.end());
FolderInfo& folder_info = folder_map_.at(folder_id);
folder_info.items.insert(id);
}
return app_map_.at(id);
}
bool RemoteAppsModel::HasApp(const std::string& id) const {
return app_map_.find(id) != app_map_.end();
}
RemoteAppsModel::AppInfo& RemoteAppsModel::GetAppInfo(const std::string& id) {
DCHECK(app_map_.find(id) != app_map_.end());
return app_map_.at(id);
}
const std::map<std::string, RemoteAppsModel::AppInfo>&
RemoteAppsModel::GetAllAppInfo() const {
return app_map_;
}
RemoteAppsModel::FolderInfo& RemoteAppsModel::AddFolder(
const std::string& folder_name) {
std::string folder_id = id_generator_->GenerateId();
auto it = folder_map_.insert(folder_map_.begin(),
{folder_id, FolderInfo(folder_id, folder_name)});
return it->second;
}
bool RemoteAppsModel::HasFolder(const std::string& folder_id) const {
return folder_map_.find(folder_id) != folder_map_.end();
}
RemoteAppsModel::FolderInfo& RemoteAppsModel::GetFolderInfo(
const std::string& folder_id) {
DCHECK(folder_map_.find(folder_id) != folder_map_.end());
return folder_map_.at(folder_id);
}
void RemoteAppsModel::DeleteApp(const std::string& id) {
DCHECK(HasApp(id));
auto it = app_map_.find(id);
const std::string& folder_id = it->second.folder_id;
if (!folder_id.empty()) {
auto folder_it = folder_map_.find(folder_id);
folder_it->second.items.erase(id);
}
app_map_.erase(it);
}
void RemoteAppsModel::DeleteFolder(const std::string& folder_id) {
DCHECK(HasFolder(folder_id));
auto it = folder_map_.find(folder_id);
const std::set<std::string>& app_set = it->second.items;
for (const auto& id : app_set) {
DCHECK(app_map_.find(id) != app_map_.end());
AppInfo& info = app_map_.at(id);
info.folder_id.clear();
}
folder_map_.erase(it);
}
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MODEL_H_
#define CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MODEL_H_
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "chrome/browser/chromeos/remote_apps/id_generator.h"
#include "ui/gfx/image/image_skia.h"
#include "url/gurl.h"
namespace chromeos {
// Class which stores the state of Remote Apps added by |RemoteAppsManager| and
// maintains the association between the apps and folders.
class RemoteAppsModel {
public:
struct AppInfo {
AppInfo(const std::string& id,
const std::string& name,
const GURL& icon_url,
std::string folder_id);
AppInfo(const AppInfo& other);
~AppInfo();
const std::string id;
const std::string name;
const GURL icon_url;
std::string folder_id;
gfx::ImageSkia icon;
};
struct FolderInfo {
FolderInfo(const std::string& id, const std::string& name);
FolderInfo(const FolderInfo& other);
~FolderInfo();
const std::string id;
const std::string name;
std::set<std::string> items;
};
RemoteAppsModel();
RemoteAppsModel(const RemoteAppsModel&) = delete;
RemoteAppsModel& operator=(const RemoteAppsModel&) = delete;
~RemoteAppsModel();
// Adds an app. If |folder_id| is non-empty, the caller should ensure that
// the folder exists by calling |HasFolder()| first. The app is added to the
// corresponding folder.
RemoteAppsModel::AppInfo& AddApp(const std::string& name,
const GURL& icon_url,
const std::string& folder_id);
// Returns true if an app with ID |id| exists in the model.
bool HasApp(const std::string& id) const;
// Returns the |AppInfo| of the app with ID |id|. The caller should ensure
// that the app exists by calling |HasApp()| first.
RemoteAppsModel::AppInfo& GetAppInfo(const std::string& id);
// Returns a map from string |id| to |AppInfo| for all apps in the model.
const std::map<std::string, AppInfo>& GetAllAppInfo() const;
// Adds a folder.
RemoteAppsModel::FolderInfo& AddFolder(const std::string& folder_name);
// Returns true if a folder with ID |folder_id| exists.
bool HasFolder(const std::string& folder_id) const;
// Returns the |FolderInfo| of the folder with ID |folder_id|. The caller
// should ensure that the folder exists by calling |HasFolder()| first.
RemoteAppsModel::FolderInfo& GetFolderInfo(const std::string& folder_id);
// Deletes the app with ID |id|. The caller should ensure that the app exists
// by calling |HasApp()| first.
void DeleteApp(const std::string& id);
// Deletes the folder with ID |folder_id|. The caller should ensure that the
// folder exists by calling |HasFolder()| first. All items in the folder are
// moved out of the folder.
void DeleteFolder(const std::string& folder_id);
void SetIdGeneratorForTesting(std::unique_ptr<IdGenerator> id_generator) {
id_generator_ = std::move(id_generator);
}
private:
std::unique_ptr<IdGenerator> id_generator_;
std::map<std::string, AppInfo> app_map_;
std::map<std::string, FolderInfo> folder_map_;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_MODEL_H_
// Copyright 2020 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 "chrome/browser/chromeos/remote_apps/remote_apps_model.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace {
constexpr char kId1[] = "id1";
constexpr char kId2[] = "id2";
constexpr char kId3[] = "id3";
std::unique_ptr<RemoteAppsModel> SetUpModel() {
std::unique_ptr<RemoteAppsModel> model = std::make_unique<RemoteAppsModel>();
std::unique_ptr<FakeIdGenerator> id_generator =
std::make_unique<FakeIdGenerator>(
std::vector<std::string>{kId1, kId2, kId3});
model->SetIdGeneratorForTesting(std::move(id_generator));
return model;
}
} // namespace
using RemoteAppsModelUnittest = testing::Test;
TEST_F(RemoteAppsModelUnittest, AddApp) {
const std::string name = "name";
const GURL icon_url = GURL("icon_url");
std::unique_ptr<RemoteAppsModel> model = SetUpModel();
const RemoteAppsModel::AppInfo& info =
model->AddApp(name, icon_url, std::string());
EXPECT_EQ(kId1, info.id);
EXPECT_EQ(name, info.name);
EXPECT_EQ(icon_url, info.icon_url);
EXPECT_EQ(std::string(), info.folder_id);
EXPECT_TRUE(model->HasApp(info.id));
// Check |GetAppInfo()|.
const RemoteAppsModel::AppInfo& info2 = model->GetAppInfo(info.id);
EXPECT_EQ(kId1, info2.id);
EXPECT_EQ(name, info2.name);
EXPECT_EQ(icon_url, info2.icon_url);
EXPECT_EQ(std::string(), info2.folder_id);
model->DeleteApp(info.id);
EXPECT_FALSE(model->HasApp(info.id));
}
TEST_F(RemoteAppsModelUnittest, GetAllAppInfo) {
const std::string name = "name";
const GURL icon_url = GURL("icon_url");
const std::string name2 = "name2";
const GURL icon_url2 = GURL("icon_url2");
std::unique_ptr<RemoteAppsModel> model = SetUpModel();
model->AddApp(name, icon_url, std::string());
model->AddApp(name2, icon_url2, std::string());
const std::map<std::string, RemoteAppsModel::AppInfo>& infos =
model->GetAllAppInfo();
EXPECT_EQ(2u, infos.size());
const RemoteAppsModel::AppInfo& info = infos.at(kId1);
EXPECT_EQ(kId1, info.id);
EXPECT_EQ(name, info.name);
EXPECT_EQ(icon_url, info.icon_url);
const RemoteAppsModel::AppInfo& info2 = infos.at(kId2);
EXPECT_EQ(kId2, info2.id);
EXPECT_EQ(name2, info2.name);
EXPECT_EQ(icon_url2, info2.icon_url);
}
TEST_F(RemoteAppsModelUnittest, AddFolder) {
const std::string folder_name = "folder_name";
const std::string name = "name";
const GURL icon_url = GURL("icon_url");
std::unique_ptr<RemoteAppsModel> model = SetUpModel();
const RemoteAppsModel::FolderInfo& folder_info =
model->AddFolder(folder_name);
const std::string folder_id = folder_info.id;
EXPECT_EQ(kId1, folder_id);
EXPECT_EQ(folder_name, folder_info.name);
EXPECT_EQ(0u, folder_info.items.size());
// Check |GetFolderInfo()|.
const RemoteAppsModel::FolderInfo& folder_info2 =
model->GetFolderInfo(folder_info.id);
EXPECT_EQ(kId1, folder_id);
EXPECT_EQ(folder_name, folder_info2.name);
EXPECT_EQ(0u, folder_info2.items.size());
model->DeleteFolder(folder_id);
EXPECT_FALSE(model->HasFolder(folder_id));
}
TEST_F(RemoteAppsModelUnittest, FolderWithMultipleApps) {
const std::string folder_name = "folder_name";
const std::string name = "name";
const GURL icon_url = GURL("icon_url");
std::unique_ptr<RemoteAppsModel> model = SetUpModel();
const RemoteAppsModel::FolderInfo& folder_info =
model->AddFolder(folder_name);
std::string folder_id = folder_info.id;
EXPECT_EQ(kId1, folder_info.id);
EXPECT_EQ(folder_name, folder_info.name);
EXPECT_EQ(0u, folder_info.items.size());
EXPECT_TRUE(model->HasFolder(folder_id));
const RemoteAppsModel::AppInfo& info =
model->AddApp(name, icon_url, folder_id);
EXPECT_EQ(kId2, info.id);
EXPECT_EQ(folder_id, info.folder_id);
EXPECT_EQ(1u, folder_info.items.size());
EXPECT_EQ(1u, folder_info.items.count(info.id));
// Add second app.
const RemoteAppsModel::AppInfo& info2 =
model->AddApp(name, icon_url, folder_id);
EXPECT_EQ(kId3, info2.id);
EXPECT_EQ(2u, folder_info.items.size());
EXPECT_EQ(1u, folder_info.items.count(info2.id));
// Check that app is removed from folder when app deleted.
model->DeleteApp(info.id);
EXPECT_EQ(1u, folder_info.items.size());
// Check that app is removed from folder when folder is deleted.
model->DeleteFolder(folder_id);
EXPECT_EQ(std::string(), info2.folder_id);
}
} // namespace chromeos
...@@ -256,9 +256,10 @@ void AppServiceContextMenu::OnGetMenuModel( ...@@ -256,9 +256,10 @@ void AppServiceContextMenu::OnGetMenuModel(
if (build_extension_menu_before_default) if (build_extension_menu_before_default)
BuildExtensionAppShortcutsMenu(menu_model.get()); BuildExtensionAppShortcutsMenu(menu_model.get());
// Create default items. // Create default items for non-Remote apps.
if (app_id() != extension_misc::kChromeAppId && if (app_id() != extension_misc::kChromeAppId &&
app_type_ != apps::mojom::AppType::kUnknown) { app_type_ != apps::mojom::AppType::kUnknown &&
app_type_ != apps::mojom::AppType::kRemote) {
app_list::AppContextMenu::BuildMenu(menu_model.get()); app_list::AppContextMenu::BuildMenu(menu_model.get());
} }
......
...@@ -2453,6 +2453,7 @@ if (!is_android) { ...@@ -2453,6 +2453,7 @@ if (!is_android) {
"../browser/chromeos/printing/test_printer_configurer.cc", "../browser/chromeos/printing/test_printer_configurer.cc",
"../browser/chromeos/printing/test_printer_configurer.h", "../browser/chromeos/printing/test_printer_configurer.h",
"../browser/chromeos/profiles/profile_helper_browsertest.cc", "../browser/chromeos/profiles/profile_helper_browsertest.cc",
"../browser/chromeos/remote_apps/remote_apps_manager_browsertest.cc",
"../browser/chromeos/shutdown_policy_browsertest.cc", "../browser/chromeos/shutdown_policy_browsertest.cc",
"../browser/chromeos/startup_settings_cache_browsertest.cc", "../browser/chromeos/startup_settings_cache_browsertest.cc",
"../browser/chromeos/system/device_disabling_browsertest.cc", "../browser/chromeos/system/device_disabling_browsertest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment