[go: up one dir, main page]

blob: 8b088f1fc06ac5fb8dd49704cf6ea3452476043d [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/chromeos/child_accounts/screen_time_controller.h"
#include <algorithm>
#include <string>
#include <utility>
#include "ash/public/cpp/login_screen.h"
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/timer/timer.h"
#include "chrome/browser/chromeos/child_accounts/consumer_status_reporting_service.h"
#include "chrome/browser/chromeos/child_accounts/consumer_status_reporting_service_factory.h"
#include "chrome/browser/chromeos/child_accounts/time_limit_override.h"
#include "chrome/browser/chromeos/login/lock/screen_locker.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/media_client.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chromeos/dbus/session_manager/session_manager_client.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/browser/browser_context.h"
namespace chromeos {
namespace {
constexpr base::TimeDelta kUsageTimeLimitWarningTime =
base::TimeDelta::FromMinutes(15);
// Dictionary keys for prefs::kScreenTimeLastState.
constexpr char kScreenStateLocked[] = "locked";
constexpr char kScreenStateCurrentPolicyType[] = "active_policy";
constexpr char kScreenStateTimeUsageLimitEnabled[] = "time_usage_limit_enabled";
constexpr char kScreenStateRemainingUsage[] = "remaining_usage";
constexpr char kScreenStateUsageLimitStarted[] = "usage_limit_started";
constexpr char kScreenStateNextStateChangeTime[] = "next_state_change_time";
constexpr char kScreenStateNextPolicyType[] = "next_active_policy";
constexpr char kScreenStateNextUnlockTime[] = "next_unlock_time";
ash::AuthDisabledReason ConvertLockReason(
usage_time_limit::PolicyType active_policy) {
switch (active_policy) {
case usage_time_limit::PolicyType::kFixedLimit:
return ash::AuthDisabledReason::kTimeWindowLimit;
case usage_time_limit::PolicyType::kUsageLimit:
return ash::AuthDisabledReason::kTimeUsageLimit;
case usage_time_limit::PolicyType::kOverride:
return ash::AuthDisabledReason::kTimeLimitOverride;
case usage_time_limit::PolicyType::kNoPolicy:
break;
}
NOTREACHED();
return ash::AuthDisabledReason();
}
} // namespace
// static
void ScreenTimeController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kScreenTimeLastState);
registry->RegisterDictionaryPref(prefs::kUsageTimeLimit);
registry->RegisterDictionaryPref(prefs::kTimeLimitLocalOverride);
}
ScreenTimeController::ScreenTimeController(content::BrowserContext* context)
: context_(context),
pref_service_(Profile::FromBrowserContext(context)->GetPrefs()),
clock_(base::DefaultClock::GetInstance()),
next_state_timer_(std::make_unique<base::OneShotTimer>()),
usage_time_limit_warning_timer_(std::make_unique<base::OneShotTimer>()),
last_policy_(pref_service_->GetDictionary(prefs::kUsageTimeLimit)
->CreateDeepCopy()),
time_limit_notifier_(context) {
session_manager::SessionManager::Get()->AddObserver(this);
if (base::FeatureList::IsEnabled(features::kUsageTimeStateNotifier))
UsageTimeStateNotifier::GetInstance()->AddObserver(this);
system::TimezoneSettings::GetInstance()->AddObserver(this);
chromeos::SystemClockClient::Get()->AddObserver(this);
pref_change_registrar_.Init(pref_service_);
pref_change_registrar_.Add(
prefs::kUsageTimeLimit,
base::BindRepeating(&ScreenTimeController::OnPolicyChanged,
base::Unretained(this)));
if (base::FeatureList::IsEnabled(features::kParentAccessCode))
parent_access::ParentAccessService::Get().AddObserver(this);
}
ScreenTimeController::~ScreenTimeController() {
if (base::FeatureList::IsEnabled(features::kParentAccessCode))
parent_access::ParentAccessService::Get().RemoveObserver(this);
session_manager::SessionManager::Get()->RemoveObserver(this);
if (base::FeatureList::IsEnabled(features::kUsageTimeStateNotifier))
UsageTimeStateNotifier::GetInstance()->RemoveObserver(this);
system::TimezoneSettings::GetInstance()->RemoveObserver(this);
SystemClockClient::Get()->RemoveObserver(this);
}
void ScreenTimeController::AddObserver(Observer* observer) {
DCHECK(observer);
observers_.AddObserver(observer);
}
void ScreenTimeController::RemoveObserver(Observer* observer) {
DCHECK(observer);
observers_.RemoveObserver(observer);
}
base::TimeDelta ScreenTimeController::GetScreenTimeDuration() {
return ConsumerStatusReportingServiceFactory::GetForBrowserContext(context_)
->GetChildScreenTime();
}
void ScreenTimeController::SetClocksForTesting(
const base::Clock* clock,
const base::TickClock* tick_clock,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
clock_ = clock;
next_state_timer_ = std::make_unique<base::OneShotTimer>(tick_clock);
next_state_timer_->SetTaskRunner(task_runner);
usage_time_limit_warning_timer_ =
std::make_unique<base::OneShotTimer>(tick_clock);
usage_time_limit_warning_timer_->SetTaskRunner(task_runner);
}
void ScreenTimeController::NotifyUsageTimeLimitWarningForTesting() {
for (Observer& observer : observers_)
observer.UsageTimeLimitWarning();
}
void ScreenTimeController::CheckTimeLimit(const std::string& source) {
VLOG(1) << "Checking time limits (source=" << source << ")";
// Stop all timers. They will be rescheduled below.
ResetStateTimers();
ResetInSessionTimers();
ResetWarningTimers();
base::Time now = clock_->Now();
const icu::TimeZone& time_zone =
system::TimezoneSettings::GetInstance()->GetTimezone();
base::Optional<usage_time_limit::State> last_state = GetLastStateFromPref();
const base::DictionaryValue* time_limit =
pref_service_->GetDictionary(prefs::kUsageTimeLimit);
const base::DictionaryValue* local_override =
pref_service_->GetDictionary(prefs::kTimeLimitLocalOverride);
// TODO(agawronska): Usage timestamp should be passed instead of second |now|.
usage_time_limit::State state = usage_time_limit::GetState(
time_limit->CreateDeepCopy(), local_override, GetScreenTimeDuration(),
now, now, &time_zone, last_state);
SaveCurrentStateToPref(state);
VLOG(1) << "Screen should be locked is set to " << state.is_locked;
if (state.is_locked) {
OnScreenLockByPolicy(state.active_policy, state.next_unlock_time);
DCHECK(!state.next_unlock_time.is_null());
if (!session_manager::SessionManager::Get()->IsScreenLocked()) {
// This status report are going to be done in EventBasedStatusReporting if
// this feature is enabled.
if (!base::FeatureList::IsEnabled(features::kEventBasedStatusReporting)) {
VLOG(1) << "Request status report before locking screen.";
ConsumerStatusReportingServiceFactory::GetForBrowserContext(context_)
->RequestImmediateStatusReport();
}
ForceScreenLockByPolicy();
}
} else {
OnScreenLockByPolicyEnd();
base::Optional<TimeLimitNotifier::LimitType> notification_type =
ConvertPolicyType(state.next_state_active_policy);
if (notification_type.has_value()) {
// Schedule notification based on the remaining screen time until lock.
// TODO(crbug.com/898000): Dismiss a shown notification when it no longer
// applies.
const base::TimeDelta remaining_time = state.next_state_change_time - now;
time_limit_notifier_.MaybeScheduleLockNotifications(
notification_type.value(), remaining_time);
}
if (base::FeatureList::IsEnabled(features::kUsageTimeStateNotifier))
ScheduleUsageTimeLimitWarning(state);
}
// Trigger policy update notifications.
auto updated_policy_types = usage_time_limit::UpdatedPolicyTypes(
last_policy_, time_limit->CreateDeepCopy());
for (const auto& policy_type : updated_policy_types) {
base::Optional<base::Time> lock_time;
if (policy_type == usage_time_limit::PolicyType::kOverride)
lock_time = state.next_state_change_time;
base::Optional<TimeLimitNotifier::LimitType> notification_type =
ConvertPolicyType(policy_type);
DCHECK(notification_type);
time_limit_notifier_.ShowPolicyUpdateNotification(notification_type.value(),
lock_time);
}
last_policy_ = time_limit->CreateDeepCopy();
// TODO(agawronska): We are creating UsageTimeLimitProcessor second time in
// this method. Could expected reset time be returned as a part of the state?
base::Time next_get_state_time = std::min(
state.next_state_change_time,
usage_time_limit::GetExpectedResetTime(time_limit->CreateDeepCopy(),
local_override, now, &time_zone));
if (!next_get_state_time.is_null()) {
VLOG(1) << "Scheduling state change timer in " << next_get_state_time - now;
next_state_timer_->Start(
FROM_HERE, next_get_state_time - now,
base::BindRepeating(&ScreenTimeController::CheckTimeLimit,
base::Unretained(this), "next_state_timer_"));
}
}
void ScreenTimeController::ForceScreenLockByPolicy() {
DCHECK(!session_manager::SessionManager::Get()->IsScreenLocked());
// Avoid abrupt session restart that looks like a crash and happens when lock
// screen is requested before sign in completion. It is safe, because time
// limits will be reevaluated when session state changes to active.
// TODO(agawronska): Remove the flag when it is confirmed that this does not
// cause a bug (https://crbug.com/924844).
if (base::FeatureList::IsEnabled(features::kDMServerOAuthForChildUser) &&
session_manager::SessionManager::Get()->session_state() !=
session_manager::SessionState::ACTIVE) {
return;
}
chromeos::SessionManagerClient::Get()->RequestLockScreen();
}
void ScreenTimeController::OnAccessCodeValidation(
bool result,
base::Optional<AccountId> account_id) {
AccountId current_user_id =
chromeos::ProfileHelper::Get()
->GetUserByProfile(Profile::FromBrowserContext(context_))
->GetAccountId();
if (!result || !account_id || account_id.value() != current_user_id)
return;
if (!session_manager::SessionManager::Get()->IsScreenLocked())
return;
usage_time_limit::TimeLimitOverride local_override(
usage_time_limit::TimeLimitOverride::Action::kUnlock, clock_->Now(),
base::nullopt);
// Replace previous local override stored in pref, because PAC can only be
// entered if previous override is not active anymore.
pref_service_->Set(prefs::kTimeLimitLocalOverride,
local_override.ToDictionary());
pref_service_->CommitPendingWrite();
CheckTimeLimit("OnAccessCodeValidation");
}
void ScreenTimeController::OnScreenLockByPolicy(
usage_time_limit::PolicyType active_policy,
base::Time next_unlock_time) {
if (!session_manager::SessionManager::Get()->IsScreenLocked())
return;
// Show lock message.
AccountId account_id =
chromeos::ProfileHelper::Get()
->GetUserByProfile(Profile::FromBrowserContext(context_))
->GetAccountId();
ScreenLocker::default_screen_locker()->DisableAuthForUser(
account_id,
ash::AuthDisabledData(ConvertLockReason(active_policy), next_unlock_time,
GetScreenTimeDuration()));
// Add parent access code button.
if (base::FeatureList::IsEnabled(features::kParentAccessCode))
ash::LoginScreen::Get()->ShowParentAccessButton(true);
// Prevent media from continuing to play after device is locked.
MediaClient::Get()->SuspendMediaSessions();
}
void ScreenTimeController::OnScreenLockByPolicyEnd() {
if (!session_manager::SessionManager::Get()->IsScreenLocked())
return;
AccountId account_id =
chromeos::ProfileHelper::Get()
->GetUserByProfile(Profile::FromBrowserContext(context_))
->GetAccountId();
ScreenLocker::default_screen_locker()->EnableAuthForUser(account_id);
if (base::FeatureList::IsEnabled(features::kParentAccessCode))
ash::LoginScreen::Get()->ShowParentAccessButton(false);
}
void ScreenTimeController::OnPolicyChanged() {
CheckTimeLimit("OnPolicyChanged");
}
void ScreenTimeController::ResetStateTimers() {
VLOG(1) << "Stopping state timers";
next_state_timer_->Stop();
}
void ScreenTimeController::ResetWarningTimers() {
VLOG(1) << "Stopping warning timers";
usage_time_limit_warning_timer_->Stop();
}
void ScreenTimeController::ResetInSessionTimers() {
VLOG(1) << "Stopping in-session timers";
time_limit_notifier_.UnscheduleNotifications();
}
void ScreenTimeController::ScheduleUsageTimeLimitWarning(
const usage_time_limit::State& state) {
if (state.next_state_active_policy ==
usage_time_limit::PolicyType::kUsageLimit &&
UsageTimeStateNotifier::GetInstance()->GetState() ==
UsageTimeStateNotifier::UsageTimeState::ACTIVE) {
base::Time now = clock_->Now();
base::TimeDelta time_until_next_state = state.next_state_change_time - now;
base::TimeDelta delay =
time_until_next_state < kUsageTimeLimitWarningTime
? base::TimeDelta()
: time_until_next_state - kUsageTimeLimitWarningTime;
VLOG(1) << "Scheduling usage time limit warning in " << delay.InMinutes()
<< " minutes.";
usage_time_limit_warning_timer_->Start(
FROM_HERE, delay,
base::BindRepeating(&ScreenTimeController::UsageTimeLimitWarning,
base::Unretained(this)));
}
}
void ScreenTimeController::SaveCurrentStateToPref(
const usage_time_limit::State& state) {
auto state_dict =
std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
state_dict->SetKey(kScreenStateLocked, base::Value(state.is_locked));
state_dict->SetKey(kScreenStateCurrentPolicyType,
base::Value(static_cast<int>(state.active_policy)));
state_dict->SetKey(kScreenStateTimeUsageLimitEnabled,
base::Value(state.is_time_usage_limit_enabled));
state_dict->SetKey(kScreenStateRemainingUsage,
base::Value(base::checked_cast<int>(
state.remaining_usage.InMilliseconds())));
state_dict->SetKey(kScreenStateUsageLimitStarted,
base::Value(state.time_usage_limit_started.ToDoubleT()));
state_dict->SetKey(kScreenStateNextStateChangeTime,
base::Value(state.next_state_change_time.ToDoubleT()));
state_dict->SetKey(
kScreenStateNextPolicyType,
base::Value(static_cast<int>(state.next_state_active_policy)));
state_dict->SetKey(kScreenStateNextUnlockTime,
base::Value(state.next_unlock_time.ToDoubleT()));
pref_service_->Set(prefs::kScreenTimeLastState, *state_dict);
pref_service_->CommitPendingWrite();
}
base::Optional<usage_time_limit::State>
ScreenTimeController::GetLastStateFromPref() {
const base::DictionaryValue* last_state =
pref_service_->GetDictionary(prefs::kScreenTimeLastState);
usage_time_limit::State result;
if (last_state->empty())
return base::nullopt;
// Verify is_locked from the pref is a boolean value.
const base::Value* is_locked = last_state->FindKey(kScreenStateLocked);
if (!is_locked || !is_locked->is_bool())
return base::nullopt;
result.is_locked = is_locked->GetBool();
// Verify active policy type is a value of usage_time_limit::PolicyType.
const base::Value* active_policy =
last_state->FindKey(kScreenStateCurrentPolicyType);
// TODO(crbug.com/823536): Add kCount in usage_time_limit::PolicyType
// instead of checking kUsageLimit here.
if (!active_policy || !active_policy->is_int() ||
active_policy->GetInt() < 0 ||
active_policy->GetInt() >
static_cast<int>(usage_time_limit::PolicyType::kUsageLimit)) {
return base::nullopt;
}
result.active_policy =
static_cast<usage_time_limit::PolicyType>(active_policy->GetInt());
// Verify time_usage_limit_enabled from the pref is a boolean value.
const base::Value* time_usage_limit_enabled =
last_state->FindKey(kScreenStateTimeUsageLimitEnabled);
if (!time_usage_limit_enabled || !time_usage_limit_enabled->is_bool())
return base::nullopt;
result.is_time_usage_limit_enabled = time_usage_limit_enabled->GetBool();
// Verify remaining_usage from the pref is a int value.
const base::Value* remaining_usage =
last_state->FindKey(kScreenStateRemainingUsage);
if (!remaining_usage || !remaining_usage->is_int())
return base::nullopt;
result.remaining_usage =
base::TimeDelta::FromMilliseconds(remaining_usage->GetInt());
// Verify time_usage_limit_started from the pref is a double value.
const base::Value* time_usage_limit_started =
last_state->FindKey(kScreenStateUsageLimitStarted);
if (!time_usage_limit_started || !time_usage_limit_started->is_double())
return base::nullopt;
result.time_usage_limit_started =
base::Time::FromDoubleT(time_usage_limit_started->GetDouble());
// Verify next_state_change_time from the pref is a double value.
const base::Value* next_state_change_time =
last_state->FindKey(kScreenStateNextStateChangeTime);
if (!next_state_change_time || !next_state_change_time->is_double())
return base::nullopt;
result.next_state_change_time =
base::Time::FromDoubleT(next_state_change_time->GetDouble());
// Verify next policy type is a value of usage_time_limit::PolicyType.
const base::Value* next_active_policy =
last_state->FindKey(kScreenStateNextPolicyType);
if (!next_active_policy || !next_active_policy->is_int() ||
next_active_policy->GetInt() < 0 ||
next_active_policy->GetInt() >
static_cast<int>(usage_time_limit::PolicyType::kUsageLimit)) {
return base::nullopt;
}
result.next_state_active_policy =
static_cast<usage_time_limit::PolicyType>(next_active_policy->GetInt());
// Verify next_unlock_time from the pref is a double value.
const base::Value* next_unlock_time =
last_state->FindKey(kScreenStateNextUnlockTime);
if (!next_unlock_time || !next_unlock_time->is_double())
return base::nullopt;
result.next_unlock_time =
base::Time::FromDoubleT(next_unlock_time->GetDouble());
return result;
}
void ScreenTimeController::UsageTimeLimitWarning() {
base::Time now = clock_->Now();
const icu::TimeZone& time_zone =
system::TimezoneSettings::GetInstance()->GetTimezone();
const base::DictionaryValue* time_limit =
pref_service_->GetDictionary(prefs::kUsageTimeLimit);
const base::DictionaryValue* local_override =
pref_service_->GetDictionary(prefs::kTimeLimitLocalOverride);
base::Optional<base::TimeDelta> remaining_usage =
usage_time_limit::GetRemainingTimeUsage(
time_limit->CreateDeepCopy(), local_override, now,
GetScreenTimeDuration(), &time_zone);
// Remaining time usage can be bigger than |kUsageTimeLimitWarningTime|
// because it is counted in another class so the timers might be called with
// some delay. We must ensure that the observers will be called when the
// usage time is less than |kUsageTimeLimitWarningTime|.
if (remaining_usage && remaining_usage < kUsageTimeLimitWarningTime) {
for (Observer& observer : observers_)
observer.UsageTimeLimitWarning();
} else {
CheckTimeLimit("UsageTimeLimitWarning");
}
}
base::Optional<TimeLimitNotifier::LimitType>
ScreenTimeController::ConvertPolicyType(
usage_time_limit::PolicyType policy_type) {
switch (policy_type) {
case usage_time_limit::PolicyType::kFixedLimit:
return base::make_optional(TimeLimitNotifier::LimitType::kBedTime);
break;
case usage_time_limit::PolicyType::kUsageLimit:
return base::make_optional(TimeLimitNotifier::LimitType::kScreenTime);
break;
case usage_time_limit::PolicyType::kOverride:
return base::make_optional(TimeLimitNotifier::LimitType::kOverride);
break;
case usage_time_limit::PolicyType::kNoPolicy:
return base::nullopt;
}
}
void ScreenTimeController::OnSessionStateChanged() {
session_manager::SessionState session_state =
session_manager::SessionManager::Get()->session_state();
if (base::FeatureList::IsEnabled(features::kUsageTimeStateNotifier)) {
base::Optional<usage_time_limit::State> last_state = GetLastStateFromPref();
if (session_state == session_manager::SessionState::LOCKED && last_state &&
last_state->is_locked) {
OnScreenLockByPolicy(last_state->active_policy,
last_state->next_unlock_time);
}
return;
}
if (session_state == session_manager::SessionState::LOCKED) {
base::Optional<usage_time_limit::State> last_state = GetLastStateFromPref();
if (last_state && last_state->is_locked) {
OnScreenLockByPolicy(last_state->active_policy,
last_state->next_unlock_time);
}
ResetInSessionTimers();
} else if (session_state == session_manager::SessionState::ACTIVE) {
CheckTimeLimit("OnSessionStateChanged");
}
}
void ScreenTimeController::OnUsageTimeStateChange(
const UsageTimeStateNotifier::UsageTimeState state) {
if (state == UsageTimeStateNotifier::UsageTimeState::INACTIVE) {
ResetWarningTimers();
ResetInSessionTimers();
} else {
CheckTimeLimit("OnUsageTimeStateChange");
}
}
void ScreenTimeController::TimezoneChanged(const icu::TimeZone& timezone) {
CheckTimeLimit("TimezoneChanged");
}
void ScreenTimeController::SystemClockUpdated() {
CheckTimeLimit("SystemClockUpdated");
}
} // namespace chromeos