blob: 7c2bff03435d35474ad7ecf22a3a85e0aa81509a [file] [log] [blame]
// 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 Polymer element for displaying information about WiFi,
* Cellular, or virtual networks.
*/
import 'chrome://resources/cr_components/chromeos/network/network_list.m.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
import 'chrome://resources/cr_elements/policy/cr_policy_indicator.m.js';
import 'chrome://resources/cr_elements/md_select_css.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import '../os_settings_icons_css.js';
import '../../settings_shared.css.js';
import 'chrome://resources/cr_components/localized_link/localized_link.js';
import './cellular_networks_list.js';
import './network_always_on_vpn.js';
import {CrPolicyNetworkBehaviorMojo, CrPolicyNetworkBehaviorMojoInterface} from 'chrome://resources/cr_components/chromeos/network/cr_policy_network_behavior_mojo.m.js';
import {MojoInterfaceProvider, MojoInterfaceProviderImpl} from 'chrome://resources/cr_components/chromeos/network/mojo_interface_provider.m.js';
import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/cr_components/chromeos/network/network_listener_behavior.m.js';
import {OncMojo} from 'chrome://resources/cr_components/chromeos/network/onc_mojo.m.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
import {Route, Router} from '../../router.js';
import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
import {recordSettingChange} from '../metrics_recorder.js';
import {routes} from '../os_route.js';
import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
import {RouteOriginBehavior, RouteOriginBehaviorImpl, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
import {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page_browser_proxy.js';
const mojom = chromeos.networkConfig.mojom;
/**
* @constructor
* @extends {PolymerElement}
* @implements {NetworkListenerBehaviorInterface}
* @implements {CrPolicyNetworkBehaviorMojoInterface}
* @implements {DeepLinkingBehaviorInterface}
* @implements {RouteObserverBehaviorInterface}
* @implements {RouteOriginBehaviorInterface}
* @implements {I18nBehaviorInterface}
*/
const SettingsInternetSubpageElementBase = mixinBehaviors(
[
NetworkListenerBehavior,
CrPolicyNetworkBehaviorMojo,
DeepLinkingBehavior,
RouteObserverBehavior,
RouteOriginBehavior,
I18nBehavior,
],
PolymerElement);
/** @polymer */
class SettingsInternetSubpageElement extends
SettingsInternetSubpageElementBase {
static get is() {
return 'settings-internet-subpage';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/**
* Highest priority connected network or null. Provided by
* settings-internet-page (but set in network-summary).
* @type {?OncMojo.NetworkStateProperties|undefined}
*/
defaultNetwork: Object,
/**
* Device state for the network type. Note: when both Cellular and Tether
* are available this will always be set to the Cellular device state and
* |tetherDeviceState| will be set to the Tether device state.
* @type {!OncMojo.DeviceStateProperties|undefined}
*/
deviceState: Object,
/**
* If both Cellular and Tether technologies exist, we combine the subpages
* and set this to the device state for Tether.
* @type {!OncMojo.DeviceStateProperties|undefined}
*/
tetherDeviceState: Object,
/** @type {!chromeos.networkConfig.mojom.GlobalPolicy|undefined} */
globalPolicy: Object,
/**
* List of third party (Extension + Arc) VPN providers.
* @type {!Array<!chromeos.networkConfig.mojom.VpnProvider>}
*/
vpnProviders: Array,
showSpinner: {
type: Boolean,
notify: true,
value: false,
},
isConnectedToNonCellularNetwork: {
type: Boolean,
},
isCellularSetupActive: {
type: Boolean,
},
/**
* List of all network state data for the network type.
* @private {!Array<!OncMojo.NetworkStateProperties>}
*/
networkStateList_: {
type: Array,
value() {
return [];
},
},
/**
* Dictionary of lists of network states for third party VPNs.
* @private {!Object<!Array<!OncMojo.NetworkStateProperties>>}
*/
thirdPartyVpns_: {
type: Object,
value() {
return {};
},
},
/** @private */
isShowingVpn_: {
type: Boolean,
computed: 'computeIsShowingVpn_(deviceState)',
reflectToAttribute: true,
},
/**
* Whether the browser/ChromeOS is managed by their organization
* through enterprise policies.
* @private
*/
isManaged_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('isManaged');
},
},
/**
* Always-on VPN operating mode.
* @private {!chromeos.networkConfig.mojom.AlwaysOnVpnMode|undefined}
*/
alwaysOnVpnMode_: Number,
/**
* Always-on VPN service automatically started on login.
* @private {!string|undefined}
*/
alwaysOnVpnService_: String,
/**
* List of potential Tether hosts whose "Google Play Services"
* notifications are disabled (these notifications are required to use
* Instant Tethering).
* @private {!Array<string>}
*/
notificationsDisabledDeviceNames_: {
type: Array,
value() {
return [];
},
},
/**
* Whether to show technology badge on mobile network icons.
* @private
*/
showTechnologyBadge_: {
type: Boolean,
value() {
return loadTimeData.valueExists('showTechnologyBadge') &&
loadTimeData.getBoolean('showTechnologyBadge');
},
},
/** @private */
hasCompletedScanSinceLastEnabled_: {
type: Boolean,
value: false,
},
/**
* False if VPN is disabled by policy.
* @private {boolean}
*/
vpnIsEnabled_: {
type: Boolean,
value: false,
},
/**
* Contains the settingId of any deep link that wasn't able to be shown,
* null otherwise.
* @private {?Setting}
*/
pendingSettingId_: {
type: Number,
value: null,
},
/**
* Used by DeepLinkingBehavior to focus this page's deep links.
* @type {!Set<!Setting>}
*/
supportedSettingIds: {
type: Object,
value: () => new Set([
Setting.kWifiOnOff,
Setting.kWifiAddNetwork,
Setting.kMobileOnOff,
Setting.kInstantTetheringOnOff,
Setting.kAddESimNetwork,
]),
},
};
}
static get observers() {
return [
'deviceStateChanged_(deviceState)',
'onAlwaysOnVpnChanged_(alwaysOnVpnMode_, alwaysOnVpnService_)',
];
}
/** @override */
constructor() {
super();
/** RouteOriginBehavior override */
this.route_ = routes.INTERNET_NETWORKS;
/** @private {number|null} */
this.scanIntervalId_ = null;
/** @private {!InternetPageBrowserProxy} */
this.browserProxy_ = InternetPageBrowserProxyImpl.getInstance();
/** @private {!chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
this.networkConfig_ =
MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
}
/** @override */
ready() {
super.ready();
this.browserProxy_.setGmsCoreNotificationsDisabledDeviceNamesCallback(
(notificationsDisabledDeviceNames) => {
this.notificationsDisabledDeviceNames_ =
notificationsDisabledDeviceNames;
});
this.browserProxy_.requestGmsCoreNotificationsDisabledDeviceNames();
this.addFocusConfig(routes.KNOWN_NETWORKS, '#knownNetworksSubpageButton');
}
/** override */
disconnectedCallback() {
super.disconnectedCallback();
this.stopScanning_();
}
/**
* Overridden from DeepLinkingBehavior.
* @param {!Setting} settingId
* @return {boolean}
*/
beforeDeepLinkAttempt(settingId) {
if (settingId === Setting.kAddESimNetwork) {
afterNextRender(this, () => {
const deepLinkElement =
this.shadowRoot.querySelector('cellular-networks-list')
.getAddEsimButton();
if (!deepLinkElement || deepLinkElement.hidden) {
console.warn(`Element with deep link id ${settingId} not focusable.`);
return;
}
this.showDeepLinkElement(deepLinkElement);
});
return false;
}
if (settingId === Setting.kInstantTetheringOnOff) {
// Wait for element to load.
afterNextRender(this, () => {
// If both Cellular and Instant Tethering are enabled, we show a special
// toggle for Instant Tethering. If it exists, deep link to it.
const tetherEnabled =
this.shadowRoot.querySelector('#tetherEnabledButton');
if (tetherEnabled) {
this.showDeepLinkElement(tetherEnabled);
return;
}
// Otherwise, the device does not support Cellular and Instant Tethering
// on/off is controlled by the top-level "Mobile data" toggle instead.
const deviceEnabled =
this.shadowRoot.querySelector('#deviceEnabledButton');
if (deviceEnabled) {
this.showDeepLinkElement(deviceEnabled);
return;
}
console.warn(`Element with deep link id ${settingId} not focusable.`);
});
// Stop deep link attempt since we completed it manually.
return false;
}
return true;
}
/**
* RouteObserverBehavior
* @param {!Route} newRoute
* @param {!Route=} oldRoute
* @protected
*/
currentRouteChanged(newRoute, oldRoute) {
if (newRoute !== routes.INTERNET_NETWORKS) {
this.stopScanning_();
return;
}
this.init();
RouteOriginBehaviorImpl.currentRouteChanged.call(this, newRoute, oldRoute);
this.attemptDeepLink().then(result => {
if (!result.deepLinkShown && result.pendingSettingId) {
// Store any deep link settingId that wasn't shown so we can try again
// in getNetworkStateList_.
this.pendingSettingId_ = result.pendingSettingId;
}
});
}
init() {
// Clear any stale data.
this.networkStateList_ = [];
this.thirdPartyVpns_ = {};
this.hasCompletedScanSinceLastEnabled_ = false;
this.showSpinner = false;
// Request the list of networks and start scanning if necessary.
this.getNetworkStateList_();
this.updateScanning_();
// Get always-on VPN configuration.
this.updateAlwaysOnVpnPreferences_();
}
/**
* NetworkListenerBehavior override
* @param {!Array<OncMojo.NetworkStateProperties>} networks
*/
onActiveNetworksChanged(networks) {
this.getNetworkStateList_();
}
/** NetworkListenerBehavior override */
onNetworkStateListChanged() {
this.getNetworkStateList_();
this.updateAlwaysOnVpnPreferences_();
}
/** NetworkListenerBehavior override */
onVpnProvidersChanged() {
if (this.deviceState.type !== mojom.NetworkType.kVPN) {
return;
}
this.getNetworkStateList_();
}
/** @private */
deviceStateChanged_() {
if (this.deviceState !== undefined) {
// Set |vpnIsEnabled_| to be used for VPN special cases.
if (this.deviceState.type === mojom.NetworkType.kVPN) {
this.vpnIsEnabled_ = this.deviceState.deviceState ===
chromeos.networkConfig.mojom.DeviceStateType.kEnabled;
}
// A scan has completed if the spinner was active (i.e., scanning was
// active) and the device is no longer scanning.
this.hasCompletedScanSinceLastEnabled_ = this.showSpinner &&
!this.deviceState.scanning &&
this.deviceState.deviceState === mojom.DeviceStateType.kEnabled;
// If the cellular network list is showing and currently inhibited, there
// is a separate spinner that shows in the CellularNetworkList.
if (this.shouldShowCellularNetworkList_() && this.isDeviceInhibited_()) {
this.showSpinner = false;
} else {
this.showSpinner = !!this.deviceState.scanning;
}
}
// Scans should only be triggered by the "networks" subpage.
if (Router.getInstance().getCurrentRoute() !== routes.INTERNET_NETWORKS) {
this.stopScanning_();
return;
}
this.getNetworkStateList_();
this.updateScanning_();
}
/** @private */
updateScanning_() {
if (!this.deviceState) {
return;
}
if (this.shouldStartScan_()) {
this.startScanning_();
return;
}
}
/**
* @return {boolean}
* @private
*/
shouldStartScan_() {
// Scans should be kicked off from the Wi-Fi networks subpage.
if (this.deviceState.type === mojom.NetworkType.kWiFi) {
return true;
}
// Scans should be kicked off from the Mobile data subpage, as long as it
// includes Tether networks.
if (this.deviceState.type === mojom.NetworkType.kTether ||
(this.deviceState.type === mojom.NetworkType.kCellular &&
this.tetherDeviceState)) {
return true;
}
return false;
}
/** @private */
startScanning_() {
if (this.scanIntervalId_ != null) {
return;
}
const INTERVAL_MS = 10 * 1000;
let type = this.deviceState.type;
if (type === mojom.NetworkType.kCellular && this.tetherDeviceState) {
// Only request tether scan. Cellular scan is disruptive and should
// only be triggered by explicit user action.
type = mojom.NetworkType.kTether;
}
this.networkConfig_.requestNetworkScan(type);
this.scanIntervalId_ = window.setInterval(() => {
this.networkConfig_.requestNetworkScan(type);
}, INTERVAL_MS);
}
/** @private */
stopScanning_() {
if (this.scanIntervalId_ == null) {
return;
}
window.clearInterval(this.scanIntervalId_);
this.scanIntervalId_ = null;
}
/** @private */
getNetworkStateList_() {
if (!this.deviceState) {
return;
}
const filter = {
filter: chromeos.networkConfig.mojom.FilterType.kVisible,
limit: chromeos.networkConfig.mojom.NO_LIMIT,
networkType: this.deviceState.type,
};
this.networkConfig_.getNetworkStateList(filter).then(response => {
this.onGetNetworks_(response.result);
// Check if we have yet to focus a deep-linked element.
if (!this.pendingSettingId_) {
return;
}
this.showDeepLink(this.pendingSettingId_).then(result => {
if (result.deepLinkShown) {
this.pendingSettingId_ = null;
}
});
});
}
/**
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStates
* @private
*/
onGetNetworks_(networkStates) {
if (!this.deviceState) {
// Edge case when device states change before this callback.
return;
}
// For the Cellular/Mobile subpage, also request Tether networks.
if (this.deviceState.type === mojom.NetworkType.kCellular &&
this.tetherDeviceState) {
const filter = {
filter: chromeos.networkConfig.mojom.FilterType.kVisible,
limit: chromeos.networkConfig.mojom.NO_LIMIT,
networkType: mojom.NetworkType.kTether,
};
this.networkConfig_.getNetworkStateList(filter).then(response => {
this.set('networkStateList_', networkStates.concat(response.result));
});
return;
}
// For VPNs, separate out third party (Extension + Arc) VPNs.
if (this.deviceState.type === mojom.NetworkType.kVPN) {
const builtinNetworkStates = [];
const thirdPartyVpns = {};
networkStates.forEach(state => {
assert(state.type === mojom.NetworkType.kVPN);
switch (state.typeState.vpn.type) {
case mojom.VpnType.kIKEv2:
case mojom.VpnType.kL2TPIPsec:
case mojom.VpnType.kOpenVPN:
case mojom.VpnType.kWireGuard:
builtinNetworkStates.push(state);
break;
case mojom.VpnType.kArc:
// Only show connected Arc VPNs.
if (!OncMojo.connectionStateIsConnected(state.connectionState)) {
break;
}
// Otherwise Arc VPNs are treated the same as Extension VPNs.
case mojom.VpnType.kExtension:
const providerId = state.typeState.vpn.providerId;
thirdPartyVpns[providerId] = thirdPartyVpns[providerId] || [];
thirdPartyVpns[providerId].push(state);
break;
}
});
networkStates = builtinNetworkStates;
this.thirdPartyVpns_ = thirdPartyVpns;
}
this.set('networkStateList_', networkStates);
}
/**
* Returns an ordered list of VPN providers for all third party VPNs and any
* other known providers.
* @param {!Array<!chromeos.networkConfig.mojom.VpnProvider>} vpnProviders
* @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
* @return {!Array<!chromeos.networkConfig.mojom.VpnProvider>}
* @private
*/
getVpnProviders_(vpnProviders, thirdPartyVpns) {
// First add providers for configured thirdPartyVpns. This list will
// generally be empty or small.
const configuredProviders = [];
for (const vpnList of Object.values(thirdPartyVpns)) {
assert(vpnList.length > 0);
// All vpns in the list will have the same type and provider id.
const vpn = vpnList[0].typeState.vpn;
const provider = {
type: vpn.type,
providerId: vpn.providerId,
providerName: vpn.providerName || vpn.providerId,
appId: '',
lastLaunchTime: {internalValue: 0},
};
configuredProviders.push(provider);
}
// Next update or append known third party providers.
const unconfiguredProviders = [];
for (const provider of vpnProviders) {
const idx = configuredProviders.findIndex(
p => p.providerId === provider.providerId);
if (idx >= 0) {
configuredProviders[idx] = provider;
} else {
unconfiguredProviders.push(provider);
}
}
return configuredProviders.concat(unconfiguredProviders);
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean} True if the device is enabled or if it is a VPN.
* Note: This function will always return true for VPN because VPNs can be
* disabled by policy only for built-in VPNs (OpenVPN & L2TP). So even
* when VPNs are disabled by policy; the VPN network summary item should
* still be visible and actionable to show details for other VPN
* providers.
* @private
*/
deviceIsEnabled_(deviceState) {
return !!deviceState &&
(deviceState.type === mojom.NetworkType.kVPN ||
deviceState.deviceState === mojom.DeviceStateType.kEnabled);
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {string} onstr
* @param {string} offstr
* @return {string}
* @private
*/
getOffOnString_(deviceState, onstr, offstr) {
return this.deviceIsEnabled_(deviceState) ? onstr : offstr;
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
enableToggleIsVisible_(deviceState) {
return !!deviceState && deviceState.type !== mojom.NetworkType.kEthernet &&
deviceState.type !== mojom.NetworkType.kVPN;
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
enableToggleIsEnabled_(deviceState) {
if (!deviceState) {
return false;
}
if (deviceState.deviceState ===
chromeos.networkConfig.mojom.DeviceStateType.kProhibited) {
return false;
}
if (OncMojo.deviceStateIsIntermediate(deviceState.deviceState)) {
return false;
}
return !this.isDeviceInhibited_();
}
/**
* @return {boolean}
* @private
*/
isDeviceInhibited_() {
if (!this.deviceState) {
return false;
}
return OncMojo.deviceIsInhibited(this.deviceState);
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {string}
* @private
*/
getToggleA11yString_(deviceState) {
if (!this.enableToggleIsVisible_(deviceState)) {
return '';
}
switch (deviceState.type) {
case mojom.NetworkType.kTether:
case mojom.NetworkType.kCellular:
return this.i18n('internetToggleMobileA11yLabel');
case mojom.NetworkType.kWiFi:
return this.i18n('internetToggleWiFiA11yLabel');
}
assertNotReached();
return '';
}
/**
* @param {!mojom.VpnProvider} provider
* @return {string}
* @private
*/
getAddThirdPartyVpnA11yString_(provider) {
return this.i18n('internetAddThirdPartyVPN', provider.providerName || '');
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!mojom.GlobalPolicy} globalPolicy
* @return {boolean}
* @private
*/
allowAddConnection_(deviceState, globalPolicy) {
if (!this.deviceIsEnabled_(deviceState)) {
return false;
}
return globalPolicy && !globalPolicy.allowOnlyPolicyWifiNetworksToConnect;
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!mojom.GlobalPolicy} globalPolicy
* @return {boolean}
* @private
*/
showAddWifiButton_(deviceState, globalPolicy) {
if (!deviceState || deviceState.type !== mojom.NetworkType.kWiFi) {
return false;
}
return this.allowAddConnection_(deviceState, globalPolicy);
}
/**
* @private
* @param {!string} type
*/
dispatchShowConfigEvent_(type) {
const event = new CustomEvent('show-config', {
bubbles: true,
composed: true,
detail: {type},
});
this.dispatchEvent(event);
}
/** @private */
onAddWifiButtonTap_() {
assert(this.deviceState, 'Device state is falsey - Wifi expected.');
const type = this.deviceState.type;
assert(type === mojom.NetworkType.kWiFi, 'Wifi type expected.');
this.dispatchShowConfigEvent_(OncMojo.getNetworkTypeString(type));
}
/** @private */
onAddVpnButtonTap_() {
assert(this.deviceState, 'Device state is falsey - VPN expected.');
const type = this.deviceState.type;
assert(type === mojom.NetworkType.kVPN, 'VPN type expected.');
this.dispatchShowConfigEvent_(OncMojo.getNetworkTypeString(type));
}
/**
* @param {!{model: !{item: !mojom.VpnProvider}}} event
* @private
*/
onAddThirdPartyVpnTap_(event) {
const provider = event.model.item;
this.browserProxy_.addThirdPartyVpn(provider.appId);
recordSettingChange();
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
knownNetworksIsVisible_(deviceState) {
return !!deviceState && deviceState.type === mojom.NetworkType.kWiFi;
}
/**
* Event triggered when the known networks button is clicked.
* @private
*/
onKnownNetworksTap_() {
assert(this.deviceState.type === mojom.NetworkType.kWiFi);
const showKnownNetworksEvent = new CustomEvent('show-known-networks', {
bubbles: true,
composed: true,
detail: this.deviceState.type,
});
this.dispatchEvent(showKnownNetworksEvent);
}
/**
* Event triggered when the enable button is toggled.
* @param {!Event} event
* @private
*/
onDeviceEnabledChange_(event) {
assert(this.deviceState);
const deviceEnabledToggledEvent =
new CustomEvent('device-enabled-toggled', {
bubbles: true,
composed: true,
detail: {
enabled: !this.deviceIsEnabled_(this.deviceState),
type: this.deviceState.type,
},
});
this.dispatchEvent(deviceEnabledToggledEvent);
}
/**
* @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
* @param {!mojom.VpnProvider} provider
* @return {!Array<!OncMojo.NetworkStateProperties>}
* @private
*/
getThirdPartyVpnNetworks_(thirdPartyVpns, provider) {
return thirdPartyVpns[provider.providerId] || [];
}
/**
* @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
* @param {!mojom.VpnProvider} provider
* @return {boolean}
* @private
*/
haveThirdPartyVpnNetwork_(thirdPartyVpns, provider) {
const list = this.getThirdPartyVpnNetworks_(thirdPartyVpns, provider);
return !!list.length;
}
/**
* Event triggered when a network list item is selected.
* @param {!{target: HTMLElement, detail: !OncMojo.NetworkStateProperties}} e
* @private
*/
onNetworkSelected_(e) {
assert(this.globalPolicy);
assert(this.defaultNetwork !== undefined);
const networkState = e.detail;
e.target.blur();
if (this.canAttemptConnection_(networkState)) {
const networkConnectEvent = new CustomEvent('network-connect', {
bubbles: true,
composed: true,
detail: {networkState},
});
this.dispatchEvent(networkConnectEvent);
recordSettingChange();
return;
}
const showDetailEvent = new CustomEvent('show-detail', {
bubbles: true,
composed: true,
detail: networkState,
});
this.dispatchEvent(showDetailEvent);
}
/**
* @param {!OncMojo.NetworkStateProperties} state The network state.
* @return {boolean}
* @private
*/
isBlockedByPolicy_(state) {
if (state.type !== mojom.NetworkType.kWiFi &&
state.type !== mojom.NetworkType.kCellular) {
return false;
}
if (this.isPolicySource(state.source) || !this.globalPolicy) {
return false;
}
if (state.type === mojom.NetworkType.kCellular) {
return !!this.globalPolicy.allowOnlyPolicyCellularNetworks;
}
return !!this.globalPolicy.allowOnlyPolicyWifiNetworksToConnect ||
(!!this.globalPolicy.allowOnlyPolicyWifiNetworksToConnectIfAvailable &&
!!this.deviceState && !!this.deviceState.managedNetworkAvailable) ||
(!!this.globalPolicy.blockedHexSsids &&
this.globalPolicy.blockedHexSsids.includes(
state.typeState.wifi.hexSsid));
}
/**
* Determines whether or not it is possible to attempt a connection to the
* provided network (e.g., whether it's possible to connect or configure the
* network for connection).
* @param {!OncMojo.NetworkStateProperties} state The network state.
* @private
*/
canAttemptConnection_(state) {
if (state.connectionState !== mojom.ConnectionStateType.kNotConnected) {
return false;
}
if (this.isBlockedByPolicy_(state)) {
return false;
}
// VPNs can only be connected if there is an existing network connection to
// use with the VPN.
if (state.type === mojom.NetworkType.kVPN &&
(!this.defaultNetwork ||
!OncMojo.connectionStateIsConnected(
this.defaultNetwork.connectionState))) {
return false;
}
// Locked SIM profiles must be unlocked before a connection can occur.
if (state.type === mojom.NetworkType.kCellular &&
state.typeState.cellular.simLocked) {
return false;
}
return true;
}
/**
* @param {string} typeString
* @param {OncMojo.DeviceStateProperties} device
* @return {boolean}
* @private
*/
matchesType_(typeString, device) {
return !!device &&
device.type === OncMojo.getNetworkTypeFromString(typeString);
}
/**
* @param {!Array<!OncMojo.NetworkStateProperties>}
* networkStateList
* @return {boolean}
* @private
*/
shouldShowNetworkList_(networkStateList) {
if (this.shouldShowCellularNetworkList_()) {
return false;
}
if (!!this.deviceState &&
this.deviceState.type === mojom.NetworkType.kVPN) {
return this.shouldShowVpnList_();
}
return this.networkStateList_.length > 0;
}
/**
* @return {boolean} True if native VPN is not disabled by policy and there
* are more than one VPN network configured.
* @private
*/
shouldShowVpnList_() {
return this.vpnIsEnabled_ && this.networkStateList_.length > 0;
}
/**
* @return {boolean}
* @private
*/
shouldShowCellularNetworkList_() {
// Only shown if the currently-active subpage is for Cellular networks.
return !!this.deviceState &&
this.deviceState.type === mojom.NetworkType.kCellular;
}
/**
* @param {!Array<!OncMojo.NetworkStateProperties>}
* networkStateList
* @return {boolean}
* @private
*/
hideNoNetworksMessage_(networkStateList) {
return this.shouldShowCellularNetworkList_() ||
this.shouldShowNetworkList_(networkStateList);
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!OncMojo.DeviceStateProperties|undefined} tetherDeviceState
* @return {string}
* @private
*/
getNoNetworksInnerHtml_(deviceState, tetherDeviceState) {
const type = deviceState.type;
if (type === mojom.NetworkType.kTether ||
(type === mojom.NetworkType.kCellular && this.tetherDeviceState)) {
return this.i18nAdvanced('internetNoNetworksMobileData');
}
if (type === mojom.NetworkType.kVPN) {
return this.i18n('internetNoNetworks');
}
// If a scan has not yet completed since the device was last enabled, it may
// be the case that scan results are still in the process of arriving, so
// display a message stating that scanning is in progress. If a scan has
// already completed and there are still no networks present, this implies
// that there has been sufficient time to find a network, so display a
// messages stating that there are no networks. See https://crbug.com/974169
// for more details.
return this.hasCompletedScanSinceLastEnabled_ ?
this.i18n('internetNoNetworks') :
this.i18n('networkScanningLabel');
}
/**
* @param {!Array<string>} notificationsDisabledDeviceNames
* @return {boolean}
* @private
*/
showGmsCoreNotificationsSection_(notificationsDisabledDeviceNames) {
return notificationsDisabledDeviceNames.length > 0;
}
/**
* @param {!Array<string>} notificationsDisabledDeviceNames
* @return {string}
* @private
*/
getGmsCoreNotificationsDevicesString_(notificationsDisabledDeviceNames) {
if (notificationsDisabledDeviceNames.length === 1) {
return this.i18n(
'gmscoreNotificationsOneDeviceSubtitle',
notificationsDisabledDeviceNames[0]);
}
if (notificationsDisabledDeviceNames.length === 2) {
return this.i18n(
'gmscoreNotificationsTwoDevicesSubtitle',
notificationsDisabledDeviceNames[0],
notificationsDisabledDeviceNames[1]);
}
return this.i18n('gmscoreNotificationsManyDevicesSubtitle');
}
/**
* @return {boolean}
* @private
*/
computeIsShowingVpn_() {
if (!this.deviceState) {
return false;
}
return this.matchesType_(
OncMojo.getNetworkTypeString(mojom.NetworkType.kVPN), this.deviceState);
}
/**
* Tells when VPN preferences section should be displayed. It is
* displayed when the preferences are applicable to the current device.
* @return {boolean}
* @private
*/
shouldShowVpnPreferences_() {
if (!this.deviceState) {
return false;
}
// For now the section only contain always-on VPN settings. It should not be
// displayed on managed devices while the legacy always-on VPN based on ARC
// is not replaced/extended by the new implementation.
return !this.isManaged_ && this.isShowingVpn_;
}
/**
* Generates the list of VPN services available for always-on. It keeps from
* the network list only the supported technologies.
* @return {!Array<!OncMojo.NetworkStateProperties>}
* @private
*/
getAlwaysOnVpnNetworks_() {
if (!this.deviceState || this.deviceState.type !== mojom.NetworkType.kVPN) {
return [];
}
/** @type {!Array<!OncMojo.NetworkStateProperties>} */
const alwaysOnVpnList = this.networkStateList_.slice();
for (const vpnList of Object.values(this.thirdPartyVpns_)) {
assert(vpnList.length > 0);
// Exclude incompatible VPN technologies:
// - TODO(b/188864779): ARC VPNs are not supported yet,
// - Chrome VPN apps are deprecated and incompatible with lockdown mode
// (see b/206910855).
if (vpnList[0].typeState.vpn.type === mojom.VpnType.kArc ||
vpnList[0].typeState.vpn.type === mojom.VpnType.kExtension) {
continue;
}
alwaysOnVpnList.push(...vpnList);
}
return alwaysOnVpnList;
}
/**
* Fetches the always-on VPN configuration from network config.
* @private
*/
updateAlwaysOnVpnPreferences_() {
if (!this.deviceState || this.deviceState.type !== mojom.NetworkType.kVPN) {
return;
}
this.networkConfig_.getAlwaysOnVpn().then(result => {
this.alwaysOnVpnMode_ = result.properties.mode;
this.alwaysOnVpnService_ = result.properties.serviceGuid;
});
}
/**
* Handles a change in |alwaysOnVpnMode_| or |alwaysOnVpnService_|
* triggered via the observer.
* @private
*/
onAlwaysOnVpnChanged_() {
if (this.alwaysOnVpnMode_ === undefined ||
this.alwaysOnVpnService_ === undefined) {
return;
}
/** @type {!chromeos.networkConfig.mojom.AlwaysOnVpnProperties} */
const properties = {
mode: this.alwaysOnVpnMode_,
serviceGuid: this.alwaysOnVpnService_,
};
this.networkConfig_.setAlwaysOnVpn(properties);
}
}
customElements.define(
SettingsInternetSubpageElement.is, SettingsInternetSubpageElement);