Commit e69702c9 authored by nancy's avatar nancy Committed by Commit Bot

Reland: Add App Service Instance related classes.

Design doc:
go/app-service-instance-registry-design-doc
go/appservice-for-per-app-time-limit-design-doc

This CL adds the App Service Instance classes:
Instance
InstanceUpdate
InstanceRegistry

InstanceRegistry depends on Instance and InstanceUpdate to calculate
the instance updates to notify the client.

BUG=1011235

Change-Id: Ia4f41304f1034bd33000c9a6235633054a1baeb4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1902172Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Commit-Queue: Nancy Wang <nancylingwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#713468}
parent 3b3eff1c
...@@ -3725,6 +3725,7 @@ jumbo_static_library("browser") { ...@@ -3725,6 +3725,7 @@ jumbo_static_library("browser") {
deps += [ deps += [
"//ash/public/cpp", "//ash/public/cpp",
"//chrome/browser/chromeos", "//chrome/browser/chromeos",
"//chrome/services/app_service/public/cpp:instance_update",
"//chromeos/components/account_manager", "//chromeos/components/account_manager",
"//chromeos/components/sync_wifi", "//chromeos/components/sync_wifi",
"//chromeos/services/assistant/public:feature_flags", "//chromeos/services/assistant/public:feature_flags",
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "chrome/browser/apps/app_service/uninstall_dialog.h" #include "chrome/browser/apps/app_service/uninstall_dialog.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/services/app_service/app_service_impl.h" #include "chrome/services/app_service/app_service_impl.h"
#include "chrome/services/app_service/public/cpp/instance_registry.h"
#include "chrome/services/app_service/public/cpp/intent_filter_util.h" #include "chrome/services/app_service/public/cpp/intent_filter_util.h"
#include "chrome/services/app_service/public/cpp/intent_util.h" #include "chrome/services/app_service/public/cpp/intent_util.h"
#include "chrome/services/app_service/public/mojom/types.mojom.h" #include "chrome/services/app_service/public/mojom/types.mojom.h"
...@@ -164,6 +165,12 @@ apps::AppRegistryCache& AppServiceProxy::AppRegistryCache() { ...@@ -164,6 +165,12 @@ apps::AppRegistryCache& AppServiceProxy::AppRegistryCache() {
return cache_; return cache_;
} }
#if defined(OS_CHROMEOS)
apps::InstanceRegistry& AppServiceProxy::InstanceRegistry() {
return instance_registry_;
}
#endif
apps::PreferredApps& AppServiceProxy::PreferredApps() { apps::PreferredApps& AppServiceProxy::PreferredApps() {
return preferred_apps_; return preferred_apps_;
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "chrome/services/app_service/public/cpp/app_registry_cache.h" #include "chrome/services/app_service/public/cpp/app_registry_cache.h"
#include "chrome/services/app_service/public/cpp/icon_cache.h" #include "chrome/services/app_service/public/cpp/icon_cache.h"
#include "chrome/services/app_service/public/cpp/icon_coalescer.h" #include "chrome/services/app_service/public/cpp/icon_coalescer.h"
#include "chrome/services/app_service/public/cpp/instance_registry.h"
#include "chrome/services/app_service/public/cpp/preferred_apps.h" #include "chrome/services/app_service/public/cpp/preferred_apps.h"
#include "components/keyed_service/core/keyed_service.h" #include "components/keyed_service/core/keyed_service.h"
#include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h"
...@@ -54,6 +55,10 @@ class AppServiceProxy : public KeyedService, ...@@ -54,6 +55,10 @@ class AppServiceProxy : public KeyedService,
apps::AppRegistryCache& AppRegistryCache(); apps::AppRegistryCache& AppRegistryCache();
apps::PreferredApps& PreferredApps(); apps::PreferredApps& PreferredApps();
#if defined(OS_CHROMEOS)
apps::InstanceRegistry& InstanceRegistry();
#endif
// apps::IconLoader overrides. // apps::IconLoader overrides.
apps::mojom::IconKeyPtr GetIconKey(const std::string& app_id) override; apps::mojom::IconKeyPtr GetIconKey(const std::string& app_id) override;
std::unique_ptr<IconLoader::Releaser> LoadIconFromIconKey( std::unique_ptr<IconLoader::Releaser> LoadIconFromIconKey(
...@@ -204,7 +209,10 @@ class AppServiceProxy : public KeyedService, ...@@ -204,7 +209,10 @@ class AppServiceProxy : public KeyedService,
std::unique_ptr<CrostiniApps> crostini_apps_; std::unique_ptr<CrostiniApps> crostini_apps_;
std::unique_ptr<ExtensionApps> extension_apps_; std::unique_ptr<ExtensionApps> extension_apps_;
std::unique_ptr<ExtensionApps> extension_web_apps_; std::unique_ptr<ExtensionApps> extension_web_apps_;
bool arc_is_registered_ = false; bool arc_is_registered_ = false;
apps::InstanceRegistry instance_registry_;
#endif // OS_CHROMEOS #endif // OS_CHROMEOS
Profile* profile_; Profile* profile_;
......
...@@ -15,6 +15,24 @@ source_set("app_update") { ...@@ -15,6 +15,24 @@ source_set("app_update") {
] ]
} }
if (is_chromeos) {
source_set("instance_update") {
sources = [
"instance.cc",
"instance.h",
"instance_registry.cc",
"instance_registry.h",
"instance_update.cc",
"instance_update.h",
]
deps = [
"//skia",
"//ui/aura",
"//ui/compositor",
]
}
}
source_set("icon_loader") { source_set("icon_loader") {
sources = [ sources = [
"icon_cache.cc", "icon_cache.cc",
...@@ -88,4 +106,13 @@ source_set("unit_tests") { ...@@ -88,4 +106,13 @@ source_set("unit_tests") {
":icon_loader", ":icon_loader",
"//testing/gtest", "//testing/gtest",
] ]
if (is_chromeos) {
sources += [
"instance_registry_unittest.cc",
"instance_update_unittest.cc",
]
deps += [ ":instance_update" ]
}
} }
// Copyright 2019 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/services/app_service/public/cpp/instance.h"
#include <memory>
#include "ui/aura/window.h"
namespace apps {
Instance::Instance(const std::string& app_id, aura::Window* window)
: app_id_(app_id), window_(window) {
state_ = InstanceState::kUnknown;
}
Instance::~Instance() = default;
std::unique_ptr<Instance> Instance::Clone() {
auto instance = std::make_unique<Instance>(this->AppId(), this->Window());
instance->SetLaunchId(this->LaunchId());
instance->UpdateState(this->State(), this->LastUpdatedTime());
return instance;
}
void Instance::UpdateState(InstanceState state,
const base::Time& last_updated_time) {
state_ = state;
last_updated_time_ = last_updated_time;
}
} // namespace apps
// Copyright 2019 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.
#ifndef CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_H_
#define CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_H_
#include <memory>
#include <string>
#include "base/time/time.h"
namespace aura {
class Window;
}
namespace apps {
enum InstanceState {
kUnknown = 0,
kStarted = 0x01,
kRunning = 0x02,
kActive = 0x04,
kVisible = 0x08,
};
// Instance is used to represent an App Instance, or a running app.
class Instance {
public:
Instance(const std::string& app_id, aura::Window* window);
~Instance();
Instance(const Instance&) = delete;
Instance& operator=(const Instance&) = delete;
std::unique_ptr<Instance> Clone();
void SetLaunchId(const std::string& launch_id) { launch_id_ = launch_id; }
void UpdateState(InstanceState state, const base::Time& last_updated_time);
const std::string& AppId() const { return app_id_; }
aura::Window* Window() const { return window_; }
const std::string& LaunchId() const { return launch_id_; }
InstanceState State() const { return state_; }
const base::Time& LastUpdatedTime() const { return last_updated_time_; }
private:
std::string app_id_;
// window_ is owned by ash and will be deleted when the user closes the
// window. Instance itself doesn't observe the window. The window's observer
// is responsible to delete Instance from InstanceRegistry when the window is
// destroyed.
aura::Window* window_;
std::string launch_id_;
InstanceState state_;
base::Time last_updated_time_;
};
} // namespace apps
#endif // CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_H_
// Copyright 2019 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/services/app_service/public/cpp/instance_registry.h"
#include <memory>
#include <utility>
#include "chrome/services/app_service/public/cpp/instance.h"
#include "chrome/services/app_service/public/cpp/instance_update.h"
namespace apps {
InstanceRegistry::Observer::Observer(InstanceRegistry* instance_registry) {
Observe(instance_registry);
}
InstanceRegistry::Observer::Observer() = default;
InstanceRegistry::Observer::~Observer() {
if (instance_registry_) {
instance_registry_->RemoveObserver(this);
}
}
void InstanceRegistry::Observer::Observe(InstanceRegistry* instance_registry) {
if (instance_registry == instance_registry_) {
return;
}
if (instance_registry_) {
instance_registry_->RemoveObserver(this);
}
instance_registry_ = instance_registry;
if (instance_registry_) {
instance_registry_->AddObserver(this);
}
}
InstanceRegistry::InstanceRegistry() = default;
InstanceRegistry::~InstanceRegistry() {
for (auto& obs : observers_) {
obs.OnInstanceRegistryWillBeDestroyed(this);
}
DCHECK(!observers_.might_have_observers());
}
void InstanceRegistry::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void InstanceRegistry::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void InstanceRegistry::OnInstances(const Instances& deltas) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
if (!deltas_in_progress_.empty()) {
for (auto& delta : deltas) {
deltas_pending_.push_back(delta.get()->Clone());
}
return;
}
DoOnInstances(std::move(deltas));
while (!deltas_pending_.empty()) {
Instances pending;
pending.swap(deltas_pending_);
DoOnInstances(std::move(pending));
}
}
void InstanceRegistry::DoOnInstances(const Instances& deltas) {
// Merge any deltas elements that have the same window. If an observer's
// OnInstanceUpdate calls back into this InstanceRegistry, we can present a
// single delta for any given window.
for (auto& delta : deltas) {
auto d_iter = deltas_in_progress_.find(delta->Window());
if (d_iter != deltas_in_progress_.end()) {
InstanceUpdate::Merge(d_iter->second, delta.get());
} else {
deltas_in_progress_[delta->Window()] = delta.get();
}
}
// The remaining for loops range over the deltas_in_progress_ map, not the
// deltas vector, so that OninstanceUpdate is called only once per unique
// window. Notify the observers for every de-duplicated delta.
for (const auto& d_iter : deltas_in_progress_) {
auto s_iter = states_.find(d_iter.first);
Instance* state =
(s_iter != states_.end()) ? s_iter->second.get() : nullptr;
Instance* delta = d_iter.second;
for (auto& obs : observers_) {
obs.OnInstanceUpdate(InstanceUpdate(state, delta));
}
}
// Update the states for every de-duplicated delta.
for (const auto& d_iter : deltas_in_progress_) {
auto s_iter = states_.find(d_iter.first);
Instance* state =
(s_iter != states_.end()) ? s_iter->second.get() : nullptr;
Instance* delta = d_iter.second;
if (state) {
InstanceUpdate::Merge(state, delta);
} else {
states_.insert(std::make_pair(delta->Window(), (delta->Clone())));
}
}
deltas_in_progress_.clear();
}
} // namespace apps
// Copyright 2019 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.
#ifndef CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_REGISTRY_H_
#define CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_REGISTRY_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/sequence_checker.h"
#include "chrome/services/app_service/public/cpp/instance.h"
#include "chrome/services/app_service/public/cpp/instance_update.h"
namespace aura {
class Window;
}
namespace apps {
// InstanceRegistry keeps all of the Instances seen by AppServiceProxy.
// It also keeps the "sum" of those previous deltas, so that observers of this
// object can be updated with the InstanceUpdate structure. It can also be
// queried synchronously.
//
// This class is not thread-safe.
class InstanceRegistry {
public:
class Observer : public base::CheckedObserver {
public:
Observer(const Observer&) = delete;
Observer& operator=(const Observer&) = delete;
// The InstanceUpdate argument shouldn't be accessed after OnInstanceUpdate
// returns.
virtual void OnInstanceUpdate(const InstanceUpdate& update) = 0;
// Called when the InstanceRegistry object (the thing that this observer
// observes) will be destroyed. In response, the observer, |this|, should
// call "instance_registry->RemoveObserver(this)", whether directly or
// indirectly (e.g. via ScopedObserver::Remove or via Observe(nullptr)).
virtual void OnInstanceRegistryWillBeDestroyed(InstanceRegistry* cache) = 0;
protected:
// Use this constructor when the observer |this| is tied to a single
// InstanceRegistry for its entire lifetime, or until the observee (the
// InstanceRegistry) is destroyed, whichever comes first.
explicit Observer(InstanceRegistry* cache);
// Use this constructor when the observer |this| wants to observe a
// InstanceRegistry for part of its lifetime. It can then call Observe() to
// start and stop observing.
Observer();
~Observer() override;
// Start observing a different InstanceRegistry. |instance_registry| may be
// nullptr, meaning to stop observing.
void Observe(InstanceRegistry* instance_registry);
private:
InstanceRegistry* instance_registry_ = nullptr;
};
InstanceRegistry();
~InstanceRegistry();
InstanceRegistry(const InstanceRegistry&) = delete;
InstanceRegistry& operator=(const InstanceRegistry&) = delete;
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
using InstancePtr = std::unique_ptr<Instance>;
using Instances = std::vector<InstancePtr>;
// Notification and merging might be delayed until after OnInstances returns.
// For example, suppose that the initial set of states is (a0, b0, c0) for
// three app_id's ("a", "b", "c"). Now suppose OnInstances is called with two
// updates (b1, c1), and when notified of b1, an observer calls OnInstances
// again with (c2, d2). The c1 delta should be processed before the c2 delta,
// as it was sent first: c2 should be merged (with "newest wins" semantics)
// onto c1 and not vice versa. This means that processing c2 (scheduled by the
// second OnInstances call) should wait until the first OnInstances call has
// finished processing b1 (and then c1), which means that processing c2 is
// delayed until after the second OnInstances call returns.
//
// The caller presumably calls OnInstances(std::move(deltas)).
void OnInstances(const Instances& deltas);
// Calls f, a void-returning function whose arguments are (const
// apps::InstanceUpdate&), on each window in the instance_registry.
//
// f's argument is an apps::InstanceUpdate instead of an Instance* so that
// callers can more easily share code with Observer::OnInstanceUpdate (which
// also takes an apps::InstanceUpdate), and an apps::InstanceUpdate also has a
// StateIsNull method.
//
// The apps::InstanceUpdate argument to f shouldn't be accessed after f
// returns.
//
// f must be synchronous, and if it asynchronously calls ForEachInstance
// again, it's not guaranteed to see a consistent state.
template <typename FunctionType>
void ForEachInstance(FunctionType f) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
for (const auto& s_iter : states_) {
apps::Instance* state = s_iter.second.get();
auto d_iter = deltas_in_progress_.find(s_iter.first);
apps::Instance* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
f(apps::InstanceUpdate(state, delta));
}
for (const auto& d_iter : deltas_in_progress_) {
apps::Instance* delta = d_iter.second;
auto s_iter = states_.find(d_iter.first);
if (s_iter != states_.end()) {
continue;
}
f(apps::InstanceUpdate(nullptr, delta));
}
}
// Calls f, a void-returning function whose arguments are (const
// apps::InstanceUpdate&), on the instance in the instance_registry with the
// given window. It will return true (and call f) if there is such an
// instance, otherwise it will return false (and not call f). The
// InstanceUpdate argument to f has the same semantics as for ForEachInstance,
// above.
//
// f must be synchronous, and if it asynchronously calls ForOneInstance again,
// it's not guaranteed to see a consistent state.
template <typename FunctionType>
bool ForOneInstance(const aura::Window* window, FunctionType f) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
auto s_iter = states_.find(window);
apps::Instance* state =
(s_iter != states_.end()) ? s_iter->second.get() : nullptr;
auto d_iter = deltas_in_progress_.find(window);
apps::Instance* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
if (state || delta) {
f(apps::InstanceUpdate(state, delta));
return true;
}
return false;
}
private:
void DoOnInstances(const Instances& deltas);
base::ObserverList<Observer> observers_;
// Maps from window to the latest state: the "sum" of all previous deltas.
std::map<const aura::Window*, InstancePtr> states_;
// Track the deltas being processed or are about to be processed by
// OnInstances. They are separate to manage the "notification and merging
// might be delayed until after OnInstances returns" concern described above.
//
// OnInstances calls DoOnInstances zero or more times. If we're nested, so
// that there's multiple OnInstances call to this InstanceRegistry in the call
// stack, the deeper OnInstances call simply adds work to deltas_pending_ and
// returns without calling DoOnInstances. If we're not nested, OnInstances
// calls DoOnInstances one or more times; "more times" happens if
// DoOnInstances notifying observers leads to more OnInstances calls that
// enqueue deltas_pending_ work. The deltas_in_progress_ map (keyed by window)
// contains those deltas being considered by DoOnInstances.
//
// Nested OnInstances calls are expected to be rare (but still dealt with
// sensibly). In the typical case, OnInstances should call DoOnInstances
// exactly once, and deltas_pending_ will stay empty.
std::map<const aura::Window*, Instance*> deltas_in_progress_;
Instances deltas_pending_;
SEQUENCE_CHECKER(my_sequence_checker_);
};
} // namespace apps
#endif // CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_REGISTRY_H_
// Copyright 2019 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 <memory>
#include <vector>
#include "chrome/services/app_service/public/cpp/instance.h"
#include "chrome/services/app_service/public/cpp/instance_registry.h"
#include "chrome/services/app_service/public/cpp/instance_update.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
class InstanceRegistryTest : public testing::Test,
public apps::InstanceRegistry::Observer {
protected:
static std::unique_ptr<apps::Instance> MakeInstance(
const char* app_id,
aura::Window* window,
apps::InstanceState state = apps::InstanceState::kUnknown,
base::Time time = base::Time()) {
std::unique_ptr<apps::Instance> instance =
std::make_unique<apps::Instance>(app_id, window);
instance->UpdateState(state, time);
return instance;
}
void CallForEachInstance(apps::InstanceRegistry& instance_registry) {
instance_registry.ForEachInstance(
[this](const apps::InstanceUpdate& update) {
OnInstanceUpdate(update);
});
}
apps::InstanceState GetState(apps::InstanceRegistry& instance_registry,
const aura::Window* window) {
apps::InstanceState state = apps::InstanceState::kUnknown;
instance_registry.ForOneInstance(
window, [&state](const apps::InstanceUpdate& update) {
state = update.State();
});
return state;
}
// apps::InstanceRegistry::Observer overrides.
void OnInstanceUpdate(const apps::InstanceUpdate& update) override {
EXPECT_NE("", update.AppId());
if (update.StateChanged() &&
update.State() == apps::InstanceState::kRunning) {
num_running_apps_++;
}
updated_ids_.insert(update.AppId());
updated_windows_.insert(update.Window());
}
void OnInstanceRegistryWillBeDestroyed(
apps::InstanceRegistry* instance_registry) override {
// The test code explicitly calls both AddObserver and RemoveObserver.
NOTREACHED();
}
int num_running_apps_ = 0;
std::set<std::string> updated_ids_;
std::set<const aura::Window*> updated_windows_;
};
// In the tests below, just "recursive" means that instance_registry.OnInstances
// calls observer.OnInstanceUpdate which calls instance_registry.ForEachInstance
// and instance_registry.ForOneInstance. "Super-recursive" means that
// instance_registry.OnInstances calls observer.OnInstanceUpdate calls
// instance_registry.OnInstances which calls observer.OnInstanceUpdate.
class InstanceRecursiveObserver : public apps::InstanceRegistry::Observer {
public:
explicit InstanceRecursiveObserver(apps::InstanceRegistry* instance_registry)
: instance_registry_(instance_registry) {
Observe(instance_registry);
}
~InstanceRecursiveObserver() override = default;
void PrepareForOnInstances(int expected_num_instances,
apps::InstanceState expected_status_for_p,
std::vector<std::unique_ptr<apps::Instance>>*
super_recursive_instances = nullptr) {
expected_num_instances_ = expected_num_instances;
expected_status_for_p_ = expected_status_for_p;
num_instances_seen_on_instance_update_ = 0;
if (super_recursive_instances) {
super_recursive_instances_.swap(*super_recursive_instances);
}
}
int NumInstancesSeenOnInstanceUpdate() {
return num_instances_seen_on_instance_update_;
}
protected:
// apps::InstanceRegistry::Observer overrides.
void OnInstanceUpdate(const apps::InstanceUpdate& outer) override {
int num_instance = 0;
instance_registry_->ForEachInstance(
[this, &outer, &num_instance](const apps::InstanceUpdate& inner) {
if (outer.Window() == inner.Window()) {
ExpectEq(outer, inner);
}
if (inner.AppId() == "p") {
EXPECT_EQ(expected_status_for_p_, inner.State());
}
num_instance++;
});
EXPECT_TRUE(instance_registry_->ForOneInstance(
outer.Window(), [&outer](const apps::InstanceUpdate& inner) {
ExpectEq(outer, inner);
}));
if (expected_num_instances_ >= 0) {
EXPECT_EQ(expected_num_instances_, num_instance);
}
std::vector<std::unique_ptr<apps::Instance>> super_recursive;
while (!super_recursive_instances_.empty()) {
std::unique_ptr<apps::Instance> instance =
std::move(super_recursive_instances_.back());
if (instance.get() == nullptr) {
// This is the placeholder 'punctuation'.
super_recursive_instances_.pop_back();
break;
}
super_recursive.push_back(std::move(instance));
super_recursive_instances_.pop_back();
}
if (!super_recursive.empty()) {
instance_registry_->OnInstances(std::move(super_recursive));
}
num_instances_seen_on_instance_update_++;
}
void OnInstanceRegistryWillBeDestroyed(
apps::InstanceRegistry* instance_registry) override {
Observe(nullptr);
}
static void ExpectEq(const apps::InstanceUpdate& outer,
const apps::InstanceUpdate& inner) {
EXPECT_EQ(outer.AppId(), inner.AppId());
EXPECT_EQ(outer.StateIsNull(), inner.StateIsNull());
EXPECT_EQ(outer.Window(), inner.Window());
EXPECT_EQ(outer.State(), inner.State());
}
apps::InstanceRegistry* instance_registry_;
apps::InstanceState expected_status_for_p_;
int expected_num_instances_;
int num_instances_seen_on_instance_update_;
// Non-empty when this.OnInstanceUpdate should trigger more
// instance_registry_.OnInstances calls.
//
// During OnInstanceUpdate, this vector (a stack) is popped from the back
// until a nullptr 'punctuation' element (a group terminator) is seen. If that
// group of popped elements (in LIFO order) is non-empty, that group forms the
// vector of App's passed to instance_registry_.OnInstances.
std::vector<std::unique_ptr<apps::Instance>> super_recursive_instances_;
};
TEST_F(InstanceRegistryTest, ForEachInstance) {
std::vector<std::unique_ptr<apps::Instance>> deltas;
apps::InstanceRegistry instance_registry;
updated_windows_.clear();
updated_ids_.clear();
CallForEachInstance(instance_registry);
EXPECT_EQ(0u, updated_windows_.size());
EXPECT_EQ(0u, updated_ids_.size());
deltas.clear();
aura::Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
aura::Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
aura::Window window3(nullptr);
window3.Init(ui::LAYER_NOT_DRAWN);
deltas.push_back(MakeInstance("a", &window1));
deltas.push_back(MakeInstance("b", &window2));
deltas.push_back(MakeInstance("c", &window3));
instance_registry.OnInstances(std::move(deltas));
updated_windows_.clear();
updated_ids_.clear();
CallForEachInstance(instance_registry);
EXPECT_EQ(3u, updated_windows_.size());
EXPECT_EQ(3u, updated_ids_.size());
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window1));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window2));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window3));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("a"));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("b"));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("c"));
deltas.clear();
aura::Window window4(nullptr);
window4.Init(ui::LAYER_NOT_DRAWN);
deltas.push_back(MakeInstance("a", &window1, apps::InstanceState::kRunning));
deltas.push_back(MakeInstance("c", &window4));
instance_registry.OnInstances(std::move(deltas));
updated_windows_.clear();
updated_ids_.clear();
CallForEachInstance(instance_registry);
EXPECT_EQ(4u, updated_windows_.size());
EXPECT_EQ(3u, updated_ids_.size());
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window1));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window2));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window3));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window4));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("a"));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("b"));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("c"));
// Test that ForOneApp succeeds for window4 and fails for window5.
bool found_window4 = false;
EXPECT_TRUE(instance_registry.ForOneInstance(
&window4, [&found_window4](const apps::InstanceUpdate& update) {
found_window4 = true;
EXPECT_EQ("c", update.AppId());
}));
EXPECT_TRUE(found_window4);
bool found_window5 = false;
aura::Window window5(nullptr);
window5.Init(ui::LAYER_NOT_DRAWN);
EXPECT_FALSE(instance_registry.ForOneInstance(
&window5, [&found_window5](const apps::InstanceUpdate& update) {
found_window5 = true;
}));
EXPECT_FALSE(found_window5);
}
TEST_F(InstanceRegistryTest, Observer) {
std::vector<std::unique_ptr<apps::Instance>> deltas;
apps::InstanceRegistry instance_registry;
instance_registry.AddObserver(this);
num_running_apps_ = 0;
updated_windows_.clear();
updated_ids_.clear();
deltas.clear();
aura::Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
aura::Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
aura::Window window3(nullptr);
window3.Init(ui::LAYER_NOT_DRAWN);
deltas.push_back(MakeInstance("a", &window1));
deltas.push_back(MakeInstance("c", &window2));
deltas.push_back(MakeInstance("a", &window3));
instance_registry.OnInstances(std::move(deltas));
EXPECT_EQ(0, num_running_apps_);
EXPECT_EQ(3u, updated_windows_.size());
EXPECT_EQ(2u, updated_ids_.size());
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window1));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window2));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window3));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("a"));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("c"));
num_running_apps_ = 0;
updated_ids_.clear();
deltas.clear();
aura::Window window4(nullptr);
window4.Init(ui::LAYER_NOT_DRAWN);
deltas.push_back(MakeInstance("b", &window4));
deltas.push_back(MakeInstance("c", &window2, apps::InstanceState::kRunning));
instance_registry.OnInstances(std::move(deltas));
EXPECT_EQ(1, num_running_apps_);
EXPECT_EQ(2u, updated_ids_.size());
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window2));
EXPECT_NE(updated_windows_.end(), updated_windows_.find(&window4));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("b"));
EXPECT_NE(updated_ids_.end(), updated_ids_.find("c"));
instance_registry.RemoveObserver(this);
num_running_apps_ = 0;
updated_windows_.clear();
updated_ids_.clear();
deltas.clear();
aura::Window window5(nullptr);
window5.Init(ui::LAYER_NOT_DRAWN);
deltas.push_back(MakeInstance("f", &window5, apps::InstanceState::kRunning));
instance_registry.OnInstances(std::move(deltas));
EXPECT_EQ(0, num_running_apps_);
EXPECT_EQ(0u, updated_windows_.size());
EXPECT_EQ(0u, updated_ids_.size());
}
TEST_F(InstanceRegistryTest, Recursive) {
std::vector<std::unique_ptr<apps::Instance>> deltas;
apps::InstanceRegistry instance_registry;
InstanceRecursiveObserver observer(&instance_registry);
observer.PrepareForOnInstances(2, apps::InstanceState::kRunning);
deltas.clear();
aura::Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
aura::Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
deltas.push_back(MakeInstance("o", &window1, apps::InstanceState::kRunning));
deltas.push_back(MakeInstance("p", &window2, apps::InstanceState::kRunning));
instance_registry.OnInstances(std::move(deltas));
EXPECT_EQ(2, observer.NumInstancesSeenOnInstanceUpdate());
observer.PrepareForOnInstances(3, apps::InstanceState::kRunning);
deltas.clear();
aura::Window window3(nullptr);
window3.Init(ui::LAYER_NOT_DRAWN);
deltas.push_back(MakeInstance("p", &window2, apps::InstanceState::kRunning));
deltas.push_back(MakeInstance("q", &window3, apps::InstanceState::kRunning));
instance_registry.OnInstances(std::move(deltas));
EXPECT_EQ(2, observer.NumInstancesSeenOnInstanceUpdate());
observer.PrepareForOnInstances(3, apps::InstanceState::kActive);
deltas.clear();
deltas.push_back(MakeInstance("p", &window2, apps::InstanceState::kRunning));
deltas.push_back(MakeInstance("p", &window2, apps::InstanceState::kRunning));
deltas.push_back(MakeInstance("p", &window2, apps::InstanceState::kActive));
instance_registry.OnInstances(std::move(deltas));
EXPECT_EQ(1, observer.NumInstancesSeenOnInstanceUpdate());
}
TEST_F(InstanceRegistryTest, SuperRecursive) {
std::vector<std::unique_ptr<apps::Instance>> deltas;
apps::InstanceRegistry instance_registry;
InstanceRecursiveObserver observer(&instance_registry);
// Set up a series of OnInstances to be called during
// observer.OnInstanceUpdate:
// - the 1st update is {'a', &window2, kActive}.
// - the 2nd update is {'b', &window3, kActive}.
// - the 3rd update is {'c', &window4, kActive}.
// - the 4th update is {'b', &window5, kVisible}.
// - the 5th update is {'c', &window4, kVisible}.
// - the 6td update is {'b', &window3, kRunning}.
// - the 7th update is {'a', &window2, kRunning}.
// - the 8th update is {'b', &window1, kStarted}.
//
// The vector is processed in LIFO order with nullptr punctuation to
// terminate each group. See the comment on the
// RecursiveObserver::super_recursive_apps_ field.
std::vector<std::unique_ptr<apps::Instance>> super_recursive_apps;
aura::Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
aura::Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
aura::Window window3(nullptr);
window3.Init(ui::LAYER_NOT_DRAWN);
aura::Window window4(nullptr);
window4.Init(ui::LAYER_NOT_DRAWN);
aura::Window window5(nullptr);
window5.Init(ui::LAYER_NOT_DRAWN);
super_recursive_apps.push_back(nullptr);
super_recursive_apps.push_back(
MakeInstance("b", &window1, apps::InstanceState::kStarted));
super_recursive_apps.push_back(nullptr);
super_recursive_apps.push_back(nullptr);
super_recursive_apps.push_back(MakeInstance("a", &window2));
super_recursive_apps.push_back(nullptr);
super_recursive_apps.push_back(MakeInstance("b", &window3));
super_recursive_apps.push_back(
MakeInstance("a", &window2, apps::InstanceState::kRunning));
super_recursive_apps.push_back(
MakeInstance("b", &window3, apps::InstanceState::kRunning));
super_recursive_apps.push_back(nullptr);
super_recursive_apps.push_back(nullptr);
super_recursive_apps.push_back(
MakeInstance("c", &window4, apps::InstanceState::kVisible));
super_recursive_apps.push_back(
MakeInstance("b", &window5, apps::InstanceState::kVisible));
observer.PrepareForOnInstances(-1, apps::InstanceState::kRunning,
&super_recursive_apps);
deltas.clear();
deltas.push_back(MakeInstance("a", &window2, apps::InstanceState::kActive));
deltas.push_back(MakeInstance("b", &window3, apps::InstanceState::kActive));
deltas.push_back(MakeInstance("c", &window4, apps::InstanceState::kActive));
instance_registry.OnInstances(std::move(deltas));
// After all of that, check that for each window, the last delta won.
EXPECT_EQ(apps::InstanceState::kStarted,
GetState(instance_registry, &window1));
EXPECT_EQ(apps::InstanceState::kRunning,
GetState(instance_registry, &window2));
EXPECT_EQ(apps::InstanceState::kRunning,
GetState(instance_registry, &window3));
EXPECT_EQ(apps::InstanceState::kVisible,
GetState(instance_registry, &window4));
EXPECT_EQ(apps::InstanceState::kVisible,
GetState(instance_registry, &window5));
}
// Copyright 2019 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/services/app_service/public/cpp/instance_update.h"
#include "base/strings/string_util.h"
#include "chrome/services/app_service/public/cpp/instance.h"
#include "ui/aura/window.h"
namespace apps {
// static
void InstanceUpdate::Merge(Instance* state, const Instance* delta) {
DCHECK(state);
if (!delta) {
return;
}
if ((delta->AppId() != state->AppId()) ||
delta->Window() != state->Window()) {
LOG(ERROR) << "inconsistent (app_id, window): (" << delta->AppId() << ", "
<< delta->Window() << ") vs (" << state->AppId() << ", "
<< state->Window() << ") ";
DCHECK(false);
return;
}
if (!delta->LaunchId().empty()) {
state->SetLaunchId(delta->LaunchId());
}
if (delta->State() != InstanceState::kUnknown) {
state->UpdateState(delta->State(), delta->LastUpdatedTime());
}
// When adding new fields to the Instance class, this function should also be
// updated.
}
InstanceUpdate::InstanceUpdate(Instance* state, Instance* delta)
: state_(state), delta_(delta) {
DCHECK(state_ || delta_);
if (state_ && delta_) {
DCHECK(state_->AppId() == delta->AppId());
DCHECK(state_->Window() == delta->Window());
}
}
bool InstanceUpdate::StateIsNull() const {
return state_ == nullptr;
}
const std::string& InstanceUpdate::AppId() const {
return delta_ ? delta_->AppId() : state_->AppId();
}
const aura::Window* InstanceUpdate::Window() const {
return delta_ ? delta_->Window() : state_->Window();
}
const std::string& InstanceUpdate::LaunchId() const {
if (delta_ && !delta_->LaunchId().empty()) {
return delta_->LaunchId();
}
if (state_ && !state_->LaunchId().empty()) {
return state_->LaunchId();
}
return base::EmptyString();
}
bool InstanceUpdate::LaunchIdChanged() const {
return delta_ && !delta_->LaunchId().empty() &&
(!state_ || (delta_->LaunchId() != state_->LaunchId()));
}
InstanceState InstanceUpdate::State() const {
if (delta_ && (delta_->State() != InstanceState::kUnknown)) {
return delta_->State();
}
if (state_) {
return state_->State();
}
return InstanceState::kUnknown;
}
bool InstanceUpdate::StateChanged() const {
return delta_ && (delta_->State() != InstanceState::kUnknown) &&
(!state_ || (delta_->State() != state_->State()));
}
base::Time InstanceUpdate::LastUpdatedTime() const {
if (delta_ && !delta_->LastUpdatedTime().is_null()) {
return delta_->LastUpdatedTime();
}
if (state_ && !state_->LastUpdatedTime().is_null()) {
return state_->LastUpdatedTime();
}
return base::Time();
}
bool InstanceUpdate::LastUpdatedTimeChanged() const {
return delta_ && !delta_->LastUpdatedTime().is_null() &&
(!state_ || (delta_->LastUpdatedTime() != state_->LastUpdatedTime()));
}
} // namespace apps
// Copyright 2019 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.
#ifndef CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_UPDATE_H_
#define CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_UPDATE_H_
#include <string>
#include "base/time/time.h"
#include "chrome/services/app_service/public/cpp/instance.h"
namespace aura {
class Window;
}
namespace apps {
class Instance;
// Wraps two Instance's, a prior state and a delta on top of that state. The
// state is conceptually the "sum" of all of the previous deltas, with
// "addition" or "merging" simply being that the most recent version of each
// field "wins".
//
// The state may be nullptr, meaning that there are no previous deltas.
// Alternatively, the delta may be nullptr, meaning that there is no change in
// state. At least one of state and delta must be non-nullptr.
//
// The combination of the two (state and delta) can answer questions such as:
// - What is the app's launch_id? If the delta knows, that's the answer.
// Otherwise, ask the state.
// - Is the app launched? Likewise, if the delta says yes or no, that's the
// answer. Otherwise, the delta says "unknown", ask the state.
//
// An InstanceUpdate is read-only once constructed. All of its fields and
// methods are const. The constructor caller must guarantee that the Instance
// pointer remain valid for the lifetime of the AppUpdate.
//
// See the below two design docs for more details:
// go/app-service-instance-registry-design-doc
// go/appservice-for-per-app-time-limit-design-doc
class InstanceUpdate {
public:
// Modifies |state| by copying over all of |delta|'s known fields: those
// fields whose values aren't "unknown" or invalid. The |state| may not be
// nullptr.
static void Merge(Instance* state, const Instance* delta);
// At most one of |state| or |delta| may be nullptr.
InstanceUpdate(Instance* state, Instance* delta);
InstanceUpdate(const InstanceUpdate&) = delete;
InstanceUpdate& operator=(const InstanceUpdate&) = delete;
// Returns whether this is the first update for the given window.
// Equivalently, there are no previous deltas for the window.
bool StateIsNull() const;
const std::string& AppId() const;
const aura::Window* Window() const;
const std::string& LaunchId() const;
bool LaunchIdChanged() const;
InstanceState State() const;
bool StateChanged() const;
base::Time LastUpdatedTime() const;
bool LastUpdatedTimeChanged() const;
private:
Instance* state_;
Instance* delta_;
};
} // namespace apps
#endif // CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_INSTANCE_UPDATE_H_
// Copyright 2019 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/services/app_service/public/cpp/instance_update.h"
#include "chrome/services/app_service/public/cpp/instance.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
namespace {
const char app_id[] = "abcdefgh";
const char test_launch_id0[] = "abc";
const char test_launch_id1[] = "xyz";
} // namespace
class InstanceUpdateTest : public testing::Test {
protected:
void ExpectNoChange() {
expect_launch_id_changed_ = false;
expect_state_changed_ = false;
expect_last_updated_time_changed_ = false;
}
void CheckExpects(const apps::InstanceUpdate& u) {
EXPECT_EQ(expect_launch_id_, u.LaunchId());
EXPECT_EQ(expect_launch_id_changed_, u.LaunchIdChanged());
EXPECT_EQ(expect_state_, u.State());
EXPECT_EQ(expect_state_changed_, u.StateChanged());
EXPECT_EQ(expect_last_updated_time_, u.LastUpdatedTime());
EXPECT_EQ(expect_last_updated_time_changed_, u.LastUpdatedTimeChanged());
}
void TestInstanceUpdate(apps::Instance* state, apps::Instance* delta) {
apps::InstanceUpdate u(state, delta);
EXPECT_EQ(app_id, u.AppId());
EXPECT_EQ(state == nullptr, u.StateIsNull());
expect_launch_id_ = base::EmptyString();
expect_state_ = apps::InstanceState::kUnknown;
expect_last_updated_time_ = base::Time();
ExpectNoChange();
CheckExpects(u);
if (delta) {
delta->SetLaunchId(test_launch_id0);
expect_launch_id_ = test_launch_id0;
expect_launch_id_changed_ = true;
CheckExpects(u);
}
if (state) {
state->SetLaunchId(test_launch_id0);
expect_launch_id_ = test_launch_id0;
expect_launch_id_changed_ = false;
CheckExpects(u);
}
if (state) {
apps::InstanceUpdate::Merge(state, delta);
ExpectNoChange();
CheckExpects(u);
}
if (delta) {
delta->SetLaunchId(test_launch_id1);
expect_launch_id_ = test_launch_id1;
expect_launch_id_changed_ = true;
CheckExpects(u);
}
// State and StateTime tests.
if (state) {
state->UpdateState(apps::InstanceState::kRunning,
base::Time::FromDoubleT(1000.0));
expect_state_ = apps::InstanceState::kRunning;
expect_last_updated_time_ = base::Time::FromDoubleT(1000.0);
expect_state_changed_ = false;
expect_last_updated_time_changed_ = false;
CheckExpects(u);
}
if (delta) {
delta->UpdateState(apps::InstanceState::kActive,
base::Time::FromDoubleT(2000.0));
expect_state_ = apps::InstanceState::kActive;
expect_last_updated_time_ = base::Time::FromDoubleT(2000.0);
expect_state_changed_ = true;
expect_last_updated_time_changed_ = true;
CheckExpects(u);
}
if (state) {
apps::InstanceUpdate::Merge(state, delta);
ExpectNoChange();
CheckExpects(u);
}
}
std::string expect_launch_id_;
bool expect_launch_id_changed_;
apps::InstanceState expect_state_;
bool expect_state_changed_;
base::Time expect_last_updated_time_;
bool expect_last_updated_time_changed_;
};
TEST_F(InstanceUpdateTest, StateIsNonNull) {
aura::Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
std::unique_ptr<apps::Instance> state =
std::make_unique<apps::Instance>(app_id, &window);
TestInstanceUpdate(state.get(), nullptr);
}
TEST_F(InstanceUpdateTest, DeltaIsNonNull) {
aura::Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
std::unique_ptr<apps::Instance> delta =
std::make_unique<apps::Instance>(app_id, &window);
TestInstanceUpdate(nullptr, delta.get());
}
TEST_F(InstanceUpdateTest, BothAreNonNull) {
aura::Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
std::unique_ptr<apps::Instance> state =
std::make_unique<apps::Instance>(app_id, &window);
std::unique_ptr<apps::Instance> delta =
std::make_unique<apps::Instance>(app_id, &window);
TestInstanceUpdate(state.get(), delta.get());
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment