blob: dcf0a2c72b543a8d9305d3987494c4bd570901d4 [file] [log] [blame]
// Copyright (c) 2017 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/ash/policy/handlers/minimum_version_policy_handler.h"
#include <algorithm>
#include <string>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/system_tray.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/app_mode/app_launch_utils.h"
#include "chrome/browser/ash/notifications/update_required_notification.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_chromeos.h"
#include "chrome/browser/ash/policy/handlers/minimum_version_policy_handler_delegate_impl.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/ui/ash/system_tray_client_impl.h"
#include "chrome/browser/upgrade_detector/build_state.h"
#include "chrome/browser/upgrade_detector/upgrade_detector.h"
#include "chrome/common/pref_names.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/network/network_handler.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/settings/cros_settings_names.h"
#include "chromeos/settings/cros_settings_provider.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/chromeos/devicetype_utils.h"
using MinimumVersionRequirement =
policy::MinimumVersionPolicyHandler::MinimumVersionRequirement;
namespace policy {
namespace {
const int kOneWeekEolNotificationInDays = 7;
PrefService* local_state() {
return g_browser_process->local_state();
}
MinimumVersionPolicyHandler::NetworkStatus GetCurrentNetworkStatus() {
chromeos::NetworkStateHandler* network_state_handler =
chromeos::NetworkHandler::Get()->network_state_handler();
const chromeos::NetworkState* current_network =
network_state_handler->DefaultNetwork();
if (!current_network || !current_network->IsConnectedState())
return MinimumVersionPolicyHandler::NetworkStatus::kOffline;
if (network_state_handler->default_network_is_metered())
return MinimumVersionPolicyHandler::NetworkStatus::kMetered;
return MinimumVersionPolicyHandler::NetworkStatus::kAllowed;
}
void OpenNetworkSettings() {
ash::SystemTray::Get()->ShowNetworkDetailedViewBubble();
}
void OpenEnterpriseInfoPage() {
SystemTrayClientImpl::Get()->ShowEnterpriseInfo();
}
std::string GetEnterpriseManager() {
return g_browser_process->platform_part()
->browser_policy_connector_chromeos()
->GetEnterpriseDomainManager();
}
BuildState* GetBuildState() {
return g_browser_process->GetBuildState();
}
int GetDaysRounded(base::TimeDelta time) {
return base::ClampRound(time / base::TimeDelta::FromDays(1));
}
chromeos::UpdateEngineClient* GetUpdateEngineClient() {
return chromeos::DBusThreadManager::Get()->GetUpdateEngineClient();
}
// Overrides the relaunch notification style to required and configures the
// relaunch deadline according to the deadline.
void OverrideRelaunchNotification(base::Time deadline) {
UpgradeDetector* upgrade_detector = UpgradeDetector::GetInstance();
upgrade_detector->OverrideRelaunchNotificationToRequired(true);
upgrade_detector->OverrideHighAnnoyanceDeadline(deadline);
}
// Resets the overridden relaunch notification style and deadline.
void ResetRelaunchNotification() {
UpgradeDetector* upgrade_detector = UpgradeDetector::GetInstance();
upgrade_detector->ResetOverriddenDeadline();
upgrade_detector->OverrideRelaunchNotificationToRequired(false);
}
} // namespace
const char MinimumVersionPolicyHandler::kRequirements[] = "requirements";
const char MinimumVersionPolicyHandler::kChromeOsVersion[] = "chromeos_version";
const char MinimumVersionPolicyHandler::kWarningPeriod[] = "warning_period";
const char MinimumVersionPolicyHandler::kEolWarningPeriod[] =
"aue_warning_period";
const char MinimumVersionPolicyHandler::kUnmanagedUserRestricted[] =
"unmanaged_user_restricted";
MinimumVersionRequirement::MinimumVersionRequirement(
const base::Version version,
const base::TimeDelta warning,
const base::TimeDelta eol_warning)
: minimum_version_(version),
warning_time_(warning),
eol_warning_time_(eol_warning) {}
std::unique_ptr<MinimumVersionRequirement>
MinimumVersionRequirement::CreateInstanceIfValid(
const base::DictionaryValue* dict) {
const std::string* version = dict->FindStringKey(kChromeOsVersion);
if (!version)
return nullptr;
base::Version minimum_version(*version);
if (!minimum_version.IsValid())
return nullptr;
auto warning = dict->FindIntKey(kWarningPeriod);
base::TimeDelta warning_time =
base::TimeDelta::FromDays(warning.has_value() ? warning.value() : 0);
auto eol_warning = dict->FindIntKey(kEolWarningPeriod);
base::TimeDelta eol_warning_time = base::TimeDelta::FromDays(
eol_warning.has_value() ? eol_warning.value() : 0);
return std::make_unique<MinimumVersionRequirement>(
minimum_version, warning_time, eol_warning_time);
}
int MinimumVersionRequirement::Compare(
const MinimumVersionRequirement* other) const {
const int version_compare = version().CompareTo(other->version());
if (version_compare != 0)
return version_compare;
if (warning() != other->warning())
return (warning() > other->warning() ? 1 : -1);
if (eol_warning() != other->eol_warning())
return (eol_warning() > other->eol_warning() ? 1 : -1);
return 0;
}
MinimumVersionPolicyHandler::MinimumVersionPolicyHandler(
Delegate* delegate,
ash::CrosSettings* cros_settings)
: delegate_(delegate),
cros_settings_(cros_settings),
clock_(base::DefaultClock::GetInstance()) {
policy_subscription_ = cros_settings_->AddSettingsObserver(
chromeos::kDeviceMinimumVersion,
base::BindRepeating(&MinimumVersionPolicyHandler::OnPolicyChanged,
weak_factory_.GetWeakPtr()));
// Fire it once so we're sure we get an invocation on startup.
OnPolicyChanged();
}
MinimumVersionPolicyHandler::~MinimumVersionPolicyHandler() {
GetBuildState()->RemoveObserver(this);
StopObservingNetwork();
GetUpdateEngineClient()->RemoveObserver(this);
}
void MinimumVersionPolicyHandler::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void MinimumVersionPolicyHandler::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool MinimumVersionPolicyHandler::CurrentVersionSatisfies(
const MinimumVersionRequirement& requirement) const {
base::Version platform_version(delegate_->GetCurrentVersion());
if (platform_version.IsValid())
return delegate_->GetCurrentVersion().CompareTo(requirement.version()) >= 0;
return true;
}
bool MinimumVersionPolicyHandler::IsPolicyRestrictionAppliedForUser() const {
return delegate_->IsUserEnterpriseManaged() || unmanaged_user_restricted_;
}
// static
void MinimumVersionPolicyHandler::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterTimePref(prefs::kUpdateRequiredTimerStartTime,
base::Time());
registry->RegisterTimeDeltaPref(prefs::kUpdateRequiredWarningPeriod,
base::TimeDelta());
}
bool MinimumVersionPolicyHandler::ShouldShowUpdateRequiredEolBanner() const {
return !RequirementsAreSatisfied() && IsPolicyRestrictionAppliedForUser() &&
eol_reached_;
}
bool MinimumVersionPolicyHandler::IsDeadlineTimerRunningForTesting() const {
return update_required_deadline_timer_.IsRunning();
}
bool MinimumVersionPolicyHandler::IsPolicyApplicable() {
bool device_managed = delegate_->IsDeviceEnterpriseManaged();
bool is_kiosk = delegate_->IsKioskMode();
return device_managed && !is_kiosk;
}
void MinimumVersionPolicyHandler::OnPolicyChanged() {
chromeos::CrosSettingsProvider::TrustedStatus status =
cros_settings_->PrepareTrustedValues(
base::BindOnce(&MinimumVersionPolicyHandler::OnPolicyChanged,
weak_factory_.GetWeakPtr()));
if (status != chromeos::CrosSettingsProvider::TRUSTED ||
!IsPolicyApplicable() ||
!chromeos::features::IsMinimumChromeVersionEnabled()) {
VLOG(1) << "Ignore policy change - policy is not applicable or settings "
"are not trusted.";
return;
}
const base::DictionaryValue* policy_value;
if (!cros_settings_->GetDictionary(chromeos::kDeviceMinimumVersion,
&policy_value)) {
VLOG(1) << "Revoke policy - policy is unset or value is incorrect.";
HandleUpdateNotRequired();
return;
}
const base::Value* entries = policy_value->FindListKey(kRequirements);
if (!entries || entries->GetList().empty()) {
VLOG(1) << "Revoke policy - empty policy requirements.";
HandleUpdateNotRequired();
return;
}
auto restricted = policy_value->FindBoolKey(kUnmanagedUserRestricted);
unmanaged_user_restricted_ = restricted.value_or(false);
std::vector<std::unique_ptr<MinimumVersionRequirement>> configs;
for (const auto& item : entries->GetList()) {
const base::DictionaryValue* dict;
if (item.GetAsDictionary(&dict)) {
std::unique_ptr<MinimumVersionRequirement> instance =
MinimumVersionRequirement::CreateInstanceIfValid(dict);
if (instance)
configs.push_back(std::move(instance));
}
}
// Select the strongest config whose requirements are not satisfied by the
// current version. The strongest config is chosen as the one whose minimum
// required version is greater and closest to the current version. In case of
// conflict. preference is given to the one with lesser warning time or eol
// warning time.
int strongest_config_idx = -1;
std::vector<std::unique_ptr<MinimumVersionRequirement>>
update_required_configs;
for (unsigned int i = 0; i < configs.size(); i++) {
MinimumVersionRequirement* item = configs[i].get();
if (!CurrentVersionSatisfies(*item) &&
(strongest_config_idx == -1 ||
item->Compare(configs[strongest_config_idx].get()) < 0))
strongest_config_idx = i;
}
if (strongest_config_idx != -1) {
// Update is required if at least one config exists whose requirements are
// not satisfied by the current version.
std::unique_ptr<MinimumVersionRequirement> strongest_config =
std::move(configs[strongest_config_idx]);
if (!state_ || state_->Compare(strongest_config.get()) != 0) {
state_ = std::move(strongest_config);
FetchEolInfo();
}
} else {
// Update is not required as the requirements of all of the configs in the
// policy are satisfied by the current Chrome OS version. We could also
// reach here at the time of login if the device was rebooted to apply the
// downloaded update, in which case it is needed to reset the local state.
HandleUpdateNotRequired();
}
}
void MinimumVersionPolicyHandler::HandleUpdateNotRequired() {
VLOG(2) << "Update is not required.";
// Reset the state including any running timers.
Reset();
// Hide update required screen if it is visible and switch back to the login
// screen.
if (delegate_->IsLoginSessionState())
delegate_->HideUpdateRequiredScreenIfShown();
ResetRelaunchNotification();
}
void MinimumVersionPolicyHandler::Reset() {
deadline_reached_ = false;
eol_reached_ = false;
update_required_deadline_ = base::Time();
update_required_time_ = base::Time();
update_required_deadline_timer_.Stop();
notification_timer_.Stop();
GetBuildState()->RemoveObserver(this);
state_.reset();
HideNotification();
notification_handler_.reset();
ResetLocalState();
StopObservingNetwork();
}
void MinimumVersionPolicyHandler::ResetOnUpdateCompleted() {
update_required_deadline_timer_.Stop();
notification_timer_.Stop();
GetBuildState()->RemoveObserver(this);
HideNotification();
notification_handler_.reset();
}
void MinimumVersionPolicyHandler::FetchEolInfo() {
// Return if update required state is null meaning all requirements are
// satisfied.
if (!state_)
return;
update_required_time_ = clock_->Now();
// Request the End of Life (Auto Update Expiration) status.
GetUpdateEngineClient()->GetEolInfo(
base::BindOnce(&MinimumVersionPolicyHandler::OnFetchEolInfo,
weak_factory_.GetWeakPtr()));
}
void MinimumVersionPolicyHandler::OnFetchEolInfo(
const chromeos::UpdateEngineClient::EolInfo info) {
if (!chromeos::switches::IsAueReachedForUpdateRequiredForTest() &&
(info.eol_date.is_null() || info.eol_date > update_required_time_)) {
// End of life is not reached. Start update with |warning_time_|.
eol_reached_ = false;
HandleUpdateRequired(state_->warning());
} else {
// End of life is reached. Start update with |eol_warning_time_|.
eol_reached_ = true;
HandleUpdateRequired(state_->eol_warning());
}
if (!fetch_eol_callback_.is_null())
std::move(fetch_eol_callback_).Run();
}
void MinimumVersionPolicyHandler::HandleUpdateRequired(
base::TimeDelta warning_time) {
const base::Time stored_timer_start_time =
local_state()->GetTime(prefs::kUpdateRequiredTimerStartTime);
const base::TimeDelta stored_warning_time =
local_state()->GetTimeDelta(prefs::kUpdateRequiredWarningPeriod);
base::Time previous_deadline = stored_timer_start_time + stored_warning_time;
// If update is already required, use the existing timer start time to
// calculate the new deadline. Else use |update_required_time_|. Do not reduce
// the warning time if policy is already applied.
if (stored_timer_start_time.is_null()) {
update_required_deadline_ = update_required_time_ + warning_time;
} else {
update_required_deadline_ =
stored_timer_start_time + std::max(stored_warning_time, warning_time);
}
VLOG(1) << "Update is required with "
<< "update required time " << update_required_time_
<< " warning time " << warning_time
<< " and update required deadline " << update_required_deadline_;
const bool deadline_reached =
update_required_deadline_ <= update_required_time_;
if (deadline_reached) {
// As per the policy, the deadline for the user cannot reduce.
// This case can be encountered when :-
// a) Update was not required before and now critical update is required.
// b) Update was required and warning time has expired when device is
// rebooted.
// Update the local state with the current policy values.
if (update_required_deadline_ > previous_deadline)
UpdateLocalState(warning_time);
OnDeadlineReached();
return;
}
// Need to start the timer even if the deadline is same as the previous one to
// handle the case of Chrome reboot.
if (update_required_deadline_timer_.IsRunning() &&
update_required_deadline_timer_.desired_run_time() ==
update_required_deadline_) {
DLOG(WARNING) << "Deadline is same as previous and timer is running.";
return;
}
// This case can be encountered when :-
// a) Update was not required before and now update is required with a
// warning time. b) Policy has been updated with new values and update is
// still required.
// Hide update required screen if it is shown on the login screen.
if (delegate_->IsLoginSessionState())
delegate_->HideUpdateRequiredScreenIfShown();
// The |deadline| can only be equal to or greater than the
// |previous_deadline|. No need to update the local state if the deadline has
// not been extended.
if (update_required_deadline_ > previous_deadline)
UpdateLocalState(warning_time);
// The device has already downloaded the update in-session and waiting for
// reboot to apply it.
if (GetBuildState()->update_type() == BuildState::UpdateType::kNormalUpdate) {
OverrideRelaunchNotification(update_required_deadline_);
DLOG(WARNING) << "Update is already installed.";
return;
}
StartDeadlineTimer(update_required_deadline_);
if (!eol_reached_)
StartObservingUpdate();
ShowAndScheduleNotification(update_required_deadline_);
}
void MinimumVersionPolicyHandler::ResetLocalState() {
local_state()->ClearPref(prefs::kUpdateRequiredTimerStartTime);
local_state()->ClearPref(prefs::kUpdateRequiredWarningPeriod);
}
void MinimumVersionPolicyHandler::UpdateLocalState(
base::TimeDelta warning_time) {
base::Time timer_start_time =
local_state()->GetTime(prefs::kUpdateRequiredTimerStartTime);
if (timer_start_time.is_null()) {
local_state()->SetTime(prefs::kUpdateRequiredTimerStartTime,
update_required_time_);
}
local_state()->SetTimeDelta(prefs::kUpdateRequiredWarningPeriod,
warning_time);
local_state()->CommitPendingWrite();
}
void MinimumVersionPolicyHandler::StartDeadlineTimer(base::Time deadline) {
// Start the timer to expire when deadline is reached and the device is not
// updated to meet the policy requirements.
update_required_deadline_timer_.Start(
FROM_HERE, deadline,
base::BindOnce(&MinimumVersionPolicyHandler::OnDeadlineReached,
weak_factory_.GetWeakPtr()));
}
void MinimumVersionPolicyHandler::StartObservingUpdate() {
auto* build_state = GetBuildState();
if (!build_state->HasObserver(this))
build_state->AddObserver(this);
}
absl::optional<int> MinimumVersionPolicyHandler::GetTimeRemainingInDays() {
const base::Time now = clock_->Now();
if (!state_ || update_required_deadline_ <= now)
return absl::nullopt;
base::TimeDelta time_remaining = update_required_deadline_ - now;
return GetDaysRounded(time_remaining);
}
void MinimumVersionPolicyHandler::MaybeShowNotificationOnLogin() {
// |days| could be null if |update_required_deadline_timer_| expired while
// login was in progress, else we would have shown the update required screen
// at startup.
absl::optional<int> days = GetTimeRemainingInDays();
if (days && days.value() <= 1)
MaybeShowNotification(base::TimeDelta::FromDays(days.value()));
}
void MinimumVersionPolicyHandler::MaybeShowNotification(
base::TimeDelta warning) {
const NetworkStatus status = GetCurrentNetworkStatus();
if ((!eol_reached_ && status == NetworkStatus::kAllowed) ||
!delegate_->IsUserLoggedIn() || !IsPolicyRestrictionAppliedForUser()) {
return;
}
if (!notification_handler_) {
notification_handler_ = std::make_unique<ash::UpdateRequiredNotification>();
}
NotificationType type = NotificationType::kNoConnection;
base::OnceClosure button_click_callback;
std::string manager = GetEnterpriseManager();
std::u16string device_type = ui::GetChromeOSDeviceName();
auto close_callback =
base::BindOnce(&MinimumVersionPolicyHandler::StopObservingNetwork,
weak_factory_.GetWeakPtr());
if (eol_reached_) {
VLOG(2) << "Showing end of life notification.";
type = NotificationType::kEolReached;
button_click_callback = base::BindOnce(&OpenEnterpriseInfoPage);
} else if (status == NetworkStatus::kMetered) {
VLOG(2) << "Showing metered network notification.";
type = NotificationType::kMeteredConnection;
button_click_callback = base::BindOnce(
&MinimumVersionPolicyHandler::UpdateOverMeteredPermssionGranted,
weak_factory_.GetWeakPtr());
} else if (status == NetworkStatus::kOffline) {
VLOG(2) << "Showing no network notification.";
button_click_callback = base::BindOnce(&OpenNetworkSettings);
} else {
NOTREACHED();
return;
}
notification_handler_->Show(type, warning, manager, device_type,
std::move(button_click_callback),
std::move(close_callback));
if (!eol_reached_) {
chromeos::NetworkStateHandler* network_state_handler =
chromeos::NetworkHandler::Get()->network_state_handler();
if (!network_state_handler->HasObserver(this))
network_state_handler->AddObserver(this, FROM_HERE);
}
}
void MinimumVersionPolicyHandler::ShowAndScheduleNotification(
base::Time deadline) {
const base::Time now = clock_->Now();
if (deadline <= now)
return;
base::Time expiry;
base::TimeDelta time_remaining = deadline - now;
int days_remaining = GetDaysRounded(time_remaining);
// Network limitation notifications are shown when policy is received and on
// the last day. End of life notifications are shown when policy is received,
// one week before EOL and on the last day. No need to schedule a notification
// if it is already the last day.
if (eol_reached_ && days_remaining > kOneWeekEolNotificationInDays) {
expiry =
deadline - base::TimeDelta::FromDays(kOneWeekEolNotificationInDays);
} else if (days_remaining > 1) {
expiry = deadline - base::TimeDelta::FromDays(1);
}
VLOG(2) << "Next notification scheduled for " << expiry;
MaybeShowNotification(base::TimeDelta::FromDays(days_remaining));
if (!expiry.is_null()) {
notification_timer_.Start(
FROM_HERE, expiry,
base::BindOnce(
&MinimumVersionPolicyHandler::ShowAndScheduleNotification,
weak_factory_.GetWeakPtr(), deadline));
}
}
void MinimumVersionPolicyHandler::OnUpdate(const BuildState* build_state) {
// If the device has been successfully updated, the relaunch notifications
// will reboot it for applying the updates.
VLOG(1) << "Update installed successfully at " << clock_->Now()
<< " with deadline " << update_required_deadline_;
GetUpdateEngineClient()->RemoveObserver(this);
if (build_state->update_type() == BuildState::UpdateType::kNormalUpdate) {
ResetOnUpdateCompleted();
OverrideRelaunchNotification(update_required_deadline_);
}
}
void MinimumVersionPolicyHandler::HideNotification() const {
if (notification_handler_)
notification_handler_->Hide();
}
void MinimumVersionPolicyHandler::DefaultNetworkChanged(
const chromeos::NetworkState* network) {
// Close notification if network has switched to one that allows updates.
const NetworkStatus status = GetCurrentNetworkStatus();
if (status == NetworkStatus::kAllowed && notification_handler_) {
HideNotification();
}
}
void MinimumVersionPolicyHandler::StopObservingNetwork() {
if (!chromeos::NetworkHandler::IsInitialized())
return;
chromeos::NetworkStateHandler* network_state_handler =
chromeos::NetworkHandler::Get()->network_state_handler();
network_state_handler->RemoveObserver(this, FROM_HERE);
}
void MinimumVersionPolicyHandler::UpdateOverMeteredPermssionGranted() {
VLOG(1) << "Permission for update over metered network granted.";
chromeos::UpdateEngineClient* const update_engine_client =
GetUpdateEngineClient();
if (!update_engine_client->HasObserver(this))
update_engine_client->AddObserver(this);
update_engine_client->RequestUpdateCheck(
base::BindOnce(&MinimumVersionPolicyHandler::OnUpdateCheckStarted,
weak_factory_.GetWeakPtr()));
}
void MinimumVersionPolicyHandler::OnUpdateCheckStarted(
chromeos::UpdateEngineClient::UpdateCheckResult result) {
VLOG(1) << "Update check started.";
if (result != chromeos::UpdateEngineClient::UPDATE_RESULT_SUCCESS)
GetUpdateEngineClient()->RemoveObserver(this);
}
void MinimumVersionPolicyHandler::UpdateStatusChanged(
const update_engine::StatusResult& status) {
if (status.current_operation() ==
update_engine::Operation::NEED_PERMISSION_TO_UPDATE) {
GetUpdateEngineClient()->SetUpdateOverCellularOneTimePermission(
status.new_version(), status.new_size(),
base::BindOnce(&MinimumVersionPolicyHandler::
OnSetUpdateOverCellularOneTimePermission,
weak_factory_.GetWeakPtr()));
}
}
void MinimumVersionPolicyHandler::OnSetUpdateOverCellularOneTimePermission(
bool success) {
if (success)
UpdateOverMeteredPermssionGranted();
else
GetUpdateEngineClient()->RemoveObserver(this);
}
void MinimumVersionPolicyHandler::OnDeadlineReached() {
deadline_reached_ = true;
if (delegate_->IsLoginSessionState() && !delegate_->IsLoginInProgress()) {
// Show update required screen over the login screen.
delegate_->ShowUpdateRequiredScreen();
} else if (delegate_->IsUserLoggedIn() &&
IsPolicyRestrictionAppliedForUser()) {
// Terminate the current user session to show update required
// screen on the login screen if the user is managed or
// |unmanaged_user_restricted_| is set to true.
delegate_->RestartToLoginScreen();
}
// No action is required if -
// 1) The user signed in is not managed. Once the un-managed user signs out or
// the device is rebooted, the policy handler will be called again to show the
// update required screen if required.
// 2) Login is in progress. This would be handled in-session once user logs
// in, the user would be logged out and update required screen is shown.
// 3) Device has just been enrolled. The login screen would check and show the
// update required screen.
}
} // namespace policy