// Copyright 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. /** * @fileoverview * Settings page for managing MultiDevice features. */ cr.exportPath('settings'); Polymer({ is: 'settings-multidevice-page', behaviors: [ settings.RouteObserverBehavior, MultiDeviceFeatureBehavior, WebUIListenerBehavior, ], properties: { /** Preferences state. */ prefs: {type: Object}, /** * A Map specifying which element should be focused when exiting a subpage. * The key of the map holds a settings.Route path, and the value holds a * query selector that identifies the desired element. * @private {!Map} */ focusConfig_: { type: Object, value: function() { const map = new Map(); if (settings.routes.MULTIDEVICE_FEATURES) { map.set( settings.routes.MULTIDEVICE_FEATURES.path, '#multidevice-item .subpage-arrow'); } return map; }, }, /** * Authentication token provided by password-prompt-dialog. * @private {string} */ authToken_: { type: String, value: '', }, /** * Feature which the user has requested to be enabled but could not be * enabled immediately because authentication (i.e., entering a password) is * required. This value is initialized to null, is set when the password * dialog is opened, and is reset to null again once the password dialog is * closed. * @private {?settings.MultiDeviceFeature} */ featureToBeEnabledOnceAuthenticated_: { type: Number, value: null, }, /** @private {boolean} */ showPasswordPromptDialog_: { type: Boolean, value: false, }, }, listeners: { 'close': 'onDialogClose_', 'feature-toggle-clicked': 'onFeatureToggleClicked_', 'forget-device-requested': 'onForgetDeviceRequested_', }, /** @private {?settings.MultiDeviceBrowserProxy} */ browserProxy_: null, /** @override */ ready: function() { this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance(); this.addWebUIListener( 'settings.updateMultidevicePageContentData', this.onPageContentDataChanged_.bind(this)); this.browserProxy_.getPageContentData().then( this.onPageContentDataChanged_.bind(this)); }, /** * Overridden from settings.RouteObserverBehavior. * @protected */ currentRouteChanged: function() { this.leaveNestedPageIfNoHostIsSet_(); }, /** * CSS class for the
containing all the text in the multidevice-item *
, i.e. the label and sublabel. If the host is set, the Better Together * icon appears so before the text (i.e. text div is 'middle' class). * @return {string} * @private */ getMultiDeviceItemLabelBlockCssClass_: function() { return this.isHostSet() ? 'middle' : 'start'; }, /** * @return {string} Translated item label. * @private */ getLabelText_: function() { return this.pageContentData.hostDeviceName || this.i18n('multideviceSetupItemHeading'); }, /** * @return {string} Translated sublabel with a "learn more" link. * @private */ getSubLabelInnerHtml_: function() { if (!this.isSuiteAllowedByPolicy()) { return this.i18nAdvanced('multideviceSetupSummary'); } switch (this.pageContentData.mode) { case settings.MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS: return this.i18nAdvanced('multideviceNoHostText'); case settings.MultiDeviceSettingsMode.NO_HOST_SET: return this.i18nAdvanced('multideviceSetupSummary'); case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER: // Intentional fall-through. case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION: return this.i18nAdvanced('multideviceVerificationText'); default: return this.isSuiteOn() ? this.i18n('multideviceEnabled') : this.i18n('multideviceDisabled'); } }, /** * @return {string} Translated button text. * @private */ getButtonText_: function() { switch (this.pageContentData.mode) { case settings.MultiDeviceSettingsMode.NO_HOST_SET: return this.i18n('multideviceSetupButton'); case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER: // Intentional fall-through. case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION: return this.i18n('multideviceVerifyButton'); default: return ''; } }, /** * @return {boolean} * @private */ shouldShowButton_: function() { return [ settings.MultiDeviceSettingsMode.NO_HOST_SET, settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER, settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION, ].includes(this.pageContentData.mode); }, /** * @return {boolean} * @private */ shouldShowToggle_: function() { return this.pageContentData.mode === settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED; }, /** * Whether to show the separator bar and, if the state calls for a chevron * (a.k.a. subpage arrow) routing to the subpage, the chevron. * @return {boolean} * @private */ shouldShowSeparatorAndSubpageArrow_: function() { return this.pageContentData.mode !== settings.MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS; }, /** * @return {boolean} * @private */ doesClickOpenSubpage_: function() { return this.isHostSet(); }, /** @private */ handleItemClick_: function(event) { // We do not open the subpage if the click was on a link. if (event.path[0].tagName === 'A') { event.stopPropagation(); return; } if (!this.isHostSet()) { return; } settings.navigateTo(settings.routes.MULTIDEVICE_FEATURES); }, /** @private */ handleButtonClick_: function(event) { event.stopPropagation(); switch (this.pageContentData.mode) { case settings.MultiDeviceSettingsMode.NO_HOST_SET: this.browserProxy_.showMultiDeviceSetupDialog(); return; case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER: // Intentional fall-through. case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION: // If this device is waiting for action on the server or the host // device, clicking the button should trigger this action. this.browserProxy_.retryPendingHostSetup(); } }, /** @private */ openPasswordPromptDialog_: function() { this.showPasswordPromptDialog_ = true; }, onDialogClose_: function(event) { event.stopPropagation(); if (event.path.some( element => element.id === 'multidevicePasswordPrompt')) { this.onPasswordPromptDialogClose_(); } }, /** @private */ onPasswordPromptDialogClose_: function() { // The password prompt should only be shown when there is a feature waiting // to be enabled. assert(this.featureToBeEnabledOnceAuthenticated_ !== null); // If |this.authToken_| is set when the dialog has been closed, this means // that the user entered the correct password into the dialog. Thus, send // all pending features to be enabled. if (this.authToken_) { this.browserProxy_.setFeatureEnabledState( this.featureToBeEnabledOnceAuthenticated_, true /* enabled */, this.authToken_); // Reset |this.authToken_| now that it has been used. This ensures that // users cannot keep an old auth token and reuse it on an subsequent // request. this.authToken_ = ''; } // Either the feature was enabled above or the user canceled the request by // clicking "Cancel" on the password dialog. Thus, there is no longer a need // to track any pending feature. this.featureToBeEnabledOnceAuthenticated_ = null; // Remove the password prompt dialog from the DOM. this.showPasswordPromptDialog_ = false; }, /** * Attempt to enable the provided feature. If not authenticated (i.e., * |authToken_| is invalid), display the password prompt to begin the * authentication process. * * @param {!CustomEvent} event * @private */ onFeatureToggleClicked_: function(event) { const feature = event.detail.feature; const enabled = event.detail.enabled; // Disabling any feature does not require authentication, and enable some // features does not require authentication. if (!enabled || !this.isAuthenticationRequiredToEnable_(feature)) { this.browserProxy_.setFeatureEnabledState(feature, enabled); return; } // If the feature required authentication to be enabled, open the password // prompt dialog. This is required every time the user enables a security- // sensitive feature (i.e., use of stale auth tokens is not acceptable). this.featureToBeEnabledOnceAuthenticated_ = feature; this.openPasswordPromptDialog_(); }, /** * @param {!settings.MultiDeviceFeature} feature The feature to enable. * @return {boolean} Whether authentication is required to enable the feature. * @private */ isAuthenticationRequiredToEnable_: function(feature) { // Enabling SmartLock always requires authentication. if (feature == settings.MultiDeviceFeature.SMART_LOCK) { return true; } // Enabling any feature besides SmartLock and the Better Together suite does // not require authentication. if (feature != settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE) { return false; } const smartLockState = this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK); // If the user is enabling the Better Together suite and this change would // result in SmartLock being implicitly enabled, authentication is required. // SmartLock is implicitly enabled if it is only currently not enabled due // to the suite being disabled or due to the SmartLock host device not // having a lock screen set. return smartLockState == settings.MultiDeviceFeatureState.UNAVAILABLE_SUITE_DISABLED || smartLockState == settings.MultiDeviceFeatureState.UNAVAILABLE_INSUFFICIENT_SECURITY; }, /** @private */ onForgetDeviceRequested_: function() { this.browserProxy_.removeHostDevice(); settings.navigateTo(settings.routes.MULTIDEVICE); }, /** * Checks if the user is in a nested page without a host set and, if so, * navigates them back to the main page. * @private */ leaveNestedPageIfNoHostIsSet_: function() { // Wait for data to arrive. if (!this.pageContentData) { return; } // If the user gets to the a nested page without a host (e.g. by clicking a // stale 'existing user' notifications after forgetting their host) we // direct them back to the main settings page. if (settings.routes.MULTIDEVICE != settings.getCurrentRoute() && settings.routes.MULTIDEVICE.contains(settings.getCurrentRoute()) && !this.isHostSet()) { settings.navigateTo(settings.routes.MULTIDEVICE); } }, /** * @param {!MultiDevicePageContentData} newData * @private */ onPageContentDataChanged_: function(newData) { this.pageContentData = newData; this.leaveNestedPageIfNoHostIsSet_(); }, });