blob: d18d690960b88ecc1bcabbc335f549b43540d84f [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/barrier_callback.h"
#include "base/containers/cxx20_erase_set.h"
#include "base/feature_list.h"
#include "base/functional/bind.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/app_preload_service/app_preload_service_factory.h"
#include "chrome/browser/apps/app_preload_service/device_info_manager.h"
#include "chrome/browser/apps/app_preload_service/preload_app_definition.h"
#include "chrome/browser/apps/app_preload_service/web_app_preload_installer.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"
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";
} // 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);
AppPreloadService::AppPreloadService(Profile* profile)
: profile_(profile),
server_connector_(std::make_unique<AppPreloadServerConnector>()),
device_info_manager_(std::make_unique<DeviceInfoManager>(profile)),
web_app_installer_(std::make_unique<WebAppPreloadInstaller>(profile)) {
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(
base::OnceCallback<void(bool)> callback) {
SetInstallationCompleteCallbackForTesting(std::move(callback)); // IN-TEST
StartFirstLoginFlow();
}
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) {
server_connector_->GetAppsForFirstLogin(
device_info, profile_->GetURLLoaderFactory(),
base::BindOnce(&AppPreloadService::OnGetAppsForFirstLoginCompleted,
weak_ptr_factory_.GetWeakPtr(), start_time));
}
void AppPreloadService::OnGetAppsForFirstLoginCompleted(
base::TimeTicks start_time,
absl::optional<std::vector<PreloadAppDefinition>> apps) {
if (!apps.has_value()) {
OnFirstLoginFlowComplete(/*success=*/false, start_time);
return;
}
// Filter out any apps that should not be installed.
base::EraseIf(apps.value(), [this](const PreloadAppDefinition& app) {
return !ShouldInstallApp(app);
});
// Request installation of any remaining apps. If there are no apps to
// install, OnAllAppInstallationFinished will be called immediately.
const auto install_barrier_callback_ = base::BarrierCallback<bool>(
apps.value().size(),
base::BindOnce(&AppPreloadService::OnAllAppInstallationFinished,
weak_ptr_factory_.GetWeakPtr(), start_time));
for (const PreloadAppDefinition& app : apps.value()) {
web_app_installer_->InstallApp(app, install_barrier_callback_);
}
}
void AppPreloadService::OnAllAppInstallationFinished(
base::TimeTicks start_time,
const std::vector<bool>& results) {
OnFirstLoginFlowComplete(
base::ranges::all_of(results, [](bool b) { return b; }), start_time);
}
void AppPreloadService::OnFirstLoginFlowComplete(bool success,
base::TimeTicks start_time) {
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 currently only preload web apps.
if (app.GetPlatform() != AppType::kWeb) {
return false;
}
// We currently only install apps which were requested by the device OEM.
if (!app.IsOemApp()) {
return false;
}
// If the app is already OEM-installed, 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.
AppServiceProxy* proxy = AppServiceProxyFactory::GetForProfile(profile_);
bool oem_installed = false;
proxy->AppRegistryCache().ForOneApp(
web_app_installer_->GetAppId(app),
[&oem_installed](const AppUpdate& app) {
oem_installed = apps_util::IsInstalled(app.Readiness()) &&
app.InstallReason() == InstallReason::kOem;
});
return !oem_installed;
}
const base::Value::Dict& AppPreloadService::GetStateManager() const {
const base::Value::Dict& value =
profile_->GetPrefs()->GetDict(prefs::kApsStateManager);
return value;
}
} // namespace apps