// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/apps/apk_web_app_service.h"
#include <map>
#include <optional>
#include <utility>
#include "ash/components/arc/mojom/app.mojom.h"
#include "ash/components/arc/session/connection_holder.h"
#include "ash/constants/ash_features.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_service.h"
#include "chrome/browser/ash/apps/apk_web_app_service_factory.h"
#include "chrome/browser/ash/crosapi/browser_manager.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_install_finalizer.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "url/gurl.h"
namespace ash {
namespace {
// The pref dict is:
// {
// ...
// "web_app_apks" : {
// <web_app_id_1> : {
// "package_name" : <apk_package_name_1>,
// "should_remove": <bool>,
// "is_web_only_twa": <bool>, (deprecated, and automatically removed)
// "sha256_fingerprint": <string> (deprecated, and automatically removed)
// },
// <web_app_id_2> : {
// "package_name" : <apk_package_name_2>,
// "should_remove": <bool>
// },
// ...
// },
// ...
// }
const char kWebAppToApkDictPref[] = "web_app_apks";
const char kPackageNameKey[] = "package_name";
const char kShouldRemoveKey[] = "should_remove";
// TODO(crbug.com/40896350): Remove these keys after migrations are complete.
const char kIsWebOnlyTwaKey[] = "is_web_only_twa";
const char kSha256FingerprintKey[] = "sha256_fingerprint";
constexpr char kLastAppId[] = "last_app_id";
constexpr char kPinIndex[] = "pin_index";
constexpr char kGeneratedWebApkPackagePrefix[] = "org.chromium.webapk.";
// Default icon size in pixels to request from ARC for an icon.
const int kDefaultIconSize = 192;
bool IsAppInstalled(apps::AppRegistryCache& app_registry_cache,
const std::string& app_id) {
bool installed = false;
app_registry_cache.ForOneApp(
app_id, [&installed](const apps::AppUpdate& update) {
installed = apps_util::IsInstalled(update.Readiness());
});
return installed;
}
std::optional<webapps::AppId> GetWebAppIdForPackage(
ArcAppListPrefs::PackageInfo* package) {
if (!package || !package->web_app_info) {
return std::nullopt;
}
// TWAs do not currently support manifest IDs, so the App ID is only based off
// the start URL.
return web_app::GenerateAppId(/*manifest_id_path=*/std::nullopt,
GURL(package->web_app_info->start_url));
}
// TODO(b/304184466): Refactor this DelegateImpl to reduce code duplication.
// Delegate implementation that actually talks to ARC And Lacros.
// It looks up |ArcAppListPrefs| in the profile to find the ARC connection.
class ApkWebAppServiceDelegateImpl : public ApkWebAppService::Delegate,
public ApkWebAppInstaller::Owner {
public:
explicit ApkWebAppServiceDelegateImpl(Profile* profile)
: profile_(profile), arc_app_list_prefs_(ArcAppListPrefs::Get(profile)) {
DCHECK(arc_app_list_prefs_);
}
void MaybeInstallWebAppInLacros(const std::string& package_name,
arc::mojom::WebAppInfoPtr web_app_info,
WebAppInstallCallback callback) override {
DCHECK(web_app::IsWebAppsCrosapiEnabled());
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_app_list_prefs_->app_connection_holder(), GetPackageIcon);
if (!instance) {
return;
}
instance->GetPackageIcon(
package_name, kDefaultIconSize, /*normalize=*/false,
base::BindOnce(&ApkWebAppServiceDelegateImpl::OnDidGetWebAppIcon,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
package_name, std::move(web_app_info)));
}
void MaybeUninstallWebAppInLacros(const webapps::AppId& web_app_id,
WebAppUninstallCallback callback) override {
DCHECK(web_app::IsWebAppsCrosapiEnabled());
if (crosapi::mojom::WebAppProviderBridge* web_app_provider_bridge =
crosapi::CrosapiManager::Get()
->crosapi_ash()
->web_app_service_ash()
->GetWebAppProviderBridge()) {
web_app_provider_bridge->WebAppUninstalledInArc(web_app_id,
std::move(callback));
}
}
void MaybeUninstallPackageInArc(const std::string& package_name) override {
if (auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_app_list_prefs_->app_connection_holder(), UninstallPackage)) {
instance->UninstallPackage(package_name);
}
}
private:
void OnDidGetWebAppIcon(WebAppInstallCallback callback,
const std::string& package_name,
arc::mojom::WebAppInfoPtr web_app_info,
arc::mojom::RawIconPngDataPtr icon) {
// Track the upcoming installation attempt.
std::string web_app_id =
web_app::GenerateAppId(std::nullopt, GURL(web_app_info->start_url));
ApkWebAppService::Get(profile_)->AddInstallingWebApkPackageName(
web_app_id, package_name);
ApkWebAppInstaller::Install(profile_, package_name, std::move(web_app_info),
std::move(icon), std::move(callback),
weak_ptr_factory_.GetWeakPtr());
}
raw_ptr<Profile> profile_;
raw_ptr<ArcAppListPrefs, DanglingUntriaged> arc_app_list_prefs_;
// Must go last.
base::WeakPtrFactory<ApkWebAppServiceDelegateImpl> weak_ptr_factory_{this};
};
} // namespace
ApkWebAppService::Delegate::~Delegate() = default;
// static
ApkWebAppService* ApkWebAppService::Get(Profile* profile) {
return ApkWebAppServiceFactory::GetForProfile(profile);
}
// static
void ApkWebAppService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(kWebAppToApkDictPref);
}
ApkWebAppService::ApkWebAppService(Profile* profile, Delegate* test_delegate)
: profile_(profile),
arc_app_list_prefs_(nullptr),
real_delegate_(std::make_unique<ApkWebAppServiceDelegateImpl>(profile)),
test_delegate_(test_delegate) {
DCHECK(web_app::AreWebAppsEnabled(profile));
apps::AppRegistryCache& app_registry_cache =
apps::AppServiceProxyFactory::GetForProfile(profile)->AppRegistryCache();
app_registry_cache_observer_.Observe(&app_registry_cache);
// Can be null in tests.
arc_app_list_prefs_ = ArcAppListPrefs::Get(profile);
if (arc_app_list_prefs_) {
arc_app_list_prefs_observer_.Observe(arc_app_list_prefs_.get());
}
if (web_app::IsWebAppsCrosapiEnabled()) {
// null in unit tests
if (auto* browser_manager = crosapi::BrowserManager::Get()) {
keep_alive_ = browser_manager->KeepAlive(
crosapi::BrowserManager::Feature::kApkWebAppService);
}
crosapi::WebAppServiceAsh* web_app_service_ash =
crosapi::CrosapiManager::Get()->crosapi_ash()->web_app_service_ash();
web_app_service_observer_.Observe(web_app_service_ash);
}
}
ApkWebAppService::~ApkWebAppService() = default;
bool ApkWebAppService::IsWebOnlyTwa(const webapps::AppId& app_id) {
std::optional<std::string> package_name = GetPackageNameForWebApp(app_id);
if (!package_name) {
return false;
}
std::unique_ptr<ArcAppListPrefs::PackageInfo> package =
arc_app_list_prefs_->GetPackage(*package_name);
if (!(package && package->web_app_info)) {
return false;
}
return package->web_app_info->is_web_only_twa;
}
bool ApkWebAppService::IsWebAppInstalledFromArc(
const webapps::AppId& web_app_id) {
// The web app will only be in prefs under this key if it was installed from
// ARC++.
return WebAppToApks().FindDict(web_app_id) != nullptr;
}
bool ApkWebAppService::IsWebAppShellPackage(const std::string& package_name) {
// If there is no associated web app ID, the package name is not a web app
// shell package.
return GetWebAppIdForPackageName(package_name).has_value();
}
std::optional<std::string> ApkWebAppService::GetPackageNameForWebApp(
const webapps::AppId& app_id,
bool include_installing_apks) {
if (const base::Value::Dict* app_dict = WebAppToApks().FindDict(app_id)) {
if (const std::string* value = app_dict->FindString(kPackageNameKey)) {
return *value;
}
}
// If requested, check whether there is a package name for the web app among
// the currently installing web app apks.
auto it = currently_installing_apks_.find(app_id);
if (include_installing_apks && it != currently_installing_apks_.end()) {
return it->second;
}
return std::nullopt;
}
std::optional<std::string> ApkWebAppService::GetPackageNameForWebApp(
const GURL& url) {
auto* web_app_provider = web_app::WebAppProvider::GetForWebApps(profile_);
if (!web_app_provider) {
return std::nullopt;
}
std::optional<webapps::AppId> app_id =
web_app_provider->registrar_unsafe().FindAppWithUrlInScope(url);
if (!app_id) {
return std::nullopt;
}
return GetPackageNameForWebApp(app_id.value());
}
std::optional<std::string> ApkWebAppService::GetWebAppIdForPackageName(
const std::string& package_name) {
for (auto [web_app_id, web_app_info_value] : WebAppToApks()) {
const std::string* web_app_package_name =
web_app_info_value.GetDict().FindString(kPackageNameKey);
if (web_app_package_name && *web_app_package_name == package_name) {
return web_app_id;
}
}
return std::nullopt;
}
std::optional<std::string> ApkWebAppService::GetCertificateSha256Fingerprint(
const webapps::AppId& app_id) {
std::optional<std::string> package_name = GetPackageNameForWebApp(app_id);
if (!package_name) {
return std::nullopt;
}
std::unique_ptr<ArcAppListPrefs::PackageInfo> package =
arc_app_list_prefs_->GetPackage(*package_name);
if (!(package && package->web_app_info)) {
return std::nullopt;
}
return package->web_app_info->certificate_sha256_fingerprint;
}
void ApkWebAppService::SetWebAppInstalledCallbackForTesting(
WebAppCallbackForTesting web_app_installed_callback) {
web_app_installed_callback_ = std::move(web_app_installed_callback);
}
void ApkWebAppService::SetWebAppUninstalledCallbackForTesting(
WebAppCallbackForTesting web_app_uninstalled_callback) {
web_app_uninstalled_callback_ = std::move(web_app_uninstalled_callback);
}
void ApkWebAppService::MaybeInstallWebApp(
const std::string& package_name,
arc::mojom::WebAppInfoPtr web_app_info) {
if (web_app::IsWebAppsCrosapiEnabled()) {
GetDelegate().MaybeInstallWebAppInLacros(
package_name, std::move(web_app_info),
base::BindOnce(&ApkWebAppService::OnDidFinishInstall,
weak_ptr_factory_.GetWeakPtr(), package_name));
return;
}
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_app_list_prefs_->app_connection_holder(), GetPackageIcon);
if (!instance) {
return;
}
instance->GetPackageIcon(
package_name, kDefaultIconSize, /*normalize=*/false,
base::BindOnce(&ApkWebAppService::OnDidGetWebAppIcon,
weak_ptr_factory_.GetWeakPtr(), package_name,
std::move(web_app_info)));
}
void ApkWebAppService::MaybeUninstallWebApp(const webapps::AppId& web_app_id) {
if (web_app::IsWebAppsCrosapiEnabled()) {
GetDelegate().MaybeUninstallWebAppInLacros(
web_app_id, base::BindOnce(&ApkWebAppService::OnDidRemoveInstallSource,
weak_ptr_factory_.GetWeakPtr(), web_app_id));
return;
}
if (!IsWebAppInstalledFromArc(web_app_id)) {
// Do not uninstall a web app that was not installed via ApkWebAppInstaller.
return;
}
auto* provider = web_app::WebAppProvider::GetForWebApps(profile_);
DCHECK(provider);
provider->scheduler().RemoveInstallManagementMaybeUninstall(
web_app_id, web_app::WebAppManagement::kWebAppStore,
webapps::WebappUninstallSource::kArc,
base::BindOnce(&ApkWebAppService::OnDidRemoveInstallSource,
weak_ptr_factory_.GetWeakPtr(), web_app_id));
}
void ApkWebAppService::MaybeUninstallArcPackage(
const std::string& package_name) {
if (web_app::IsWebAppsCrosapiEnabled()) {
GetDelegate().MaybeUninstallPackageInArc(package_name);
return;
}
if (auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_app_list_prefs_->app_connection_holder(), UninstallPackage)) {
instance->UninstallPackage(package_name);
}
}
void ApkWebAppService::UpdateShelfPin(
const std::string& package_name,
const arc::mojom::WebAppInfoPtr& web_app_info) {
std::string new_app_id;
// Compute the current app id. It may have changed if the package has been
// updated from an Android app to a web app, or vice versa.
if (!web_app_info.is_null()) {
new_app_id = web_app::GenerateAppId(
/*manifest_id=*/std::nullopt, GURL(web_app_info->start_url));
} else {
// Get the first app in the package. If there are multiple apps in the
// package there is no way to determine which app is more suitable to
// replace the previous web app shortcut. For simplicity we will just use
// the first one.
DCHECK(arc_app_list_prefs_);
std::unordered_set<std::string> apps =
arc_app_list_prefs_->GetAppsForPackage(package_name);
if (!apps.empty()) {
new_app_id = *apps.begin();
}
}
// Query for the old app id, which is cached in the package dict to ensure it
// isn't overwritten before this method can run.
const base::Value* last_app_id_value =
arc_app_list_prefs_->GetPackagePrefs(package_name, kLastAppId);
std::string last_app_id;
if (last_app_id_value && last_app_id_value->is_string()) {
last_app_id = last_app_id_value->GetString();
}
if (new_app_id != last_app_id && !new_app_id.empty()) {
arc_app_list_prefs_->SetPackagePrefs(package_name, kLastAppId,
base::Value(new_app_id));
if (!last_app_id.empty()) {
auto* shelf_controller = ChromeShelfController::instance();
if (!shelf_controller) {
return;
}
int index = shelf_controller->PinnedItemIndexByAppID(last_app_id);
// The previously installed app has been uninstalled or hidden, in this
// instance get the saved pin index and pin at that place.
if (index == ChromeShelfController::kInvalidIndex) {
const base::Value* saved_index =
arc_app_list_prefs_->GetPackagePrefs(package_name, kPinIndex);
if (!(saved_index && saved_index->is_int())) {
return;
}
shelf_controller->PinAppAtIndex(new_app_id, saved_index->GetInt());
arc_app_list_prefs_->SetPackagePrefs(
package_name, kPinIndex,
base::Value(ChromeShelfController::kInvalidIndex));
} else {
shelf_controller->ReplacePinnedItem(last_app_id, new_app_id);
}
}
}
}
void ApkWebAppService::Shutdown() {
// Can be null in tests.
if (arc_app_list_prefs_) {
arc_app_list_prefs_ = nullptr;
}
}
void ApkWebAppService::OnPackageInstalled(
const arc::mojom::ArcPackageInfo& package_info) {
SyncArcAndWebApps();
}
void ApkWebAppService::OnPackageRemoved(const std::string& package_name,
bool uninstalled) {
std::optional<std::string> web_app_id =
GetWebAppIdForPackageName(package_name);
if (web_app_id) {
const base::Value::Dict* app_dict = WebAppToApks().FindDict(*web_app_id);
if (app_dict && app_dict->FindBool(kShouldRemoveKey).value_or(false)) {
// This package removal was triggered by web app removal, so cleanup and
// do not kick off the uninstallation loop again.
ScopedDictPrefUpdate(profile_->GetPrefs(), kWebAppToApkDictPref)
->Remove(*web_app_id);
} else {
// Package was removed by the user in ARC.
SyncArcAndWebApps();
}
}
}
void ApkWebAppService::OnPackageListInitialRefreshed() {
arc_initialized_ = true;
SyncArcAndWebApps();
}
void ApkWebAppService::OnArcAppListPrefsDestroyed() {
arc_app_list_prefs_observer_.Reset();
}
void ApkWebAppService::OnAppUpdate(const apps::AppUpdate& update) {
if (update.AppType() == apps::AppType::kWeb &&
update.Readiness() == apps::Readiness::kUninstalledByUser) {
MaybeRemoveArcPackageForWebApp(update.AppId());
}
}
void ApkWebAppService::OnAppTypeInitialized(apps::AppType app_type) {
if (app_type == apps::AppType::kWeb) {
// Web apps are published, try syncing.
SyncArcAndWebApps();
}
}
void ApkWebAppService::OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) {
app_registry_cache_observer_.Reset();
}
void ApkWebAppService::OnWebAppProviderBridgeConnected() {
SyncArcAndWebApps();
}
void ApkWebAppService::OnWebAppServiceAshDestroyed() {
web_app_service_observer_.Reset();
}
void ApkWebAppService::MaybeRemoveArcPackageForWebApp(
const webapps::AppId& web_app_id) {
std::optional<std::string> package_name = GetPackageNameForWebApp(web_app_id);
std::string removed_package_name;
if (package_name) {
ScopedDictPrefUpdate web_apps_to_apks(profile_->GetPrefs(),
kWebAppToApkDictPref);
std::unique_ptr<ArcAppListPrefs::PackageInfo> package =
arc_app_list_prefs_->GetPackage(*package_name);
if (package && package->web_app_info) {
// Mark for removal and kick off the sync.
web_apps_to_apks->EnsureDict(web_app_id)->Set(kShouldRemoveKey, true);
SyncArcAndWebApps();
removed_package_name = *package_name;
} else {
// 1) ARC package was already removed and triggered web app
// uninstallation, so there is nothing to remove.
// 2) ARC package is no longer a web app.
//
// In either case we clean up the prefs and finish.
web_apps_to_apks->Remove(web_app_id);
}
}
// Post task to make sure that all observers get fired before the callback
// called.
if (web_app_uninstalled_callback_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(web_app_uninstalled_callback_),
removed_package_name, web_app_id));
}
}
void ApkWebAppService::OnDidGetWebAppIcon(
const std::string& package_name,
arc::mojom::WebAppInfoPtr web_app_info,
arc::mojom::RawIconPngDataPtr icon) {
// Track the upcoming installation attempt.
std::string web_app_id =
web_app::GenerateAppId(std::nullopt, GURL(web_app_info->start_url));
AddInstallingWebApkPackageName(web_app_id, package_name);
ApkWebAppInstaller::Install(
profile_, package_name, std::move(web_app_info), std::move(icon),
base::BindOnce(&ApkWebAppService::OnDidFinishInstall,
weak_ptr_factory_.GetWeakPtr(), package_name),
weak_ptr_factory_.GetWeakPtr());
}
void ApkWebAppService::OnDidFinishInstall(
const std::string& package_name,
const webapps::AppId& web_app_id,
bool is_web_only_twa,
const std::optional<std::string> sha256_fingerprint,
webapps::InstallResultCode code) {
bool success = false;
if (web_app::IsWebAppsCrosapiEnabled()) {
success = webapps::IsSuccess(code);
} else {
success = code == webapps::InstallResultCode::kSuccessNewInstall;
}
if (success) {
// Set a pref to map |web_app_id| to |package_name| for future
// uninstallation.
ScopedDictPrefUpdate dict_update(profile_->GetPrefs(),
kWebAppToApkDictPref);
base::Value::Dict* web_app_dict = dict_update->EnsureDict(web_app_id);
web_app_dict->Set(kPackageNameKey, package_name);
// Set that the app should not be removed next time the ARC container starts
// up. This is to ensure that web apps which are uninstalled in the browser
// while the ARC container isn't running can be marked for uninstallation
// when the container starts up again.
web_app_dict->Set(kShouldRemoveKey, false);
}
RemoveInstallingWebApkPackageName(web_app_id);
// For testing.
if (web_app_installed_callback_) {
std::move(web_app_installed_callback_).Run(package_name, web_app_id);
}
}
void ApkWebAppService::OnDidRemoveInstallSource(
const webapps::AppId& app_id,
webapps::UninstallResultCode code) {
{
// The web app may still exist, but is no longer managed by an ARC package,
// so remove it from the tracking dictionary.
ScopedDictPrefUpdate web_apps_to_apks(profile_->GetPrefs(),
kWebAppToApkDictPref);
web_apps_to_apks->Remove(app_id);
}
if (web_app_uninstalled_callback_) {
std::move(web_app_uninstalled_callback_)
.Run(/*removed_package_name=*/"", app_id);
}
}
const base::Value::Dict& ApkWebAppService::WebAppToApks() const {
const base::Value::Dict& value =
profile_->GetPrefs()->GetDict(kWebAppToApkDictPref);
return value;
}
void ApkWebAppService::SyncArcAndWebApps() {
// Check that we have the initial state of both ARC packages and installed web
// apps before attempting to reconcile installation state.
apps::AppRegistryCache& app_registry_cache =
apps::AppServiceProxyFactory::GetForProfile(profile_)->AppRegistryCache();
if (!app_registry_cache.IsAppTypeInitialized(apps::AppType::kWeb)) {
return;
}
if (!arc_initialized_) {
return;
}
std::vector<std::string> remove_from_prefs;
for (const auto [web_app_id, web_app_info_value] : WebAppToApks()) {
auto& web_app_info_dict = web_app_info_value.GetDict();
const std::string* package_name =
web_app_info_dict.FindString(kPackageNameKey);
DCHECK(package_name);
if (!package_name ||
base::StartsWith(*package_name, kGeneratedWebApkPackagePrefix)) {
// This shouldn't happen, but clean up bad data anyway.
remove_from_prefs.push_back(web_app_id);
continue;
}
if (!IsAppInstalled(app_registry_cache, web_app_id) &&
!web_app_info_dict.FindBool(kShouldRemoveKey).value_or(false)) {
// If the entry is for a non-existent web app AND isn't a marker for ARC
// package uninstallation, it's stale (possibly due to a crash before web
// app uninstallation callback is processed), so just remove it.
remove_from_prefs.push_back(web_app_id);
}
}
for (const auto& p : remove_from_prefs) {
ScopedDictPrefUpdate(profile_->GetPrefs(), kWebAppToApkDictPref)->Remove(p);
}
// Collect currently installed ARC packages.
std::map<std::string, std::unique_ptr<ArcAppListPrefs::PackageInfo>>
arc_packages;
for (const std::string& package_name :
arc_app_list_prefs_->GetPackagesFromPrefs()) {
// Automatically generated WebAPKs have their lifecycle managed by
// WebApkManager and do not need to be considered here.
if (base::StartsWith(package_name, kGeneratedWebApkPackagePrefix)) {
continue;
}
arc_packages[package_name] = arc_app_list_prefs_->GetPackage(package_name);
}
// Map of package names which have migrated a web app from one package to
// another. There should only be one package at a time on Play Store that
// installs a particular web app. However, when migrating between packages,
// some users might end up with two packages that are trying to install the
// same web app. In this case, the packages will conflict over which one is
// associated with the web app. We solve this by silently migrating installs
// from the "deprecated" package to the "canonical" package.
base::flat_map<std::string, std::string> migration_packages{
{"com.google.android.apps.tachyon", // Canonical package.
"com.google.android.apps.meetings"} // Deprecated package.
};
for (const auto& [canonical_package, deprecated_package] :
migration_packages) {
// We only perform a migration if both packages are installed and both
// trying to install the same web app.
if (!base::Contains(arc_packages, canonical_package) ||
!base::Contains(arc_packages, deprecated_package)) {
continue;
}
std::optional<webapps::AppId> canonical_id =
GetWebAppIdForPackage(arc_packages.at(canonical_package).get());
std::optional<webapps::AppId> deprecated_id =
GetWebAppIdForPackage(arc_packages.at(deprecated_package).get());
if (!canonical_id.has_value() || canonical_id != deprecated_id) {
continue;
}
// If the web app is currently installed but pointing to the deprecated
// package, switch to the canonical package. If the web app is not currently
// installed, it will be installed later in this Sync call.
if (GetPackageNameForWebApp(*canonical_id) == deprecated_package) {
ScopedDictPrefUpdate dict_update(profile_->GetPrefs(),
kWebAppToApkDictPref);
base::Value::Dict* app_id_dict = dict_update->EnsureDict(*canonical_id);
app_id_dict->Set(kPackageNameKey, canonical_package);
}
// Uninstalling the deprecated package is asynchronous, so we also need to
// remove the package from `arc_packages` to prevent it from being
// considered during the rest of this Sync call.
MaybeUninstallArcPackage(deprecated_package);
arc_packages.erase(deprecated_package);
}
// For each ARC package, decide if a matching web app needs to be installed or
// uninstalled, if an ARC package becomes a non-web-app package.
for (const auto& [package_name, package] : arc_packages) {
std::optional<std::string> web_app_id =
GetWebAppIdForPackageName(package_name);
bool was_web_app = web_app_id.has_value();
bool is_web_app = !package->web_app_info.is_null();
if (!was_web_app && is_web_app) {
UpdateShelfPin(package_name, package->web_app_info);
// The package is a web app but we don't have a corresponding browser-side
// artifact. Install it.
MaybeInstallWebApp(package->package_name,
std::move(package->web_app_info));
} else if (was_web_app && !is_web_app) {
UpdateShelfPin(package_name, package->web_app_info);
// The package was a web app, but now isn't. Remove the web app.
DCHECK(web_app_id);
MaybeUninstallWebApp(*web_app_id);
}
}
// For each web app entry, check if needs to be uninstalled, or matching ARC
// package needs to be uninstalled.
std::vector<std::string> arc_apps_to_uninstall;
std::vector<std::string> web_apps_to_uninstall;
for (const auto [web_app_id, web_app_info_value] : WebAppToApks()) {
auto& web_app_info_dict = web_app_info_value.GetDict();
const std::string* package_name =
web_app_info_dict.FindString(kPackageNameKey);
DCHECK(package_name);
if (!package_name) {
// This shouldn't happen, but ignore bad data anyway.
continue;
}
// If we see any app which has obsolete pref values, remove them
// automatically.
if (web_app_info_dict.contains(kIsWebOnlyTwaKey) ||
web_app_info_dict.contains(kSha256FingerprintKey)) {
RemoveObsoletePrefValues(web_app_id);
}
if (base::Contains(arc_packages, *package_name)) {
if (web_app_info_dict.FindBool(kShouldRemoveKey).value_or(false)) {
// ARC app should be uninstalled.
arc_apps_to_uninstall.push_back(*package_name);
}
} else {
// Web app should be uninstalled.
web_apps_to_uninstall.push_back(web_app_id);
}
}
for (const std::string& package_name : arc_apps_to_uninstall) {
MaybeUninstallArcPackage(package_name);
}
for (const std::string& web_app_id : web_apps_to_uninstall) {
MaybeUninstallWebApp(web_app_id);
}
}
// TODO(crbug.com/40896350): Remove this code after migrations are complete.
void ApkWebAppService::RemoveObsoletePrefValues(
const webapps::AppId& web_app_id) {
ScopedDictPrefUpdate dict_update(profile_->GetPrefs(), kWebAppToApkDictPref);
base::Value::Dict* app_id_dict = dict_update->EnsureDict(web_app_id);
app_id_dict->Remove(kIsWebOnlyTwaKey);
app_id_dict->Remove(kSha256FingerprintKey);
}
void ApkWebAppService::AddInstallingWebApkPackageName(
const std::string& app_id,
const std::string& package_name) {
currently_installing_apks_[app_id] = package_name;
}
void ApkWebAppService::RemoveInstallingWebApkPackageName(
const std::string& app_id) {
std::string package_name = currently_installing_apks_[app_id];
if (ash::features::ArePromiseIconsEnabled() && !package_name.empty()) {
apps::AppServiceProxyFactory::GetForProfile(profile_)
->PromiseAppService()
->OnApkWebAppInstallationFinished(package_name);
}
currently_installing_apks_.erase(app_id);
}
} // namespace ash