// 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_installer.h"
#include <memory>
#include <utility>
#include <vector>
#include "ash/components/arc/mojom/app.mojom.h"
#include "ash/components/arc/test/arc_util_test_support.h"
#include "ash/components/arc/test/connection_holder_util.h"
#include "ash/components/arc/test/fake_app_instance.h"
#include "ash/public/cpp/shelf_model.h"
#include "base/containers/contains.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/bind.h"
#include "base/test/test_future.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/ash/apps/apk_web_app_service.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app.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_icon_generator.h"
#include "chrome/browser/web_applications/web_app_install_finalizer.h"
#include "chrome/browser/web_applications/web_app_proto_utils.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kPackageName[] = "com.google.maps";
const char kAppTitle[] = "Google Maps";
const char kAppUrl[] = "https://www.google.com/maps/";
const char kAppScope[] = "https://www.google.com/";
constexpr char kLastAppId[] = "last_app_id";
const char kAppActivity[] = "test.app.activity";
const char kAppActivity1[] = "test.app1.activity";
const char kPackageName1[] = "com.test.app";
const char kAppTitle1[] = "Test";
const char kAppUrl1[] = "https://www.test.app";
const char kAppScope1[] = "https://www.test.app/";
std::unique_ptr<web_app::WebAppInstallInfo> CreateWebAppInstallInfo(
const GURL& url) {
auto web_app_install_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(url);
web_app_install_info->title = u"App Title";
web_app_install_info->theme_color = SK_ColorBLUE;
web_app_install_info->scope = url.Resolve("scope");
web_app_install_info->display_mode = web_app::DisplayMode::kBrowser;
web_app_install_info->user_display_mode =
web_app::mojom::UserDisplayMode::kStandalone;
const std::vector<web_app::SquareSizePx> sizes_px{web_app::icon_size::k256,
web_app::icon_size::k512};
const std::vector<SkColor> colors{SK_ColorRED, SK_ColorYELLOW};
web_app::AddIconsToWebAppInstallInfo(
web_app_install_info.get(), url,
{{web_app::IconPurpose::ANY, sizes_px, colors}});
return web_app_install_info;
}
void ExpectInitialIconInfosFromWebAppInstallInfo(
const std::vector<apps::IconInfo>& icon_infos,
const GURL& url) {
EXPECT_EQ(2u, icon_infos.size());
EXPECT_EQ(url.Resolve("icon-256.png"), icon_infos[0].url);
EXPECT_EQ(256, icon_infos[0].square_size_px);
EXPECT_EQ(apps::IconInfo::Purpose::kAny, icon_infos[0].purpose);
EXPECT_EQ(url.Resolve("icon-512.png"), icon_infos[1].url);
EXPECT_EQ(512, icon_infos[1].square_size_px);
EXPECT_EQ(apps::IconInfo::Purpose::kAny, icon_infos[1].purpose);
}
void ExpectInitialManifestFieldsFromWebAppInstallInfo(
web_app::WebAppIconManager& icon_manager,
const web_app::WebApp* web_app,
const GURL& url) {
// Manifest fields:
EXPECT_EQ(web_app->untranslated_name(), "App Title");
EXPECT_EQ(web_app->start_url(), url);
EXPECT_EQ(web_app->scope().spec(), url.Resolve("scope"));
EXPECT_EQ(web_app->display_mode(), web_app::DisplayMode::kBrowser);
ASSERT_TRUE(web_app->theme_color().has_value());
EXPECT_EQ(SK_ColorBLUE, web_app->theme_color().value());
ASSERT_TRUE(web_app->sync_proto().has_theme_color());
EXPECT_EQ(SK_ColorBLUE, web_app->sync_proto().theme_color());
EXPECT_EQ("App Title", web_app->sync_proto().name());
EXPECT_EQ(url.Resolve("scope").spec(), web_app->sync_proto().scope());
{
SCOPED_TRACE("web_app->manifest_icons()");
ExpectInitialIconInfosFromWebAppInstallInfo(web_app->manifest_icons(), url);
}
{
SCOPED_TRACE("web_app->sync_proto().icon_infos");
std::optional<std::vector<apps::IconInfo>> parsed_icon_infos =
web_app::ParseAppIconInfos(
"ExpectInitialManifestFieldsFromWebAppInstallInfo",
web_app->sync_proto().icon_infos());
ASSERT_TRUE(parsed_icon_infos.has_value());
ExpectInitialIconInfosFromWebAppInstallInfo(parsed_icon_infos.value(), url);
}
// Manifest Resources:
EXPECT_EQ(web_app::IconManagerReadAppIconPixel(
icon_manager, web_app->app_id(), /*size=*/256),
SK_ColorRED);
EXPECT_EQ(web_app::IconManagerReadAppIconPixel(
icon_manager, web_app->app_id(), /*size=*/512),
SK_ColorYELLOW);
// User preferences:
EXPECT_EQ(web_app->user_display_mode(),
web_app::mojom::UserDisplayMode::kStandalone);
}
} // namespace
namespace ash {
class ApkWebAppInstallerBrowserTest
: public InProcessBrowserTest,
public web_app::WebAppInstallManagerObserver,
public ArcAppListPrefs::Observer {
public:
ApkWebAppInstallerBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
arc::SetArcAvailableCommandLineForTesting(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
arc::ArcSessionManager::SetUiEnabledForTesting(false);
}
void EnableArc() {
arc::SetArcPlayStoreEnabledForProfile(browser()->profile(), true);
arc_app_list_prefs_ = ArcAppListPrefs::Get(browser()->profile());
DCHECK(arc_app_list_prefs_);
base::RunLoop run_loop;
arc_app_list_prefs_->SetDefaultAppsReadyCallback(run_loop.QuitClosure());
run_loop.Run();
app_instance_ = std::make_unique<arc::FakeAppInstance>(arc_app_list_prefs_);
arc_app_list_prefs_->app_connection_holder()->SetInstance(
app_instance_.get());
WaitForInstanceReady(arc_app_list_prefs_->app_connection_holder());
arc_app_list_prefs_->AddObserver(this);
}
void DisableArc() {
arc_app_list_prefs_->app_connection_holder()->CloseInstance(
app_instance_.get());
app_instance_.reset();
arc::ArcSessionManager::Get()->Shutdown();
arc_app_list_prefs_ = nullptr;
}
void SetUpWebApps() {
provider_ = web_app::WebAppProvider::GetForTest(browser()->profile());
DCHECK(provider_);
observation_.Observe(&provider_->install_manager());
}
void TearDownWebApps() {
provider_ = nullptr;
observation_.Reset();
}
void SetUpOnMainThread() override {
EnableArc();
app_instance_->SendRefreshPackageList({});
SetUpWebApps();
}
void TearDownOnMainThread() override {
DisableArc();
TearDownWebApps();
}
arc::mojom::ArcPackageInfoPtr GetWebAppPackage(
const std::string& package_name,
const std::string& app_title = kAppTitle,
const std::string& app_url = kAppUrl,
const std::string& app_scope = kAppScope) {
auto package = GetArcAppPackage(package_name, app_title);
package->web_app_info = GetWebAppInfo(app_title, app_url, app_scope);
return package;
}
arc::mojom::ArcPackageInfoPtr GetArcAppPackage(
const std::string& package_name,
const std::string& app_title) {
auto package = arc::mojom::ArcPackageInfo::New();
package->package_name = package_name;
package->package_version = 1;
package->last_backup_android_id = 1;
package->last_backup_time = 1;
package->sync = true;
return package;
}
arc::mojom::WebAppInfoPtr GetWebAppInfo(
const std::string& app_title,
const std::string& app_url = kAppUrl,
const std::string& app_scope = kAppScope) {
return arc::mojom::WebAppInfo::New(app_title, app_url, app_scope, 100000);
}
ApkWebAppService* apk_web_app_service() {
return ApkWebAppService::Get(browser()->profile());
}
web_app::WebAppIconManager& icon_manager() {
return web_app::WebAppProvider::GetForTest(browser()->profile())
->icon_manager();
}
// Sets a callback to be called whenever an app is completely uninstalled and
// removed from the Registrar.
void set_app_uninstalled_callback(
base::RepeatingCallback<void(const webapps::AppId&)> callback) {
app_uninstalled_callback_ = callback;
}
// web_app::WebAppInstallManagerObserver overrides.
void OnWebAppInstalled(const webapps::AppId& web_app_id) override {
installed_web_app_ids_.push_back(web_app_id);
installed_web_app_names_.push_back(
provider_->registrar_unsafe().GetAppShortName(web_app_id));
}
void OnWebAppWillBeUninstalled(const webapps::AppId& web_app_id) override {
uninstalled_web_app_ids_.push_back(web_app_id);
}
void OnWebAppUninstalled(
const webapps::AppId& app_id,
webapps::WebappUninstallSource uninstall_source) override {
if (app_uninstalled_callback_) {
app_uninstalled_callback_.Run(app_id);
}
}
// ArcAppListPrefs::Observer:
void OnPackageRemoved(const std::string& package_name,
bool uninstalled) override {
if (uninstalled) {
removed_packages_.push_back(package_name);
}
}
void Reset() {
removed_packages_.clear();
installed_web_app_ids_.clear();
installed_web_app_names_.clear();
uninstalled_web_app_ids_.clear();
}
protected:
base::ScopedObservation<web_app::WebAppInstallManager,
web_app::WebAppInstallManagerObserver>
observation_{this};
raw_ptr<ArcAppListPrefs, DanglingUntriaged> arc_app_list_prefs_ = nullptr;
raw_ptr<web_app::WebAppProvider> provider_ = nullptr;
std::unique_ptr<arc::FakeAppInstance> app_instance_;
base::RepeatingCallback<void(const webapps::AppId&)>
app_uninstalled_callback_;
std::vector<std::string> removed_packages_;
std::vector<webapps::AppId> installed_web_app_ids_;
std::vector<std::string> installed_web_app_names_;
std::vector<webapps::AppId> uninstalled_web_app_ids_;
};
class ApkWebAppInstallerDelayedArcStartBrowserTest
: public ApkWebAppInstallerBrowserTest {
// Don't start ARC.
void SetUpOnMainThread() override { SetUpWebApps(); }
// Don't tear down ARC.
void TearDownOnMainThread() override { TearDownWebApps(); }
};
class ApkWebAppInstallerWithShelfControllerBrowserTest
: public ApkWebAppInstallerBrowserTest {
public:
// ApkWebAppInstallerBrowserTest
void SetUpOnMainThread() override {
ApkWebAppInstallerBrowserTest::SetUpOnMainThread();
shelf_controller_ = ChromeShelfController::instance();
ASSERT_TRUE(shelf_controller_);
}
protected:
raw_ptr<ChromeShelfController, DanglingUntriaged> shelf_controller_;
};
// Test the full installation and uninstallation flow.
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest, InstallAndUninstall) {
ApkWebAppService* service = apk_web_app_service();
webapps::AppId app_id;
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(1u, installed_web_app_names_.size());
EXPECT_EQ(1u, installed_web_app_ids_.size());
EXPECT_EQ(kAppTitle, installed_web_app_names_[0]);
EXPECT_EQ(web_app_id, installed_web_app_ids_[0]);
EXPECT_EQ(kPackageName, package_name);
app_id = web_app_id;
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
EXPECT_TRUE(service->IsWebAppShellPackage(kPackageName));
EXPECT_TRUE(service->IsWebAppInstalledFromArc(app_id));
EXPECT_EQ(service->GetPackageNameForWebApp(app_id), kPackageName);
EXPECT_EQ(service->GetWebAppIdForPackageName(kPackageName), app_id);
// Now send an uninstallation call from ARC, which should uninstall the
// installed web app.
{
base::RunLoop run_loop;
service->SetWebAppUninstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(1u, uninstalled_web_app_ids_.size());
EXPECT_EQ(app_id, uninstalled_web_app_ids_[0]);
// No UninstallPackage happened.
EXPECT_EQ("", package_name);
run_loop.Quit();
}));
app_instance_->SendPackageUninstalled(kPackageName);
run_loop.Run();
}
EXPECT_FALSE(service->IsWebAppShellPackage(kPackageName));
EXPECT_FALSE(service->IsWebAppInstalledFromArc(app_id));
EXPECT_EQ(service->GetPackageNameForWebApp(app_id), std::nullopt);
EXPECT_EQ(service->GetWebAppIdForPackageName(kPackageName), std::nullopt);
}
// Test installation via PackageListRefreshed.
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest, PackageListRefreshed) {
ApkWebAppService* service = apk_web_app_service();
std::vector<arc::mojom::ArcPackageInfoPtr> packages;
packages.push_back(GetWebAppPackage(kPackageName, kAppTitle));
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(1u, installed_web_app_names_.size());
EXPECT_EQ(1u, installed_web_app_ids_.size());
EXPECT_EQ(kAppTitle, installed_web_app_names_[0]);
EXPECT_EQ(web_app_id, installed_web_app_ids_[0]);
run_loop.Quit();
}));
app_instance_->SendRefreshPackageList(std::move(packages));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerDelayedArcStartBrowserTest,
PRE_DelayedUninstall) {
ApkWebAppService* service = apk_web_app_service();
// Start up ARC and install two web app packages.
EnableArc();
app_instance_->SendRefreshPackageList({});
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(1u, installed_web_app_names_.size());
EXPECT_EQ(1u, installed_web_app_ids_.size());
EXPECT_EQ(kAppTitle, installed_web_app_names_[0]);
EXPECT_EQ(web_app_id, installed_web_app_ids_[0]);
EXPECT_EQ(kPackageName, package_name);
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(2u, installed_web_app_names_.size());
EXPECT_EQ(2u, installed_web_app_ids_.size());
EXPECT_EQ(kAppTitle1, installed_web_app_names_[1]);
EXPECT_EQ(web_app_id, installed_web_app_ids_[1]);
EXPECT_EQ(kPackageName1, package_name);
run_loop.Quit();
}));
app_instance_->SendPackageAdded(
GetWebAppPackage(kPackageName1, kAppTitle1, kAppUrl1, kAppScope1));
run_loop.Run();
}
DisableArc();
}
// Tests uninstallation while ARC isn't running.
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerDelayedArcStartBrowserTest,
DelayedUninstall) {
ApkWebAppService* service = apk_web_app_service();
// Uninstall both apps from the PRE_ test on the web apps side. ARC
// uninstallation should be processed once ARC starts up.
std::vector<webapps::AppId> uninstall_ids = {
web_app::GenerateAppId(std::nullopt, GURL(kAppUrl)),
web_app::GenerateAppId(std::nullopt, GURL(kAppUrl1))};
for (const auto& id : uninstall_ids) {
base::RunLoop run_loop;
provider_->scheduler().RemoveUserUninstallableManagements(
id, webapps::WebappUninstallSource::kShelf,
base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
EXPECT_EQ(code, webapps::UninstallResultCode::kAppRemoved);
run_loop.Quit();
}));
run_loop.Run();
}
EnableArc();
// Trigger a package refresh, which should call to ARC to remove the packages.
std::vector<arc::mojom::ArcPackageInfoPtr> packages;
packages.push_back(GetWebAppPackage(kPackageName, kAppTitle));
packages.push_back(
GetWebAppPackage(kPackageName1, kAppTitle1, kAppUrl1, kAppScope1));
app_instance_->SendRefreshPackageList(std::move(packages));
EXPECT_EQ(2u, removed_packages_.size());
EXPECT_TRUE(base::Contains(removed_packages_, kPackageName));
EXPECT_TRUE(base::Contains(removed_packages_, kPackageName1));
EXPECT_EQ(std::nullopt, service->GetPackageNameForWebApp(kAppUrl));
EXPECT_EQ(std::nullopt, service->GetPackageNameForWebApp(kAppUrl1));
arc_app_list_prefs_->RemoveObserver(this);
DisableArc();
}
// Test an upgrade that becomes a web app and then stops being a web app.
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest,
UpgradeToWebAppAndToArcApp) {
ApkWebAppService* service = apk_web_app_service();
app_instance_->SendPackageAdded(GetArcAppPackage(kPackageName, kAppTitle));
EXPECT_TRUE(installed_web_app_ids_.empty());
EXPECT_TRUE(uninstalled_web_app_ids_.empty());
// Send a second package added call from ARC, upgrading the package to a web
// app.
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(1u, installed_web_app_names_.size());
EXPECT_EQ(1u, installed_web_app_ids_.size());
EXPECT_TRUE(uninstalled_web_app_ids_.empty());
EXPECT_EQ(kAppTitle, installed_web_app_names_[0]);
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
// Send an package added call from ARC, upgrading the package to not be a
// web app. The web app should be uninstalled.
{
base::RunLoop run_loop;
service->SetWebAppUninstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(uninstalled_web_app_ids_[0], installed_web_app_ids_[0]);
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetArcAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
Reset();
EXPECT_TRUE(installed_web_app_ids_.empty());
EXPECT_TRUE(installed_web_app_names_.empty());
// Upgrade the package to a web app again and make sure it is installed again.
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(1u, installed_web_app_names_.size());
EXPECT_EQ(1u, installed_web_app_ids_.size());
EXPECT_EQ(kAppTitle, installed_web_app_names_[0]);
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
}
// Test that when an ARC-installed Web App is uninstalled and then reinstalled
// as a regular web app, it is not treated as ARC-installed.
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest,
UninstallAndReinstallAsWebApp) {
ApkWebAppService* service = apk_web_app_service();
// Install the Web App from ARC.
webapps::AppId app_id;
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(web_app_id, installed_web_app_ids_[0]);
app_id = web_app_id;
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
ASSERT_TRUE(service->IsWebAppInstalledFromArc(app_id));
// Uninstall the Web App from ARC.
{
base::RunLoop run_loop;
// Wait until the app is completely uninstalled.
set_app_uninstalled_callback(
base::BindLambdaForTesting([&](const webapps::AppId& web_app_id) {
EXPECT_EQ(app_id, web_app_id);
run_loop.Quit();
}));
app_instance_->SendPackageUninstalled(kPackageName);
run_loop.Run();
}
ASSERT_FALSE(service->IsWebAppInstalledFromArc(app_id));
// Reinstall the Web App through the Browser.
webapps::AppId non_arc_app_id = web_app::test::InstallDummyWebApp(
browser()->profile(), kAppTitle, GURL(kAppUrl));
ASSERT_EQ(app_id, non_arc_app_id);
ASSERT_FALSE(service->IsWebAppInstalledFromArc(app_id));
}
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerWithShelfControllerBrowserTest,
CheckPinStateAfterUpdate) {
ApkWebAppService* service = apk_web_app_service();
app_instance_->SendPackageAdded(GetArcAppPackage(kPackageName, kAppTitle));
const std::string arc_app_id =
ArcAppListPrefs::GetAppId(kPackageName, kAppActivity);
/// Create an app and add to the package.
std::vector<arc::mojom::AppInfoPtr> apps;
apps.emplace_back(arc::mojom::AppInfo::New(kAppTitle, kPackageName,
kAppActivity, true /* sticky */));
app_instance_->SendPackageAppListRefreshed(kPackageName, apps);
EXPECT_TRUE(installed_web_app_ids_.empty());
EXPECT_TRUE(uninstalled_web_app_ids_.empty());
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id));
// Pin the app to the shelf.
PinAppWithIDToShelf(arc_app_id);
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id));
int pin_index = shelf_controller_->PinnedItemIndexByAppID(arc_app_id);
arc_app_list_prefs_->SetPackagePrefs(kPackageName, kLastAppId,
base::Value(arc_app_id));
std::string keep_web_app_id;
// Update ARC app to web app and check that the pinned app has
// been updated.
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
keep_web_app_id = web_app_id;
EXPECT_EQ(1u, installed_web_app_names_.size());
EXPECT_EQ(1u, installed_web_app_ids_.size());
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id));
EXPECT_TRUE(shelf_controller_->IsAppPinned(keep_web_app_id));
int new_index =
shelf_controller_->PinnedItemIndexByAppID(keep_web_app_id);
EXPECT_EQ(pin_index, new_index);
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
// Move the pin location of the app.
app_instance_->SendPackageAdded(GetArcAppPackage(kPackageName1, kAppTitle));
const std::string arc_app_id1 =
ArcAppListPrefs::GetAppId(kPackageName1, kAppActivity1);
shelf_controller_->PinAppAtIndex(arc_app_id1, pin_index);
EXPECT_EQ(pin_index, shelf_controller_->PinnedItemIndexByAppID(arc_app_id1));
// The app that was previously pinned will be shifted one to the right.
pin_index += 1;
EXPECT_EQ(pin_index,
shelf_controller_->PinnedItemIndexByAppID(keep_web_app_id));
// Update to ARC app and check the pinned app has updated.
{
base::RunLoop run_loop;
service->SetWebAppUninstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& web_app_id) {
EXPECT_EQ(1u, uninstalled_web_app_ids_.size());
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app_id));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id));
int new_index = shelf_controller_->PinnedItemIndexByAppID(arc_app_id);
EXPECT_EQ(pin_index, new_index);
EXPECT_FALSE(shelf_controller_->IsAppPinned(keep_web_app_id));
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetArcAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
}
// Test that when a regular synced Web App is installed first and the same ARC
// Web App is installed we don't overwrite manifest fields obtained from full
// online install (especially sync fallback data).
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest,
InstallRegularWebAppFirstThenInstallFromArc) {
ApkWebAppService* service = apk_web_app_service();
// Install the Web App as if the user installs it.
std::unique_ptr<web_app::WebAppInstallInfo> web_app_install_info =
CreateWebAppInstallInfo(GURL(kAppUrl));
webapps::AppId app_id = web_app::test::InstallWebApp(
browser()->profile(), std::move(web_app_install_info),
/*overwrite_existing_manifest_fields=*/true,
webapps::WebappInstallSource::SYNC);
ASSERT_FALSE(service->IsWebAppInstalledFromArc(app_id));
const web_app::WebApp* web_app =
provider_->registrar_unsafe().GetAppById(app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->IsSynced());
EXPECT_FALSE(web_app->IsWebAppStoreInstalledApp());
{
SCOPED_TRACE("Expect initial manifest fields.");
ExpectInitialManifestFieldsFromWebAppInstallInfo(icon_manager(), web_app,
GURL(kAppUrl));
}
// Install the Web App from ARC.
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(
base::BindLambdaForTesting([&](const std::string& package_name,
const webapps::AppId& installed_app_id) {
EXPECT_EQ(app_id, installed_app_id);
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
ASSERT_TRUE(service->IsWebAppInstalledFromArc(app_id));
EXPECT_EQ(web_app, provider_->registrar_unsafe().GetAppById(app_id));
EXPECT_TRUE(web_app->IsSynced());
EXPECT_TRUE(web_app->IsWebAppStoreInstalledApp());
{
SCOPED_TRACE("Expect same manifest fields, no overwrites.");
ExpectInitialManifestFieldsFromWebAppInstallInfo(icon_manager(), web_app,
GURL(kAppUrl));
}
}
// Test that when ARC Web App is installed first and then same regular synced
// Web App is installed we overwrite the apk manifest fields with fields
// obtained from full online install (especially sync fallback data).
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest,
InstallFromArcFirstThenRegularWebApp) {
ApkWebAppService* service = apk_web_app_service();
webapps::AppId app_id;
// Install the Web App from ARC.
{
base::RunLoop run_loop;
service->SetWebAppInstalledCallbackForTesting(base::BindLambdaForTesting(
[&](const std::string& package_name, const webapps::AppId& apk_app_id) {
app_id = apk_app_id;
run_loop.Quit();
}));
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
run_loop.Run();
}
ASSERT_TRUE(service->IsWebAppInstalledFromArc(app_id));
const web_app::WebApp* web_app =
provider_->registrar_unsafe().GetAppById(app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->IsWebAppStoreInstalledApp());
EXPECT_FALSE(web_app->IsSynced());
// Install the Web App as if the user installs it.
std::unique_ptr<web_app::WebAppInstallInfo> web_app_install_info =
CreateWebAppInstallInfo(GURL(kAppUrl));
webapps::AppId web_app_id = web_app::test::InstallWebApp(
browser()->profile(), std::move(web_app_install_info),
/*overwrite_existing_manifest_fields=*/true,
webapps::WebappInstallSource::SYNC);
ASSERT_EQ(app_id, web_app_id);
EXPECT_TRUE(service->IsWebAppInstalledFromArc(app_id));
EXPECT_EQ(web_app, provider_->registrar_unsafe().GetAppById(app_id));
EXPECT_TRUE(web_app->IsWebAppStoreInstalledApp());
EXPECT_TRUE(web_app->IsSynced());
{
SCOPED_TRACE(
"Expect online manifest fields, the offline fields from ARC have been "
"overwritten.");
ExpectInitialManifestFieldsFromWebAppInstallInfo(icon_manager(), web_app,
GURL(kAppUrl));
}
}
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerDelayedArcStartBrowserTest,
RemoveWebAppWhenArcDisabled) {
EnableArc();
app_instance_->SendRefreshPackageList({});
ApkWebAppService* service = apk_web_app_service();
// Install the Web App from ARC.
base::test::TestFuture<const std::string&, const webapps::AppId&>
installed_future;
service->SetWebAppInstalledCallbackForTesting(installed_future.GetCallback());
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
webapps::AppId installed_app_id = installed_future.Get<1>();
ASSERT_TRUE(service->IsWebAppInstalledFromArc(installed_app_id));
// Disable ARC through settings and check that the app was uninstalled.
base::test::TestFuture<const std::string&, const webapps::AppId&>
uninstalled_future;
service->SetWebAppUninstalledCallbackForTesting(
uninstalled_future.GetCallback());
arc::SetArcPlayStoreEnabledForProfile(browser()->profile(), false);
DisableArc();
ASSERT_EQ(uninstalled_future.Get<1>(), installed_app_id);
}
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest,
MigrateFromDeprecatedToCanonical) {
constexpr char kCanonicalPackage[] = "com.google.android.apps.tachyon";
constexpr char kDeprecatedPackage[] = "com.google.android.apps.meetings";
ApkWebAppService* service = apk_web_app_service();
base::test::TestFuture<const std::string&, const webapps::AppId&>
installed_future;
service->SetWebAppInstalledCallbackForTesting(installed_future.GetCallback());
app_instance_->SendPackageAdded(GetWebAppPackage(kDeprecatedPackage));
webapps::AppId installed_app_id = installed_future.Get<1>();
ASSERT_EQ(service->GetPackageNameForWebApp(installed_app_id),
kDeprecatedPackage);
// Installing the canonical web app package should migrate away from the
// deprecated package.
app_instance_->SendPackageAdded(GetWebAppPackage(kCanonicalPackage));
ASSERT_EQ(service->GetPackageNameForWebApp(installed_app_id),
kCanonicalPackage);
ASSERT_THAT(removed_packages_, testing::Contains(kDeprecatedPackage));
}
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerDelayedArcStartBrowserTest,
MigrateAndInstallWebApp) {
constexpr char kCanonicalPackage[] = "com.google.android.apps.tachyon";
constexpr char kDeprecatedPackage[] = "com.google.android.apps.meetings";
ApkWebAppService* service = apk_web_app_service();
base::test::TestFuture<const std::string&, const webapps::AppId&>
installed_future;
service->SetWebAppInstalledCallbackForTesting(installed_future.GetCallback());
// Install both canonical and deprecated apps at once, without the web app
// being installed yet. The deprecated package should uninstall and the web
// app should install associated with the canonical app.
auto deprecated_package = GetWebAppPackage(kDeprecatedPackage);
auto canonical_package = GetWebAppPackage(kCanonicalPackage);
std::vector<arc::mojom::ArcPackageInfoPtr> packages;
packages.push_back(std::move(deprecated_package));
packages.push_back(std::move(canonical_package));
EnableArc();
app_instance_->SendRefreshPackageList(std::move(packages));
webapps::AppId installed_app_id = installed_future.Get<1>();
ASSERT_EQ(service->GetPackageNameForWebApp(installed_app_id),
kCanonicalPackage);
ASSERT_THAT(removed_packages_, testing::Contains(kDeprecatedPackage));
}
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest,
MigrateOnUpgradeToWebApp) {
constexpr char kCanonicalPackage[] = "com.google.android.apps.tachyon";
constexpr char kDeprecatedPackage[] = "com.google.android.apps.meetings";
ApkWebAppService* service = apk_web_app_service();
base::test::TestFuture<const std::string&, const webapps::AppId&>
installed_future;
service->SetWebAppInstalledCallbackForTesting(installed_future.GetCallback());
// Install the canonical app as an Android app, and the deprecated app as a
// web app. We should not migrate yet.
app_instance_->SendPackageAdded(
GetArcAppPackage(kCanonicalPackage, kAppTitle));
app_instance_->SendPackageAdded(GetWebAppPackage(kDeprecatedPackage));
webapps::AppId installed_app_id = installed_future.Get<1>();
ASSERT_EQ(service->GetPackageNameForWebApp(installed_app_id),
kDeprecatedPackage);
// When the canonical package updates to a web app, the migration should go
// ahead.
app_instance_->SendPackageAdded(GetWebAppPackage(kCanonicalPackage));
ASSERT_EQ(service->GetPackageNameForWebApp(installed_app_id),
kCanonicalPackage);
ASSERT_THAT(removed_packages_, testing::Contains(kDeprecatedPackage));
}
IN_PROC_BROWSER_TEST_F(ApkWebAppInstallerBrowserTest,
InstallAndUninstallArcOverUserInstall) {
ApkWebAppService* service = apk_web_app_service();
// Install the Web App as if the user installed it.
std::unique_ptr<web_app::WebAppInstallInfo> web_app_install_info =
CreateWebAppInstallInfo(GURL(kAppUrl));
webapps::AppId app_id = web_app::test::InstallWebApp(
browser()->profile(), std::move(web_app_install_info),
/*overwrite_existing_manifest_fields=*/true,
webapps::WebappInstallSource::SYNC);
// Then also install the Web App from ARC.
{
base::test::TestFuture<const std::string&, const webapps::AppId&>
installed_future;
service->SetWebAppInstalledCallbackForTesting(
installed_future.GetCallback());
app_instance_->SendPackageAdded(GetWebAppPackage(kPackageName, kAppTitle));
webapps::AppId arc_app_id = installed_future.Get<1>();
ASSERT_EQ(app_id, arc_app_id);
}
ASSERT_TRUE(service->IsWebAppInstalledFromArc(app_id));
// Uninstall the Web App from ARC.
base::test::TestFuture<const std::string&, const webapps::AppId&>
uninstalled_future;
service->SetWebAppUninstalledCallbackForTesting(
uninstalled_future.GetCallback());
app_instance_->SendPackageUninstalled(kPackageName);
ASSERT_TRUE(uninstalled_future.Wait());
// The app should still be installed, but is no longer registered in
// ApkWebAppService.
ASSERT_FALSE(service->IsWebAppInstalledFromArc(app_id));
ASSERT_TRUE(provider_->registrar_unsafe().GetAppById(app_id));
}
} // namespace ash