blob: 2c0b9a08960026926570577f7d4bddff07f87a7b [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.
/**
* @fileoverview
* 'settings-account-manager' is the settings subpage containing controls to
* list, add and delete Secondary Google Accounts.
*/
import 'chrome://resources/cr_components/localized_link/localized_link.js';
import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
import 'chrome://resources/cr_elements/policy/cr_policy_indicator.m.js';
import 'chrome://resources/cr_elements/policy/cr_tooltip_icon.m.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import '../../settings_shared.css.js';
import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
import {getImage} from 'chrome://resources/js/icon.js';
import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {loadTimeData} from '../../i18n_setup.js';
import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
import {Route} from '../../router.js';
import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
import {recordSettingChange} from '../metrics_recorder.js';
import {routes} from '../os_route.js';
import {ParentalControlsBrowserProxy, ParentalControlsBrowserProxyImpl} from '../parental_controls_page/parental_controls_browser_proxy.js';
import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
import {Account, AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from './account_manager_browser_proxy.js';
/**
* @constructor
* @extends {PolymerElement}
* @implements {DeepLinkingBehaviorInterface}
* @implements {I18nBehaviorInterface}
* @implements {WebUIListenerBehaviorInterface}
* @implements {RouteObserverBehaviorInterface}
*/
const SettingsAccountManagerElementBase = mixinBehaviors(
[
DeepLinkingBehavior,
I18nBehavior,
WebUIListenerBehavior,
RouteObserverBehavior,
],
PolymerElement);
/** @polymer */
class SettingsAccountManagerElement extends SettingsAccountManagerElementBase {
static get is() {
return 'settings-account-manager';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/**
* List of Accounts.
* @type {!Array<Account>}
*/
accounts_: {
type: Array,
value() {
return [];
},
},
/**
* Primary / Device account.
* @private {?Account}
*/
deviceAccount_: Object,
/**
* The targeted account for menu operations.
* @private {?Account}
*/
actionMenuAccount_: Object,
/** @private {boolean} */
isChildUser_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('isChild');
},
},
/**
* True if device account is managed.
* @private {boolean}
*/
isDeviceAccountManaged_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('isDeviceAccountManaged');
},
readOnly: true,
},
/**
* @return {boolean} True if secondary account sign-ins are allowed, false
* otherwise.
* @private
*/
isSecondaryGoogleAccountSigninAllowed_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('secondaryGoogleAccountSigninAllowed');
},
},
/**
* @return {boolean} True if `kArcAccountRestrictionsEnabled` feature is
* enabled, false otherwise.
* @private
*/
isArcAccountRestrictionsEnabled_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('arcAccountRestrictionsEnabled');
},
},
/**
* Used by DeepLinkingBehavior to focus this page's deep links.
* @type {!Set<!Setting>}
*/
supportedSettingIds: {
type: Object,
value: () => new Set([
Setting.kAddAccount,
Setting.kRemoveAccount,
]),
},
};
}
constructor() {
super();
/** @private {!AccountManagerBrowserProxy} */
this.browserProxy_ = AccountManagerBrowserProxyImpl.getInstance();
}
/** @override */
connectedCallback() {
super.connectedCallback();
this.addWebUIListener('accounts-changed', this.refreshAccounts_.bind(this));
}
/** @override */
ready() {
super.ready();
this.refreshAccounts_();
}
/**
* @param {!Route} newRoute
* @param {!Route=} oldRoute
*/
currentRouteChanged(newRoute, oldRoute) {
if (newRoute !== routes.ACCOUNT_MANAGER) {
return;
}
this.attemptDeepLink();
}
/**
* @return {string} account manager description text.
* @private
*/
getAccountManagerDescription_() {
if (this.isChildUser_ && this.isSecondaryGoogleAccountSigninAllowed_) {
return loadTimeData.getString('accountManagerChildDescription');
}
return loadTimeData.getString('accountManagerDescription');
}
/**
* @return {string} account manager 'add account' label.
* @private
*/
getAddAccountLabel_() {
if (this.isChildUser_ && this.isSecondaryGoogleAccountSigninAllowed_) {
return loadTimeData.getString('addSchoolAccountLabel');
}
return loadTimeData.getString('addAccountLabel');
}
/**
* @return {string} accounts list header (e.g. 'Secondary accounts' for
* regular users or 'School accounts' for child users).
* @private
*/
getAccountListHeader_() {
return this.isChildUser_ ?
loadTimeData.getString('accountListHeaderChild') :
loadTimeData.getString('accountListHeader');
}
/**
* @return {string} accounts list description.
* @private
*/
getAccountListDescription_() {
return this.isChildUser_ ?
loadTimeData.getString('accountListChildDescription') :
loadTimeData.getString('accountListDescription');
}
/**
* @return {string} 'Secondary Accounts disabled' message depending on
* account type
* @private
*/
getSecondaryAccountsDisabledUserMessage_() {
return this.isChildUser_
? this.i18n('accountManagerSecondaryAccountsDisabledChildText')
: this.i18n('accountManagerSecondaryAccountsDisabledText');
}
/**
* @return {string} class name for account list header class.
* @private
*/
getAccountListHeaderClass_() {
return this.isArcAccountRestrictionsEnabled_ ?
'account-list-header-description with-padding' :
'account-list-header-description';
}
/**
* @param {string} iconUrl
* @return {string} A CSS image-set for multiple scale factors.
* @private
*/
getIconImageSet_(iconUrl) {
return getImage(iconUrl);
}
/**
* @param {!Event} event
* @private
*/
addAccount_(event) {
recordSettingChange(
Setting.kAddAccount, {intValue: this.accounts_.length + 1});
this.browserProxy_.addAccount();
}
/**
* @param {!Account} account
* @return {boolean} True if the account reauthentication button should be
* shown, false otherwise.
* @private
*/
shouldShowReauthenticationButton_(account) {
// Device account re-authentication cannot be handled in-session, primarily
// because the user may have changed their password (leading to an LST
// invalidation) and we do not have a mechanism to change the cryptohome
// password in-session.
return !account.isDeviceAccount && !account.isSignedIn;
}
/**
* @return {boolean} True if managed badge should be shown next to the device
* account picture.
* @private
*/
shouldShowManagedBadge_() {
return this.isDeviceAccountManaged_ && !this.isChildUser_;
}
/**
* @return {string} icon
* @private
*/
getManagedAccountTooltipIcon_() {
if (this.isChildUser_) {
return 'cr20:kite';
}
if (this.isDeviceAccountManaged_) {
return 'cr20:domain';
}
return '';
}
/**
* @return {string} description text
* @private
*/
getManagementDescription_() {
if (this.isChildUser_) {
return loadTimeData.getString('accountManagerManagementDescription');
}
if (!this.deviceAccount_) {
return '';
}
if (!this.deviceAccount_.organization) {
if (this.isDeviceAccountManaged_) {
console.error(
'The device account is managed, but the organization is not set.');
}
return '';
}
// Format: 'This account is managed by
// <a target="_blank" href="chrome://management">google.com</a>'.
// Where href will be set by <localized-link>.
return loadTimeData.getStringF(
'accountManagerManagementDescription',
this.deviceAccount_.organization);
}
/**
* @param {boolean} unmigrated
* @private
*/
getAccountManagerSignedOutName_(unmigrated) {
return this.i18n(unmigrated ? 'accountManagerUnmigratedAccountName'
: 'accountManagerSignedOutAccountName');
}
/**
* @param {boolean} unmigrated
* @private
*/
getAccountManagerSignedOutLabel_(unmigrated) {
return this.i18n(unmigrated ? 'accountManagerMigrationLabel'
: 'accountManagerReauthenticationLabel');
}
/**
* @param {!Account} account
* @private
*/
getAccountManagerSignedOutTitle_(account) {
const label = account.unmigrated ? 'accountManagerMigrationTooltip'
: 'accountManagerReauthenticationTooltip';
return loadTimeData.getStringF(label, account.email);
}
/**
* @param {!Account} account
* @private
*/
getMoreActionsTitle_(account) {
return loadTimeData.getStringF('accountManagerMoreActionsTooltip',
account.email);
}
/**
* @return {!Array<Account>} list of accounts.
* @private
*/
getSecondaryAccounts_() {
return this.accounts_.filter(account => !account.isDeviceAccount);
}
/**
* @param {!CustomEvent<!{model: !{item: !Account}}>} event
* @private
*/
onReauthenticationTap_(event) {
if (event.model.item.unmigrated) {
this.browserProxy_.migrateAccount(event.model.item.email);
} else {
this.browserProxy_.reauthenticateAccount(event.model.item.email);
}
}
/** @private */
onManagedIconClick_() {
if (this.isChildUser_) {
ParentalControlsBrowserProxyImpl.getInstance().launchFamilyLinkSettings();
}
}
/**
* @private
*/
refreshAccounts_() {
this.browserProxy_.getAccounts().then(accounts => {
this.set('accounts_', accounts);
const deviceAccount = accounts.find(account => account.isDeviceAccount);
if (!deviceAccount) {
console.error('Cannot find device account.');
return;
}
this.deviceAccount_ = deviceAccount;
});
}
/**
* Opens the Account actions menu.
* @param {!{model: !{item: Account}, target: !HTMLElement}} event
* @private
*/
onAccountActionsMenuButtonTap_(event) {
this.actionMenuAccount_ = event.model.item;
/** @type {!CrActionMenuElement} */ (
this.shadowRoot.querySelector('cr-action-menu'))
.showAt(event.target);
}
/**
* If Lacros is not enabled, removes the account pointed to by
* |this.actionMenuAccount_|.
* If Lacros is enabled, shows a warning dialog that the user needs to
* confirm before removing the account.
* @private
*/
onRemoveAccountTap_() {
this.shadowRoot.querySelector('cr-action-menu').close();
if (loadTimeData.getBoolean('lacrosEnabled') &&
this.actionMenuAccount_.isManaged) {
this.$.removeConfirmationDialog.showModal();
} else {
this.browserProxy_.removeAccount(
/** @type {?Account} */ (this.actionMenuAccount_));
this.actionMenuAccount_ = null;
this.shadowRoot.querySelector('#add-account-button').focus();
}
}
/**
* The user chooses not to remove the account after seeing the warning
* dialog, and taps the cancel button.
* @private
*/
onRemoveAccountDialogCancelTap_() {
this.actionMenuAccount_ = null;
this.$.removeConfirmationDialog.cancel();
this.shadowRoot.querySelector('#add-account-button').focus();
}
/**
* After seeing the warning dialog, the user chooses to removes the account
* pointed to by |this.actionMenuAccount_|, and taps the remove button.
* @private
*/
onRemoveAccountDialogRemoveTap_() {
this.browserProxy_.removeAccount(
/** @type {?Account} */ (this.actionMenuAccount_));
this.actionMenuAccount_ = null;
this.$.removeConfirmationDialog.close();
this.shadowRoot.querySelector('#add-account-button').focus();
}
/**
* Get the test for button that changes ARC availability.
* @private
*/
getChangeArcAvailabilityLabel_() {
if (!this.actionMenuAccount_) {
return '';
}
return this.actionMenuAccount_.isAvailableInArc ?
this.i18n('accountStopUsingInArcButtonLabel') :
this.i18n('accountUseInArcButtonLabel');
}
/**
* Change ARC availability for |this.actionMenuAccount_|.
* Closes the 'More actions' menu and focuses the 'More actions' button for
* |this.actionMenuAccount_|.
* @private
*/
onChangeArcAvailability_() {
this.shadowRoot.querySelector('cr-action-menu').close();
const newArcAvailability = !this.actionMenuAccount_.isAvailableInArc;
this.browserProxy_.changeArcAvailability(
this.actionMenuAccount_, newArcAvailability);
const actionMenuAccountIndex =
this.shadowRoot.querySelector('#account-list')
.items.indexOf(this.actionMenuAccount_);
if (actionMenuAccountIndex >= 0) {
// Focus 'More actions' button for the current account.
this.shadowRoot
.querySelectorAll('.icon-more-vert')[actionMenuAccountIndex]
.focus();
} else {
console.error(
'Couldn\'t find active account in the list: ',
this.actionMenuAccount_);
this.shadowRoot.querySelector('#add-account-button').focus();
}
this.actionMenuAccount_ = null;
}
}
customElements.define(
SettingsAccountManagerElement.is, SettingsAccountManagerElement);