Commit 3288ac49 authored by Aga Wronska's avatar Aga Wronska Committed by Commit Bot

Adds app activity registry for Per-App Time Limits

AppActivityRegistry class is responsible for:
* collecting information about app state and usage
* combining app usage information with restrictions from policy
* storing the data between the sessions

This changelist creates the skeleton of the class and will be
followed with remaining functionality implementation.

Bug: 1015658
Change-Id: I306830f756c974371551493379f6edb510f6e5c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1954448Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Aga Wronska <agawronska@chromium.org>
Cr-Commit-Position: refs/heads/master@{#722629}
parent a9a3c32e
...@@ -789,6 +789,8 @@ source_set("chromeos") { ...@@ -789,6 +789,8 @@ source_set("chromeos") {
"child_accounts/time_limit_notifier.h", "child_accounts/time_limit_notifier.h",
"child_accounts/time_limit_override.cc", "child_accounts/time_limit_override.cc",
"child_accounts/time_limit_override.h", "child_accounts/time_limit_override.h",
"child_accounts/time_limits/app_activity_registry.cc",
"child_accounts/time_limits/app_activity_registry.h",
"child_accounts/time_limits/app_service_wrapper.cc", "child_accounts/time_limits/app_service_wrapper.cc",
"child_accounts/time_limits/app_service_wrapper.h", "child_accounts/time_limits/app_service_wrapper.h",
"child_accounts/time_limits/app_time_controller.cc", "child_accounts/time_limits/app_time_controller.cc",
......
...@@ -7,8 +7,10 @@ ...@@ -7,8 +7,10 @@
#include "base/time/time.h" #include "base/time/time.h"
#include "chrome/browser/chromeos/child_accounts/time_limits/app_time_controller.h" #include "chrome/browser/chromeos/child_accounts/time_limits/app_time_controller.h"
#include "chrome/browser/chromeos/child_accounts/time_limits/web_time_limit_enforcer.h" #include "chrome/browser/chromeos/child_accounts/time_limits/web_time_limit_enforcer.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace chromeos { namespace chromeos {
ChildUserService::TestApi::TestApi(ChildUserService* service) ChildUserService::TestApi::TestApi(ChildUserService* service)
...@@ -27,8 +29,11 @@ app_time::WebTimeLimitEnforcer* ChildUserService::TestApi::web_time_enforcer() { ...@@ -27,8 +29,11 @@ app_time::WebTimeLimitEnforcer* ChildUserService::TestApi::web_time_enforcer() {
} }
ChildUserService::ChildUserService(content::BrowserContext* context) { ChildUserService::ChildUserService(content::BrowserContext* context) {
if (app_time::AppTimeController::ArePerAppTimeLimitsEnabled()) DCHECK(context);
app_time_controller_ = std::make_unique<app_time::AppTimeController>(); if (app_time::AppTimeController::ArePerAppTimeLimitsEnabled()) {
app_time_controller_ = std::make_unique<app_time::AppTimeController>(
Profile::FromBrowserContext(context));
}
} }
ChildUserService::~ChildUserService() = default; ChildUserService::~ChildUserService() = default;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "chrome/browser/chromeos/child_accounts/child_user_service_factory.h" #include "chrome/browser/chromeos/child_accounts/child_user_service_factory.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/chromeos/child_accounts/child_user_service.h" #include "chrome/browser/chromeos/child_accounts/child_user_service.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/keyed_service/content/browser_context_dependency_manager.h"
...@@ -25,7 +26,9 @@ ChildUserServiceFactory* ChildUserServiceFactory::GetInstance() { ...@@ -25,7 +26,9 @@ ChildUserServiceFactory* ChildUserServiceFactory::GetInstance() {
ChildUserServiceFactory::ChildUserServiceFactory() ChildUserServiceFactory::ChildUserServiceFactory()
: BrowserContextKeyedServiceFactory( : BrowserContextKeyedServiceFactory(
"ChildUserServiceFactory", "ChildUserServiceFactory",
BrowserContextDependencyManager::GetInstance()) {} BrowserContextDependencyManager::GetInstance()) {
DependsOn(apps::AppServiceProxyFactory::GetInstance());
}
ChildUserServiceFactory::~ChildUserServiceFactory() = default; ChildUserServiceFactory::~ChildUserServiceFactory() = default;
......
// 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/browser/chromeos/child_accounts/time_limits/app_activity_registry.h"
#include "base/stl_util.h"
namespace chromeos {
namespace app_time {
AppActivityRegistry::AppDetails::AppDetails() = default;
AppActivityRegistry::AppDetails::AppDetails(const AppActivity& activity)
: activity(activity) {}
AppActivityRegistry::AppDetails::AppDetails(const AppDetails&) = default;
AppActivityRegistry::AppDetails& AppActivityRegistry::AppDetails::operator=(
const AppDetails&) = default;
AppActivityRegistry::AppDetails::~AppDetails() = default;
AppActivityRegistry::AppActivityRegistry(AppServiceWrapper* app_service_wrapper)
: app_service_wrapper_(app_service_wrapper) {
DCHECK(app_service_wrapper_);
app_service_wrapper_->AddObserver(this);
}
AppActivityRegistry::~AppActivityRegistry() {
app_service_wrapper_->RemoveObserver(this);
}
void AppActivityRegistry::OnAppInstalled(const AppId& app_id) {
// App might be already present in registry, because we preserve info between
// sessions and app service does not. Make sure not to override cached state.
if (base::Contains(activity_registry_, app_id))
Add(app_id);
}
void AppActivityRegistry::OnAppUninstalled(const AppId& app_id) {
// TODO(agawronska): Consider DCHECK instead of it. Not sure if there are
// legit cases when we might go out of sync with AppService.
if (base::Contains(activity_registry_, app_id))
SetAppState(app_id, AppState::kUninstalled);
}
void AppActivityRegistry::OnAppAvailable(const AppId& app_id) {
if (base::Contains(activity_registry_, app_id))
SetAppState(app_id, AppState::kAvailable);
}
void AppActivityRegistry::OnAppBlocked(const AppId& app_id) {
if (base::Contains(activity_registry_, app_id))
SetAppState(app_id, AppState::kBlocked);
}
void AppActivityRegistry::Add(const AppId& app_id) {
auto result = activity_registry_.emplace(
app_id, AppDetails(AppActivity(AppState::kAvailable)));
DCHECK(result.second);
}
AppState AppActivityRegistry::GetAppState(const AppId& app_id) const {
DCHECK(base::Contains(activity_registry_, app_id));
return activity_registry_.at(app_id).activity.app_state();
}
void AppActivityRegistry::SetAppState(const AppId& app_id, AppState app_state) {
DCHECK(base::Contains(activity_registry_, app_id));
activity_registry_.at(app_id).activity.SetAppState(app_state);
}
void AppActivityRegistry::CleanRegistry() {
for (auto it = activity_registry_.cbegin();
it != activity_registry_.cend();) {
if (GetAppState(it->first) == AppState::kUninstalled) {
it = activity_registry_.erase(it);
} else {
++it;
}
}
}
} // namespace app_time
} // namespace chromeos
// 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_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMITS_APP_ACTIVITY_REGISTRY_H_
#define CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMITS_APP_ACTIVITY_REGISTRY_H_
#include <map>
#include "chrome/browser/chromeos/child_accounts/time_limits/app_service_wrapper.h"
#include "chrome/browser/chromeos/child_accounts/time_limits/app_types.h"
namespace chromeos {
namespace app_time {
// Keeps track of app activity and time limits information.
// Stores app activity between user session. Information about uninstalled apps
// are removed from the registry after activity was uploaded to server or after
// 30 days if upload did not happen.
class AppActivityRegistry : public AppServiceWrapper::EventListener {
public:
explicit AppActivityRegistry(AppServiceWrapper* app_service_wrapper);
AppActivityRegistry(const AppActivityRegistry&) = delete;
AppActivityRegistry& operator=(const AppActivityRegistry&) = delete;
~AppActivityRegistry() override;
// AppServiceWrapper::EventListener:
void OnAppInstalled(const AppId& app_id) override;
void OnAppUninstalled(const AppId& app_id) override;
void OnAppAvailable(const AppId& app_id) override;
void OnAppBlocked(const AppId& app_id) override;
private:
// Bundles detailed data stored for a specific app.
struct AppDetails {
AppDetails();
explicit AppDetails(const AppActivity& activity);
AppDetails(const AppDetails&);
AppDetails& operator=(const AppDetails&);
~AppDetails();
// Contains information about current app state and logged activity.
AppActivity activity{AppState::kAvailable};
// Contains information about restriction set for the app.
base::Optional<AppLimit> limit;
};
// Adds an ap to the registry if it does not exist.
void Add(const AppId& app_id);
// Convenience methods to access state of the app identified by |app_id|.
// Should only be called if app exists in the registry.
AppState GetAppState(const AppId& app_id) const;
void SetAppState(const AppId& app_id, AppState app_state);
// Removes uninstalled apps from the registry. Should be called after the
// recent data was successfully uploaded to server.
void CleanRegistry();
// Owned by AppTimeController.
AppServiceWrapper* const app_service_wrapper_;
std::map<AppId, AppDetails> activity_registry_;
};
} // namespace app_time
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMITS_APP_ACTIVITY_REGISTRY_H_
...@@ -75,12 +75,25 @@ void AppServiceWrapper::OnAppUpdate(const apps::AppUpdate& update) { ...@@ -75,12 +75,25 @@ void AppServiceWrapper::OnAppUpdate(const apps::AppUpdate& update) {
switch (update.Readiness()) { switch (update.Readiness()) {
case apps::mojom::Readiness::kReady: case apps::mojom::Readiness::kReady:
for (auto& listener : listeners_) for (auto& listener : listeners_)
listener.OnAppInstalled(app_id); if (update.StateIsNull()) {
// It is the first update about this app.
// Note that AppService does not store info between sessions and this
// will be called at the beginning of every session.
listener.OnAppInstalled(app_id);
} else {
listener.OnAppAvailable(app_id);
}
break; break;
case apps::mojom::Readiness::kUninstalledByUser: case apps::mojom::Readiness::kUninstalledByUser:
for (auto& listener : listeners_) for (auto& listener : listeners_)
listener.OnAppUninstalled(app_id); listener.OnAppUninstalled(app_id);
break; break;
case apps::mojom::Readiness::kDisabledByUser:
case apps::mojom::Readiness::kDisabledByPolicy:
case apps::mojom::Readiness::kDisabledByBlacklist:
for (auto& listener : listeners_)
listener.OnAppBlocked(app_id);
break;
default: default:
break; break;
} }
......
...@@ -23,16 +23,19 @@ namespace app_time { ...@@ -23,16 +23,19 @@ namespace app_time {
class AppId; class AppId;
// Wrapper around AppService. // Wrapper around AppService.
// Provides abstraction layer for Per-App Time Limits. Takes care of types // Provides abstraction layer for Per-App Time Limits (PATL). Takes care of
// conversions and data filetering, so those operations are not spread around // types conversions and data filetering, so those operations are not spread
// the per-app time limits code. // around the PATL code.
class AppServiceWrapper : public apps::AppRegistryCache::Observer { class AppServiceWrapper : public apps::AppRegistryCache::Observer {
public: public:
// Notifies listeners about app state changes. // Notifies listeners about app state changes.
// Listener only get updates about apps that are relevant for PATL feature.
class EventListener : public base::CheckedObserver { class EventListener : public base::CheckedObserver {
public: public:
virtual void OnAppInstalled(const AppId& app_id) {} virtual void OnAppInstalled(const AppId& app_id) {}
virtual void OnAppUninstalled(const AppId& app_id) {} virtual void OnAppUninstalled(const AppId& app_id) {}
virtual void OnAppAvailable(const AppId& app_id) {}
virtual void OnAppBlocked(const AppId& app_id) {}
}; };
explicit AppServiceWrapper(Profile* profile); explicit AppServiceWrapper(Profile* profile);
......
...@@ -71,6 +71,8 @@ class AppServiceWrapperTest : public testing::Test { ...@@ -71,6 +71,8 @@ class AppServiceWrapperTest : public testing::Test {
MOCK_METHOD1(OnAppInstalled, void(const AppId& app_id)); MOCK_METHOD1(OnAppInstalled, void(const AppId& app_id));
MOCK_METHOD1(OnAppUninstalled, void(const AppId& app_id)); MOCK_METHOD1(OnAppUninstalled, void(const AppId& app_id));
MOCK_METHOD1(OnAppAvailable, void(const AppId& app_id));
MOCK_METHOD1(OnAppBlocked, void(const AppId& app_id));
}; };
protected: protected:
...@@ -119,6 +121,17 @@ class AppServiceWrapperTest : public testing::Test { ...@@ -119,6 +121,17 @@ class AppServiceWrapperTest : public testing::Test {
task_environment_.RunUntilIdle(); task_environment_.RunUntilIdle();
} }
void SimulateAppDisabled(const AppId& app_id,
const std::string& app_name,
bool disabled) {
const std::string& package_name = app_id.app_id();
arc::mojom::AppInfo app = CreateArcAppInfo(package_name, app_name);
app.suspended = disabled;
arc_test_.app_instance()->SendPackageAppListRefreshed(package_name, {app});
task_environment_.RunUntilIdle();
}
private: private:
content::BrowserTaskEnvironment task_environment_; content::BrowserTaskEnvironment task_environment_;
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
...@@ -181,5 +194,20 @@ TEST_F(AppServiceWrapperTest, ArcAppInstallation) { ...@@ -181,5 +194,20 @@ TEST_F(AppServiceWrapperTest, ArcAppInstallation) {
EXPECT_EQ(app2, installed_apps[0]); EXPECT_EQ(app2, installed_apps[0]);
} }
TEST_F(AppServiceWrapperTest, ArcAppDisabled) {
// Install ARC app.
const AppId app(apps::mojom::AppType::kArc, kArcPackage1);
EXPECT_CALL(test_listener(), OnAppInstalled(app)).Times(1);
SimulateAppInstalled(app, kArcApp1);
// Make app disabled.
EXPECT_CALL(test_listener(), OnAppBlocked(app)).Times(1);
SimulateAppDisabled(app, kArcApp1, true);
// Re-enable app.
EXPECT_CALL(test_listener(), OnAppAvailable(app)).Times(1);
SimulateAppDisabled(app, kArcApp1, false);
}
} // namespace app_time } // namespace app_time
} // namespace chromeos } // namespace chromeos
...@@ -5,7 +5,10 @@ ...@@ -5,7 +5,10 @@
#include "chrome/browser/chromeos/child_accounts/time_limits/app_time_controller.h" #include "chrome/browser/chromeos/child_accounts/time_limits/app_time_controller.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "chrome/browser/chromeos/child_accounts/time_limits/app_activity_registry.h"
#include "chrome/browser/chromeos/child_accounts/time_limits/app_service_wrapper.h"
#include "chrome/browser/chromeos/child_accounts/time_limits/web_time_limit_enforcer.h" #include "chrome/browser/chromeos/child_accounts/time_limits/web_time_limit_enforcer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
namespace chromeos { namespace chromeos {
...@@ -16,7 +19,12 @@ bool AppTimeController::ArePerAppTimeLimitsEnabled() { ...@@ -16,7 +19,12 @@ bool AppTimeController::ArePerAppTimeLimitsEnabled() {
return base::FeatureList::IsEnabled(features::kPerAppTimeLimits); return base::FeatureList::IsEnabled(features::kPerAppTimeLimits);
} }
AppTimeController::AppTimeController() { AppTimeController::AppTimeController(Profile* profile)
: app_service_wrapper_(std::make_unique<AppServiceWrapper>(profile)),
app_registry_(
std::make_unique<AppActivityRegistry>(app_service_wrapper_.get())) {
DCHECK(profile);
if (WebTimeLimitEnforcer::IsEnabled()) if (WebTimeLimitEnforcer::IsEnabled())
web_time_enforcer_ = std::make_unique<WebTimeLimitEnforcer>(); web_time_enforcer_ = std::make_unique<WebTimeLimitEnforcer>();
} }
......
...@@ -7,17 +7,20 @@ ...@@ -7,17 +7,20 @@
#include <memory> #include <memory>
class Profile;
namespace chromeos { namespace chromeos {
namespace app_time { namespace app_time {
class AppActivityRegistry;
class AppServiceWrapper;
class WebTimeLimitEnforcer; class WebTimeLimitEnforcer;
// Coordinates per-app time limit for child user. // Coordinates per-app time limit for child user.
class AppTimeController { class AppTimeController {
public: public:
static bool ArePerAppTimeLimitsEnabled(); static bool ArePerAppTimeLimitsEnabled();
explicit AppTimeController(Profile* profile);
AppTimeController();
AppTimeController(const AppTimeController&) = delete; AppTimeController(const AppTimeController&) = delete;
AppTimeController& operator=(const AppTimeController&) = delete; AppTimeController& operator=(const AppTimeController&) = delete;
~AppTimeController(); ~AppTimeController();
...@@ -29,6 +32,8 @@ class AppTimeController { ...@@ -29,6 +32,8 @@ class AppTimeController {
WebTimeLimitEnforcer* web_time_enforcer() { return web_time_enforcer_.get(); } WebTimeLimitEnforcer* web_time_enforcer() { return web_time_enforcer_.get(); }
private: private:
std::unique_ptr<AppServiceWrapper> app_service_wrapper_;
std::unique_ptr<AppActivityRegistry> app_registry_;
std::unique_ptr<WebTimeLimitEnforcer> web_time_enforcer_; std::unique_ptr<WebTimeLimitEnforcer> web_time_enforcer_;
}; };
......
...@@ -8,6 +8,30 @@ namespace chromeos { ...@@ -8,6 +8,30 @@ namespace chromeos {
namespace app_time { namespace app_time {
namespace {
std::string AppTypeToString(apps::mojom::AppType app_type) {
switch (app_type) {
case apps::mojom::AppType::kUnknown:
return "Unknown";
case apps::mojom::AppType::kArc:
return "Arc";
case apps::mojom::AppType::kWeb:
return "Web";
case apps::mojom::AppType::kExtension:
return "Extension";
case apps::mojom::AppType::kBuiltIn:
return "Built in";
case apps::mojom::AppType::kCrostini:
return "Crostini";
case apps::mojom::AppType::kMacNative:
return "Mac native";
}
NOTREACHED();
}
} // namespace
AppId::AppId(apps::mojom::AppType app_type, const std::string& app_id) AppId::AppId(apps::mojom::AppType app_type, const std::string& app_id)
: app_type_(app_type), app_id_(app_id) { : app_type_(app_type), app_id_(app_id) {
DCHECK(!app_id.empty()); DCHECK(!app_id.empty());
...@@ -31,6 +55,15 @@ bool AppId::operator!=(const AppId& rhs) const { ...@@ -31,6 +55,15 @@ bool AppId::operator!=(const AppId& rhs) const {
return !(*this == rhs); return !(*this == rhs);
} }
bool AppId::operator<(const AppId& rhs) const {
return app_id_ < rhs.app_id();
}
std::ostream& operator<<(std::ostream& out, const AppId& id) {
return out << " [" << AppTypeToString(id.app_type()) << " : " << id.app_id()
<< "]";
}
AppLimit::AppLimit(AppRestriction restriction, AppLimit::AppLimit(AppRestriction restriction,
base::Optional<base::TimeDelta> daily_limit, base::Optional<base::TimeDelta> daily_limit,
base::Time last_updated) base::Time last_updated)
......
...@@ -36,7 +36,7 @@ enum class AppState { ...@@ -36,7 +36,7 @@ enum class AppState {
kLimitReached, kLimitReached,
// App is uninstalled. Activity might still be preserved and reported for // App is uninstalled. Activity might still be preserved and reported for
// recently uninstalled apps. // recently uninstalled apps.
kUninstall, kUninstalled,
}; };
// Identifies an app for app time limits. // Identifies an app for app time limits.
...@@ -57,6 +57,8 @@ class AppId { ...@@ -57,6 +57,8 @@ class AppId {
bool operator==(const AppId&) const; bool operator==(const AppId&) const;
bool operator!=(const AppId&) const; bool operator!=(const AppId&) const;
bool operator<(const AppId&) const;
friend std::ostream& operator<<(std::ostream&, const AppId&);
private: private:
apps::mojom::AppType app_type_ = apps::mojom::AppType::kUnknown; apps::mojom::AppType app_type_ = apps::mojom::AppType::kUnknown;
......
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