blob: 915c8aec9bd7e29743ff8989a545d7d16caedfc0 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include <stddef.h>
#include <string>
#include <utility>
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "base/values.h"
#include "chrome/browser/chromeos/arc/arc_session_manager.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/arc/policy/arc_policy_util.h"
#include "chrome/browser/chromeos/login/session/user_session_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs_factory.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/app_list/arc/arc_package_syncable_service.h"
#include "chrome/browser/ui/app_list/arc/arc_pai_starter.h"
#include "chrome/grit/generated_resources.h"
#include "components/arc/arc_prefs.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/arc_util.h"
#include "components/arc/connection_holder.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 "ui/base/l10n/l10n_util.h"
namespace {
constexpr char kActivity[] = "activity";
constexpr char kIconResourceId[] = "icon_resource_id";
constexpr char kInstallTime[] = "install_time";
constexpr char kIntentUri[] = "intent_uri";
constexpr char kInvalidatedIcons[] = "invalidated_icons";
constexpr char kLastBackupAndroidId[] = "last_backup_android_id";
constexpr char kLastBackupTime[] = "last_backup_time";
constexpr char kLastLaunchTime[] = "lastlaunchtime";
constexpr char kLaunchable[] = "launchable";
constexpr char kName[] = "name";
constexpr char kNotificationsEnabled[] = "notifications_enabled";
constexpr char kPackageName[] = "package_name";
constexpr char kPackageVersion[] = "package_version";
constexpr char kSticky[] = "sticky";
constexpr char kShortcut[] = "shortcut";
constexpr char kShouldSync[] = "should_sync";
constexpr char kSuspended[] = "suspended";
constexpr char kSystem[] = "system";
constexpr char kUninstalled[] = "uninstalled";
constexpr char kVPNProvider[] = "vpnprovider";
constexpr base::TimeDelta kDetectDefaultAppAvailabilityTimeout =
base::TimeDelta::FromMinutes(1);
// Provider of write access to a dictionary storing ARC prefs.
class ScopedArcPrefUpdate : public DictionaryPrefUpdate {
public:
ScopedArcPrefUpdate(PrefService* service,
const std::string& id,
const std::string& path)
: DictionaryPrefUpdate(service, path), id_(id) {}
~ScopedArcPrefUpdate() override {}
// DictionaryPrefUpdate overrides:
base::DictionaryValue* Get() override {
base::DictionaryValue* dict = DictionaryPrefUpdate::Get();
base::Value* dict_item =
dict->FindKeyOfType(id_, base::Value::Type::DICTIONARY);
if (!dict_item) {
dict_item = dict->SetKey(id_, base::Value(base::Value::Type::DICTIONARY));
}
return static_cast<base::DictionaryValue*>(dict_item);
}
private:
const std::string id_;
DISALLOW_COPY_AND_ASSIGN(ScopedArcPrefUpdate);
};
// Accessor for deferred set notifications enabled requests in prefs.
class SetNotificationsEnabledDeferred {
public:
explicit SetNotificationsEnabledDeferred(PrefService* prefs)
: prefs_(prefs) {}
void Put(const std::string& app_id, bool enabled) {
DictionaryPrefUpdate update(
prefs_, arc::prefs::kArcSetNotificationsEnabledDeferred);
base::DictionaryValue* const dict = update.Get();
dict->SetKey(app_id, base::Value(enabled));
}
bool Get(const std::string& app_id, bool* enabled) {
const base::DictionaryValue* dict =
prefs_->GetDictionary(arc::prefs::kArcSetNotificationsEnabledDeferred);
return dict->GetBoolean(app_id, enabled);
}
void Remove(const std::string& app_id) {
DictionaryPrefUpdate update(
prefs_, arc::prefs::kArcSetNotificationsEnabledDeferred);
base::DictionaryValue* const dict = update.Get();
dict->RemoveWithoutPathExpansion(app_id, /* out_value */ nullptr);
}
private:
PrefService* const prefs_;
};
bool InstallIconFromFileThread(const base::FilePath& icon_path,
const std::vector<uint8_t>& content_png) {
DCHECK(!content_png.empty());
base::CreateDirectory(icon_path.DirName());
int wrote =
base::WriteFile(icon_path, reinterpret_cast<const char*>(&content_png[0]),
content_png.size());
if (wrote != static_cast<int>(content_png.size())) {
VLOG(2) << "Failed to write ARC icon file: " << icon_path.MaybeAsASCII()
<< ".";
if (!base::DeleteFile(icon_path, false)) {
VLOG(2) << "Couldn't delete broken icon file" << icon_path.MaybeAsASCII()
<< ".";
}
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::DeleteFile(path, true);
DCHECK(deleted);
}
// TODO(crbug.com/672829): 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::DictionaryValue* dict,
const std::string& key,
int64_t* value) {
DCHECK(dict);
std::string value_str;
if (!dict->GetStringWithoutPathExpansion(key, &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;
}
base::FilePath ToIconPath(const base::FilePath& app_path,
ui::ScaleFactor scale_factor) {
DCHECK(!app_path.empty());
switch (scale_factor) {
case ui::SCALE_FACTOR_100P:
return app_path.AppendASCII("icon_100p.png");
case ui::SCALE_FACTOR_125P:
return app_path.AppendASCII("icon_125p.png");
case ui::SCALE_FACTOR_133P:
return app_path.AppendASCII("icon_133p.png");
case ui::SCALE_FACTOR_140P:
return app_path.AppendASCII("icon_140p.png");
case ui::SCALE_FACTOR_150P:
return app_path.AppendASCII("icon_150p.png");
case ui::SCALE_FACTOR_180P:
return app_path.AppendASCII("icon_180p.png");
case ui::SCALE_FACTOR_200P:
return app_path.AppendASCII("icon_200p.png");
case ui::SCALE_FACTOR_250P:
return app_path.AppendASCII("icon_250p.png");
case ui::SCALE_FACTOR_300P:
return app_path.AppendASCII("icon_300p.png");
default:
NOTREACHED();
return base::FilePath();
}
}
// 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.ready != info2.ready || info1.suspended != info2.suspended ||
info1.show_in_launcher != info2.show_in_launcher ||
info1.launchable != info2.launchable;
}
} // namespace
// static
ArcAppListPrefs* ArcAppListPrefs::Create(
Profile* profile,
arc::ConnectionHolder<arc::mojom::AppInstance, arc::mojom::AppHost>*
app_connection_holder) {
return new ArcAppListPrefs(profile, app_connection_holder);
}
// static
void ArcAppListPrefs::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(arc::prefs::kArcApps);
registry->RegisterDictionaryPref(arc::prefs::kArcPackages);
registry->RegisterDictionaryPref(
arc::prefs::kArcSetNotificationsEnabledDeferred);
}
// 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;
}
std::string ArcAppListPrefs::GetAppIdByPackageName(
const std::string& package_name) const {
const base::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
if (!apps)
return std::string();
for (const auto& it : apps->DictItems()) {
const base::Value& value = it.second;
const base::Value* installed_package_name =
value.FindKeyOfType(kPackageName, base::Value::Type::STRING);
if (!installed_package_name ||
installed_package_name->GetString() != package_name)
continue;
const base::Value* activity_name =
value.FindKeyOfType(kActivity, base::Value::Type::STRING);
return activity_name ? GetAppId(package_name, activity_name->GetString())
: std::string();
}
return std::string();
}
ArcAppListPrefs::ArcAppListPrefs(
Profile* profile,
arc::ConnectionHolder<arc::mojom::AppInstance, arc::mojom::AppHost>*
app_connection_holder)
: profile_(profile),
prefs_(profile->GetPrefs()),
app_connection_holder_(app_connection_holder),
default_apps_(this, profile),
weak_ptr_factory_(this) {
DCHECK(profile);
DCHECK(app_connection_holder);
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
const base::FilePath& base_path = profile->GetPath();
base_path_ = base_path.AppendASCII(arc::prefs::kArcApps);
invalidated_icon_scale_factor_mask_ = 0;
for (ui::ScaleFactor scale_factor : ui::GetSupportedScaleFactors())
invalidated_icon_scale_factor_mask_ |= (1U << scale_factor);
arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
if (!arc_session_manager)
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());
// Once default apps are ready OnDefaultAppsReady is called.
// Not always set in unit_tests
arc::ArcPolicyBridge* policy_bridge =
arc::ArcPolicyBridge::GetForBrowserContext(profile_);
if (policy_bridge)
policy_bridge->AddObserver(this);
}
ArcAppListPrefs::~ArcAppListPrefs() {
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.
DCHECK_EQ(profile_, arc_session_manager->profile());
OnArcPlayStoreEnabledChanged(
arc::IsArcPlayStoreEnabledForProfile(profile_));
}
arc_session_manager->AddObserver(this);
}
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,
ui::ScaleFactor scale_factor) const {
const ArcDefaultAppList::AppInfo* default_app = default_apps_.GetApp(app_id);
if (!default_app || default_app->app_path.empty())
return base::FilePath();
return ToIconPath(default_app->app_path, scale_factor);
}
base::FilePath ArcAppListPrefs::GetIconPath(
const std::string& app_id,
ui::ScaleFactor scale_factor) const {
return ToIconPath(GetAppPath(app_id), scale_factor);
}
bool ArcAppListPrefs::IsIconRequestRecorded(
const std::string& app_id,
ui::ScaleFactor scale_factor) const {
const auto iter = request_icon_recorded_.find(app_id);
if (iter == request_icon_recorded_.end())
return false;
return iter->second & (1 << scale_factor);
}
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,
ui::ScaleFactor scale_factor) {
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)
return;
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to load icon for non-registered app: " << app_id << ".";
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] |= (1 << scale_factor);
if (!ready_apps_.count(app_id))
return;
if (!app_connection_holder_->IsConnected()) {
// AppInstance should be ready since we have app_id in ready_apps_. This
// can happen in browser_tests.
return;
}
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
if (!app_info) {
VLOG(2) << "Failed to get app info: " << app_id << ".";
return;
}
if (app_info->icon_resource_id.empty()) {
auto* app_instance =
ARC_GET_INSTANCE_FOR_METHOD(app_connection_holder_, RequestAppIcon);
// Version 0 instance should always be available here because IsConnected()
// returned true above.
DCHECK(app_instance);
app_instance->RequestAppIcon(
app_info->package_name, app_info->activity,
static_cast<arc::mojom::ScaleFactor>(scale_factor));
} else {
auto* app_instance =
ARC_GET_INSTANCE_FOR_METHOD(app_connection_holder_, RequestIcon);
if (!app_instance)
return; // The instance version on ARC side was too old.
app_instance->RequestIcon(
app_info->icon_resource_id,
static_cast<arc::mojom::ScaleFactor>(scale_factor),
base::Bind(&ArcAppListPrefs::OnIcon, base::Unretained(this), app_id,
static_cast<arc::mojom::ScaleFactor>(scale_factor)));
}
}
void ArcAppListPrefs::MaybeRequestIcon(const std::string& app_id,
ui::ScaleFactor scale_factor) {
if (!IsIconRequestRecorded(app_id, scale_factor))
RequestIcon(app_id, scale_factor);
}
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)) {
SetNotificationsEnabledDeferred(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;
SetNotificationsEnabledDeferred(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::DictionaryValue* package = nullptr;
const base::DictionaryValue* packages =
prefs_->GetDictionary(arc::prefs::kArcPackages);
if (!packages ||
!packages->GetDictionaryWithoutPathExpansion(package_name, &package))
return std::unique_ptr<PackageInfo>();
bool uninstalled = false;
if (package->GetBoolean(kUninstalled, &uninstalled) && uninstalled)
return nullptr;
int32_t package_version = 0;
int64_t last_backup_android_id = 0;
int64_t last_backup_time = 0;
bool should_sync = false;
bool system = false;
bool vpn_provider = false;
GetInt64FromPref(package, kLastBackupAndroidId, &last_backup_android_id);
GetInt64FromPref(package, kLastBackupTime, &last_backup_time);
package->GetInteger(kPackageVersion, &package_version);
package->GetBoolean(kShouldSync, &should_sync);
package->GetBoolean(kSystem, &system);
package->GetBoolean(kVPNProvider, &vpn_provider);
return std::make_unique<PackageInfo>(package_name, package_version,
last_backup_android_id, last_backup_time,
should_sync, system, vpn_provider);
}
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_.app_map()) {
if (default_apps_.HasApp(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::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
DCHECK(apps);
// crx_file::id_util is de-facto utility for id generation.
for (base::DictionaryValue::Iterator app_id(*apps); !app_id.IsAtEnd();
app_id.Advance()) {
if (!crx_file::id_util::IdIsValid(app_id.key()))
continue;
ids.push_back(app_id.key());
}
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 std::unique_ptr<AppInfo>();
const base::DictionaryValue* app = nullptr;
const base::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
if (!apps || !apps->GetDictionaryWithoutPathExpansion(app_id, &app))
return std::unique_ptr<AppInfo>();
std::string name;
std::string package_name;
std::string activity;
std::string intent_uri;
std::string icon_resource_id;
bool suspended = false;
bool sticky = false;
bool notifications_enabled = true;
bool shortcut = false;
bool launchable = true;
app->GetString(kName, &name);
app->GetString(kPackageName, &package_name);
app->GetString(kActivity, &activity);
app->GetString(kIntentUri, &intent_uri);
app->GetString(kIconResourceId, &icon_resource_id);
app->GetBoolean(kSuspended, &suspended);
app->GetBoolean(kSticky, &sticky);
app->GetBoolean(kNotificationsEnabled, &notifications_enabled);
app->GetBoolean(kShortcut, &shortcut);
app->GetBoolean(kLaunchable, &launchable);
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, kLastLaunchTime, &last_launch_time_internal)) {
last_launch_time = base::Time::FromInternalValue(last_launch_time_internal);
}
bool deferred;
if (SetNotificationsEnabledDeferred(prefs_).Get(app_id, &deferred))
notifications_enabled = deferred;
return std::make_unique<AppInfo>(
name, package_name, activity, intent_uri, icon_resource_id,
last_launch_time, GetInstallTime(app_id), sticky, notifications_enabled,
ready_apps_.count(app_id) > 0 /* ready */, suspended,
launchable && arc::ShouldShowInLauncher(app_id), shortcut, launchable);
}
bool ArcAppListPrefs::IsRegistered(const std::string& app_id) const {
if ((!IsArcAlive() || !IsArcAndroidEnabledForProfile(profile_)) &&
!default_apps_.HasApp(app_id))
return false;
const base::DictionaryValue* app = nullptr;
const base::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
return apps && apps->GetDictionaryWithoutPathExpansion(app_id, &app);
}
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;
}
void ArcAppListPrefs::SetLastLaunchTime(const std::string& app_id) {
if (!IsRegistered(app_id)) {
NOTREACHED();
return;
}
// Usage time on hidden should not be tracked.
if (!arc::ShouldShowInLauncher(app_id))
return;
const base::Time time = base::Time::Now();
ScopedArcPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::DictionaryValue* app_dict = update.Get();
const std::string string_value = base::Int64ToString(time.ToInternalValue());
app_dict->SetString(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()->is_directly_started() &&
!user_manager->IsLoggedInAsKioskApp() &&
!user_manager->IsLoggedInAsArcKioskApp() &&
!chromeos::UserSessionManager::GetInstance()
->ui_shown_time()
.is_null()) {
UMA_HISTOGRAM_CUSTOM_TIMES(
"Arc.FirstAppLaunchRequest.TimeDelta",
time - chromeos::UserSessionManager::GetInstance()->ui_shown_time(),
base::TimeDelta::FromSeconds(1), base::TimeDelta::FromMinutes(2), 20);
}
}
}
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();
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) {
if (!default_apps_.HasPackage(package_name))
RemovePackageFromPrefs(prefs_, package_name);
for (auto& observer : observer_list_)
observer.OnPackageRemoved(package_name, false);
}
}
void ArcAppListPrefs::OnArcPlayStoreEnabledChanged(bool enabled) {
SetDefaultAppsFilterLevel();
// TODO(victorhsieh): Implement opt-in and opt-out.
if (arc::ShouldArcAlwaysStart())
return;
if (enabled)
NotifyRegisteredApps();
else
RemoveAllAppsAndPackages();
}
void ArcAppListPrefs::SetDefaultAppsFilterLevel() {
// There is no a blacklisting 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_)) {
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() {
// Apply uninstalled packages now.
const std::vector<std::string> uninstalled_package_names =
GetPackagesFromPrefs(false /* check_arc_alive */, false /* installed */);
for (const auto& uninstalled_package_name : uninstalled_package_names)
default_apps_.MaybeMarkPackageUninstalled(uninstalled_package_name, true);
SetDefaultAppsFilterLevel();
default_apps_ready_ = true;
if (!default_apps_ready_callback_.is_null())
default_apps_ready_callback_.Run();
StartPrefs();
}
void ArcAppListPrefs::OnPolicySent(const std::string& policy) {
// Update set of packages installed by policy.
packages_by_policy_ =
arc::policy_util::GetRequestedPackagesFromArcPolicy(policy);
}
void ArcAppListPrefs::Shutdown() {
arc::ArcPolicyBridge* policy_bridge =
arc::ArcPolicyBridge::GetForBrowserContext(profile_);
if (policy_bridge)
policy_bridge->RemoveObserver(this);
}
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_.app_map()) {
const std::string& app_id = default_app.first;
if (!default_apps_.HasApp(app_id))
continue;
// Skip already tracked app.
if (tracked_apps_.count(app_id)) {
// Icon should be already taken from the cache. Play Store icon is loaded
// from internal resources.
if (ready_apps_.count(app_id) || app_id == arc::kPlayStoreAppId)
continue;
// Notify that icon is ready for default app.
for (auto& observer : observer_list_) {
for (ui::ScaleFactor scale_factor : ui::GetSupportedScaleFactors())
observer.OnAppIconUpdated(app_id, scale_factor);
}
continue;
}
const ArcDefaultAppList::AppInfo& app_info = *default_app.second.get();
AddAppAndShortcut(app_info.name, app_info.package_name, app_info.activity,
std::string() /* intent_uri */,
std::string() /* icon_resource_id */, false /* sticky */,
false /* notifications_enabled */, false /* app_ready */,
false /* suspended */, false /* shortcut */,
true /* launchable */);
}
}
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;
}
ScopedArcPrefUpdate update(prefs_, package_name, arc::prefs::kArcPackages);
return update.Get()->FindKey(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;
}
ScopedArcPrefUpdate update(prefs_, package_name, arc::prefs::kArcPackages);
update.Get()->SetKey(key, std::move(value));
}
void ArcAppListPrefs::SetDefaltAppsReadyCallback(base::Closure callback) {
DCHECK(!callback.is_null());
DCHECK(default_apps_ready_callback_.is_null());
default_apps_ready_callback_ = callback;
if (default_apps_ready_)
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::OnConnectionReady() {
// 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();
}
void ArcAppListPrefs::OnConnectionClosed() {
DisableAllApps();
installing_packages_count_ = 0;
default_apps_installations_.clear();
detect_default_app_availability_timeout_.Stop();
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();
}
void ArcAppListPrefs::HandleTaskCreated(const base::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)) {
SetLastLaunchTime(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 */, false /* sticky */,
false /* notifications_enabled */, true /* app_ready */,
false /* suspended */, false /* shortcut */,
false /* launchable */);
}
}
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 bool sticky,
const bool notifications_enabled,
const bool app_ready,
const bool suspended,
const bool shortcut,
const bool launchable) {
const std::string app_id = shortcut ? GetAppId(package_name, intent_uri)
: GetAppId(package_name, activity);
// Do not add Play Store app for Public Session and Kiosk modes.
if (app_id == arc::kPlayStoreAppId && arc::IsRobotOrOfflineDemoAccountMode())
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);
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);
if (updated_name != app_old_info->name) {
for (auto& observer : observer_list_)
observer.OnAppNameUpdated(app_id, updated_name);
}
}
ScopedArcPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::DictionaryValue* app_dict = update.Get();
app_dict->SetString(kName, updated_name);
app_dict->SetString(kPackageName, package_name);
app_dict->SetString(kActivity, activity);
app_dict->SetString(kIntentUri, intent_uri);
app_dict->SetString(kIconResourceId, icon_resource_id);
app_dict->SetBoolean(kSuspended, suspended);
app_dict->SetBoolean(kSticky, sticky);
app_dict->SetBoolean(kNotificationsEnabled, notifications_enabled);
app_dict->SetBoolean(kShortcut, shortcut);
app_dict->SetBoolean(kLaunchable, launchable);
// 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() && NeedSetInstallTime(package_name)) {
std::string install_time_str =
base::Int64ToString(base::Time::Now().ToInternalValue());
app_dict->SetString(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, base::Time(), GetInstallTime(app_id),
sticky, notifications_enabled, app_ready, suspended,
launchable && arc::ShouldShowInLauncher(app_id), shortcut,
launchable);
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);
tracked_apps_.insert(app_id);
}
if (app_ready) {
int icon_update_mask = 0;
app_dict->GetInteger(kInvalidatedIcons, &icon_update_mask);
auto pending_icons = request_icon_recorded_.find(app_id);
if (pending_icons != request_icon_recorded_.end())
icon_update_mask |= pending_icons->second;
for (ui::ScaleFactor scale_factor : ui::GetSupportedScaleFactors()) {
if (icon_update_mask & (1 << scale_factor))
RequestIcon(app_id, scale_factor);
}
bool deferred_notifications_enabled;
if (SetNotificationsEnabledDeferred(prefs_).Get(
app_id, &deferred_notifications_enabled)) {
SetNotificationsEnabled(app_id, deferred_notifications_enabled);
}
}
}
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);
// app_id may be released by observers, get the path first. It should be done
// before removing prefs entry in order not to mix with pre-build default apps
// files.
const base::FilePath app_path = GetAppPath(app_id);
// Remove from prefs.
DictionaryPrefUpdate update(prefs_, arc::prefs::kArcApps);
base::DictionaryValue* apps = update.Get();
const bool removed = apps->Remove(app_id, nullptr);
DCHECK(removed);
DCHECK(tracked_apps_.count(app_id));
for (auto& observer : observer_list_)
observer.OnAppRemoved(app_id);
tracked_apps_.erase(app_id);
// Remove local data on file system.
base::PostTaskWithTraits(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::Bind(&DeleteAppFolderFromFileThread, app_path));
}
void ArcAppListPrefs::AddOrUpdatePackagePrefs(
PrefService* prefs, const arc::mojom::ArcPackageInfo& package) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
const std::string& package_name = package.package_name;
default_apps_.MaybeMarkPackageUninstalled(package_name, false);
if (package_name.empty()) {
VLOG(2) << "Package name cannot be empty.";
return;
}
ScopedArcPrefUpdate update(prefs, package_name, arc::prefs::kArcPackages);
base::DictionaryValue* package_dict = update.Get();
const std::string id_str =
base::Int64ToString(package.last_backup_android_id);
const std::string time_str = base::Int64ToString(package.last_backup_time);
int old_package_version = -1;
package_dict->GetInteger(kPackageVersion, &old_package_version);
package_dict->SetBoolean(kShouldSync, package.sync);
package_dict->SetInteger(kPackageVersion, package.package_version);
package_dict->SetString(kLastBackupAndroidId, id_str);
package_dict->SetString(kLastBackupTime, time_str);
package_dict->SetBoolean(kSystem, package.system);
package_dict->SetBoolean(kUninstalled, false);
package_dict->SetBoolean(kVPNProvider, package.vpn_provider);
if (old_package_version == -1 ||
old_package_version == package.package_version) {
return;
}
InvalidatePackageIcons(package_name);
}
void ArcAppListPrefs::RemovePackageFromPrefs(PrefService* prefs,
const std::string& package_name) {
default_apps_.MaybeMarkPackageUninstalled(package_name, true);
if (!default_apps_.HasPackage(package_name)) {
DictionaryPrefUpdate update(prefs, arc::prefs::kArcPackages);
base::DictionaryValue* packages = update.Get();
const bool removed = packages->RemoveWithoutPathExpansion(package_name,
nullptr);
DCHECK(removed);
} else {
ScopedArcPrefUpdate update(prefs, package_name, arc::prefs::kArcPackages);
base::DictionaryValue* package_dict = update.Get();
package_dict->SetBoolean(kUninstalled, true);
}
}
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) {
AddAppAndShortcut(
app->name, app->package_name, app->activity,
std::string() /* intent_uri */, std::string() /* icon_resource_id */,
app->sticky, app->notifications_enabled, true /* app_ready */,
app->suspended, false /* shortcut */, true /* launchable */);
}
// 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();
}
}
}
void ArcAppListPrefs::DetectDefaultAppAvailability() {
for (const auto& package : default_apps_.GetActivePackages()) {
// Check if already installed or installation in progress.
if (!GetPackage(package) && !default_apps_installations_.count(package))
HandlePackageRemoved(package);
}
}
void ArcAppListPrefs::MaybeSetDefaultAppLoadingTimeout() {
// 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::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;
}
AddAppAndShortcut(
app_info.name, app_info.package_name, app_info.activity,
std::string() /* intent_uri */, std::string() /* icon_resource_id */,
app_info.sticky, app_info.notifications_enabled, true /* app_ready */,
app_info.suspended, false /* shortcut */, true /* launchable */);
}
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);
{
ScopedArcPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
base::DictionaryValue* app_dict = update.Get();
app_dict->SetInteger(kInvalidatedIcons,
invalidated_icon_scale_factor_mask_);
}
for (ui::ScaleFactor scale_factor : ui::GetSupportedScaleFactors())
MaybeRequestIcon(app_id, scale_factor);
}
void ArcAppListPrefs::InvalidatePackageIcons(const std::string& package_name) {
for (const std::string& app_id : GetAppsForPackage(package_name))
InvalidateAppIcons(app_id);
}
bool ArcAppListPrefs::NeedSetInstallTime(
const std::string& package_name) const {
// If checked package is in active default list that means it is installed by
// PAI and install time should not be recorded. Once package is not in active
// default list then this package was removed from default and user installs
// it manually. In last case we have to record install time.
if (default_apps_.GetActivePackages().count(package_name))
return false;
// Check if package is installed by policy. In this case don't set install
// time.
if (packages_by_policy_.count(package_name))
return false;
// TODO(b/34248841) - Handle apps, installed by sync.
return true;
}
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 =
GetAppsForPackage(package_name);
default_apps_.MaybeMarkPackageUninstalled(package_name, false);
for (const auto& app : apps) {
const std::string app_id = GetAppId(app->package_name, app->activity);
apps_to_remove.erase(app_id);
AddApp(*app);
}
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, false /* sticky */,
false /* notifications_enabled */, true /* app_ready */,
false /* suspended */, true /* shortcut */, true /* launchable */);
}
void ArcAppListPrefs::OnUninstallShortcut(const std::string& package_name,
const std::string& intent_uri) {
std::vector<std::string> shortcuts_to_remove;
const base::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
for (base::DictionaryValue::Iterator app_it(*apps); !app_it.IsAtEnd();
app_it.Advance()) {
const base::Value* value = &app_it.value();
const base::DictionaryValue* app;
bool shortcut;
std::string installed_package_name;
std::string installed_intent_uri;
if (!value->GetAsDictionary(&app) ||
!app->GetBoolean(kShortcut, &shortcut) ||
!app->GetString(kPackageName, &installed_package_name) ||
!app->GetString(kIntentUri, &installed_intent_uri)) {
VLOG(2) << "Failed to extract information for " << app_it.key() << ".";
continue;
}
if (!shortcut || installed_package_name != package_name ||
installed_intent_uri != intent_uri) {
continue;
}
shortcuts_to_remove.push_back(app_it.key());
}
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_shortcuts */);
}
std::unordered_set<std::string> ArcAppListPrefs::GetAppsAndShortcutsForPackage(
const std::string& package_name,
bool include_shortcuts) const {
std::unordered_set<std::string> app_set;
const base::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
for (base::DictionaryValue::Iterator app_it(*apps); !app_it.IsAtEnd();
app_it.Advance()) {
const base::Value* value = &app_it.value();
const base::DictionaryValue* app;
if (!value->GetAsDictionary(&app)) {
NOTREACHED();
continue;
}
std::string app_package;
if (!app->GetString(kPackageName, &app_package)) {
NOTREACHED();
continue;
}
if (package_name != app_package)
continue;
if (!include_shortcuts) {
bool shortcut = false;
if (app->GetBoolean(kShortcut, &shortcut) && shortcut)
continue;
}
app_set.insert(app_it.key());
}
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, true /* include_shortcuts */);
for (const auto& app_id : apps_to_remove)
RemoveApp(app_id);
RemovePackageFromPrefs(prefs_, package_name);
}
void ArcAppListPrefs::OnPackageRemoved(const std::string& package_name) {
HandlePackageRemoved(package_name);
for (auto& observer : observer_list_)
observer.OnPackageRemoved(package_name, true);
}
void ArcAppListPrefs::OnAppIcon(const std::string& package_name,
const std::string& activity,
arc::mojom::ScaleFactor scale_factor,
const std::vector<uint8_t>& icon_png_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_NE(0u, icon_png_data.size());
std::string app_id = GetAppId(package_name, activity);
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to update icon for non-registered app: " << app_id;
return;
}
InstallIcon(app_id, static_cast<ui::ScaleFactor>(scale_factor),
icon_png_data);
}
void ArcAppListPrefs::OnIcon(const std::string& app_id,
arc::mojom::ScaleFactor scale_factor,
const std::vector<uint8_t>& icon_png_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_NE(0u, icon_png_data.size());
if (!IsRegistered(app_id)) {
VLOG(2) << "Request to update icon for non-registered app: " << app_id;
return;
}
InstallIcon(app_id, static_cast<ui::ScaleFactor>(scale_factor),
icon_png_data);
}
void ArcAppListPrefs::OnTaskCreated(int32_t task_id,
const std::string& package_name,
const std::string& activity,
const base::Optional<std::string>& name,
const base::Optional<std::string>& intent) {
HandleTaskCreated(name, package_name, activity);
for (auto& observer : observer_list_) {
observer.OnTaskCreated(task_id,
package_name,
activity,
intent.value_or(std::string()));
}
}
void ArcAppListPrefs::OnTaskDescriptionUpdated(
int32_t task_id,
const std::string& label,
const std::vector<uint8_t>& icon_png_data) {
for (auto& observer : observer_list_)
observer.OnTaskDescriptionUpdated(task_id, label, icon_png_data);
}
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::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
for (base::DictionaryValue::Iterator app(*apps); !app.IsAtEnd();
app.Advance()) {
const base::DictionaryValue* app_dict;
std::string app_package_name;
if (!app.value().GetAsDictionary(&app_dict) ||
!app_dict->GetString(kPackageName, &app_package_name)) {
NOTREACHED();
continue;
}
if (app_package_name != package_name) {
continue;
}
ScopedArcPrefUpdate update(prefs_, app.key(), arc::prefs::kArcApps);
base::DictionaryValue* updateing_app_dict = update.Get();
updateing_app_dict->SetBoolean(kNotificationsEnabled, enabled);
}
for (auto& observer : observer_list_)
observer.OnNotificationsEnabledChanged(package_name, enabled);
}
bool ArcAppListPrefs::IsUnknownPackage(const std::string& package_name) const {
return !GetPackage(package_name) && sync_service_ &&
!sync_service_->IsPackageSyncing(package_name);
}
void ArcAppListPrefs::OnPackageAdded(
arc::mojom::ArcPackageInfoPtr package_info) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
AddOrUpdatePackagePrefs(prefs_, *package_info);
for (auto& observer : observer_list_)
observer.OnPackageInstalled(*package_info);
}
void ArcAppListPrefs::OnPackageModified(
arc::mojom::ArcPackageInfoPtr package_info) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
AddOrUpdatePackagePrefs(prefs_, *package_info);
for (auto& observer : observer_list_)
observer.OnPackageModified(*package_info);
}
void ArcAppListPrefs::OnPackageListRefreshed(
std::vector<arc::mojom::ArcPackageInfoPtr> packages) {
DCHECK(IsArcAndroidEnabledForProfile(profile_));
const std::vector<std::string> old_packages(GetPackagesFromPrefs());
std::unordered_set<std::string> current_packages;
for (const auto& package : packages) {
AddOrUpdatePackagePrefs(prefs_, *package);
current_packages.insert((*package).package_name);
}
for (const auto& package_name : old_packages) {
if (!current_packages.count(package_name))
RemovePackageFromPrefs(prefs_, package_name);
}
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::DictionaryValue* package_prefs =
prefs_->GetDictionary(arc::prefs::kArcPackages);
for (base::DictionaryValue::Iterator package(*package_prefs);
!package.IsAtEnd(); package.Advance()) {
const base::DictionaryValue* package_info;
if (!package.value().GetAsDictionary(&package_info)) {
NOTREACHED();
continue;
}
bool uninstalled = false;
package_info->GetBoolean(kUninstalled, &uninstalled);
if (installed != !uninstalled)
continue;
packages.push_back(package.key());
}
return packages;
}
base::Time ArcAppListPrefs::GetInstallTime(const std::string& app_id) const {
const base::DictionaryValue* app = nullptr;
const base::DictionaryValue* apps =
prefs_->GetDictionary(arc::prefs::kArcApps);
if (!apps || !apps->GetDictionaryWithoutPathExpansion(app_id, &app))
return base::Time();
std::string install_time_str;
if (!app->GetString(kInstallTime, &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,
ui::ScaleFactor scale_factor,
const std::vector<uint8_t>& content_png) {
const base::FilePath icon_path = GetIconPath(app_id, scale_factor);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::Bind(&InstallIconFromFileThread, icon_path, content_png),
base::Bind(&ArcAppListPrefs::OnIconInstalled,
weak_ptr_factory_.GetWeakPtr(), app_id, scale_factor));
}
void ArcAppListPrefs::OnIconInstalled(const std::string& app_id,
ui::ScaleFactor scale_factor,
bool install_succeed) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!install_succeed)
return;
ScopedArcPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps);
int invalidated_icon_mask = 0;
base::DictionaryValue* app_dict = update.Get();
app_dict->GetInteger(kInvalidatedIcons, &invalidated_icon_mask);
invalidated_icon_mask &= (~(1 << scale_factor));
app_dict->SetInteger(kInvalidatedIcons, invalidated_icon_mask);
for (auto& observer : observer_list_)
observer.OnAppIconUpdated(app_id, scale_factor);
}
void ArcAppListPrefs::OnInstallationStarted(
const base::Optional<std::string>& package_name) {
++installing_packages_count_;
if (!package_name.has_value())
return;
if (default_apps_.HasPackage(*package_name))
default_apps_installations_.insert(*package_name);
for (auto& observer : observer_list_)
observer.OnInstallationStarted(*package_name);
}
void ArcAppListPrefs::OnInstallationFinished(
arc::mojom::InstallationResultPtr result) {
if (result && default_apps_.HasPackage(result->package_name)) {
default_apps_installations_.erase(result->package_name);
if (!result->success && !GetPackage(result->package_name))
HandlePackageRemoved(result->package_name);
}
if (result) {
for (auto& observer : observer_list_)
observer.OnInstallationFinished(result->package_name, result->success);
}
if (!installing_packages_count_) {
VLOG(2) << "Received unexpected installation finished event";
return;
}
--installing_packages_count_;
}
void ArcAppListPrefs::NotifyAppStatesChanged(const std::string& app_id) {
std::unique_ptr<AppInfo> app_info = GetApp(app_id);
DCHECK(app_info);
for (auto& observer : observer_list_)
observer.OnAppStatesChanged(app_id, *app_info);
}
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 base::Time& last_launch_time,
const base::Time& install_time,
bool sticky,
bool notifications_enabled,
bool ready,
bool suspended,
bool show_in_launcher,
bool shortcut,
bool launchable)
: name(name),
package_name(package_name),
activity(activity),
intent_uri(intent_uri),
icon_resource_id(icon_resource_id),
last_launch_time(last_launch_time),
install_time(install_time),
sticky(sticky),
notifications_enabled(notifications_enabled),
ready(ready),
suspended(suspended),
show_in_launcher(show_in_launcher),
shortcut(shortcut),
launchable(launchable) {
// If app is not launchable it also does not show in launcher.
DCHECK(launchable || !show_in_launcher);
}
// Need to add explicit destructor for chromium style checker error:
// Complex class/struct needs an explicit out-of-line destructor
ArcAppListPrefs::AppInfo::~AppInfo() {}
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 system,
bool vpn_provider)
: 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),
system(system),
vpn_provider(vpn_provider) {}