blob: 279d65105c112587fe5f12feaa552727a8d17668 [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.
*/
(function() {
const mojom = chromeos.networkConfig.mojom;
Polymer({
is: 'settings-internet-subpage',
behaviors: [
CrNetworkListenerBehavior,
CrPolicyNetworkBehaviorMojo,
settings.RouteObserverBehavior,
I18nBehavior,
],
properties: {
/**
* 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,
},
/**
* List of all network state data for the network type.
* @private {!Array<!OncMojo.NetworkStateProperties>}
*/
networkStateList_: {
type: Array,
value: function() {
return [];
},
},
/**
* Dictionary of lists of network states for third party VPNs.
* @private {!Object<!Array<!OncMojo.NetworkStateProperties>>}
*/
thirdPartyVpns_: {
type: Object,
value: function() {
return {};
},
},
/**
* 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: function() {
return [];
},
},
/**
* Whether to show technology badge on mobile network icons.
* @private
*/
showTechnologyBadge_: {
type: Boolean,
value: function() {
return loadTimeData.valueExists('showTechnologyBadge') &&
loadTimeData.getBoolean('showTechnologyBadge');
}
},
/** @private */
hasCompletedScanSinceLastEnabled_: {
type: Boolean,
value: false,
},
},
observers: ['deviceStateChanged_(deviceState)'],
/** @private {number|null} */
scanIntervalId_: null,
/** @private {settings.InternetPageBrowserProxy} */
browserProxy_: null,
/** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
networkConfig_: null,
/** @override */
created: function() {
this.browserProxy_ = settings.InternetPageBrowserProxyImpl.getInstance();
this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
.getMojoServiceRemote();
},
/** @override */
ready: function() {
this.browserProxy_.setGmsCoreNotificationsDisabledDeviceNamesCallback(
this.onNotificationsDisabledDeviceNamesReceived_.bind(this));
this.browserProxy_.requestGmsCoreNotificationsDisabledDeviceNames();
},
/** override */
detached: function() {
this.stopScanning_();
},
/**
* settings.RouteObserverBehavior
* @param {!settings.Route} route
* @protected
*/
currentRouteChanged: function(route) {
if (route != settings.routes.INTERNET_NETWORKS) {
this.stopScanning_();
return;
}
this.init();
},
init: function() {
// 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_();
},
/**
* CrNetworkListenerBehavior override
* @param {!Array<OncMojo.NetworkStateProperties>} networks
*/
onActiveNetworksChanged: function(networks) {
this.getNetworkStateList_();
},
/** CrNetworkListenerBehavior override */
onNetworkStateListChanged: function() {
this.getNetworkStateList_();
},
/** CrNetworkListenerBehavior override */
onVpnProvidersChanged: function() {
if (this.deviceState.type != mojom.NetworkType.kVPN) {
return;
}
this.getNetworkStateList_();
},
/** @private */
deviceStateChanged_: function() {
if (this.deviceState !== undefined) {
// 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;
this.showSpinner = !!this.deviceState.scanning;
}
// Scans should only be triggered by the "networks" subpage.
if (settings.getCurrentRoute() != settings.routes.INTERNET_NETWORKS) {
this.stopScanning_();
return;
}
this.getNetworkStateList_();
this.updateScanning_();
},
/** @private */
updateScanning_: function() {
if (!this.deviceState) {
return;
}
if (this.shouldStartScan_()) {
this.startScanning_();
return;
}
},
/**
* @return {boolean}
* @private
*/
shouldStartScan_: function() {
// 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_: function() {
if (this.scanIntervalId_ != null) {
return;
}
const INTERVAL_MS = 10 * 1000;
this.networkConfig_.requestNetworkScan(this.deviceState.type);
this.scanIntervalId_ = window.setInterval(() => {
this.networkConfig_.requestNetworkScan(this.deviceState.type);
}, INTERVAL_MS);
},
/** @private */
stopScanning_: function() {
if (this.scanIntervalId_ == null) {
return;
}
window.clearInterval(this.scanIntervalId_);
this.scanIntervalId_ = null;
},
/** @private */
getNetworkStateList_: function() {
if (!this.deviceState) {
return;
}
const filter = {
filter: chromeos.networkConfig.mojom.FilterType.kVisible,
limit: chromeos.networkConfig.mojom.kNoLimit,
networkType: this.deviceState.type,
};
this.networkConfig_.getNetworkStateList(filter).then(response => {
this.onGetNetworks_(response.result);
});
},
/**
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStates
* @private
*/
onGetNetworks_: function(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.kNoLimit,
networkType: mojom.NetworkType.kTether,
};
this.networkConfig_.getNetworkStateList(filter).then(response => {
const tetherNetworkStates = response.result;
this.networkStateList_ = networkStates.concat(tetherNetworkStates);
});
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.kL2TPIPsec:
case mojom.VpnType.kOpenVPN:
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.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 {!Array<string>} notificationsDisabledDeviceNames
* @private
*/
onNotificationsDisabledDeviceNamesReceived_: function(
notificationsDisabledDeviceNames) {
this.notificationsDisabledDeviceNames_ = notificationsDisabledDeviceNames;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean} Whether or not the device state is enabled.
* @private
*/
deviceIsEnabled_: function(deviceState) {
return !!deviceState &&
deviceState.deviceState ==
chromeos.networkConfig.mojom.DeviceStateType.kEnabled;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {string} onstr
* @param {string} offstr
* @return {string}
* @private
*/
getOffOnString_: function(deviceState, onstr, offstr) {
return this.deviceIsEnabled_(deviceState) ? onstr : offstr;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
enableToggleIsVisible_: function(deviceState) {
return !!deviceState && deviceState.type != mojom.NetworkType.kEthernet &&
deviceState.type != mojom.NetworkType.kVPN;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
enableToggleIsEnabled_: function(deviceState) {
return !!deviceState &&
deviceState.deviceState !=
chromeos.networkConfig.mojom.DeviceStateType.kProhibited;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {string}
* @private
*/
getToggleA11yString_: function(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_: function(provider) {
return this.i18n('internetAddThirdPartyVPN', provider.providerName || '');
},
/**
* @param {!mojom.GlobalPolicy} globalPolicy
* @return {boolean}
* @private
*/
allowAddConnection_: function(globalPolicy) {
return globalPolicy && !globalPolicy.allowOnlyPolicyNetworksToConnect;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!mojom.GlobalPolicy} globalPolicy
* @return {boolean}
* @private
*/
showAddButton_: function(deviceState, globalPolicy) {
if (!deviceState || deviceState.type != mojom.NetworkType.kWiFi) {
return false;
}
if (!this.deviceIsEnabled_(deviceState)) {
return false;
}
return this.allowAddConnection_(globalPolicy);
},
/** @private */
onAddButtonTap_: function() {
assert(this.deviceState);
const type = this.deviceState.type;
assert(type != mojom.NetworkType.kCellular);
this.fire('show-config', {type: OncMojo.getNetworkTypeString(type)});
},
/**
* @param {!{model: !{item: !mojom.VpnProvider}}} event
* @private
*/
onAddThirdPartyVpnTap_: function(event) {
const provider = event.model.item;
this.browserProxy_.addThirdPartyVpn(provider.appId);
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
knownNetworksIsVisible_: function(deviceState) {
return !!deviceState && deviceState.type == mojom.NetworkType.kWiFi;
},
/**
* Event triggered when the known networks button is clicked.
* @private
*/
onKnownNetworksTap_: function() {
assert(this.deviceState.type == mojom.NetworkType.kWiFi);
this.fire('show-known-networks', this.deviceState.type);
},
/**
* Event triggered when the enable button is toggled.
* @param {!Event} event
* @private
*/
onDeviceEnabledChange_: function(event) {
assert(this.deviceState);
this.fire('device-enabled-toggled', {
enabled: !this.deviceIsEnabled_(this.deviceState),
type: this.deviceState.type
});
},
/**
* @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
* @param {!mojom.VpnProvider} provider
* @return {!Array<!OncMojo.NetworkStateProperties>}
* @private
*/
getThirdPartyVpnNetworks_: function(thirdPartyVpns, provider) {
return thirdPartyVpns[provider.providerId] || [];
},
/**
* @param {!Object<!Array<!OncMojo.NetworkStateProperties>>} thirdPartyVpns
* @param {!mojom.VpnProvider} provider
* @return {boolean}
* @private
*/
haveThirdPartyVpnNetwork_: function(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_: function(e) {
assert(this.globalPolicy);
assert(this.defaultNetwork !== undefined);
const networkState = e.detail;
e.target.blur();
if (this.canAttemptConnection_(networkState)) {
this.fire('network-connect', {networkState: networkState});
return;
}
this.fire('show-detail', networkState);
},
/**
* @param {!OncMojo.NetworkStateProperties} state The network state.
* @return {boolean}
* @private
*/
isBlockedByPolicy_: function(state) {
if (state.type != mojom.NetworkType.kWiFi ||
this.isPolicySource(state.source) || !this.globalPolicy) {
return false;
}
return !!this.globalPolicy.allowOnlyPolicyNetworksToConnect ||
(!!this.globalPolicy.allowOnlyPolicyNetworksToConnectIfAvailable &&
!!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_: function(state) {
if (state.connectionState != mojom.ConnectionStateType.kNotConnected) {
return false;
}
if (this.isBlockedByPolicy_(state)) {
return false;
}
if (state.type == mojom.NetworkType.kVPN &&
(!this.defaultNetwork ||
!OncMojo.connectionStateIsConnected(
this.defaultNetwork.connectionState))) {
return false;
}
// Cellular networks do not have a configuration flow, so it's not possible
// to attempt a connection if the network is not conncetable.
if (state.type == mojom.NetworkType.kCellular && !state.connectable) {
return false;
}
return true;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!OncMojo.DeviceStateProperties|undefined} tetherDeviceState
* @return {boolean}
* @private
*/
tetherToggleIsVisible_: function(deviceState, tetherDeviceState) {
return !!deviceState && deviceState.type == mojom.NetworkType.kCellular &&
!!tetherDeviceState;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!OncMojo.DeviceStateProperties|undefined} tetherDeviceState
* @return {boolean}
* @private
*/
tetherToggleIsEnabled_: function(deviceState, tetherDeviceState) {
return this.tetherToggleIsVisible_(deviceState, tetherDeviceState) &&
this.enableToggleIsEnabled_(tetherDeviceState) &&
tetherDeviceState.deviceState !=
chromeos.networkConfig.mojom.DeviceStateType.kUninitialized;
},
/**
* @param {!Event} event
* @private
*/
onTetherEnabledChange_: function(event) {
this.fire('device-enabled-toggled', {
enabled: !this.deviceIsEnabled_(this.tetherDeviceState),
type: mojom.NetworkType.kTether,
});
event.stopPropagation();
},
/**
* @param {string} typeString
* @param {OncMojo.DeviceStateProperties} device
* @return {boolean}
* @private
*/
matchesType_: function(typeString, device) {
return device &&
device.type == OncMojo.getNetworkTypeFromString(typeString);
},
/**
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
* @return {boolean}
* @private
*/
shouldShowNetworkList_: function(networkStateList) {
return networkStateList.length > 0;
},
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!OncMojo.DeviceStateProperties|undefined} tetherDeviceState
* @return {string}
* @private
*/
getNoNetworksInnerHtml_: function(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_: function(notificationsDisabledDeviceNames) {
return notificationsDisabledDeviceNames.length > 0;
},
/**
* @param {!Array<string>} notificationsDisabledDeviceNames
* @return {string}
* @private
*/
getGmsCoreNotificationsDevicesString_: function(
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');
},
});
})();