blob: 277255af3e09f88b93c58cbc87ae4af16bcc148e [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include <optional>
#include <string_view>
#include <utility>
#include "base/containers/contains.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "components/services/app_service/public/cpp/app.h"
#include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/services/app_service/public/cpp/types_util.h"
namespace apps {
AppRegistryCache::Observer::Observer() = default;
AppRegistryCache::Observer::~Observer() {
CHECK(!IsInObserverList());
}
AppRegistryCache::AppRegistryCache() : account_id_(EmptyAccountId()) {}
AppRegistryCache::~AppRegistryCache() {
for (auto& obs : observers_) {
obs.OnAppRegistryCacheWillBeDestroyed(this);
}
DCHECK(observers_.empty());
AppRegistryCacheWrapper::Get().RemoveAppRegistryCache(this);
}
void AppRegistryCache::AddObserver(Observer* observer) {
DCHECK(observer);
observers_.AddObserver(observer);
}
void AppRegistryCache::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
AppType AppRegistryCache::GetAppType(std::string_view app_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
auto d_iter = deltas_in_progress_.find(app_id);
if (d_iter != deltas_in_progress_.end()) {
return d_iter->second->app_type;
}
auto s_iter = states_.find(app_id);
return (s_iter != states_.end()) ? s_iter->second->app_type
: AppType::kUnknown;
}
std::vector<AppPtr> AppRegistryCache::GetAllApps() {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
std::vector<AppPtr> apps;
for (const auto& s_iter : states_) {
const App* state = s_iter.second.get();
apps.push_back(state->Clone());
auto d_iter = deltas_in_progress_.find(s_iter.first);
const App* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
AppUpdate::Merge(apps[apps.size() - 1].get(), delta);
}
for (const auto& d_iter : deltas_in_progress_) {
if (!base::Contains(states_, d_iter.first)) {
// Call AppUpdate::Merge to set the init value for the icon key's
// `update_version` to keep the consistency as AppRegistryCache's
// `states_`.
auto app =
std::make_unique<App>(d_iter.second->app_type, d_iter.second->app_id);
AppUpdate::Merge(app.get(), d_iter.second);
apps.push_back(std::move(app));
}
}
return apps;
}
void AppRegistryCache::SetAccountId(const AccountId& account_id) {
account_id_ = account_id;
}
const std::set<AppType>& AppRegistryCache::InitializedAppTypes() const {
return initialized_app_types_;
}
bool AppRegistryCache::IsAppTypeInitialized(apps::AppType app_type) const {
return base::Contains(initialized_app_types_, app_type);
}
bool AppRegistryCache::IsAppTypePublished(apps::AppType app_type) const {
return base::Contains(published_app_types_, app_type);
}
bool AppRegistryCache::IsAppInstalled(std::string_view app_id) const {
bool installed = false;
ForOneApp(app_id, [&installed](const AppUpdate& update) {
installed = apps_util::IsInstalled(update.Readiness());
});
return installed;
}
void AppRegistryCache::ReinitializeForTesting() {
states_.clear();
deltas_in_progress_.clear();
deltas_pending_.clear();
in_progress_initialized_app_types_.clear();
published_app_types_.clear();
// We can't clear initialized_app_types_ here as observers
// expect each type to be initialized only once.
}
void AppRegistryCache::OnAppsForTesting(std::vector<AppPtr> deltas,
apps::AppType app_type,
bool should_notify_initialized) {
OnApps(std::move(deltas), app_type, should_notify_initialized);
}
void AppRegistryCache::OnApps(std::vector<AppPtr> deltas,
apps::AppType app_type,
bool should_notify_initialized) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
if (should_notify_initialized) {
DCHECK_NE(apps::AppType::kUnknown, app_type);
if (!IsAppTypePublished(app_type)) {
published_app_types_.insert(app_type);
for (auto& obs : observers_) {
obs.OnAppTypePublishing(deltas, app_type);
}
}
if (!IsAppTypeInitialized(app_type)) {
in_progress_initialized_app_types_.insert(app_type);
}
}
OnApps(std::move(deltas));
}
void AppRegistryCache::OnApps(std::vector<AppPtr> deltas) {
if (!deltas_in_progress_.empty()) {
std::move(deltas.begin(), deltas.end(),
std::back_inserter(deltas_pending_));
return;
}
DoOnApps(std::move(deltas));
while (!deltas_pending_.empty()) {
std::vector<AppPtr> pending;
pending.swap(deltas_pending_);
DoOnApps(std::move(pending));
}
OnAppTypeInitialized();
}
void AppRegistryCache::DoOnApps(std::vector<AppPtr> deltas) {
// Merge any deltas elements that have the same app_id. If an observer's
// OnAppUpdate calls back into this AppRegistryCache then we can therefore
// present a single delta for any given app_id.
bool is_pending = false;
for (auto& delta : deltas) {
if (is_pending) {
deltas_pending_.push_back(std::move(delta));
continue;
}
if (delta->readiness == Readiness::kRemoved) {
// As soon as we see a removed update, we need to defer future merges to
// avoid cases where a removed update is merged with a future non-removed
// update.
//
// For example, for below apps deltas:
// 1. {App1: ready}
// 2. {App1: uninstalled, App1: removed}
// 3. {App1: disabled by policy, App2: ready}
//
// The expected result for OnAppUpdate is:
// {App1: ready}
// {App1: uninstalled}
// Then App1 is removed in AppRegistry, GetAppType returns kUnknown.
// {App1: is added as disabled by policy}
// {App2: ready}
is_pending = true;
}
auto d_iter = deltas_in_progress_.find(delta->app_id);
if (d_iter != deltas_in_progress_.end()) {
if (delta->readiness == Readiness::kRemoved) {
// Ensure that removed deltas are *not* merged, so that the last update
// before the merge is sent separately.
deltas_pending_.push_back(std::move(delta));
} else {
// Call AppUpdate::MergeDelta to merge the icon key's `update_version`.
AppUpdate::MergeDelta(d_iter->second, delta.get());
}
} else {
deltas_in_progress_[delta->app_id] = delta.get();
}
}
// The remaining for loops range over the deltas_in_progress_ map, not
// the deltas vector, so that OnAppUpdate is called only once per unique
// app_id.
// Notify the observers for every de-duplicated delta.
for (const auto& d_iter : deltas_in_progress_) {
// Do not update subscribers for removed apps.
if (d_iter.second->readiness == Readiness::kRemoved) {
continue;
}
auto s_iter = states_.find(d_iter.first);
App* state = (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
App* delta = d_iter.second;
// Skip OnAppUpdate if there is no change.
if (!AppUpdate::IsChanged(state, delta)) {
continue;
}
for (auto& obs : observers_) {
obs.OnAppUpdate(AppUpdate(state, delta, account_id_));
}
}
// Update the states for every de-duplicated delta.
for (const auto& d_iter : deltas_in_progress_) {
auto s_iter = states_.find(d_iter.first);
apps::App* state =
(s_iter != states_.end()) ? s_iter->second.get() : nullptr;
apps::App* delta = d_iter.second;
if (delta->readiness != Readiness::kRemoved) {
if (state) {
AppUpdate::Merge(state, delta);
} else {
// Call AppUpdate::Merge to set the init value for the icon key's
// `update_version`.
auto app = std::make_unique<App>(delta->app_type, delta->app_id);
AppUpdate::Merge(app.get(), delta);
states_.insert(std::make_pair(delta->app_id, std::move(app)));
}
} else {
DCHECK(!state || state->readiness != Readiness::kReady);
states_.erase(d_iter.first);
}
}
deltas_in_progress_.clear();
}
void AppRegistryCache::InitApps(apps::AppType app_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
CHECK_NE(apps::AppType::kUnknown, app_type);
if (!IsAppTypeInitialized(app_type)) {
in_progress_initialized_app_types_.insert(app_type);
}
OnAppTypeInitialized();
}
void AppRegistryCache::OnAppTypeInitialized() {
if (in_progress_initialized_app_types_.empty()) {
return;
}
// In observer's OnAppTypeInitialized callback, `OnApp` might be called to
// update the app, then this OnAppTypeInitialized might be called again. So we
// need to check the initialized `app_type` first, and remove it from
// `in_progress_initialized_app_types_` to prevent the dead loop.
std::set<AppType> in_progress_initialized_app_types;
for (auto app_type : in_progress_initialized_app_types_) {
in_progress_initialized_app_types.insert(app_type);
}
for (auto app_type : in_progress_initialized_app_types) {
in_progress_initialized_app_types_.erase(app_type);
initialized_app_types_.insert(app_type);
for (auto& obs : observers_) {
obs.OnAppTypeInitialized(app_type);
}
}
}
std::optional<AppUpdate> AppRegistryCache::GetAppUpdate(
std::string_view app_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
auto s_iter = states_.find(app_id);
const App* state = (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
auto d_iter = deltas_in_progress_.find(app_id);
const App* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
if (state || delta) {
return AppUpdate(state, delta, account_id_);
}
return std::nullopt;
}
} // namespace apps