blob: ddd4cbed8060b60dc50d808a6b3fba81cb9e8802 [file] [log] [blame]
// Copyright 2022 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/apps/app_preload_service/app_preload_service.h"
#include <memory>
#include <vector>
#include "base/auto_reset.h"
#include "base/barrier_callback.h"
#include "base/check_is_test.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "chrome/browser/apps/almanac_api_client/device_info_manager.h"
#include "chrome/browser/apps/app_preload_service/app_preload_service_factory.h"
#include "chrome/browser/apps/app_preload_service/preload_app_definition.h"
#include "chrome/browser/apps/app_service/app_install/app_install_service.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/profiles/profile.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "components/user_manager/user_manager.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace {
// The pref dict is:
// {
// ...
// "apps.app_preload_service.state_manager": {
// "first_login_flow_started": <bool>,
// "first_login_flow_completed": <bool>
// },
// ...
// }
static constexpr char kFirstLoginFlowStartedKey[] = "first_login_flow_started";
static constexpr char kFirstLoginFlowCompletedKey[] =
"first_login_flow_completed";
static constexpr char kFirstLoginFlowHistogramSuccessName[] =
"AppPreloadService.FirstLoginFlowTime.Success";
static constexpr char kFirstLoginFlowHistogramFailureName[] =
"AppPreloadService.FirstLoginFlowTime.Failure";
bool AreTestAppsEnabled() {
return base::FeatureList::IsEnabled(apps::kAppPreloadServiceEnableTestApps);
}
bool g_disable_preloads_on_startup_for_testing_ = false;
} // namespace
namespace apps {
namespace prefs {
static constexpr char kApsStateManager[] =
"apps.app_preload_service.state_manager";
} // namespace prefs
BASE_FEATURE(kAppPreloadServiceForceRun,
"AppPreloadServiceForceRun",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kAppPreloadServiceEnableTestApps,
"AppPreloadServiceEnableTestApps",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kAppPreloadServiceEnableArcApps,
"AppPreloadServiceEnableArcApps",
base::FEATURE_DISABLED_BY_DEFAULT);
AppPreloadService::AppPreloadService(Profile* profile)
: profile_(profile),
server_connector_(std::make_unique<AppPreloadServerConnector>()),
device_info_manager_(std::make_unique<DeviceInfoManager>(profile)) {
if (g_disable_preloads_on_startup_for_testing_) {
return;
}
StartFirstLoginFlow();
}
AppPreloadService::~AppPreloadService() = default;
// static
AppPreloadService* AppPreloadService::Get(Profile* profile) {
return AppPreloadServiceFactory::GetForProfile(profile);
}
// static
void AppPreloadService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(prefs::kApsStateManager);
}
void AppPreloadService::StartFirstLoginFlowForTesting(
PreloadStatusCallback callback) {
installation_complete_callback_ = std::move(callback);
StartFirstLoginFlow();
}
// static
base::AutoReset<bool> AppPreloadService::DisablePreloadsOnStartupForTesting() {
return base::AutoReset<bool>(&g_disable_preloads_on_startup_for_testing_,
true);
}
void AppPreloadService::StartFirstLoginFlow() {
auto start_time = base::TimeTicks::Now();
// Preloads currently run for new users only. The "completed" pref is only set
// when preloads finish successfully, so preloads will be retried if they have
// been "started" but never "completed".
if (user_manager::UserManager::Get()->IsCurrentUserNew()) {
ScopedDictPrefUpdate(profile_->GetPrefs(), prefs::kApsStateManager)
->Set(kFirstLoginFlowStartedKey, true);
}
bool first_run_started =
GetStateManager().FindBool(kFirstLoginFlowStartedKey).value_or(false);
bool first_run_complete =
GetStateManager().FindBool(kFirstLoginFlowCompletedKey).value_or(false);
if ((first_run_started && !first_run_complete) ||
base::FeatureList::IsEnabled(kAppPreloadServiceForceRun)) {
device_info_manager_->GetDeviceInfo(
base::BindOnce(&AppPreloadService::StartAppInstallationForFirstLogin,
weak_ptr_factory_.GetWeakPtr(), start_time));
}
}
void AppPreloadService::StartAppInstallationForFirstLogin(
base::TimeTicks start_time,
DeviceInfo device_info) {
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
profile_->GetURLLoaderFactory();
if (!url_loader_factory.get()) {
// `url_loader_factory` should only be null if we are in a non-preload
// related test. Tests that use profile builder to create their profile
// won't have `url_loader_factory` set up by default, so we bypass preloads
// code being called for those tests.
CHECK_IS_TEST();
return;
}
server_connector_->GetAppsForFirstLogin(
device_info, url_loader_factory,
base::BindOnce(&AppPreloadService::OnGetAppsForFirstLoginCompleted,
weak_ptr_factory_.GetWeakPtr(), start_time));
}
void AppPreloadService::OnGetAppsForFirstLoginCompleted(
base::TimeTicks start_time,
std::optional<std::vector<PreloadAppDefinition>> apps) {
if (!apps.has_value()) {
OnFirstLoginFlowComplete(start_time, /*success=*/false);
return;
}
std::vector<const PreloadAppDefinition*> apps_to_install;
for (const PreloadAppDefinition& app : apps.value()) {
if (ShouldInstallApp(app)) {
apps_to_install.push_back(&app);
}
}
const auto install_barrier_callback = base::BarrierCallback<bool>(
apps_to_install.size(),
base::BindOnce(&AppPreloadService::OnAppInstallationsCompleted,
weak_ptr_factory_.GetWeakPtr(), start_time));
AppInstallService& install_service =
AppServiceProxyFactory::GetForProfile(profile_)->AppInstallService();
for (const PreloadAppDefinition* app : apps_to_install) {
install_service.InstallAppHeadless(
app->IsDefaultApp() ? AppInstallSurface::kAppPreloadServiceDefault
: AppInstallSurface::kAppPreloadServiceOem,
app->ToAppInstallData(), install_barrier_callback);
}
}
void AppPreloadService::OnAppInstallationsCompleted(
base::TimeTicks start_time,
const std::vector<bool>& results) {
OnFirstLoginFlowComplete(start_time,
base::ranges::all_of(results, std::identity{}));
}
void AppPreloadService::OnFirstLoginFlowComplete(base::TimeTicks start_time,
bool success) {
if (success) {
ScopedDictPrefUpdate(profile_->GetPrefs(), prefs::kApsStateManager)
->Set(kFirstLoginFlowCompletedKey, true);
}
base::UmaHistogramMediumTimes(success ? kFirstLoginFlowHistogramSuccessName
: kFirstLoginFlowHistogramFailureName,
base::TimeTicks::Now() - start_time);
if (installation_complete_callback_) {
std::move(installation_complete_callback_).Run(success);
}
}
bool AppPreloadService::ShouldInstallApp(const PreloadAppDefinition& app) {
// We preload android apps (when feature enabled) and web apps.
if (app.GetPlatform() == AppType::kArc) {
if (!base::FeatureList::IsEnabled(apps::kAppPreloadServiceEnableArcApps)) {
return false;
}
} else if (app.GetPlatform() != AppType::kWeb) {
return false;
}
// We currently install apps which were requested by the device OEM or
// installed by default (i.e. by Google). If the testing feature is enabled,
// also install test apps.
bool install_reason_allowed = app.IsOemApp() || app.IsDefaultApp() ||
(app.IsTestApp() && AreTestAppsEnabled());
if (!install_reason_allowed) {
return false;
}
// If the app is already installed with the relevant install reason, we do not
// need to reinstall it. This avoids extra work in the case where we are
// retrying the flow after an install error for a different app.
// TODO(crbug.com/329144520) Implement already installed check for android.
if (app.GetPlatform() == AppType::kArc) {
return true;
}
InstallReason expected_reason =
app.IsDefaultApp() ? InstallReason::kDefault : InstallReason::kOem;
AppServiceProxy* proxy = AppServiceProxyFactory::GetForProfile(profile_);
bool installed = false;
proxy->AppRegistryCache().ForOneApp(
app.GetWebAppId(), [&installed, expected_reason](const AppUpdate& app) {
// It's possible that if APS requests the same app to be installed for
// multiple reasons, this check could incorrectly return false, as App
// Service only reports the highest priority install reason. This is
// acceptable since the check is just an optimization.
installed = apps_util::IsInstalled(app.Readiness()) &&
app.InstallReason() == expected_reason;
});
return !installed;
}
const base::Value::Dict& AppPreloadService::GetStateManager() const {
const base::Value::Dict& value =
profile_->GetPrefs()->GetDict(prefs::kApsStateManager);
return value;
}
} // namespace apps