// Copyright 2015 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/app_list/arc/arc_app_list_prefs.h"
#include <stddef.h>
#include <string>
#include <utility>
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/compat_mode/arc_resize_lock_manager.h"
#include "ash/components/arc/mojom/compatibility_mode.mojom.h"
#include "ash/components/arc/net/arc_net_host_impl.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/session/connection_holder.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs_factory.h"
#include "chrome/browser/ash/app_list/arc/arc_app_metrics_util.h"
#include "chrome/browser/ash/app_list/arc/arc_app_scoped_pref_update.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/app_list/arc/arc_default_app_list.h"
#include "chrome/browser/ash/app_list/arc/arc_package_install_priority_handler.h"
#include "chrome/browser/ash/app_list/arc/arc_package_syncable_service.h"
#include "chrome/browser/ash/app_list/arc/arc_pai_starter.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/policy/arc_policy_util.h"
#include "chrome/browser/ash/arc/session/arc_initial_optin_metrics_recorder.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/login/session/user_session_manager.h"
#include "chrome/browser/image_decoder/image_decoder.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "components/crx_file/id_util.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "skia/ext/image_operations.h"
#include "third_party/icu/source/common/unicode/localebuilder.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/codec/png_codec.h"
namespace {
constexpr char kActivity[] = "activity";
constexpr char kFrameworkPackageName[] = "android";
constexpr char kResizeLockState[] = "resize_lock_state";
constexpr char kResizeLockNeedsConfirmation[] =
"resize_lock_needs_confirmation";
constexpr char kGameControlsOptOut[] = "game_controls_opt_out";
constexpr char kIconResourceId[] = "icon_resource_id";
constexpr char kIconVersion[] = "icon_version";
constexpr char kInstallTime[] = "install_time";
constexpr char kIntentUri[] = "intent_uri";
constexpr char kLastBackupAndroidId[] = "last_backup_android_id";
constexpr char kLastBackupTime[] = "last_backup_time";
constexpr char kLastLaunchTime[] = "lastlaunchtime";
constexpr char kLaunchable[] = "launchable";
constexpr char kNeedFixup[] = "need_fixup";
constexpr char kName[] = "name";
constexpr char kNotificationsEnabled[] = "notifications_enabled";
constexpr char kPackageName[] = "package_name";
constexpr char kPackageVersion[] = "package_version";
constexpr char kPinIndex[] = "pin_index";
constexpr char kPermissionStates[] = "permission_states";
constexpr char kPreinstalled[] = "preinstalled";
constexpr char kSticky[] = "sticky";
constexpr char kShortcut[] = "shortcut";
constexpr char kShouldSync[] = "should_sync";
constexpr char kSuspended[] = "suspended";
constexpr char kUninstalled[] = "uninstalled";
constexpr char kVPNProvider[] = "vpnprovider";
constexpr char kPermissionStateGranted[] = "granted";
constexpr char kPermissionStateManaged[] = "managed";
constexpr char kPermissionStateDetails[] = "details";
constexpr char kPermissionStateOneTime[] = "one_time";
constexpr char kWebAppInfo[] = "web_app_info";
constexpr char kTitle[] = "title";
constexpr char kStartUrl[] = "start_url";
constexpr char kScopeUrl[] = "scope_url";
constexpr char kThemeColor[] = "theme_color";
constexpr char kIsWebOnlyTwa[] = "is_web_only_twa";
constexpr char kCertificateSha256Fingerprint[] =
"certificate_sha256_fingerprint";
constexpr char kWindowLayout[] = "window_layout";
constexpr char kWindowSizeType[] = "window_layout_type";
constexpr char kWindowResizability[] = "window_resizability";
constexpr char kWindowBounds[] = "window_bounds";
constexpr char kVersionName[] = "version_name";
constexpr char kAppSizeBytesString[] = "app_size_bytes_string";
constexpr char kDataSizeBytesString[] = "data_size_bytes_string";
constexpr char kAppCategory[] = "app_category";
constexpr char kLocaleInfo[] = "locale_info";
constexpr char kSupportedLocales[] = "supported_locales";
constexpr char kSelectedLocale[] = "selected_locale";
// Deprecated perfs fields.
constexpr char kDeprecatePackagePrefsSystem[] = "system";
// Defines maximum number of showing splash screen per user.
const int kMaxNumSplashScreen = 2;
// Defines current version for app icons. This is used for invalidation icons in
// case we change how app icons are produced on Android side. Can be updated in
// unit tests.
int current_icons_version = 1;
// Set of default app icon dips that are required to support ARC icons in all
// usage cases.
constexpr int default_app_icon_dip_sizes[] = {16, 32, 48, 64};
constexpr base::TimeDelta kDetectDefaultAppAvailabilityTimeout =
base::Minutes(1);
// Constants for UMA metrics.
constexpr const char kAppCountUmaPrefix[] = "Arc.AppCount.";
// Do not increase. See base/metrics/histogram_functions.h.
constexpr int kAppCountUmaExclusiveMax = 101;
// A smaller bucket size for apps with a lower count.
constexpr int kAppCountUmaExclusiveMaxLower = 20;
// Accessor for deferred set notifications enabled requests in prefs.
class NotificationsEnabledDeferred {
public:
explicit NotificationsEnabledDeferred(PrefService* prefs) : prefs_(prefs) {}
void Put(const std::string& app_id, bool enabled) {
ScopedDictPrefUpdate update(
prefs_, arc::prefs::kArcSetNotificationsEnabledDeferred);
update->Set(app_id, enabled);
}
bool Get(const std::string& app_id) {
const base::Value::Dict& dict =
prefs_->GetDict(arc::prefs::kArcSetNotificationsEnabledDeferred);
return dict.FindBool(app_id).value_or(false);
}
void Remove(const std::string& app_id) {
ScopedDictPrefUpdate update(
prefs_, arc::prefs::kArcSetNotificationsEnabledDeferred);
update->Remove(app_id);
}
private:
const raw_ptr<PrefService> prefs_;
};
bool WriteIconFile(const base::FilePath& icon_path,
const std::vector<uint8_t>& icon_png_data) {
if (icon_png_data.empty())
return false;
base::CreateDirectory(icon_path.DirName());
if (!base::WriteFile(icon_path, icon_png_data)) {
VLOG(2) << "Failed to write ARC icon file: " << icon_path.MaybeAsASCII()
<< ".";
if (!base::DeleteFile(icon_path)) {
VLOG(2) << "Couldn't delete broken icon file" << icon_path.MaybeAsASCII()
<< ".";
}
return false;
}
return true;
}
bool InstallIconFromFileThread(const base::FilePath& icon_path,
const base::FilePath& foreground_icon_path,
const base::FilePath& background_icon_path,
arc::mojom::RawIconPngDataPtr icon) {
const std::vector<uint8_t>& icon_png_data = icon->icon_png_data.value();
DCHECK(!icon_png_data.empty());
if (!WriteIconFile(icon_path, icon_png_data))
return false;
if (!icon->is_adaptive_icon) {
// For non-adaptive icon, save the |icon_png_data| to the
// |foreground_icon_path|, to identify the difference between migrating to
// the adaptive icon feature enabled and the non-adaptive icon case. If
// there is a |foreground_icon_path| file without a |background_icon_path|
// file, that means the icon is a non-adaptive icon. Otherwise, if there is
// no |foreground_icon_path| file, that means we haven't fetched the
// adaptive icon yet, then we should request the icon.
if (!WriteIconFile(foreground_icon_path, icon->icon_png_data.value())) {
return false;
}
return true;
}
if (!WriteIconFile(foreground_icon_path,
icon->foreground_icon_png_data.value())) {
return false;
}
if (!WriteIconFile(background_icon_path,
icon->background_icon_png_data.value())) {
return false;
}
return true;
}
void DeleteAppFolderFromFileThread(const base::FilePath& path) {
DCHECK(path.DirName().BaseName().MaybeAsASCII() == arc::prefs::kArcApps &&
(!base::PathExists(path) || base::DirectoryExists(path)));
const bool deleted = base::DeletePathRecursively(path);
DCHECK(deleted);
}
// TODO(crbug.com/40497410): Due to shutdown procedure dependency,
// ArcAppListPrefs may try to touch ArcSessionManager related stuff.
// Specifically, this returns false on shutdown phase.
// Remove this check after the shutdown behavior is fixed.
bool IsArcAlive() {
const auto* arc_session_manager = arc::ArcSessionManager::Get();
return arc_session_manager && arc_session_manager->IsAllowed();
}
// Returns true if ARC Android instance is supposed to be enabled for the
// profile. This can happen for if the user has opted in for the given profile,
// or when ARC always starts after login.
bool IsArcAndroidEnabledForProfile(const Profile* profile) {
return arc::ShouldArcAlwaysStart() ||
arc::IsArcPlayStoreEnabledForProfile(profile);
}
bool GetInt64FromPref(const base::Value::Dict* dict,
const std::string& key,
int64_t* value) {
DCHECK(dict);
const std::string* value_str = dict->FindString(key);
if (!value_str) {
VLOG(2) << "Can't find key in local pref dictionary. Invalid key: " << key
<< ".";
return false;
}
if (!base::StringToInt64(*value_str, value)) {
VLOG(2) << "Can't change string to int64_t. Invalid string value: "
<< *value_str << ".";
return false;
}
return true;
}
// Converts |rect| to base::Value, e.g. { 0, 100, 200, 300 }.
base::Value RectToValueDict(const gfx::Rect& rect) {
base::Value::Dict dict;
dict.Set("x", rect.x());
dict.Set("y", rect.y());
dict.Set("width", rect.width());
dict.Set("height", rect.height());
return base::Value(std::move(dict));
}
// Gets gfx::Rect from base::Value, e.g. { 0, 100, 200, 300 } returns
// gfx::Rect(0, 100, 200, 300). If the Value does not contains valid rect,
// returns std::nullopt.
std::optional<gfx::Rect> RectFromDictValue(const base::Value* rect_dict) {
if (!rect_dict)
return std::nullopt;
auto x = rect_dict->GetDict().FindInt("x");
auto y = rect_dict->GetDict().FindInt("y");
auto width = rect_dict->GetDict().FindInt("width");
auto height = rect_dict->GetDict().FindInt("height");
if (!x.has_value() || !y.has_value() || !width.has_value() ||
!height.has_value()) {
return std::nullopt;
}
return gfx::Rect(x.value(), y.value(), width.value(), height.value());
}
base::Value WindowLayoutToDict(
const ArcAppListPrefs::WindowLayout& window_layout) {
base::Value::Dict dict;
dict.Set(kWindowSizeType, static_cast<int32_t>(window_layout.type));
dict.Set(kWindowResizability, window_layout.resizable);
if (window_layout.bounds.has_value())
dict.Set(kWindowBounds, RectToValueDict(window_layout.bounds.value()));
return base::Value(std::move(dict));
}
ArcAppListPrefs::WindowLayout WindowLayoutFromDict(
const base::Value::Dict* dict) {
if (!dict)
return ArcAppListPrefs::WindowLayout();
const base::Value* window_bounds = dict->Find(kWindowBounds);
return ArcAppListPrefs::WindowLayout(
static_cast<arc::mojom::WindowSizeType>(
dict->FindInt(kWindowSizeType).value_or(0)),
dict->FindBool(kWindowResizability).value_or(true),
RectFromDictValue(window_bounds));
}
ArcAppListPrefs::WindowLayout WindowLayoutFromApp(
const arc::mojom::AppInfo& app) {
if (app.initial_layout.is_null())
return ArcAppListPrefs::WindowLayout();
return ArcAppListPrefs::WindowLayout(app.initial_layout->type,
app.initial_layout->resizable,
app.initial_layout->bounds);
}
// Returns true if one of state of |info1| does not match the same state in
// |info2|.
bool AreAppStatesChanged(const ArcAppListPrefs::AppInfo& info1,
const ArcAppListPrefs::AppInfo& info2) {
return info1.sticky != info2.sticky ||
info1.notifications_enabled != info2.notifications_enabled ||
info1.resize_lock_state != info2.resize_lock_state ||
info1.resize_lock_needs_confirmation !=
info2.resize_lock_needs_confirmation ||
info1.ready != info2.ready || info1.suspended != info2.suspended ||
info1.show_in_launcher != info2.show_in_launcher ||
info1.launchable != info2.launchable ||
info1.need_fixup != info2.need_fixup ||
info1.version_name != info2.version_name ||
info1.app_size_in_bytes != info2.app_size_in_bytes ||
info1.data_size_in_bytes != info2.data_size_in_bytes;
}
// We have only fixed icon dimensions for default apps, 32, 48 and 64. If
// requested dimension does not exist, use bigger one that can be downsized.
// In case requested dimension is bigger than 64, use largest possible size that
// can be upsized.
ArcAppIconDescriptor MapDefaultAppIconDescriptor(
const ArcAppIconDescriptor& descriptor) {
int default_app_dip_size;
if (descriptor.dip_size <= 32)
default_app_dip_size = 32;
else if (descriptor.dip_size <= 48)
default_app_dip_size = 48;
else
default_app_dip_size = 64;
return ArcAppIconDescriptor(default_app_dip_size, descriptor.scale_factor);
}
// Whether skip install_time for comparing two |AppInfo|.
bool ignore_compare_app_info_install_time = false;
// Reason for installation enumeration; Used for UMA counter for reason for
// install.
enum class InstallationCounterReasonEnum {
USER = 0, // Application installed by user.
DEFAULT = 1, // Application part of the default set.
OEM = 2, // OEM application.
POLICY = 3, // Installed by policy.
UNKNOWN = 4,
kMaxValue = UNKNOWN,
};
// Reasons for uninstalls. Only one, USER, for now.
enum class UninstallCounterReasonEnum {
USER = 0, // Uninstall triggered by user.
kMaxValue = USER
};
// Remove deprecated package prefs. Otherwise deprecated fields will stay on
// disks.
void MaybeRemoveDeprecatedPackagePrefs(arc::ArcAppScopedPrefUpdate&& update) {
update.Get().Remove(kDeprecatePackagePrefsSystem);
}
// Validate |locale_tag| based on IETF BCP 47 language tag.
bool IsLocaleTagValid(const std::string& locale_tag) {
UErrorCode error = U_ZERO_ERROR;
icu::LocaleBuilder().setLanguageTag(locale_tag.c_str()).build(error);
return error == U_ZERO_ERROR;
}
// In some cases when ARC is not ready (e.g. ARC hasn't booted / ARC failed to
// boot), users are still allowed to change App Settings from ChromeOS Settings
// page. Hence, there might be synchronization issue between ARC and ChromeOS
// and we should eventually re-sync them.
bool IsSelectedLocaleResyncRequired(
const base::Value::Dict& saved_package_dict,
const arc::mojom::PackageLocaleInfo& arc_locale_info,
const UpdatePackagePrefsReason& update_reason) {
// Only checks for ARC-boot package refresh.
if (update_reason != UpdatePackagePrefsReason::kOnPackageListRefreshed) {
return false;
}
const base::Value::Dict* locale_info_dict =
saved_package_dict.FindDict(kLocaleInfo);
if (!locale_info_dict) {
return false;
}
// selected_locale always exists if locale_info is present.
const std::string* saved_selected_locale =
locale_info_dict->FindString(kSelectedLocale);
CHECK(saved_selected_locale)
<< "selected_locale always exists if locale_info is present.";
// Validates if there's a mismatch between ChromeOS' saved `selected_locale`
// and ARC's previous `selected_locale`
return *saved_selected_locale != arc_locale_info.selected_locale;
}
void OnArcAppListRefreshed(Profile* profile) {
if (!arc::IsArcPlayStoreEnabledForProfile(profile))
return;
if (!arc::ArcInitialOptInMetricsRecorder::GetForProfile(profile)
->NeedReportArcAppListReady()) {
return;
}
DCHECK_EQ(ProfileManager::GetPrimaryUserProfile(), profile);
auto* prefs = ArcAppListPrefs::Get(profile);
if (!prefs)
return;
const std::vector<std::string> app_ids = prefs->GetAppIds();
int launchable = 0;
int ready = 0;
int error = 0;
for (const auto& app_id : app_ids) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (app_info) {
if (app_info->launchable)
++launchable;
if (app_info->ready)
++ready;
} else {
++error;
}
}
if (ready + error >= launchable) {
arc::ArcInitialOptInMetricsRecorder::GetForProfile(profile)
->OnArcAppListReady();
}
}
} // namespace
// static
ArcAppListPrefs* ArcAppListPrefs::Create(Profile* profile) {
return new ArcAppListPrefs(profile, nullptr);
}
// static
ArcAppListPrefs* ArcAppListPrefs::Create(
Profile* profile,
arc::ConnectionHolder<arc::mojom::AppInstance, arc::mojom::AppHost>*
app_connection_holder_for_testing) {
DCHECK(app_connection_holder_for_testing);
return new ArcAppListPrefs(profile, app_connection_holder_for_testing);
}
// static
void ArcAppListPrefs::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(arc::prefs::kArcApps);
registry->RegisterDictionaryPref(arc::prefs::kArcPackages);
registry->RegisterBooleanPref(arc::prefs::kArcPackagesIsUpToDate, false);
registry->RegisterIntegerPref(arc::prefs::kArcFrameworkVersion,
-1 /* default_value */);
registry->RegisterDictionaryPref(
arc::prefs::kArcSetNotificationsEnabledDeferred);
registry->RegisterIntegerPref(
arc::prefs::kArcShowResizeLockSplashScreenLimits, kMaxNumSplashScreen);
ArcDefaultAppList::RegisterProfilePrefs(registry);
}
// static
ArcAppListPrefs* ArcAppListPrefs::Get(content::BrowserContext* context) {
return ArcAppListPrefsFactory::GetInstance()->GetForBrowserContext(context);
}
// static
std::string ArcAppListPrefs::GetAppId(const std::string& package_name,
const std::string& activity) {
if (package_name == arc::kPlayStorePackage &&
activity == arc::kPlayStoreActivity) {
return arc::kPlayStoreAppId;
}
const std::string input = package_name + "#" + activity;
const std::string app_id = crx_file::id_util::GenerateId(input);
return app_id;
}
// static
void ArcAppListPrefs::UprevCurrentIconsVersionForTesting() {
++current_icons_version;
}
std::string ArcAppListPrefs::GetAppIdByPackageName(
const std::string& package_name) const {
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
for (const auto it : apps) {
const base::Value& value = it.second;
const std::string* installed_package_name =
value.GetDict().FindString(kPackageName);
if (!installed_package_name || *installed_package_name != package_name)
continue;
const std::string* activity_name = value.GetDict().FindString(kActivity);
return activity_name ? GetAppId(package_name, *activity_name)
: std::string();
}
return std::string();
}
ArcAppListPrefs::ArcAppListPrefs(
Profile* profile,
arc::ConnectionHolder<arc::mojom::AppInstance, arc::mojom::AppHost>*
app_connection_holder_for_testing)
: profile_(profile),
prefs_(profile->GetPrefs()),
app_connection_holder_for_testing_(app_connection_holder_for_testing),
file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
VLOG(1) << "ARC app list prefs created";
DCHECK(profile);
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
const base::FilePath& base_path = profile->GetPath();
base_path_ = base_path.AppendASCII(arc::prefs::kArcApps);
arc_app_metrics_util_ = std::make_unique<arc::ArcAppMetricsUtil>();
// Once default apps are ready OnDefaultAppsReady is called.
default_apps_ = std::make_unique<ArcDefaultAppList>(
profile, base::BindOnce(&ArcAppListPrefs::OnDefaultAppsReady,
base::Unretained(this)));
arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
if (!arc_session_manager) {
VLOG(1) << "ARC session manager is not available";
return;
}
DCHECK(arc::IsArcAllowedForProfile(profile));
const std::vector<std::string> existing_app_ids = GetAppIds();
tracked_apps_.insert(existing_app_ids.begin(), existing_app_ids.end());
// Not always set in unit_tests
arc::ArcPolicyBridge* policy_bridge =
arc::ArcPolicyBridge::GetForBrowserContext(profile_);
if (policy_bridge)
policy_bridge->AddObserver(this);
arc::ArcResizeLockManager* resize_lock_manager =
arc::ArcResizeLockManager::GetForBrowserContext(profile_);
if (resize_lock_manager)
resize_lock_manager->SetPrefDelegate(this);
arc::ArcNetHostImpl* net_host =
arc::ArcNetHostImpl::GetForBrowserContext(profile_);
if (net_host) {
net_host->SetArcAppMetadataProvider(this);
}
if (base::FeatureList::IsEnabled(arc::kSyncInstallPriority)) {
install_priority_handler_ =
std::make_unique<arc::ArcPackageInstallPriorityHandler>(profile);
}
}
ArcAppListPrefs::~ArcAppListPrefs() {
for (auto& observer : observer_list_)
observer.OnArcAppListPrefsDestroyed();
arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
if (!arc_session_manager)
return;
DCHECK(arc::ArcServiceManager::Get());
arc_session_manager->RemoveObserver(this);
app_connection_holder()->RemoveObserver(this);
}
void ArcAppListPrefs::StartPrefs() {
// Don't tie ArcAppListPrefs created with sync test profile in sync
// integration test to ArcSessionManager.
if (!ArcAppListPrefsFactory::IsFactorySetForSyncTest()) {
arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
CHECK(arc_session_manager);
if (arc_session_manager->profile()) {
// Note: If ArcSessionManager has profile, it should be as same as the one
// this instance has, because ArcAppListPrefsFactory creates an instance
// only if the given Profile meets ARC's requirement.
// Anyway, just in case, check it here and log. Only some browser tests
// will log the error. If you see the log outside browser_tests, something
// unexpected may have happened.
if (profile_ != arc_session_manager->profile()) {
LOG(ERROR)
<< "This object's profile_ and ArcSessionManager's don't match.";
}
OnArcPlayStoreEnabledChanged(
arc::IsArcPlayStoreEnabledForProfile(profile_));
}
arc_session_manager->AddObserver(this);
}
VLOG(1) << "Registering host...";
app_connection_holder()->SetHost(this);
app_connection_holder()->AddObserver(this);
if (!app_connection_holder()->IsConnected())
OnConnectionClosed();
}
base::FilePath ArcAppListPrefs::GetAppPath(const std::string& app_id) const {
return base_path_.AppendASCII(app_id);
}
base::FilePath ArcAppListPrefs::MaybeGetIconPathForDefaultApp(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor) const {
const ArcDefaultAppList::AppInfo* default_app = default_apps_->GetApp(app_id);
if (!default_app || default_app->app_path.empty())
return base::FilePath();
return default_app->app_path.AppendASCII(
MapDefaultAppIconDescriptor(descriptor).GetName());
}
base::FilePath ArcAppListPrefs::MaybeGetForegroundIconPathForDefaultApp(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor) const {
const ArcDefaultAppList::AppInfo* default_app = default_apps_->GetApp(app_id);
if (!default_app || default_app->app_path.empty())
return base::FilePath();
return default_app->app_path.AppendASCII(
MapDefaultAppIconDescriptor(descriptor).GetForegroundIconName());
}
base::FilePath ArcAppListPrefs::MaybeGetBackgroundIconPathForDefaultApp(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor) const {
const ArcDefaultAppList::AppInfo* default_app = default_apps_->GetApp(app_id);
if (!default_app || default_app->app_path.empty())
return base::FilePath();
return default_app->app_path.AppendASCII(
MapDefaultAppIconDescriptor(descriptor).GetBackgroundIconName());
}
base::FilePath ArcAppListPrefs::GetIconPath(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor) {
// TODO(khmel): Add DCHECK(GetApp(app_id));
active_icons_[app_id].insert(descriptor);
return GetAppPath(app_id).AppendASCII(descriptor.GetName());
}
base::FilePath ArcAppListPrefs::GetForegroundIconPath(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor) {
active_icons_[app_id].insert(descriptor);
return GetAppPath(app_id).AppendASCII(descriptor.GetForegroundIconName());
}
base::FilePath ArcAppListPrefs::GetBackgroundIconPath(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor) {
active_icons_[app_id].insert(descriptor);
return GetAppPath(app_id).AppendASCII(descriptor.GetBackgroundIconName());
}
bool ArcAppListPrefs::IsIconRequestRecorded(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor) const {
const auto iter = request_icon_recorded_.find(app_id);
if (iter == request_icon_recorded_.end())
return false;
return iter->second.count(descriptor);
}
void ArcAppListPrefs::MaybeRemoveIconRequestRecord(const std::string& app_id) {
request_icon_recorded_.erase(app_id);
}
void ArcAppListPrefs::ClearIconRequestRecord() {
request_icon_recorded_.clear();
}
void ArcAppListPrefs::RequestIcon(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor,
base::OnceCallback<void(arc::mojom::RawIconPngDataPtr)> callback) {
DCHECK_NE(app_id, arc::kPlayStoreAppId);
// ArcSessionManager can be terminated during test tear down, before callback
// into this function.
// TODO(victorhsieh): figure out the best way/place to handle this situation.
if (arc::ArcSessionManager::Get() == nullptr) {
std::move(callback).Run(nullptr);
return;
}
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to load icon for non-registered app: " << app_id << ".";
std::move(callback).Run(nullptr);
return;
}
// In case app is not ready, recorded request will be send to ARC when app
// becomes ready.
// This record will prevent ArcAppIcon from resending request to ARC for app
// icon when icon file decode failure is suffered in case app sends bad icon.
request_icon_recorded_[app_id].insert(descriptor);
if (!ready_apps_.count(app_id)) {
std::move(callback).Run(nullptr);
return;
}
if (!app_connection_holder()->IsConnected()) {
// AppInstance should be ready since we have app_id in ready_apps_. This
// can happen in browser_tests.
std::move(callback).Run(nullptr);
return;
}
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
std::move(callback).Run(nullptr);
return;
}
SendIconRequest(app_id, *app_info, descriptor, std::move(callback));
}
void ArcAppListPrefs::SendIconRequest(
const std::string& app_id,
const AppInfo& app_info,
const ArcAppIconDescriptor& descriptor,
base::OnceCallback<void(arc::mojom::RawIconPngDataPtr)>
icon_data_callback) {
auto callback =
base::BindOnce(&ArcAppListPrefs::OnIcon, weak_ptr_factory_.GetWeakPtr(),
app_id, descriptor, std::move(icon_data_callback));
if (app_info.icon_resource_id.empty()) {
auto* app_instance =
ARC_GET_INSTANCE_FOR_METHOD(app_connection_holder(), GetAppIcon);
if (!app_instance)
return; // Error is logged in macro.
app_instance->GetAppIcon(app_info.package_name, app_info.activity,
descriptor.GetSizeInPixels(), std::move(callback));
} else {
auto* app_instance = ARC_GET_INSTANCE_FOR_METHOD(app_connection_holder(),
GetAppShortcutIcon);
if (!app_instance)
return; // Error is logged in macro.
app_instance->GetAppShortcutIcon(app_info.icon_resource_id,
descriptor.GetSizeInPixels(),
std::move(callback));
}
}
void ArcAppListPrefs::RequestRawIconData(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor,
base::OnceCallback<void(arc::mojom::RawIconPngDataPtr)> callback) {
RequestIcon(app_id, descriptor, std::move(callback));
}
void ArcAppListPrefs::MaybeRequestIcon(const std::string& app_id,
const ArcAppIconDescriptor& descriptor) {
if (!IsIconRequestRecorded(app_id, descriptor)) {
RequestIcon(
app_id, descriptor,
base::BindOnce(&ArcAppListPrefs::InstallIcon,
weak_ptr_factory_.GetWeakPtr(), app_id, descriptor));
}
}
void ArcAppListPrefs::SetNotificationsEnabled(const std::string& app_id,
bool enabled) {
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to set notifications enabled flag for non-registered "
<< "app:" << app_id << ".";
return;
}
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
return;
}
// In case app is not ready, defer this request.
if (!ready_apps_.count(app_id)) {
NotificationsEnabledDeferred(prefs_).Put(app_id, enabled);
for (auto& observer : observer_list_)
observer.OnNotificationsEnabledChanged(app_info->package_name, enabled);
return;
}
auto* app_instance = ARC_GET_INSTANCE_FOR_METHOD(app_connection_holder(),
SetNotificationsEnabled);
if (!app_instance)
return;
NotificationsEnabledDeferred(prefs_).Remove(app_id);
app_instance->SetNotificationsEnabled(app_info->package_name, enabled);
}
void ArcAppListPrefs::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void ArcAppListPrefs::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
bool ArcAppListPrefs::HasObserver(Observer* observer) {
return observer_list_.HasObserver(observer);
}
base::RepeatingCallback<std::string(const std::string&)>
ArcAppListPrefs::GetAppIdByPackageNameCallback() {
return base::BindRepeating(
[](base::WeakPtr<ArcAppListPrefs> self, const std::string& package_name) {
if (!self)
return std::string();
return self->GetAppIdByPackageName(package_name);
},
weak_ptr_factory_.GetWeakPtr());
}
std::unique_ptr<ArcAppListPrefs::PackageInfo> ArcAppListPrefs::GetPackage(
const std::string& package_name) const {
if (!IsArcAlive() || !IsArcAndroidEnabledForProfile(profile_))
return nullptr;
const base::Value::Dict& packages = prefs_->GetDict(arc::prefs::kArcPackages);
const base::Value::Dict* package = packages.FindDict(package_name);
if (!package)
return nullptr;
if (package->FindBool(kUninstalled).value_or(false))
return nullptr;
int64_t last_backup_android_id = 0;
int64_t last_backup_time = 0;
base::flat_map<arc::mojom::AppPermission, arc::mojom::PermissionStatePtr>
permissions;
GetInt64FromPref(package, kLastBackupAndroidId, &last_backup_android_id);
GetInt64FromPref(package, kLastBackupTime, &last_backup_time);
const base::Value* permission_val = package->Find(kPermissionStates);
if (permission_val) {
const base::Value::Dict* permission_dict = permission_val->GetIfDict();
DCHECK(permission_dict);
for (const auto iter : *permission_dict) {
int64_t permission_type = -1;
base::StringToInt64(iter.first, &permission_type);
DCHECK_NE(-1, permission_type);
const base::Value& permission_state = iter.second;
const base::Value::Dict* permission_state_dict =
permission_state.GetIfDict();
if (permission_state_dict) {
bool granted = permission_state_dict->FindBool(kPermissionStateGranted)
.value_or(false);
bool managed = permission_state_dict->FindBool(kPermissionStateManaged)
.value_or(false);
const std::string* details =
permission_state_dict->FindString(kPermissionStateDetails);
std::optional<std::string> details_opt;
if (details != nullptr) {
details_opt = *details;
}
bool one_time = permission_state_dict->FindBool(kPermissionStateOneTime)
.value_or(false);
arc::mojom::AppPermission permission =
static_cast<arc::mojom::AppPermission>(permission_type);
permissions.emplace(permission,
arc::mojom::PermissionState::New(
granted, managed, details_opt, one_time));
} else {
LOG(ERROR) << "Permission state was not a dictionary.";
}
}
}
arc::mojom::WebAppInfoPtr web_app_info;
if (const base::Value* web_app_info_value = package->Find(kWebAppInfo)) {
const base::Value::Dict& web_app_info_dict = web_app_info_value->GetDict();
web_app_info = arc::mojom::WebAppInfo::New();
web_app_info->title = *web_app_info_dict.FindString(kTitle);
web_app_info->start_url = *web_app_info_dict.FindString(kStartUrl);
web_app_info->scope_url = *web_app_info_dict.FindString(kScopeUrl);
bool must_convert_to_int = base::StringToInt64(
*web_app_info_dict.FindString(kThemeColor), &web_app_info->theme_color);
DCHECK(must_convert_to_int);
web_app_info->is_web_only_twa = *web_app_info_dict.FindBool(kIsWebOnlyTwa);
if (const std::string* fingerprint =
web_app_info_dict.FindString(kCertificateSha256Fingerprint)) {
web_app_info->certificate_sha256_fingerprint = *fingerprint;
}
}
arc::mojom::PackageLocaleInfoPtr locale_info;
if (const base::Value* locale_info_value = package->Find(kLocaleInfo)) {
const base::Value::Dict& locale_info_dict = locale_info_value->GetDict();
if (const base::Value::List* supported_locales =
locale_info_dict.FindList(kSupportedLocales)) {
locale_info = arc::mojom::PackageLocaleInfo::New();
locale_info->supported_locales.reserve(supported_locales->size());
for (const base::Value& locale : *supported_locales) {
locale_info->supported_locales.emplace_back(locale.GetString());
}
locale_info->selected_locale =
*locale_info_dict.FindString(kSelectedLocale);
}
}
return std::make_unique<PackageInfo>(
package_name, package->FindInt(kPackageVersion).value_or(0),
last_backup_android_id, last_backup_time,
package->FindBool(kShouldSync).value_or(false),
package->FindBool(kVPNProvider).value_or(false),
package->FindBool(kPreinstalled).value_or(false),
package->FindBool(kGameControlsOptOut).value_or(false),
std::move(permissions), std::move(web_app_info), std::move(locale_info));
}
bool ArcAppListPrefs::IsPackageInstalled(
const std::string& package_name) const {
return GetPackage(package_name) != nullptr;
}
std::vector<std::string> ArcAppListPrefs::GetAppIds() const {
if (arc::ShouldArcAlwaysStart())
return GetAppIdsNoArcEnabledCheck();
if (!IsArcAlive() || !IsArcAndroidEnabledForProfile(profile_)) {
// Default ARC apps available before OptIn.
std::vector<std::string> ids;
for (const auto& default_app : default_apps_->GetActiveApps()) {
// Default apps are iteratively added to prefs. That generates
// |OnAppRegistered| event per app. Consumer may use this event to request
// list of all apps. Although this practice is discouraged due the
// performance reason, let be safe and in order to prevent listing of not
// yet registered apps, filter out default apps based of tracked state.
if (tracked_apps_.count(default_app.first))
ids.push_back(default_app.first);
}
return ids;
}
return GetAppIdsNoArcEnabledCheck();
}
std::vector<std::string> ArcAppListPrefs::GetAppIdsNoArcEnabledCheck() const {
std::vector<std::string> ids;
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
// crx_file::id_util is de-facto utility for id generation.
for (const auto app : apps) {
if (!crx_file::id_util::IdIsValid(app.first))
continue;
ids.push_back(app.first);
}
return ids;
}
std::unique_ptr<ArcAppListPrefs::AppInfo> ArcAppListPrefs::GetApp(
const std::string& app_id) const {
// Information for default app is available before ARC enabled.
if ((!IsArcAlive() || !IsArcAndroidEnabledForProfile(profile_)) &&
!default_apps_->HasApp(app_id)) {
return nullptr;
}
return GetAppFromPrefs(app_id);
}
std::unique_ptr<ArcAppListPrefs::AppInfo> ArcAppListPrefs::GetAppFromPrefs(
const std::string& app_id) const {
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
const base::Value::Dict* app_dict = apps.FindDict(app_id);
if (!app_dict)
return nullptr;
bool notifications_enabled =
app_dict->FindBool(kNotificationsEnabled).value_or(true);
auto resize_lock_state = static_cast<arc::mojom::ArcResizeLockState>(
app_dict->FindInt(kResizeLockState)
.value_or(
static_cast<int32_t>(arc::mojom::ArcResizeLockState::UNDEFINED)));
const bool shortcut = app_dict->FindBool(kShortcut).value_or(false);
const bool launchable = app_dict->FindBool(kLaunchable).value_or(true);
const bool need_fixup = app_dict->FindBool(kNeedFixup).value_or(false);
const std::string* maybe_name = app_dict->FindString(kName);
const std::string* maybe_package_name = app_dict->FindString(kPackageName);
const std::string* maybe_activity = app_dict->FindString(kActivity);
const std::string* maybe_intent_uri = app_dict->FindString(kIntentUri);
const std::string* maybe_icon_resource_id =
app_dict->FindString(kIconResourceId);
const std::string* maybe_version_name = app_dict->FindString(kVersionName);
std::string name = maybe_name ? *maybe_name : std::string();
std::string package_name =
maybe_package_name ? *maybe_package_name : std::string();
std::string activity = maybe_activity ? *maybe_activity : std::string();
std::string intent_uri = maybe_intent_uri ? *maybe_intent_uri : std::string();
std::string icon_resource_id =
maybe_icon_resource_id ? *maybe_icon_resource_id : std::string();
std::optional<std::string> version_name = std::nullopt;
if (maybe_version_name && *maybe_version_name != std::string())
version_name = *maybe_version_name;
DCHECK(!name.empty());
DCHECK(!shortcut || activity.empty());
DCHECK(!shortcut || !intent_uri.empty());
int64_t last_launch_time_internal = 0;
base::Time last_launch_time;
if (GetInt64FromPref(app_dict, kLastLaunchTime, &last_launch_time_internal)) {
last_launch_time = base::Time::FromInternalValue(last_launch_time_internal);
}
std::optional<uint64_t> app_size_in_bytes;
std::optional<uint64_t> data_size_in_bytes;
auto* app_size_entry = app_dict->FindString(kAppSizeBytesString);
if (app_size_entry != nullptr && !app_size_entry->empty()) {
uint64_t app_size = 0;
if (base::StringToUint64(*app_size_entry, &app_size))
app_size_in_bytes = app_size;
}
auto* data_size_entry = app_dict->FindString(kDataSizeBytesString);
if (data_size_entry != nullptr && !data_size_entry->empty()) {
uint64_t data_size = 0;
if (base::StringToUint64(*data_size_entry, &data_size))
data_size_in_bytes = data_size;
}
const bool deferred = NotificationsEnabledDeferred(prefs_).Get(app_id);
if (deferred)
notifications_enabled = deferred;
WindowLayout window_layout =
WindowLayoutFromDict(app_dict->FindDict(kWindowLayout));
const auto app_category = static_cast<arc::mojom::AppCategory>(
app_dict->FindInt(kAppCategory)
.value_or(static_cast<int32_t>(arc::mojom::AppCategory::kUndefined)));
return std::make_unique<AppInfo>(
name, package_name, activity, intent_uri, icon_resource_id, version_name,
last_launch_time, GetInstallTime(app_id),
app_dict->FindBool(kSticky).value_or(false), notifications_enabled,
resize_lock_state,
app_dict->FindBool(kResizeLockNeedsConfirmation).value_or(true),
window_layout, ready_apps_.count(app_id) > 0 /* ready */,
app_dict->FindBool(kSuspended).value_or(false),
launchable && arc::ShouldShowInLauncher(app_id), shortcut, launchable,
need_fixup, app_size_in_bytes, data_size_in_bytes, app_category);
}
bool ArcAppListPrefs::IsRegistered(const std::string& app_id) const {
if ((!IsArcAlive() || !IsArcAndroidEnabledForProfile(profile_)) &&
!default_apps_->HasApp(app_id))
return false;
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
return apps.FindDict(app_id);
}
bool ArcAppListPrefs::IsDefault(const std::string& app_id) const {
return default_apps_->HasApp(app_id);
}
bool ArcAppListPrefs::IsOem(const std::string& app_id) const {
const ArcDefaultAppList::AppInfo* app_info = default_apps_->GetApp(app_id);
return app_info && app_info->oem;
}
bool ArcAppListPrefs::IsShortcut(const std::string& app_id) const {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = GetApp(app_id);
return app_info && app_info->shortcut;
}
bool ArcAppListPrefs::IsControlledByPolicy(
const std::string& package_name) const {
return packages_by_policy_.count(package_name);
}
bool ArcAppListPrefs::IsAbleToBeLaunched(const std::string& app_id) const {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = GetApp(app_id);
return app_info && !app_info->suspended && app_info->ready &&
!app_info->need_fixup;
}
base::Time ArcAppListPrefs::PollLaunchRequestTime(const std::string& app_id) {
if (!launch_request_times_.count(app_id))
return base::Time();
const base::Time last_launch_time = launch_request_times_[app_id];
// This value should only be used once per launch.
launch_request_times_.erase(app_id);
return last_launch_time;
}
void ArcAppListPrefs::SetLaunchRequestTimeForTesting(const std::string& app_id,
base::Time timestamp) {
launch_request_times_[app_id] = timestamp;
}
void ArcAppListPrefs::SetLastLaunchTime(const std::string& app_id) {
if (!IsRegistered(app_id)) {
NOTREACHED_IN_MIGRATION();
return;
}
launch_request_times_[app_id] = base::Time::Now();
SetLastLaunchTimeInternal(app_id);
}
void ArcAppListPrefs::SetLastLaunchTimeInternal(const std::string& app_id) {
// Usage time on hidden should not be tracked.
if (!arc::ShouldShowInLauncher(app_id))
return;
const base::Time time = base::Time::Now();
arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::Value::Dict& app_dict = update.Get();
const std::string string_value = base::NumberToString(time.ToInternalValue());
app_dict.Set(kLastLaunchTime, string_value);
for (auto& observer : observer_list_)
observer.OnAppLastLaunchTimeUpdated(app_id);
if (first_launch_app_request_) {
first_launch_app_request_ = false;
// UI Shown time may not be set in unit tests.
const user_manager::UserManager* user_manager =
user_manager::UserManager::Get();
if (arc::ArcSessionManager::Get()->skipped_terms_of_service_negotiation() &&
!user_manager->IsLoggedInAsKioskApp() &&
!ash::UserSessionManager::GetInstance()->ui_shown_time().is_null()) {
UMA_HISTOGRAM_CUSTOM_TIMES(
"Arc.FirstAppLaunchRequest.TimeDelta",
time - ash::UserSessionManager::GetInstance()->ui_shown_time(),
base::Seconds(1), base::Minutes(2), 20);
}
}
}
void ArcAppListPrefs::SetLastLaunchTimeForTesting(const std::string& app_id,
base::Time timestamp) {
arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::Value::Dict& app_dict = update.Get();
const std::string string_value =
base::NumberToString(timestamp.ToInternalValue());
app_dict.Set(kLastLaunchTime, string_value);
}
void ArcAppListPrefs::DisableAllApps() {
std::unordered_set<std::string> old_ready_apps;
old_ready_apps.swap(ready_apps_);
for (auto& app_id : old_ready_apps)
NotifyAppStatesChanged(app_id);
}
void ArcAppListPrefs::NotifyRegisteredApps() {
if (apps_restored_)
return;
DCHECK(ready_apps_.empty());
std::vector<std::string> app_ids = GetAppIdsNoArcEnabledCheck();
for (const auto& app_id : app_ids) {
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
NOTREACHED_IN_MIGRATION();
continue;
}
// Default apps are reported earlier.
if (tracked_apps_.insert(app_id).second) {
for (auto& observer : observer_list_)
observer.OnAppRegistered(app_id, *app_info);
}
}
apps_restored_ = true;
}
void ArcAppListPrefs::RemoveAllAppsAndPackages() {
std::vector<std::string> app_ids = GetAppIdsNoArcEnabledCheck();
for (const auto& app_id : app_ids) {
if (!default_apps_->HasApp(app_id)) {
RemoveApp(app_id);
} else {
if (ready_apps_.count(app_id)) {
ready_apps_.erase(app_id);
NotifyAppStatesChanged(app_id);
}
}
}
DCHECK(ready_apps_.empty());
const std::vector<std::string> package_names_to_remove =
GetPackagesFromPrefs(false /* check_arc_alive */, true /* installed */);
for (const auto& package_name : package_names_to_remove) {
RemovePackageFromPrefs(package_name);
for (auto& observer : observer_list_)
observer.OnPackageRemoved(package_name, false);
}
if (!remove_all_callback_for_testing_.is_null())
std::move(remove_all_callback_for_testing_).Run();
is_remove_all_in_progress_ = false;
}
void ArcAppListPrefs::OnArcPlayStoreEnabledChanged(bool enabled) {
SetDefaultAppsFilterLevel();
// TODO(victorhsieh): Implement opt-in and opt-out.
if (arc::ShouldArcAlwaysStart())
return;
if (enabled) {
NotifyRegisteredApps();
} else {
is_remove_all_in_progress_ = true;
// Call RemoveAllAppsAndPackages asynchronous to ensure the
// arc::prefs::kArcEnabled pref change callbacks are called for all other
// components before calling RemoveAllAppsAndPackages for other components
// to prepare for ARC apps removal.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ArcAppListPrefs::RemoveAllAppsAndPackages,
weak_ptr_factory_.GetWeakPtr()));
}
}
void ArcAppListPrefs::OnArcSessionStopped(arc::ArcStopReason stop_reason) {
arc_app_metrics_util_->reportMetrics();
}
void ArcAppListPrefs::SetDefaultAppsFilterLevel() {
// There is no a blocklisting mechanism for Android apps. Until there is
// one, we have no option but to ban all pre-installed apps on Android side.
// Match this requirement and don't show pre-installed apps for managed users
// in app list.
if (arc::policy_util::IsAccountManaged(profile_)) {
if (profile_->IsChild() || ash::switches::IsTabletFormFactor()) {
// For child accounts, filter only optional apps.
// For tablet form factor devices, filter only optional apps.
default_apps_->set_filter_level(
ArcDefaultAppList::FilterLevel::OPTIONAL_APPS);
} else {
default_apps_->set_filter_level(
arc::IsArcPlayStoreEnabledForProfile(profile_)
? ArcDefaultAppList::FilterLevel::OPTIONAL_APPS
: ArcDefaultAppList::FilterLevel::ALL);
}
} else {
default_apps_->set_filter_level(ArcDefaultAppList::FilterLevel::NOTHING);
}
// Register default apps if it was not registered before.
RegisterDefaultApps();
}
void ArcAppListPrefs::OnDefaultAppsReady() {
VLOG(1) << "Default apps ready";
SetDefaultAppsFilterLevel();
default_apps_ready_ = true;
if (!default_apps_ready_callback_.is_null())
std::move(default_apps_ready_callback_).Run();
StartPrefs();
RecordAppIdsUma();
}
void ArcAppListPrefs::RecordAppIdsUma() {
// Default apps are the ones that have app icons even before opting into ARC.
// Play Store, Play Games, and PAI apps are good examples. This one can be
// 1 or more even when ARC is opted out.
size_t num_default_apps = 0;
// Sticky apps are the ones in either system or vendor image. They are called
// "sticky" because uninstalling them is not possible. 0 for opt-out users.
size_t num_sticky_apps = 0;
// "Installed" apps are the ones that the user has manually installed. This
// includes apps installed by Chrome's app sync feature. 0 for opt-out users.
size_t num_installed_apps = 0;
// Number of apps that is a vpn_provider.
size_t num_vpn_apps = 0;
const std::vector<std::string> app_ids = GetAppIds();
for (const auto& app_id : app_ids) {
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
DCHECK(app_info) << app_id;
if (!app_info)
continue;
const bool is_default = IsDefault(app_id);
const bool is_sticky = app_info->sticky;
DVLOG(1) << "App ID on startup: name=" << app_info->name
<< ", package=" << app_info->package_name
<< ", activity=" << app_info->activity << ", sticky=" << is_sticky
<< ", default=" << is_default;
if (is_default || is_sticky) {
// Some apps, such as com.android.vending, can be both default and sticky.
if (is_default)
++num_default_apps;
if (is_sticky)
++num_sticky_apps;
} else {
++num_installed_apps;
}
auto package = GetPackage(app_info->package_name);
if (package && package->vpn_provider) {
++num_vpn_apps;
}
}
const bool has_installed_apps = num_installed_apps;
VLOG(1) << "Non-PAI (aka non-default) and non-sticky (aka"
<< " not-in-system/vendor-images) ARC app(s) are "
<< (has_installed_apps ? "" : "not ") << "found.";
// Record the UMA. For more context of the metrics, see b/219115916.
base::UmaHistogramExactLinear(
base::StrCat({kAppCountUmaPrefix, "DefaultApp"}), num_default_apps,
kAppCountUmaExclusiveMax);
base::UmaHistogramExactLinear(base::StrCat({kAppCountUmaPrefix, "StickyApp"}),
num_sticky_apps, kAppCountUmaExclusiveMax);
base::UmaHistogramExactLinear(
base::StrCat({kAppCountUmaPrefix, "InstalledApp"}), num_installed_apps,
kAppCountUmaExclusiveMax);
base::UmaHistogramExactLinear(base::StrCat({kAppCountUmaPrefix, "VpnApp"}),
num_vpn_apps, kAppCountUmaExclusiveMaxLower);
base::UmaHistogramBoolean(
base::StrCat({kAppCountUmaPrefix, "HasInstalledOrUnknownApp"}),
has_installed_apps);
}
void ArcAppListPrefs::OnPolicySent(const std::string& policy) {
// Update set of packages installed by policy.
packages_by_policy_ =
arc::policy_util::GetRequestedPackagesFromArcPolicy(policy);
}
arc::mojom::ArcResizeLockState ArcAppListPrefs::GetResizeLockState(
const std::string& app_id) const {
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
return arc::mojom::ArcResizeLockState::UNDEFINED;
}
return app_info->resize_lock_state;
}
arc::mojom::AppCategory ArcAppListPrefs::GetAppCategory(
const std::string& app_id) const {
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
return arc::mojom::AppCategory::kUndefined;
}
return app_info->app_category;
}
arc::ArcPackageInstallPriorityHandler*
ArcAppListPrefs::GetInstallPriorityHandler() {
return install_priority_handler_.get();
}
void ArcAppListPrefs::SetAppLocale(const std::string& package_name,
const std::string& selected_locale) {
arc::ArcAppScopedPrefUpdate update(prefs_, package_name,
arc::prefs::kArcPackages);
base::Value::Dict& package_dict = update.Get();
package_dict.EnsureDict(kLocaleInfo)->Set(kSelectedLocale, selected_locale);
const std::string& app_id = GetAppIdByPackageName(package_name);
NotifyAppStatesChanged(app_id);
}
void ArcAppListPrefs::SetResizeLockState(const std::string& app_id,
arc::mojom::ArcResizeLockState state) {
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to set ret resize lock for non-registered app:"
<< app_id << ".";
return;
}
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
return;
}
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (!arc_service_manager)
return;
auto* compatibility_mode =
arc_service_manager->arc_bridge_service()->compatibility_mode();
if (!compatibility_mode->IsConnected())
return;
auto* instance =
ARC_GET_INSTANCE_FOR_METHOD(compatibility_mode, SetResizeLockState);
if (!instance)
return;
instance->SetResizeLockState(app_info->package_name, state);
arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::Value::Dict& app_dict = update.Get();
app_dict.Set(kResizeLockState, static_cast<int32_t>(state));
// If the app is not "ready", we shouldn't fire the AppStatesChanged
// callbacks. Otherwise, it would cause a crash (See crbug.com/1276603). When
// the app is changed to "ready", ArcAppListPrefs sends the notifications
// afterwards so it's fine not to fire it here.
if (app_info->ready)
NotifyAppStatesChanged(app_id);
}
bool ArcAppListPrefs::GetResizeLockNeedsConfirmation(
const std::string& app_id) {
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
return true;
}
return app_info->resize_lock_needs_confirmation;
}
void ArcAppListPrefs::SetResizeLockNeedsConfirmation(const std::string& app_id,
bool is_needed) {
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to set resize lock confirmation for non-registered app:"
<< app_id << ".";
return;
}
arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::Value::Dict& app_dict = update.Get();
app_dict.Set(kResizeLockNeedsConfirmation, is_needed);
}
int ArcAppListPrefs::GetShowSplashScreenDialogCount() const {
return profile_->GetPrefs()->GetInteger(
arc::prefs::kArcShowResizeLockSplashScreenLimits);
}
void ArcAppListPrefs::SetShowSplashScreenDialogCount(int count) {
profile_->GetPrefs()->SetInteger(
arc::prefs::kArcShowResizeLockSplashScreenLimits, count);
}
std::string ArcAppListPrefs::GetAppPackageName(const std::string& app_id) {
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
return std::string();
}
return app_info->package_name;
}
void ArcAppListPrefs::Shutdown() {
arc::ArcPolicyBridge* policy_bridge =
arc::ArcPolicyBridge::GetForBrowserContext(profile_);
if (policy_bridge)
policy_bridge->RemoveObserver(this);
// TODO(lgcheng) remove the check once the feature is enabled.
if (install_priority_handler_) {
install_priority_handler_->Shutdown();
}
}
void ArcAppListPrefs::RegisterDefaultApps() {
// Report default apps first, note, app_map includes uninstalled and filtered
// out apps as well.
for (const auto& default_app : default_apps_->GetActiveApps()) {
const std::string& app_id = default_app.first;
DCHECK(default_apps_->HasApp(app_id));
// Skip already tracked app.
if (tracked_apps_.count(app_id)) {
// Notify that icon is ready for default app.
for (auto& observer : observer_list_) {
for (const auto& descriptor : active_icons_[app_id])
observer.OnAppIconUpdated(app_id, descriptor);
}
continue;
}
const ArcDefaultAppList::AppInfo& app_info = *default_app.second;
AddAppAndShortcut(
app_info.name, app_info.package_name, app_info.activity,
std::string() /* intent_uri */, std::string() /* icon_resource_id */,
std::nullopt /* version name */, false /* sticky */,
false /* notifications_enabled */, false /* app_ready */,
false /* suspended */, false /* shortcut */, true /* launchable */,
false /* need_fixup */, ArcAppListPrefs::WindowLayout(),
std::nullopt /* app_size */, std::nullopt /* data_size */,
GetAppCategory(app_id));
}
}
base::Value* ArcAppListPrefs::GetPackagePrefs(const std::string& package_name,
const std::string& key) {
if (!GetPackage(package_name)) {
LOG(ERROR) << package_name << " can not be found.";
return nullptr;
}
arc::ArcAppScopedPrefUpdate update(prefs_, package_name,
arc::prefs::kArcPackages);
return update->Find(key);
}
void ArcAppListPrefs::SetPackagePrefs(const std::string& package_name,
const std::string& key,
base::Value value) {
if (!GetPackage(package_name)) {
LOG(ERROR) << package_name << " can not be found.";
return;
}
arc::ArcAppScopedPrefUpdate update(prefs_, package_name,
arc::prefs::kArcPackages);
update->Set(key, std::move(value));
}
void ArcAppListPrefs::SetDefaultAppsReadyCallback(base::OnceClosure callback) {
DCHECK(!callback.is_null());
DCHECK(default_apps_ready_callback_.is_null());
default_apps_ready_callback_ = std::move(callback);
if (default_apps_ready_)
std::move(default_apps_ready_callback_).Run();
}
void ArcAppListPrefs::SimulateDefaultAppAvailabilityTimeoutForTesting() {
if (!detect_default_app_availability_timeout_.IsRunning())
return;
detect_default_app_availability_timeout_.Stop();
DetectDefaultAppAvailability();
}
void ArcAppListPrefs::SetRemoveAllCallbackForTesting(
base::OnceClosure callback) {
DCHECK(!callback.is_null());
remove_all_callback_for_testing_ = std::move(callback);
}
void ArcAppListPrefs::OnConnectionReady() {
VLOG(1) << "App instance connection is ready.";
// Note, sync_service_ may be nullptr in testing.
sync_service_ = arc::ArcPackageSyncableService::Get(profile_);
is_initialized_ = false;
if (!app_list_refreshed_callback_.is_null())
std::move(app_list_refreshed_callback_).Run();
for (auto& observer : observer_list_)
observer.OnAppConnectionReady();
}
void ArcAppListPrefs::OnConnectionClosed() {
VLOG(1) << "App instance connection is closed.";
DisableAllApps();
installing_packages_count_ = 0;
packages_to_be_added_.clear();
apps_installations_.clear();
CancelDefaultAppLoadingTimeout();
ClearIconRequestRecord();
if (sync_service_) {
sync_service_->StopSyncing(syncer::ARC_PACKAGE);
sync_service_ = nullptr;
}
is_initialized_ = false;
package_list_initial_refreshed_ = false;
app_list_refreshed_callback_.Reset();
// TODO(lgcheng) remove the check once the feature is enabled.
if (install_priority_handler_) {
install_priority_handler_->Clear();
}
for (auto& observer : observer_list_)
observer.OnAppConnectionClosed();
}
void ArcAppListPrefs::HandleTaskCreated(const std::optional<std::string>& name,
const std::string& package_name,
const std::string& activity) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
const std::string app_id = GetAppId(package_name, activity);
if (IsRegistered(app_id)) {
SetLastLaunchTimeInternal(app_id);
} else {
// Create runtime app entry that is valid for the current user session. This
// entry is not shown in App Launcher and only required for shelf
// integration.
AddAppAndShortcut(
name.value_or(std::string()), package_name, activity,
std::string() /* intent_uri */, std::string() /* icon_resource_id */,
std::nullopt /* version_name */, false /* sticky */,
false /* notifications_enabled */, true /* app_ready */,
false /* suspended */, false /* shortcut */, false /* launchable */,
false /* need_fixup */, ArcAppListPrefs::WindowLayout(),
std::nullopt /* app_size */, std::nullopt /* data_size */,
GetAppCategory(app_id));
}
}
void ArcAppListPrefs::AddAppAndShortcut(
const std::string& name,
const std::string& package_name,
const std::string& activity,
const std::string& intent_uri,
const std::string& icon_resource_id,
const std::optional<std::string>& version_name,
const bool sticky,
const bool notifications_enabled,
const bool app_ready,
const bool suspended,
const bool shortcut,
const bool launchable,
const bool need_fixup,
const WindowLayout& initial_window_layout,
const std::optional<uint64_t> app_size_in_bytes,
const std::optional<uint64_t> data_size_in_bytes,
const arc::mojom::AppCategory app_category) {
const std::string app_id = shortcut ? GetAppId(package_name, intent_uri)
: GetAppId(package_name, activity);
// Do not add Play Store in certain conditions.
if (app_id == arc::kPlayStoreAppId) {
// TODO(khmel): Use show_in_launcher flag to hide the Play Store app.
// Display Play Store if we are in Demo Mode.
// TODO(b/154290639): Remove check for |IsDemoModeOfflineEnrolled| when
// fixed in Play Store.
if (arc::IsRobotOrOfflineDemoAccountMode() &&
!(ash::DemoSession::IsDeviceInDemoMode() &&
ash::features::ShouldShowPlayStoreInDemoMode())) {
return;
}
}
std::string updated_name = name;
// Add "(beta)" string to Play Store. See crbug.com/644576 for details.
if (app_id == arc::kPlayStoreAppId)
updated_name = l10n_util::GetStringUTF8(IDS_ARC_PLAYSTORE_ICON_TITLE_BETA);
base::Time last_launch_time;
const bool was_tracked = tracked_apps_.count(app_id);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_old_info;
if (was_tracked) {
app_old_info = GetApp(app_id);
DCHECK(app_old_info);
DCHECK(launchable);
last_launch_time = app_old_info->last_launch_time;
if (updated_name != app_old_info->name) {
for (auto& observer : observer_list_)
observer.OnAppNameUpdated(app_id, updated_name);
}
}
// Ensure to query the resize lock state from the prefs as we don't want the
// default resize lock value (UNDEFINED) to override the existing value.
const auto resize_lock_state = GetResizeLockState(app_id);
const auto resize_lock_needs_confirmation =
GetResizeLockNeedsConfirmation(app_id);
arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::Value::Dict& app_dict = update.Get();
app_dict.Set(kName, updated_name);
app_dict.Set(kPackageName, package_name);
app_dict.Set(kActivity, activity);
app_dict.Set(kIntentUri, intent_uri);
app_dict.Set(kIconResourceId, icon_resource_id);
app_dict.Set(kVersionName, version_name.value_or(std::string()));
app_dict.Set(kSuspended, suspended);
app_dict.Set(kSticky, sticky);
app_dict.Set(kNotificationsEnabled, notifications_enabled);
app_dict.Set(kResizeLockState, static_cast<int32_t>(resize_lock_state));
app_dict.Set(kResizeLockNeedsConfirmation, resize_lock_needs_confirmation);
app_dict.Set(kShortcut, shortcut);
app_dict.Set(kLaunchable, launchable);
app_dict.Set(kNeedFixup, need_fixup);
app_dict.Set(kAppCategory, static_cast<int32_t>(app_category));
app_dict.Set(kWindowLayout, WindowLayoutToDict(initial_window_layout));
if (app_size_in_bytes.has_value())
app_dict.Set(kAppSizeBytesString,
base::NumberToString(app_size_in_bytes.value()));
if (data_size_in_bytes.has_value())
app_dict.Set(kDataSizeBytesString,
base::NumberToString(data_size_in_bytes.value()));
// Note the install time is the first time the Chrome OS sees the app, not the
// actual install time in Android side.
if (GetInstallTime(app_id).is_null()) {
std::string install_time_str =
base::NumberToString(base::Time::Now().ToInternalValue());
app_dict.Set(kInstallTime, install_time_str);
}
const bool was_disabled = ready_apps_.count(app_id) == 0;
DCHECK(!(!was_disabled && !app_ready));
if (was_disabled && app_ready)
ready_apps_.insert(app_id);
AppInfo app_info(
updated_name, package_name, activity, intent_uri, icon_resource_id,
version_name, last_launch_time, GetInstallTime(app_id), sticky,
notifications_enabled, resize_lock_state, resize_lock_needs_confirmation,
initial_window_layout, app_ready, suspended,
launchable && arc::ShouldShowInLauncher(app_id), shortcut, launchable,
need_fixup, app_size_in_bytes, data_size_in_bytes, app_category);
if (was_tracked) {
if (AreAppStatesChanged(*app_old_info, app_info)) {
for (auto& observer : observer_list_)
observer.OnAppStatesChanged(app_id, app_info);
}
} else {
for (auto& observer : observer_list_)
observer.OnAppRegistered(app_id, app_info);
default_apps_->SetAppHidden(app_id, false);
tracked_apps_.insert(app_id);
// Newly installed apps are subject to ARC++ resize lock. Set the state to
// READY so the lock will be turned on next time they are launched.
SetResizeLockState(app_id, arc::mojom::ArcResizeLockState::READY);
}
// Send pending requests in case app becomes visible.
if (!app_old_info || !app_old_info->ready) {
for (const auto& descriptor : request_icon_recorded_[app_id])
RequestIcon(
app_id, descriptor,
base::BindOnce(&ArcAppListPrefs::InstallIcon,
weak_ptr_factory_.GetWeakPtr(), app_id, descriptor));
}
if (app_ready) {
const bool deferred_notifications_enabled =
NotificationsEnabledDeferred(prefs_).Get(app_id);
if (deferred_notifications_enabled)
SetNotificationsEnabled(app_id, deferred_notifications_enabled);
// Invalidate app icons in case it was already registered, becomes ready and
// icon version is updated. This allows to use previous icons until new
// icons are been prepared.
const base::Value* existing_version = app_dict.Find(kIconVersion);
if (was_tracked && (!existing_version ||
existing_version->GetInt() != current_icons_version)) {
VLOG(1) << "Invalidate icons for " << app_id << " from "
<< (existing_version ? existing_version->GetInt() : -1) << " to "
<< current_icons_version;
InvalidateAppIcons(app_id);
}
app_dict.Set(kIconVersion, base::Value(current_icons_version));
if (arc::IsArcForceCacheAppIcon() && app_id != arc::kPlayStoreAppId) {
// Request full set of app icons.
VLOG(1) << "Requested full set of app icons " << app_id;
for (const auto scale_factor : ui::GetSupportedResourceScaleFactors()) {
for (int dip_size : default_app_icon_dip_sizes) {
MaybeRequestIcon(app_id,
ArcAppIconDescriptor(dip_size, scale_factor));
}
}
}
}
OnArcAppListRefreshed(profile_);
}
void ArcAppListPrefs::RemoveApp(const std::string& app_id) {
// Delete cached icon if there is any.
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = GetApp(app_id);
if (app_info && !app_info->icon_resource_id.empty())
arc::RemoveCachedIcon(app_info->icon_resource_id);
MaybeRemoveIconRequestRecord(app_id);
// From now, app is not available.
ready_apps_.erase(app_id);
active_icons_.erase(app_id);
// In case default app, mark it as hidden.
default_apps_->SetAppHidden(app_id, true);
// Remove asyncronously local data on file system.
ScheduleAppFolderDeletion(app_id);
// Remove from prefs.
ScopedDictPrefUpdate apps_update(prefs_, arc::prefs::kArcApps);
const bool removed = apps_update->Remove(app_id);
DCHECK(removed);
// |tracked_apps_| contains apps that are reported externally as available.
// However, in case ARC++ appears as disbled on next start and had some apps
// left in prefs from the previous session, app clean up is performed on very
// early stage. Don't report |OnAppRemoved| in this case once the app was not
// reported as available for the current session.
if (!tracked_apps_.count(app_id))
return;
for (auto& observer : observer_list_)
observer.OnAppRemoved(app_id);
tracked_apps_.erase(app_id);
}
arc::ConnectionHolder<arc::mojom::AppInstance, arc::mojom::AppHost>*
ArcAppListPrefs::app_connection_holder() {
// Some tests set their own holder. If it's set, return the holder.
if (app_connection_holder_for_testing_)
return app_connection_holder_for_testing_;
auto* arc_service_manager = arc::ArcServiceManager::Get();
// The null check is for unit tests. On production, |arc_service_manager| is
// always non-null.
if (!arc_service_manager)
return nullptr;
return arc_service_manager->arc_bridge_service()->app();
}
void ArcAppListPrefs::AddOrUpdatePackagePrefs(
const arc::mojom::ArcPackageInfo& package,
const UpdatePackagePrefsReason& update_reason) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
const std::string& package_name = package.package_name;
if (package_name.empty()) {
VLOG(2) << "Package name cannot be empty.";
return;
}
arc::ArcAppScopedPrefUpdate update(prefs_, package_name,
arc::prefs::kArcPackages);
base::Value::Dict& package_dict = update.Get();
const std::string id_str =
base::NumberToString(package.last_backup_android_id);
const std::string time_str = base::NumberToString(package.last_backup_time);
int old_package_version = package_dict.FindInt(kPackageVersion).value_or(-1);
package_dict.Set(kShouldSync, package.sync);
package_dict.Set(kPackageVersion, package.package_version);
package_dict.Set(kLastBackupAndroidId, id_str);
package_dict.Set(kLastBackupTime, time_str);
package_dict.Set(kUninstalled, false);
package_dict.Set(kVPNProvider, package.vpn_provider);
package_dict.Set(kPreinstalled, package.preinstalled);
package_dict.Set(kGameControlsOptOut, package.game_controls_opt_out);
if (package.version_name)
package_dict.Set(kVersionName, package.version_name.value());
else
package_dict.Set(kVersionName, std::string());
base::Value::Dict permissions_dict;
if (package.permission_states.has_value()) {
for (const auto& [permission_type, permission_state] :
package.permission_states.value()) {
base::Value::Dict permission_state_dict;
permission_state_dict.Set(kPermissionStateGranted,
permission_state->granted);
permission_state_dict.Set(kPermissionStateManaged,
permission_state->managed);
if (permission_state->details.has_value()) {
permission_state_dict.Set(kPermissionStateDetails,
permission_state->details.value());
}
permission_state_dict.Set(kPermissionStateOneTime,
permission_state->one_time);
permissions_dict.Set(
base::NumberToString(static_cast<int64_t>(permission_type)),
std::move(permission_state_dict));
}
package_dict.Set(kPermissionStates, std::move(permissions_dict));
} else {
// Remove kPermissionStates from dict if there are no permissions.
package_dict.Remove(kPermissionStates);
}
if (package.web_app_info) {
const arc::mojom::WebAppInfo& web_app_info = *package.web_app_info;
base::Value::Dict web_app_info_dict;
web_app_info_dict.Set(kTitle, web_app_info.title);
web_app_info_dict.Set(kStartUrl, web_app_info.start_url);
web_app_info_dict.Set(kScopeUrl, web_app_info.scope_url);
web_app_info_dict.Set(kThemeColor,
base::NumberToString(web_app_info.theme_color));
web_app_info_dict.Set(kIsWebOnlyTwa, web_app_info.is_web_only_twa);
if (const auto& fingerprint = web_app_info.certificate_sha256_fingerprint) {
web_app_info_dict.Set(kCertificateSha256Fingerprint, *fingerprint);
}
package_dict.Set(kWebAppInfo, std::move(web_app_info_dict));
} else {
package_dict.Remove(kWebAppInfo);
}
if (package.locale_info &&
base::FeatureList::IsEnabled(arc::kPerAppLanguage)) {
if (IsSelectedLocaleResyncRequired(package_dict, *package.locale_info,
update_reason)) {
// Rejects ARC prefs and sends the correct locale back to Android to
// ensure eventual correctness.
const base::Value::Dict* locale_info_dict =
package_dict.EnsureDict(kLocaleInfo);
const std::string* saved_selected_locale =
locale_info_dict->FindString(kSelectedLocale);
arc::mojom::AppInstance* app_instance =
(arc::ArcServiceManager::Get()
? ARC_GET_INSTANCE_FOR_METHOD(
arc::ArcServiceManager::Get()->arc_bridge_service()->app(),
SetAppLocale)
: nullptr);
if (app_instance) {
app_instance->SetAppLocale(package_name, *saved_selected_locale);
}
} else {
// Accepts ARC prefs and save to dict.
base::Value::List supported_locales;
const arc::mojom::PackageLocaleInfo& package_locale_info =
*package.locale_info;
for (const std::string& supported_locale :
package_locale_info.supported_locales) {
if (IsLocaleTagValid(supported_locale)) {
supported_locales.Append(supported_locale);
}
}
const auto& selected_locale = package_locale_info.selected_locale;
package_dict.Set(
kLocaleInfo,
base::Value::Dict()
.Set(kSupportedLocales, std::move(supported_locales))
.Set(kSelectedLocale,
IsLocaleTagValid(selected_locale) ? selected_locale : ""));
}
} else {
package_dict.Remove(kLocaleInfo);
}
if (old_package_version == -1 ||
old_package_version == package.package_version) {
return;
}
InvalidatePackageIcons(package_name);
}
void ArcAppListPrefs::RemovePackageFromPrefs(const std::string& package_name) {
ScopedDictPrefUpdate(prefs_, arc::prefs::kArcPackages)->Remove(package_name);
OnArcAppListRefreshed(profile_);
}
void ArcAppListPrefs::OnAppListRefreshed(
std::vector<arc::mojom::AppInfoPtr> apps) {
DCHECK(app_list_refreshed_callback_.is_null());
if (!app_connection_holder()->IsConnected()) {
LOG(ERROR) << "App instance is not connected. Delaying app list refresh. "
<< "See b/70566216.";
app_list_refreshed_callback_ =
base::BindOnce(&ArcAppListPrefs::OnAppListRefreshed,
weak_ptr_factory_.GetWeakPtr(), std::move(apps));
return;
}
DCHECK(IsArcAndroidEnabledForProfile(profile_));
std::vector<std::string> old_apps = GetAppIds();
ready_apps_.clear();
for (const auto& app : apps) {
std::optional<uint64_t> app_size_in_bytes;
std::optional<uint64_t> data_size_in_bytes;
if (!app->app_storage.is_null()) {
app_size_in_bytes = app->app_storage->app_size_in_bytes;
data_size_in_bytes = app->app_storage->data_size_in_bytes;
}
AddAppAndShortcut(
app->name, app->package_name, app->activity,
std::string() /* intent_uri */, std::string() /* icon_resource_id */,
app->version_name, app->sticky, app->notifications_enabled,
true /* app_ready */, app->suspended, false /* shortcut */,
true /* launchable */, app->need_fixup, WindowLayoutFromApp(*app),
app_size_in_bytes, data_size_in_bytes, app->app_category);
}
// Detect removed ARC apps after current refresh.
for (const auto& app_id : old_apps) {
if (ready_apps_.count(app_id))
continue;
if (IsShortcut(app_id)) {
// If this is a shortcut, we just mark it as ready.
ready_apps_.insert(app_id);
NotifyAppStatesChanged(app_id);
} else {
// Default apps may not be installed yet at this moment.
if (!default_apps_->HasApp(app_id))
RemoveApp(app_id);
}
}
if (!is_initialized_) {
is_initialized_ = true;
UMA_HISTOGRAM_COUNTS_1000("Arc.AppsInstalledAtStartup", ready_apps_.size());
arc::ArcPaiStarter* pai_starter =
arc::ArcSessionManager::Get()->pai_starter();
if (pai_starter) {
pai_starter->AddOnStartCallback(
base::BindOnce(&ArcAppListPrefs::MaybeSetDefaultAppLoadingTimeout,
weak_ptr_factory_.GetWeakPtr()));
} else {
MaybeSetDefaultAppLoadingTimeout();
}
}
OnArcAppListRefreshed(profile_);
}
void ArcAppListPrefs::DetectDefaultAppAvailability() {
for (const auto& package : default_apps_->GetActivePackages()) {
// Check if already installed or installation in progress.
if (!GetPackage(package) && !apps_installations_.count(package))
HandlePackageRemoved(package);
}
}
void ArcAppListPrefs::MaybeSetDefaultAppLoadingTimeout() {
// Don't check if anything is installing or scheduled right now.
if (installing_packages_count_)
return;
// Find at least one not installed default app package.
for (const auto& package : default_apps_->GetActivePackages()) {
if (!GetPackage(package)) {
detect_default_app_availability_timeout_.Start(
FROM_HERE, kDetectDefaultAppAvailabilityTimeout, this,
&ArcAppListPrefs::DetectDefaultAppAvailability);
break;
}
}
}
void ArcAppListPrefs::CancelDefaultAppLoadingTimeout() {
detect_default_app_availability_timeout_.Stop();
}
void ArcAppListPrefs::AddApp(const arc::mojom::AppInfo& app_info) {
if ((app_info.name.empty() || app_info.package_name.empty() ||
app_info.activity.empty())) {
VLOG(2) << "App Name, package name, and activity cannot be empty.";
return;
}
std::optional<uint64_t> app_size_in_bytes;
std::optional<uint64_t> data_size_in_bytes;
if (!app_info.app_storage.is_null()) {
app_size_in_bytes = app_info.app_storage->app_size_in_bytes;
data_size_in_bytes = app_info.app_storage->data_size_in_bytes;
}
AddAppAndShortcut(
app_info.name, app_info.package_name, app_info.activity,
std::string() /* intent_uri */, std::string() /* icon_resource_id */,
app_info.version_name, app_info.sticky, app_info.notifications_enabled,
true /* app_ready */, app_info.suspended, false /* shortcut */,
true /* launchable */, app_info.need_fixup, WindowLayoutFromApp(app_info),
app_size_in_bytes, data_size_in_bytes, app_info.app_category);
}
void ArcAppListPrefs::OnAppAddedDeprecated(arc::mojom::AppInfoPtr app) {
AddApp(*app);
}
void ArcAppListPrefs::InvalidateAppIcons(const std::string& app_id) {
// Ignore Play Store app since we provide its icon in Chrome resources.
if (app_id == arc::kPlayStoreAppId)
return;
// Clean up previous icon records. They may refer to outdated icons.
MaybeRemoveIconRequestRecord(app_id);
// Clear icon cache that contains outdated icons.
ScheduleAppFolderDeletion(app_id);
// Re-request active icons.
for (const auto& descriptor : active_icons_[app_id])
MaybeRequestIcon(app_id, descriptor);
}
void ArcAppListPrefs::InvalidatePackageIcons(const std::string& package_name) {
if (package_name == kFrameworkPackageName) {
VLOG(1)
<< "Android framework was changed, refreshing icons for all packages";
for (const auto& package_name_to_invalidate : GetPackagesFromPrefs()) {
if (package_name_to_invalidate != kFrameworkPackageName)
InvalidatePackageIcons(package_name_to_invalidate);
}
}
for (const std::string& app_id : GetAppsForPackage(package_name))
InvalidateAppIcons(app_id);
}
void ArcAppListPrefs::ScheduleAppFolderDeletion(const std::string& app_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
file_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&DeleteAppFolderFromFileThread, GetAppPath(app_id)));
}
void ArcAppListPrefs::OnPackageAppListRefreshed(
const std::string& package_name,
std::vector<arc::mojom::AppInfoPtr> apps) {
if (package_name.empty()) {
VLOG(2) << "Package name cannot be empty.";
return;
}
std::unordered_set<std::string> apps_to_remove =
GetAppsAndShortcutsForPackage(package_name,
true, /* include_only_launchable_apps */
false /* include_shortcuts */);
for (const auto& app : apps) {
const std::string app_id = GetAppId(app->package_name, app->activity);
apps_to_remove.erase(app_id);
AddApp(*app);
}
arc::ArcAppScopedPrefUpdate update(prefs_, package_name,
arc::prefs::kArcPackages);
base::Value::Dict& package_dict = update.Get();
if (!apps_to_remove.empty()) {
auto* shelf_controller = ChromeShelfController::instance();
if (shelf_controller) {
int pin_index =
shelf_controller->PinnedItemIndexByAppID(*apps_to_remove.begin());
package_dict.Set(kPinIndex, pin_index);
}
}
for (const auto& app_id : apps_to_remove)
RemoveApp(app_id);
}
void ArcAppListPrefs::OnInstallShortcut(arc::mojom::ShortcutInfoPtr shortcut) {
if ((shortcut->name.empty() || shortcut->intent_uri.empty())) {
VLOG(2) << "Shortcut Name, and intent_uri cannot be empty.";
return;
}
AddAppAndShortcut(
shortcut->name, shortcut->package_name, std::string() /* activity */,
shortcut->intent_uri, shortcut->icon_resource_id,
std::nullopt /* version_name */, false /* sticky */,
false /* notifications_enabled */, true /* app_ready */,
false /* suspended */, true /* shortcut */, true /* launchable */,
false /* need_fixup */, ArcAppListPrefs::WindowLayout(),
std::nullopt /* app_size */, std::nullopt /* data_size */,
GetAppCategory(GetAppId(shortcut->package_name, shortcut->intent_uri)));
}
void ArcAppListPrefs::OnUninstallShortcut(const std::string& package_name,
const std::string& intent_uri) {
std::vector<std::string> shortcuts_to_remove;
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
for (const auto app : apps) {
if (!app.second.is_dict()) {
VLOG(2) << "Failed to extract information for " << app.first << ".";
continue;
}
const std::string* installed_package_name =
app.second.GetDict().FindString(kPackageName);
const std::string* installed_intent_uri =
app.second.GetDict().FindString(kIntentUri);
if (!installed_package_name || !installed_intent_uri) {
VLOG(2) << "Failed to extract information for " << app.first << ".";
continue;
}
const bool shortcut =
app.second.GetDict().FindBool(kShortcut).value_or(false);
if (!shortcut || *installed_package_name != package_name ||
*installed_intent_uri != intent_uri) {
continue;
}
shortcuts_to_remove.push_back(app.first);
}
for (const auto& shortcut_id : shortcuts_to_remove)
RemoveApp(shortcut_id);
}
std::unordered_set<std::string> ArcAppListPrefs::GetAppsForPackage(
const std::string& package_name) const {
return GetAppsAndShortcutsForPackage(package_name,
false, /* include_only_launchable_apps */
false /* include_shortcuts */);
}
std::unordered_set<std::string> ArcAppListPrefs::GetAppsAndShortcutsForPackage(
const std::string& package_name,
bool include_only_launchable_apps,
bool include_shortcuts) const {
std::unordered_set<std::string> app_set;
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
for (const auto app : apps) {
if (!crx_file::id_util::IdIsValid(app.first))
continue;
if (!app.second.is_dict()) {
NOTREACHED_IN_MIGRATION();
continue;
}
const std::string* app_package =
app.second.GetDict().FindString(kPackageName);
if (!app_package) {
LOG(ERROR) << "App is malformed: " << app.first;
continue;
}
if (package_name != *app_package)
continue;
if (!include_shortcuts) {
if (app.second.GetDict().FindBool(kShortcut).value_or(false))
continue;
}
if (include_only_launchable_apps) {
// Filter out non-lauchable apps.
if (!app.second.GetDict().FindBool(kLaunchable).value_or(false))
continue;
}
app_set.insert(app.first);
}
return app_set;
}
void ArcAppListPrefs::HandlePackageRemoved(const std::string& package_name) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
const std::unordered_set<std::string> apps_to_remove =
GetAppsAndShortcutsForPackage(package_name,
false /* include_only_launchable_apps */,
true /* include_shortcuts */);
for (const auto& app_id : apps_to_remove)
RemoveApp(app_id);
RemovePackageFromPrefs(package_name);
}
void ArcAppListPrefs::OnPackageRemoved(const std::string& package_name) {
UMA_HISTOGRAM_ENUMERATION("Arc.AppUninstallReason",
UninstallCounterReasonEnum::USER);
HandlePackageRemoved(package_name);
for (auto& observer : observer_list_)
observer.OnPackageRemoved(package_name, true);
}
void ArcAppListPrefs::OnIcon(
const std::string& app_id,
const ArcAppIconDescriptor& descriptor,
base::OnceCallback<void(arc::mojom::RawIconPngDataPtr)> callback,
arc::mojom::RawIconPngDataPtr icon) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!icon || !icon->icon_png_data.has_value() ||
icon->icon_png_data->empty()) {
LOG(WARNING) << "Cannot fetch icon for " << app_id;
std::move(callback).Run(nullptr);
return;
}
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to update icon for non-registered app: " << app_id;
std::move(callback).Run(nullptr);
return;
}
std::move(callback).Run(std::move(icon));
}
void ArcAppListPrefs::OnTaskCreated(int32_t task_id,
const std::string& package_name,
const std::string& activity,
const std::optional<std::string>& name,
const std::optional<std::string>& intent,
int32_t session_id) {
HandleTaskCreated(name, package_name, activity);
for (auto& observer : observer_list_) {
observer.OnTaskCreated(task_id, package_name, activity,
intent.value_or(std::string()), session_id);
}
}
void ArcAppListPrefs::OnTaskDescriptionUpdated(
int32_t task_id,
const std::string& label,
const std::vector<uint8_t>& icon_png_data) {
arc::mojom::RawIconPngDataPtr icon = arc::mojom::RawIconPngData::New();
icon->is_adaptive_icon = false;
icon->icon_png_data =
std::vector<uint8_t>(icon_png_data.begin(), icon_png_data.end());
for (auto& observer : observer_list_)
observer.OnTaskDescriptionChanged(task_id, label, *icon, 0, 0);
}
void ArcAppListPrefs::OnTaskDescriptionChanged(
int32_t task_id,
const std::string& label,
arc::mojom::RawIconPngDataPtr icon,
uint32_t primary_color,
uint32_t status_bar_color) {
for (auto& observer : observer_list_) {
observer.OnTaskDescriptionChanged(task_id, label, *icon, primary_color,
status_bar_color);
}
}
void ArcAppListPrefs::OnTaskDestroyed(int32_t task_id) {
for (auto& observer : observer_list_)
observer.OnTaskDestroyed(task_id);
}
void ArcAppListPrefs::OnTaskSetActive(int32_t task_id) {
for (auto& observer : observer_list_)
observer.OnTaskSetActive(task_id);
}
void ArcAppListPrefs::OnNotificationsEnabledChanged(
const std::string& package_name,
bool enabled) {
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
for (const auto app : apps) {
if (!app.second.is_dict()) {
NOTREACHED_IN_MIGRATION();
continue;
}
const std::string* app_package_name =
app.second.GetDict().FindString(kPackageName);
if (!app_package_name) {
LOG(ERROR) << "App is malformed: " << app.first;
continue;
}
if (*app_package_name != package_name) {
continue;
}
arc::ArcAppScopedPrefUpdate update(prefs_, app.first, arc::prefs::kArcApps);
base::Value::Dict& updating_app_dict = update.Get();
updating_app_dict.Set(kNotificationsEnabled, enabled);
}
for (auto& observer : observer_list_)
observer.OnNotificationsEnabledChanged(package_name, enabled);
}
bool ArcAppListPrefs::IsDefaultPackage(const std::string& package_name) const {
DCHECK(default_apps_ready_);
return default_apps_->HasPackage(package_name) ||
default_apps_->HasHiddenPackage(package_name);
}
void ArcAppListPrefs::OnPackageAdded(
arc::mojom::ArcPackageInfoPtr package_info) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
AddOrUpdatePackagePrefs(*package_info,
UpdatePackagePrefsReason::kOnPackageAdded);
packages_to_be_added_.erase(package_info->package_name);
UpdateArcPackagesIsUpToDatePref();
// TODO(lgcheng) remove the check once the feature is enabled.
if (install_priority_handler_) {
install_priority_handler_->ClearPackage(package_info->package_name);
}
for (auto& observer : observer_list_)
observer.OnPackageInstalled(*package_info);
}
void ArcAppListPrefs::OnPackageModified(
arc::mojom::ArcPackageInfoPtr package_info) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
AddOrUpdatePackagePrefs(*package_info,
UpdatePackagePrefsReason::kOnPackageModified);
for (auto& observer : observer_list_)
observer.OnPackageModified(*package_info);
}
void ArcAppListPrefs::OnPackageListRefreshed(
std::vector<arc::mojom::ArcPackageInfoPtr> packages) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
const base::flat_set<std::string> old_packages(GetPackagesFromPrefs());
std::set<std::string> current_packages;
for (const auto& package : packages) {
AddOrUpdatePackagePrefs(*package,
UpdatePackagePrefsReason::kOnPackageListRefreshed);
if (!base::Contains(old_packages, package->package_name)) {
for (auto& observer : observer_list_)
observer.OnPackageInstalled(*package);
} else {
MaybeRemoveDeprecatedPackagePrefs(arc::ArcAppScopedPrefUpdate(
prefs_, package->package_name, arc::prefs::kArcPackages));
}
current_packages.insert(package->package_name);
}
for (const auto& package_name : old_packages) {
if (!base::Contains(current_packages, package_name)) {
RemovePackageFromPrefs(package_name);
for (auto& observer : observer_list_)
observer.OnPackageRemoved(package_name, false);
}
}
UpdateArcPackagesIsUpToDatePref();
package_list_initial_refreshed_ = true;
for (auto& observer : observer_list_)
observer.OnPackageListInitialRefreshed();
}
std::vector<std::string> ArcAppListPrefs::GetPackagesFromPrefs() const {
return GetPackagesFromPrefs(true /* check_arc_alive */, true /* installed */);
}
std::vector<std::string> ArcAppListPrefs::GetPackagesFromPrefs(
bool check_arc_alive,
bool installed) const {
std::vector<std::string> packages;
if (check_arc_alive &&
(!IsArcAlive() || !IsArcAndroidEnabledForProfile(profile_))) {
return packages;
}
const base::Value::Dict& package_prefs =
prefs_->GetDict(arc::prefs::kArcPackages);
for (const auto package : package_prefs) {
if (!package.second.is_dict()) {
NOTREACHED_IN_MIGRATION();
continue;
}
const bool uninstalled =
package.second.GetDict().FindBool(kUninstalled).value_or(false);
if (installed != !uninstalled)
continue;
packages.push_back(package.first);
}
return packages;
}
base::Time ArcAppListPrefs::GetInstallTime(const std::string& app_id) const {
const base::Value::Dict& apps = prefs_->GetDict(arc::prefs::kArcApps);
const base::Value::Dict* app = apps.FindDict(app_id);
if (!app)
return base::Time();
const std::string* install_time_str = app->FindString(kInstallTime);
if (!install_time_str)
return base::Time();
int64_t install_time_i64;
if (!base::StringToInt64(*install_time_str, &install_time_i64))
return base::Time();
return base::Time::FromInternalValue(install_time_i64);
}
void ArcAppListPrefs::InstallIcon(const std::string& app_id,
const ArcAppIconDescriptor& descriptor,
arc::mojom::RawIconPngDataPtr icon) {
if (!icon) {
return;
}
const base::FilePath icon_path = GetIconPath(app_id, descriptor);
const base::FilePath foreground_icon_path =
GetForegroundIconPath(app_id, descriptor);
const base::FilePath background_icon_path =
GetBackgroundIconPath(app_id, descriptor);
file_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&InstallIconFromFileThread, icon_path,
foreground_icon_path, background_icon_path,
std::move(icon)),
base::BindOnce(&ArcAppListPrefs::OnIconInstalled,
weak_ptr_factory_.GetWeakPtr(), app_id, descriptor));
}
void ArcAppListPrefs::OnIconInstalled(const std::string& app_id,
const ArcAppIconDescriptor& descriptor,
bool install_succeed) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!install_succeed)
return;
for (auto& observer : observer_list_)
observer.OnAppIconUpdated(app_id, descriptor);
}
void ArcAppListPrefs::OnInstallationStarted(
const std::optional<std::string>& package_name) {
++installing_packages_count_;
CancelDefaultAppLoadingTimeout();
UpdateArcPackagesIsUpToDatePref();
if (!package_name.has_value())
return;
apps_installations_.insert(*package_name);
// Track install start time IFF app sync metrics are also being recorded
// and app is not a synced or default app. App sync metrics are only
// recorded if this is the initial session after opting in during the sync
// consent screen
if (prefs_->GetBoolean(ash::prefs::kRecordArcAppSyncMetrics) &&
!(sync_service_ && sync_service_->IsPackageSyncing(*package_name)) &&
!IsDefaultPackage(*package_name)) {
arc_app_metrics_util_->recordAppInstallStartTime(
*package_name, IsControlledByPolicy(*package_name));
}
for (auto& observer : observer_list_)
observer.OnInstallationStarted(*package_name);
}
void ArcAppListPrefs::OnInstallationProgressChanged(
const std::string& package_name,
float progress) {
for (auto& observer : observer_list_) {
observer.OnInstallationProgressChanged(package_name, progress);
}
}
void ArcAppListPrefs::OnInstallationActiveChanged(
const std::string& package_name,
bool active) {
for (auto& observer : observer_list_) {
observer.OnInstallationActiveChanged(package_name, active);
}
}
void ArcAppListPrefs::OnInstallationFinished(
arc::mojom::InstallationResultPtr result) {
if (result) {
apps_installations_.erase(result->package_name);
if (default_apps_->HasPackage(result->package_name) && !result->success &&
!GetPackage(result->package_name)) {
HandlePackageRemoved(result->package_name);
}
for (auto& observer : observer_list_)
observer.OnInstallationFinished(result->package_name, result->success,
result->is_launchable_app);
if (result->success) {
InstallationCounterReasonEnum reason =
InstallationCounterReasonEnum::USER;
std::string app_id = GetAppIdByPackageName(result->package_name);
if (IsOem(app_id)) {
reason = InstallationCounterReasonEnum::OEM;
} else if (IsDefault(app_id)) {
reason = InstallationCounterReasonEnum::DEFAULT;
} else if (IsControlledByPolicy(result->package_name)) {
reason = InstallationCounterReasonEnum::POLICY;
}
UMA_HISTOGRAM_ENUMERATION("Arc.AppInstalledReason", reason);
arc_app_metrics_util_->maybeReportInstallTimeDelta(
result->package_name, IsControlledByPolicy(result->package_name));
packages_to_be_added_.insert(result->package_name);
}
}
if (!installing_packages_count_) {
VLOG(2) << "Received unexpected installation finished event";
return;
}
--installing_packages_count_;
MaybeSetDefaultAppLoadingTimeout();
UpdateArcPackagesIsUpToDatePref();
}
void ArcAppListPrefs::NotifyAppStatesChanged(const std::string& app_id) {
std::unique_ptr<AppInfo> app_info = GetAppFromPrefs(app_id);
CHECK(app_info);
for (auto& observer : observer_list_)
observer.OnAppStatesChanged(app_id, *app_info);
}
// static
void ArcAppListPrefs::AppInfo::SetIgnoreCompareInstallTimeForTesting(
bool ignore) {
ignore_compare_app_info_install_time = ignore;
}
void ArcAppListPrefs::UpdateArcPackagesIsUpToDatePref() {
// Set kArcPackagesIsUpToDate to true if there is no active install and all
// installed packages are added to the prefs.
prefs_->SetBoolean(
arc::prefs::kArcPackagesIsUpToDate,
installing_packages_count_ == 0 && packages_to_be_added_.empty());
}
ArcAppListPrefs::AppInfo::AppInfo(
const std::string& name,
const std::string& package_name,
const std::string& activity,
const std::string& intent_uri,
const std::string& icon_resource_id,
const std::optional<std::string>& version_name,
const base::Time& last_launch_time,
const base::Time& install_time,
bool sticky,
bool notifications_enabled,
arc::mojom::ArcResizeLockState resize_lock_state,
bool resize_lock_needs_confirmation,
const WindowLayout& initial_window_layout,
bool ready,
bool suspended,
bool show_in_launcher,
bool shortcut,
bool launchable,
bool need_fixup,
const std::optional<uint64_t> app_size_in_bytes,
const std::optional<uint64_t> data_size_in_bytes,
arc::mojom::AppCategory app_category)
: name(name),
package_name(package_name),
activity(activity),
intent_uri(intent_uri),
icon_resource_id(icon_resource_id),
version_name(version_name),
last_launch_time(last_launch_time),
install_time(install_time),
sticky(sticky),
notifications_enabled(notifications_enabled),
resize_lock_state(resize_lock_state),
resize_lock_needs_confirmation(resize_lock_needs_confirmation),
initial_window_layout(initial_window_layout),
ready(ready),
suspended(suspended),
show_in_launcher(show_in_launcher),
shortcut(shortcut),
launchable(launchable),
need_fixup(need_fixup),
app_size_in_bytes(app_size_in_bytes),
data_size_in_bytes(data_size_in_bytes),
app_category(app_category) {
// If app is not launchable it also does not show in launcher.
DCHECK(launchable || !show_in_launcher);
}
ArcAppListPrefs::AppInfo::AppInfo(AppInfo&& other) = default;
ArcAppListPrefs::AppInfo& ArcAppListPrefs::AppInfo::operator=(AppInfo&& other) =
default;
// Need to add explicit destructor for chromium style checker error:
// Complex class/struct needs an explicit out-of-line destructor
ArcAppListPrefs::AppInfo::~AppInfo() = default;
bool ArcAppListPrefs::AppInfo::operator==(const AppInfo& other) const {
return name == other.name && package_name == other.package_name &&
activity == other.activity && intent_uri == other.intent_uri &&
icon_resource_id == other.icon_resource_id &&
version_name == other.version_name &&
last_launch_time == other.last_launch_time &&
(ignore_compare_app_info_install_time ||
install_time == other.install_time) &&
sticky == other.sticky &&
notifications_enabled == other.notifications_enabled &&
resize_lock_state == other.resize_lock_state &&
resize_lock_needs_confirmation ==
other.resize_lock_needs_confirmation &&
initial_window_layout == other.initial_window_layout &&
ready == other.ready && suspended == other.suspended &&
show_in_launcher == other.show_in_launcher &&
shortcut == other.shortcut && launchable == other.launchable &&
need_fixup == other.need_fixup &&
app_size_in_bytes == other.app_size_in_bytes &&
data_size_in_bytes == other.data_size_in_bytes &&
app_category == other.app_category;
}
ArcAppListPrefs::PackageInfo::PackageInfo(
const std::string& package_name,
int32_t package_version,
int64_t last_backup_android_id,
int64_t last_backup_time,
bool should_sync,
bool vpn_provider,
bool preinstalled,
bool game_controls_opt_out,
base::flat_map<arc::mojom::AppPermission, arc::mojom::PermissionStatePtr>
permissions,
arc::mojom::WebAppInfoPtr web_app_info,
arc::mojom::PackageLocaleInfoPtr locale_info)
: package_name(package_name),
package_version(package_version),
last_backup_android_id(last_backup_android_id),
last_backup_time(last_backup_time),
should_sync(should_sync),
vpn_provider(vpn_provider),
preinstalled(preinstalled),
game_controls_opt_out(game_controls_opt_out),
permissions(std::move(permissions)),
web_app_info(std::move(web_app_info)),
locale_info(std::move(locale_info)) {}
// Need to add explicit destructor for chromium style checker error:
// Complex class/struct needs an explicit out-of-line destructor
ArcAppListPrefs::PackageInfo::~PackageInfo() = default;
ArcAppListPrefs::WindowLayout::WindowLayout()
: WindowLayout(arc::mojom::WindowSizeType::kUnknown, true, std::nullopt) {}
ArcAppListPrefs::WindowLayout::WindowLayout(arc::mojom::WindowSizeType type,
bool resizable,
std::optional<gfx::Rect> bounds)
: type(type), resizable(resizable), bounds(std::move(bounds)) {}
ArcAppListPrefs::WindowLayout::WindowLayout(
const ArcAppListPrefs::WindowLayout& other) = default;
ArcAppListPrefs::WindowLayout::~WindowLayout() = default;
bool ArcAppListPrefs::WindowLayout::operator==(
const WindowLayout& other) const {
return type == other.type && resizable == other.resizable &&
bounds == other.bounds;
}
ArcAppListPrefs::Observer::~Observer() {
CHECK(!IsInObserverList());
}