blob: 6cb439ec5e5566fe0ba4123e9def0c4651b6fed6 [file] [log] [blame]
// Copyright 2018 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 <utility>
#include "chrome/browser/web_applications/web_app_install_finalizer.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/installable/installable_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_icon_generator.h"
#include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/common/web_application_info.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/skia/include/core/SkColor.h"
namespace web_app {
namespace {
// TODO(loyso): Call sites should specify Source explicitly as a part of
// AppTraits parameter object.
Source::Type InferSourceFromMetricsInstallSource(
WebappInstallSource install_source) {
switch (install_source) {
case WebappInstallSource::MENU_BROWSER_TAB:
case WebappInstallSource::MENU_CUSTOM_TAB:
case WebappInstallSource::AUTOMATIC_PROMPT_BROWSER_TAB:
case WebappInstallSource::AUTOMATIC_PROMPT_CUSTOM_TAB:
case WebappInstallSource::API_BROWSER_TAB:
case WebappInstallSource::API_CUSTOM_TAB:
case WebappInstallSource::DEVTOOLS:
case WebappInstallSource::MANAGEMENT_API:
case WebappInstallSource::AMBIENT_BADGE_BROWSER_TAB:
case WebappInstallSource::AMBIENT_BADGE_CUSTOM_TAB:
case WebappInstallSource::OMNIBOX_INSTALL_ICON:
case WebappInstallSource::SYNC:
return Source::kSync;
case WebappInstallSource::INTERNAL_DEFAULT:
case WebappInstallSource::EXTERNAL_DEFAULT:
return Source::kDefault;
case WebappInstallSource::EXTERNAL_POLICY:
return Source::kPolicy;
case WebappInstallSource::SYSTEM_DEFAULT:
return Source::kSystem;
case WebappInstallSource::ARC:
return Source::kWebAppStore;
case WebappInstallSource::COUNT:
NOTREACHED();
return Source::kSync;
}
}
Source::Type InferSourceFromExternalInstallSource(
ExternalInstallSource external_install_source) {
switch (external_install_source) {
case ExternalInstallSource::kInternalDefault:
case ExternalInstallSource::kExternalDefault:
return Source::kDefault;
case ExternalInstallSource::kExternalPolicy:
return Source::kPolicy;
case ExternalInstallSource::kSystemInstalled:
return Source::kSystem;
case ExternalInstallSource::kArc:
return Source::kWebAppStore;
}
}
std::vector<SquareSizePx> GetSquareSizePxs(
const std::map<SquareSizePx, SkBitmap>& icon_bitmaps) {
std::vector<SquareSizePx> sizes;
sizes.reserve(icon_bitmaps.size());
for (const std::pair<SquareSizePx, SkBitmap>& item : icon_bitmaps)
sizes.push_back(item.first);
return sizes;
}
} // namespace
WebAppInstallFinalizer::WebAppInstallFinalizer(Profile* profile,
WebAppSyncBridge* sync_bridge,
WebAppIconManager* icon_manager)
: profile_(profile),
sync_bridge_(sync_bridge),
icon_manager_(icon_manager) {}
WebAppInstallFinalizer::~WebAppInstallFinalizer() = default;
void WebAppInstallFinalizer::FinalizeInstall(
const WebApplicationInfo& web_app_info,
const FinalizeOptions& options,
InstallFinalizedCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// TODO(loyso): Expose Source argument as a field of AppTraits struct.
const auto source =
InferSourceFromMetricsInstallSource(options.install_source);
const AppId app_id = GenerateAppIdFromURL(web_app_info.app_url);
const WebApp* existing_web_app = sync_bridge_->registrar().GetAppById(app_id);
if (existing_web_app && !existing_web_app->is_in_sync_install()) {
// There is an existing app from other source(s). Preserve
// is_locally_installed flag value, do not modify it.
ScopedRegistryUpdate update(sync_bridge_);
WebApp* local_web_app = update->UpdateApp(app_id);
local_web_app->AddSource(source);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), app_id,
InstallResultCode::kSuccessAlreadyInstalled));
return;
}
std::unique_ptr<WebApp> web_app;
if (existing_web_app && existing_web_app->is_in_sync_install()) {
// There is an existing app awaiting for any online install completion, not
// only from kSync source. Prepare copy-on-write:
DCHECK_EQ(web_app_info.app_url, existing_web_app->launch_url());
web_app = std::make_unique<WebApp>(*existing_web_app);
// options.locally_installed is ignored here.
// |user_display_mode| is preserved here: we get it from sync.
} else {
// New app.
web_app = std::make_unique<WebApp>(app_id);
web_app->SetLaunchUrl(web_app_info.app_url);
web_app->SetIsLocallyInstalled(options.locally_installed);
web_app->SetUserDisplayMode(web_app_info.open_as_window
? DisplayMode::kStandalone
: DisplayMode::kBrowser);
}
web_app->AddSource(source);
web_app->SetIsInSyncInstall(false);
web_app->SetName(base::UTF16ToUTF8(web_app_info.title));
web_app->SetDisplayMode(web_app_info.display_mode);
web_app->SetDescription(base::UTF16ToUTF8(web_app_info.description));
web_app->SetScope(web_app_info.scope);
if (web_app_info.theme_color) {
web_app->SetThemeColor(
SkColorSetA(*web_app_info.theme_color, SK_AlphaOPAQUE));
}
WebApp::SyncData sync_data;
sync_data.name = base::UTF16ToUTF8(web_app_info.title);
sync_data.theme_color = web_app_info.theme_color;
web_app->SetSyncData(std::move(sync_data));
web_app->SetIconInfos(web_app_info.icon_infos);
web_app->SetDownloadedIconSizes(GetSquareSizePxs(web_app_info.icon_bitmaps));
icon_manager_->WriteData(
std::move(app_id), web_app_info.icon_bitmaps,
base::BindOnce(&WebAppInstallFinalizer::OnIconsDataWritten,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(web_app)));
}
void WebAppInstallFinalizer::FinalizeFallbackInstallAfterSync(
const AppId& app_id,
InstallFinalizedCallback callback) {
const WebApp* app_in_sync_install =
sync_bridge_->registrar().GetAppById(app_id);
DCHECK(app_in_sync_install);
DCHECK(app_in_sync_install->is_in_sync_install());
// Promote the app in sync install to a full user-visible app using the poor
// data that we've got from sync. Prepare copy-on-write:
auto web_app = std::make_unique<WebApp>(*app_in_sync_install);
web_app->SetIsInSyncInstall(false);
web_app->SetName(web_app->sync_data().name);
web_app->SetThemeColor(web_app->sync_data().theme_color);
// If no color has been specified, use dark gray.
const SkColor background_icon_color =
web_app->sync_data().theme_color.has_value()
? web_app->sync_data().theme_color.value()
: SK_ColorDKGRAY;
std::map<SquareSizePx, SkBitmap> icon_bitmaps =
GenerateIcons(web_app->sync_data().name, background_icon_color);
web_app->SetDownloadedIconSizes(GetSquareSizePxs(icon_bitmaps));
InstallFinalizedCallback fallback_install_callback =
base::BindOnce(&WebAppInstallFinalizer::OnFallbackInstallFinalized,
weak_ptr_factory_.GetWeakPtr(),
app_in_sync_install->app_id(), std::move(callback));
icon_manager_->WriteData(
std::move(app_id), std::move(icon_bitmaps),
base::BindOnce(&WebAppInstallFinalizer::OnIconsDataWritten,
weak_ptr_factory_.GetWeakPtr(),
std::move(fallback_install_callback), std::move(web_app)));
}
void WebAppInstallFinalizer::FinalizeUninstallAfterSync(
const AppId& app_id,
UninstallWebAppCallback callback) {
// WebAppSyncBridge::ApplySyncChangesToRegistrar does the actual
// unregistration of the app from the registry.
DCHECK(!sync_bridge_->registrar().GetAppById(app_id));
icon_manager_->DeleteData(
app_id, base::BindOnce(&WebAppInstallFinalizer::OnIconsDataDeleted,
weak_ptr_factory_.GetWeakPtr(), app_id,
std::move(callback)));
}
void WebAppInstallFinalizer::OnIconsDataDeleted(
const AppId& app_id,
UninstallWebAppCallback callback,
bool success) {
registrar().NotifyWebAppUninstalled(app_id);
std::move(callback).Run(success);
}
void WebAppInstallFinalizer::UninstallExternalWebApp(
const GURL& app_url,
ExternalInstallSource external_install_source,
UninstallWebAppCallback callback) {
base::Optional<web_app::AppId> app_id =
registrar().LookupExternalAppId(app_url);
if (!app_id.has_value()) {
LOG(WARNING) << "Couldn't uninstall app with url " << app_url
<< "; No corresponding web app for url.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), /*uninstalled=*/false));
return;
}
Source::Type source =
InferSourceFromExternalInstallSource(external_install_source);
UninstallWebAppOrRemoveSource(*app_id, source, std::move(callback));
}
bool WebAppInstallFinalizer::CanUserUninstallFromSync(
const AppId& app_id) const {
const WebApp* app = sync_bridge_->registrar().GetAppById(app_id);
return app ? app->IsSynced() : false;
}
void WebAppInstallFinalizer::UninstallWebAppFromSyncByUser(
const AppId& app_id,
UninstallWebAppCallback callback) {
DCHECK(CanUserUninstallFromSync(app_id));
UninstallWebAppOrRemoveSource(app_id, Source::kSync, std::move(callback));
}
bool WebAppInstallFinalizer::CanUserUninstallExternalApp(
const AppId& app_id) const {
// TODO(loyso): Policy Apps: Implement web_app::ManagementPolicy taking
// extensions::ManagementPolicy::UserMayModifySettings as inspiration.
const WebApp* app = sync_bridge_->registrar().GetAppById(app_id);
return app ? app->CanUserUninstallExternalApp() : false;
}
void WebAppInstallFinalizer::UninstallExternalAppByUser(
const AppId& app_id,
UninstallWebAppCallback callback) {
const WebApp* app = sync_bridge_->registrar().GetAppById(app_id);
DCHECK(app);
DCHECK(app->CanUserUninstallExternalApp());
if (app->IsDefaultApp()) {
UpdateBoolWebAppPref(profile_->GetPrefs(), app_id,
kWasExternalAppUninstalledByUser, true);
}
// UninstallExternalAppByUser can wipe out an app with multiple sources. This
// is the behavior from the old bookmark-app based system, which does not
// support incremental AddSource/RemoveSource. Here we are preserving that
// behavior for now.
// TODO(loyso): Implement different uninstall flows in UI. For example, we
// should separate UninstallWebAppFromSyncByUser from
// UninstallExternalAppByUser.
UninstallWebApp(app_id, std::move(callback));
}
bool WebAppInstallFinalizer::WasExternalAppUninstalledByUser(
const AppId& app_id) const {
return GetBoolWebAppPref(profile_->GetPrefs(), app_id,
kWasExternalAppUninstalledByUser);
}
void WebAppInstallFinalizer::FinalizeUpdate(
const WebApplicationInfo& web_app_info,
InstallFinalizedCallback callback) {
// TODO(crbug.com/926083): Implement update logic, this requires updating
// WebAppIconManager to clean out the existing icons and write new ones.
NOTIMPLEMENTED();
}
void WebAppInstallFinalizer::UninstallWebApp(const AppId& app_id,
UninstallWebAppCallback callback) {
ScopedRegistryUpdate update(sync_bridge_);
update->DeleteApp(app_id);
icon_manager_->DeleteData(
app_id, base::BindOnce(&WebAppInstallFinalizer::OnIconsDataDeleted,
weak_ptr_factory_.GetWeakPtr(), app_id,
std::move(callback)));
}
void WebAppInstallFinalizer::UninstallWebAppOrRemoveSource(
const AppId& app_id,
Source::Type source,
UninstallWebAppCallback callback) {
const WebApp* app = sync_bridge_->registrar().GetAppById(app_id);
if (!app) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
/*uninstalled=*/false));
}
if (app->HasOnlySource(source)) {
UninstallWebApp(app_id, std::move(callback));
} else {
ScopedRegistryUpdate update(sync_bridge_);
WebApp* app_to_update = update->UpdateApp(app_id);
app_to_update->RemoveSource(source);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
/*uninstalled=*/true));
}
}
void WebAppInstallFinalizer::OnIconsDataWritten(
InstallFinalizedCallback callback,
std::unique_ptr<WebApp> web_app,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(callback).Run(AppId(), InstallResultCode::kWriteDataFailed);
return;
}
AppId app_id = web_app->app_id();
std::unique_ptr<WebAppRegistryUpdate> update = sync_bridge_->BeginUpdate();
WebApp* app_to_override = update->UpdateApp(app_id);
if (app_to_override)
*app_to_override = std::move(*web_app);
else
update->CreateApp(std::move(web_app));
sync_bridge_->CommitUpdate(
std::move(update),
base::BindOnce(&WebAppInstallFinalizer::OnDatabaseCommitCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(app_id)));
}
void WebAppInstallFinalizer::OnDatabaseCommitCompleted(
InstallFinalizedCallback callback,
const AppId& app_id,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(callback).Run(AppId(), InstallResultCode::kWriteDataFailed);
return;
}
registrar().NotifyWebAppInstalled(app_id);
std::move(callback).Run(app_id, InstallResultCode::kSuccessNewInstall);
}
void WebAppInstallFinalizer::OnFallbackInstallFinalized(
const AppId& app_in_sync_install_id,
InstallFinalizedCallback callback,
const AppId& installed_app_id,
InstallResultCode code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
UMA_HISTOGRAM_ENUMERATION("Webapp.SyncInitiatedFallbackInstallResult", code);
if (!IsSuccess(code)) {
DLOG(ERROR) << "Installation failed for app in sync install. app_id="
<< app_in_sync_install_id << " code=" << static_cast<int>(code);
}
std::move(callback).Run(installed_app_id, code);
}
bool WebAppInstallFinalizer::CanRevealAppShim() const {
// TODO(loyso): Implement it.
NOTIMPLEMENTED();
return false;
}
void WebAppInstallFinalizer::RevealAppShim(const AppId& app_id) {
// TODO(loyso): Implement it.
NOTIMPLEMENTED();
}
} // namespace web_app