Commit aa14a924 authored by Saurabh Nijhara's avatar Saurabh Nijhara Committed by Commit Bot

Add banner on Chrome OS Settings for DeviceMinimumVersion policy

If an update is required as per DeviceMinimumVersion policy but the
device cannot be updated as it has reached end of life or auto
update expiration, the product requirement is to show a banner on the
top of Chrome OS Settings page.
This banner is to inform the user that device needs to be returned back
within the specified time frame. After this time frame has expired,
the user would not be able to use the device and hence, the close
button on the banner does not dismiss it forever but for just current
Settings page.
Screenshots:
https://screenshot.googleplex.com/PnKQdspE7G9
https://screenshot.googleplex.com/MGFnpRJSvrr
https://screenshot.googleplex.com/Ab2GT9bZcXH

Bug: 1110976
Change-Id: I69686b019eea524d162316d32ef483606e5154f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2314496Reviewed-by: default avatarSergey Poromov <poromov@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarRegan Hsu <hsuregan@chromium.org>
Commit-Queue: Saurabh Nijhara <snijhara@google.com>
Cr-Commit-Position: refs/heads/master@{#793667}
parent c146cac9
......@@ -18,6 +18,15 @@
<message name="IDS_SETTINGS_SECONDARY_USER_BANNER" desc="Banner displayed in settings page when the user is secondary in a multi-profile session.">
Some settings belonging to <ph name="PRIMARY_EMAIL">$1<ex>john@google.com</ex></ph> are being shared with you. These settings only affect your account when using multiple sign-in.
</message>
<message name="IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS" desc="Banner displayed in OS settings page in case update is required by policy but device has reached end-of-life and the days remaining to return the device back to the enterprise is greater than one and less than seven.">
<ph name="DOMAIN">$1<ex>example.com</ex></ph> requires you to back up your data and return this device within <ph name="DAYS_COUNT">$2<ex>10</ex></ph> days.<ph name="LINK_BEGIN">&lt;a target="_blank" href="$3<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>
</message>
<message name="IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_LAST_DAY" desc="Banner displayed in OS settings page in case update is required by policy but device has reached end-of-life and it is the last day to return the device back to the enterprise.">
<ph name="DOMAIN">$1<ex>example.com</ex></ph> requires you to back up your data and return this device today.<ph name="LINK_BEGIN">&lt;a target="_blank" href="$2<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>
</message>
<message name="IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK" desc="Banner displayed in OS settings page in case update is required by policy but device has reached end-of-life and the days remaining to return the device back to the enterprise is equal to seven.">
<ph name="DOMAIN">$1<ex>example.com</ex></ph> requires you to back up your data and return this device within 1 week.<ph name="LINK_BEGIN">&lt;a target="_blank" href="$2<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>
</message>
<!-- Settings Search Box -->
<message name="IDS_OS_SEARCH_RESULT_ROW_A11Y_RESULT_SELECTED" desc="ChromeVox alert to indicate the position number of a selected result in a list of search results and the selected result text itself, and that the user can press enter to navigate to section described by the search result.">
......
932a1b84fef78d74fa7802ad184487feaa2e9e5f
\ No newline at end of file
836fdeaa65fa654bd342623dd680ee6ff378b295
\ No newline at end of file
6dcd6792924c36dc064038d41aba50c07f105a6f
\ No newline at end of file
......@@ -450,18 +450,21 @@ void MinimumVersionPolicyHandler::StartObservingUpdate() {
build_state->AddObserver(this);
}
void MinimumVersionPolicyHandler::MaybeShowNotificationOnLogin() {
base::Optional<int> MinimumVersionPolicyHandler::GetTimeRemainingInDays() {
const base::Time now = clock_->Now();
// This should only be true if |update_required_deadline_timer_| expired while
if (!state_ || update_required_deadline_ <= now)
return base::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.
if (update_required_deadline_ <= now)
return;
base::TimeDelta time_remaining = update_required_deadline_ - now;
int days_remaining = GetDaysRounded(time_remaining);
if (days_remaining <= 1)
MaybeShowNotification(base::TimeDelta::FromDays(days_remaining));
base::Optional<int> days = GetTimeRemainingInDays();
if (days && days.value() <= 1)
MaybeShowNotification(base::TimeDelta::FromDays(days.value()));
}
void MinimumVersionPolicyHandler::MaybeShowNotification(
......
......@@ -24,11 +24,11 @@ namespace base {
class Clock;
class DictionaryValue;
class Time;
}
} // namespace base
namespace chromeos {
class UpdateRequiredNotification;
}
} // namespace chromeos
namespace policy {
......@@ -167,6 +167,10 @@ class MinimumVersionPolicyHandler
// End Of Life (Auto Update Expiration).
bool IsUpdateRequiredEol() const;
// Returns the number of days to deadline if update is required and deadline
// has not been reached. Returns null if update is not required.
base::Optional<int> GetTimeRemainingInDays();
// Callback used in tests and invoked after end-of-life status has been
// fetched from the update_engine.
void set_fetch_eol_callback_for_testing(base::OnceClosure callback) {
......
......@@ -250,6 +250,12 @@ TEST_F(MinimumVersionPolicyHandlerTest, RequirementsNotMetState) {
// No policy applied yet. Check requirements are satisfied.
EXPECT_TRUE(GetMinimumVersionPolicyHandler()->RequirementsAreSatisfied());
EXPECT_FALSE(GetState());
EXPECT_FALSE(GetMinimumVersionPolicyHandler()->GetTimeRemainingInDays());
// This is needed to wait till EOL status is fetched from the update_engine.
base::RunLoop run_loop;
GetMinimumVersionPolicyHandler()->set_fetch_eol_callback_for_testing(
run_loop.QuitClosure());
// Create policy value as a list of requirements.
base::Value requirement_list(base::Value::Type::LIST);
......@@ -269,17 +275,22 @@ TEST_F(MinimumVersionPolicyHandlerTest, RequirementsNotMetState) {
// requirement as defined in the policy description.
SetPolicyPref(CreatePolicyValue(std::move(requirement_list),
false /* unmanaged_user_restricted */));
run_loop.Run();
EXPECT_FALSE(GetMinimumVersionPolicyHandler()->RequirementsAreSatisfied());
EXPECT_TRUE(GetState());
EXPECT_TRUE(strongest_requirement);
EXPECT_EQ(GetState()->Compare(strongest_requirement.get()), 0);
EXPECT_TRUE(GetMinimumVersionPolicyHandler()->GetTimeRemainingInDays());
EXPECT_EQ(GetMinimumVersionPolicyHandler()->GetTimeRemainingInDays().value(),
kShortWarning);
// Reset the pref to empty list and verify state is reset.
base::Value requirement_list2(base::Value::Type::LIST);
SetPolicyPref(std::move(requirement_list2));
EXPECT_TRUE(GetMinimumVersionPolicyHandler()->RequirementsAreSatisfied());
EXPECT_FALSE(GetState());
EXPECT_FALSE(GetMinimumVersionPolicyHandler()->GetTimeRemainingInDays());
}
TEST_F(MinimumVersionPolicyHandlerTest, CriticalUpdates) {
......
......@@ -2,6 +2,7 @@
<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html">
<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
<link rel="import" href="main_page_behavior.html">
......@@ -25,6 +26,10 @@
<dom-module id="os-settings-page">
<template>
<style include="settings-page-styles cr-hidden-style settings-shared">
:host {
--google-yellow-50: #fef7e0;
}
:host([is-subpage-animating]) {
/* Prevent an unwanted horizontal scrollbar when transitioning back from
* a sub-page. */
......@@ -41,6 +46,21 @@
margin-top: var(--cr-section-vertical-margin);
}
.eol-warning-icon {
align-items: center;
background: var(--google-yellow-50);
border-radius: 50%;
display: flex;
height: 40px;
justify-content: center;
margin-inline-end: var(--cr-section-padding);
width: 40px;
}
.eol-warning-icon iron-icon {
margin: 0;
}
#advancedToggle {
--ink-color: currentColor;
align-items: center;
......@@ -97,6 +117,22 @@
<template is="dom-if" if="[[showBasicPage_(
currentRoute_, inSearchMode, hasExpandedSection_)]]">
<div id="basicPage">
<template is="dom-if" if="[[computeShowUpdateRequiredEolBanner_(
hasExpandedSection_, showUpdateRequiredEolBanner_)]]">
<div id="updateRequiredEolBanner"
class="settings-box two-line banner">
<div class="eol-warning-icon">
<iron-icon icon="cr20:banner-warning"></iron-icon>
</div>
<settings-localized-link id="bannerText" class="start"
localized-string="$i18n{updateRequiredEolBannerText}">
</settings-localized-link>
<cr-icon-button title="$i18n{close}" id="closeUpdateRequiredEol"
class="icon-clear" on-click="onCloseEolBannerClicked_"
aria-describedby="bannerText">
</cr-icon-button>
</div>
</template>
<div id="secondaryUserBanner" class="banner"
hidden="[[!showSecondaryUserBanner_]]">
<div id="secondaryUserIcon"></div>
......
......@@ -87,6 +87,16 @@ Polymer({
computed: 'computeShowSecondaryUserBanner_(hasExpandedSection_)',
},
/**
* Whether to show banner indicating the user to return this device as an
* update is required as per policy but the device has reached end of life.
* @private
*/
showUpdateRequiredEolBanner_: {
type: Boolean,
value: !!loadTimeData.getString('updateRequiredEolBannerText'),
},
/** @private {!settings.Route|undefined} */
currentRoute_: Object,
},
......@@ -200,6 +210,14 @@ Polymer({
loadTimeData.getBoolean('isSecondaryUser');
},
/**
* @return {boolean}
* @private
*/
computeShowUpdateRequiredEolBanner_() {
return !this.hasExpandedSection_ && this.showUpdateRequiredEolBanner_;
},
/**
* @param {!AndroidAppsInfo} info
* @private
......@@ -208,6 +226,15 @@ Polymer({
this.androidAppsInfo = info;
},
/**
* Hides the update required EOL banner. It is shown again when Settings is
* re-opened.
* @private
*/
onCloseEolBannerClicked_() {
this.showUpdateRequiredEolBanner_ = false;
},
/**
* Hides everything but the newly expanded subpage.
* @private
......
......@@ -6,8 +6,13 @@
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "base/feature_list.h"
#include "base/i18n/number_formatting.h"
#include "base/no_destructor.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/minimum_version_policy_handler.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/metrics_handler.h"
......@@ -17,6 +22,7 @@
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/browser/web_applications/system_web_app_manager.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/browser_resources.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
......@@ -54,6 +60,45 @@ void AddSearchInSettingsStrings(content::WebUIDataSource* html_source) {
base::FeatureList::IsEnabled(::chromeos::features::kNewOsSettingsSearch));
}
void AddUpdateRequiredEolStrings(content::WebUIDataSource* html_source) {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
policy::MinimumVersionPolicyHandler* handler =
connector->GetMinimumVersionPolicyHandler();
// |eol_return_banner_text| contains the update required end of life banner
// text which is left empty when the banner should not be shown.
base::string16 eol_return_banner_text;
if (handler && handler->IsUpdateRequiredEol()) {
base::Optional<int> days = handler->GetTimeRemainingInDays();
// We only need to show the banner if less than equal to one week remains to
// reach the update required deadline.
if (days && days.value() <= 7) {
// |days| could have value equal to zero if we are very close to the
// deadline.
int days_remaining = days.value() ? days.value() : 1;
base::string16 domain_name =
base::UTF8ToUTF16(connector->GetEnterpriseDisplayDomain());
base::string16 link_url =
base::UTF8ToUTF16(chrome::kChromeUIManagementURL);
if (days_remaining == 1) {
eol_return_banner_text = l10n_util::GetStringFUTF16(
IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_LAST_DAY, domain_name,
link_url);
} else if (days_remaining == 7) {
eol_return_banner_text = l10n_util::GetStringFUTF16(
IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK, domain_name,
link_url);
} else {
eol_return_banner_text = l10n_util::GetStringFUTF16(
IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS, domain_name,
base::FormatNumber(days_remaining), link_url);
}
}
}
html_source->AddString("updateRequiredEolBannerText", eol_return_banner_text);
}
} // namespace
MainSection::MainSection(Profile* profile,
......@@ -148,6 +193,7 @@ void MainSection::AddLoadTimeData(content::WebUIDataSource* html_source) {
AddSearchInSettingsStrings(html_source);
AddChromeOSUserStrings(html_source);
AddUpdateRequiredEolStrings(html_source);
policy_indicator::AddLocalizedStrings(html_source);
}
......
......@@ -123,4 +123,27 @@ suite('OSSettingsPage', function() {
assertTrue(!!section, 'Expected section ' + name);
}
});
test('Update required end of life banner visibility', function() {
Polymer.dom.flush();
assertFalse(settingsPage.showUpdateRequiredEolBanner_);
assertFalse(!!settingsPage.$$('#updateRequiredEolBanner'));
settingsPage.showUpdateRequiredEolBanner_ = true;
Polymer.dom.flush();
assertTrue(!!settingsPage.$$('#updateRequiredEolBanner'));
});
test('Update required end of life banner close button click', function() {
settingsPage.showUpdateRequiredEolBanner_ = true;
Polymer.dom.flush();
const banner = settingsPage.$$('#updateRequiredEolBanner');
assertTrue(!!banner);
const closeButton = assert(settingsPage.$$('#closeUpdateRequiredEol'));
closeButton.click();
Polymer.dom.flush();
assertFalse(settingsPage.showUpdateRequiredEolBanner_);
assertEquals('none', banner.style.display);
});
});
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