blob: ea3d818bdd3c46a1d1e8dc32d0475846d69426f0 [file] [log] [blame]
// Copyright 2018 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_default_app_list.h"
#include "base/barrier_closure.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/path_service.h"
#include "base/task/post_task.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ui/app_list/arc/arc_app_scoped_pref_update.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/common/chrome_paths.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_system.h"
namespace {
constexpr char kActivity[] = "activity";
constexpr char kAppPath[] = "app_path";
constexpr char kName[] = "name";
constexpr char kOem[] = "oem";
constexpr char kPackageName[] = "package_name";
constexpr char kDefaultApps[] = "arc.apps.default";
constexpr char kHidden[] = "hidden";
// Sub-directory wher ARC apps forward declarations are stored.
const base::FilePath::CharType kArcDirectory[] = FILE_PATH_LITERAL("arc");
const base::FilePath::CharType kArcTestDirectory[] =
FILE_PATH_LITERAL("arc_default_apps");
const base::FilePath::CharType kArcTestBoardDirectory[] =
FILE_PATH_LITERAL("arc_board_default_apps");
const base::FilePath::CharType kBoardDirectory[] =
FILE_PATH_LITERAL("/var/cache/arc_default_apps");
bool use_test_apps_directory = false;
std::unique_ptr<ArcDefaultAppList::AppInfoMap> ReadAppsFromFileThread(
const base::FilePath& base_path) {
base::FilePath root_dir;
// FileEnumerator does not work with a symbolic link dir. So map link
// to real folder in case |base_path| specifies a symbolic link.
if (!base::ReadSymbolicLink(base_path, &root_dir))
root_dir = base_path;
std::unique_ptr<ArcDefaultAppList::AppInfoMap> apps =
std::make_unique<ArcDefaultAppList::AppInfoMap>();
base::FilePath::StringType extension(".json");
base::FileEnumerator json_files(root_dir,
false, // Recursive.
base::FileEnumerator::FILES);
for (base::FilePath file = json_files.Next(); !file.empty();
file = json_files.Next()) {
if (!file.MatchesExtension(extension)) {
DVLOG(1) << "Not considering: " << file.LossyDisplayName()
<< " (does not have a .json extension)";
continue;
}
JSONFileValueDeserializer deserializer(file);
std::string error_msg;
std::unique_ptr<base::Value> app_info =
deserializer.Deserialize(nullptr, &error_msg);
if (!app_info) {
VLOG(2) << "Unable to deserialize json data: " << error_msg << " in file "
<< file.value() << ".";
continue;
}
std::unique_ptr<base::DictionaryValue> app_info_dictionary =
base::DictionaryValue::From(std::move(app_info));
CHECK(app_info_dictionary);
std::string name;
std::string package_name;
std::string activity;
std::string app_path;
bool oem = false;
app_info_dictionary->GetString(kName, &name);
app_info_dictionary->GetString(kPackageName, &package_name);
app_info_dictionary->GetString(kActivity, &activity);
app_info_dictionary->GetString(kAppPath, &app_path);
app_info_dictionary->GetBoolean(kOem, &oem);
if (name.empty() || package_name.empty() || activity.empty() ||
app_path.empty()) {
VLOG(2) << "ARC app declaration is incomplete in file " << file.value()
<< ".";
continue;
}
const std::string app_id =
ArcAppListPrefs::GetAppId(package_name, activity);
std::unique_ptr<ArcDefaultAppList::AppInfo> app =
std::make_unique<ArcDefaultAppList::AppInfo>(
name, package_name, activity, oem, root_dir.Append(app_path));
apps.get()->insert(
std::pair<std::string, std::unique_ptr<ArcDefaultAppList::AppInfo>>(
app_id, std::move(app)));
}
return apps;
}
// Returns true if default app |app_id| is marked as hidden in the prefs.
bool IsAppHidden(const PrefService* prefs, const std::string& app_id) {
const base::DictionaryValue* apps_dict = prefs->GetDictionary(kDefaultApps);
const base::DictionaryValue* app_dict;
if (!apps_dict || !apps_dict->GetDictionary(app_id, &app_dict))
return false;
bool hidden = false;
return app_dict->GetBoolean(kHidden, &hidden) && hidden;
}
} // namespace
// static
void ArcDefaultAppList::UseTestAppsDirectory() {
use_test_apps_directory = true;
}
// static
void ArcDefaultAppList::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(kDefaultApps);
}
ArcDefaultAppList::ArcDefaultAppList(Profile* profile,
base::OnceClosure ready_callback)
: profile_(profile),
ready_callback_(std::move(ready_callback)),
weak_ptr_factory_(this) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// Load default apps from two sources.
// /usr/share/google-chrome/extensions/arc - contains default apps for all
// boards that share the same image.
// /var/cache/arc_default_apps that is link to
// /usr/share/google-chrome/extensions/arc/BOARD_NAME - contains default
// apps for particular current board.
//
std::vector<base::FilePath> sources;
base::FilePath base_path;
if (!use_test_apps_directory) {
const bool valid_path = base::PathService::Get(
chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS, &base_path);
DCHECK(valid_path);
sources.push_back(base_path.Append(kArcDirectory));
sources.push_back(base::FilePath(kBoardDirectory));
} else {
const bool valid_path =
base::PathService::Get(chrome::DIR_TEST_DATA, &base_path);
DCHECK(valid_path);
sources.push_back(base_path.Append(kArcTestDirectory));
sources.push_back(base_path.Append(kArcTestBoardDirectory));
}
// Using base::Unretained(this) here is safe since we own barrier_closure_.
barrier_closure_ = base::BarrierClosure(
sources.size(),
base::BindOnce(&ArcDefaultAppList::OnAppsReady, base::Unretained(this)));
// Once ready OnAppsReady is called.
for (const auto& source : sources) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&ReadAppsFromFileThread, source),
base::BindOnce(&ArcDefaultAppList::OnAppsRead,
weak_ptr_factory_.GetWeakPtr()));
}
}
ArcDefaultAppList::~ArcDefaultAppList() = default;
void ArcDefaultAppList::OnAppsRead(std::unique_ptr<AppInfoMap> apps) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
const PrefService* const prefs = profile_->GetPrefs();
for (auto& entry : *apps.get()) {
AppInfoMap& app_map =
IsAppHidden(prefs, entry.first) ? hidden_apps_ : visible_apps_;
app_map[entry.first] = std::move(entry.second);
}
barrier_closure_.Run();
}
void ArcDefaultAppList::OnAppsReady() {
const PrefService* const prefs = profile_->GetPrefs();
// Register Play Store as default app. Some services and ArcSupportHost may
// not be available in tests.
extensions::ExtensionService* service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
const extensions::Extension* arc_host =
service ? service->GetInstalledExtension(arc::kPlayStoreAppId) : nullptr;
if (arc_host && arc::IsPlayStoreAvailable()) {
std::unique_ptr<ArcDefaultAppList::AppInfo> play_store_app =
std::make_unique<ArcDefaultAppList::AppInfo>(arc_host->name(),
arc::kPlayStorePackage,
arc::kPlayStoreActivity,
false /* oem */,
base::FilePath() /* app_path */);
AppInfoMap& app_map =
IsAppHidden(prefs, arc::kPlayStoreAppId) ? hidden_apps_ : visible_apps_;
app_map.insert(
std::pair<std::string, std::unique_ptr<ArcDefaultAppList::AppInfo>>(
arc::kPlayStoreAppId, std::move(play_store_app)));
}
std::move(ready_callback_).Run();
}
const ArcDefaultAppList::AppInfo* ArcDefaultAppList::GetApp(
const std::string& app_id) const {
if ((filter_level_ == FilterLevel::ALL) ||
(filter_level_ == FilterLevel::OPTIONAL_APPS &&
app_id != arc::kPlayStoreAppId)) {
return nullptr;
}
const auto it = visible_apps_.find(app_id);
if (it == visible_apps_.end())
return nullptr;
return it->second.get();
}
bool ArcDefaultAppList::HasApp(const std::string& app_id) const {
return GetApp(app_id) != nullptr;
}
bool ArcDefaultAppList::HasPackage(const std::string& package_name) const {
for (const auto& it : visible_apps_) {
if (it.second->package_name == package_name)
return true;
}
return false;
}
void ArcDefaultAppList::SetAppHidden(const std::string& app_id, bool hidden) {
AppInfoMap& active_map = hidden ? visible_apps_ : hidden_apps_;
AppInfoMap& inactive_map = hidden ? hidden_apps_ : visible_apps_;
auto it = active_map.find(app_id);
if (it == active_map.end())
return;
inactive_map[app_id] = std::move(it->second);
active_map.erase(it);
// Store hidden flag.
arc::ArcAppScopedPrefUpdate(profile_->GetPrefs(), app_id, kDefaultApps)
.Get()
->SetBoolean(kHidden, hidden);
}
void ArcDefaultAppList::SetAppsHiddenForPackage(
const std::string& package_name) {
std::unordered_set<std::string> apps_to_hide;
for (const auto& app : visible_apps_) {
if (app.second->package_name == package_name)
apps_to_hide.insert(app.first);
}
for (const auto& app : apps_to_hide)
SetAppHidden(app, true);
}
std::map<std::string, const ArcDefaultAppList::AppInfo*>
ArcDefaultAppList::GetActiveApps() const {
std::map<std::string, const AppInfo*> result;
for (const auto& it : visible_apps_) {
if (HasApp(it.first))
result[it.first] = it.second.get();
}
return result;
}
std::unordered_set<std::string> ArcDefaultAppList::GetActivePackages() const {
std::unordered_set<std::string> result;
for (const auto& it : GetActiveApps())
result.insert(it.second->package_name);
return result;
}
ArcDefaultAppList::AppInfo::AppInfo(const std::string& name,
const std::string& package_name,
const std::string& activity,
bool oem,
const base::FilePath app_path)
: name(name),
package_name(package_name),
activity(activity),
oem(oem),
app_path(app_path) {}
ArcDefaultAppList::AppInfo::~AppInfo() {}