blob: 9531a1f536e060158ba9562ce2fa098b45af2173 [file] [log] [blame]
// Copyright 2015 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 the network state for a specific
* type and a list of networks for that type. NOTE: It both Cellular and Tether
* technologies are available, they are combined into a single 'Mobile data'
* section. See crbug.com/726380.
*/
import 'chrome://resources/cr_components/chromeos/network/network_icon.m.js';
import 'chrome://resources/cr_components/chromeos/network/network_siminfo.m.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.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 {getSimSlotCount} from 'chrome://resources/cr_components/chromeos/network/cellular_utils.m.js';
import {CrPolicyNetworkBehaviorMojo, CrPolicyNetworkBehaviorMojoInterface} from 'chrome://resources/cr_components/chromeos/network/cr_policy_network_behavior_mojo.m.js';
import {OncMojo} from 'chrome://resources/cr_components/chromeos/network/onc_mojo.m.js';
import {CrPolicyIndicatorType} from 'chrome://resources/cr_elements/policy/cr_policy_indicator_behavior.m.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
const mojom = chromeos.networkConfig.mojom;
/**
* @constructor
* @extends {PolymerElement}
* @implements {CrPolicyNetworkBehaviorMojoInterface}
* @implements {I18nBehaviorInterface}
*/
const NetworkSummaryItemElementBase =
mixinBehaviors([CrPolicyNetworkBehaviorMojo, I18nBehavior], PolymerElement);
/** @polymer */
class NetworkSummaryItemElement extends NetworkSummaryItemElementBase {
static get is() {
return 'network-summary-item';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/**
* Device state for the network type. This might briefly be undefined if
* a device becomes unavailable.
* @type {!OncMojo.DeviceStateProperties|undefined}
*/
deviceState: {
type: Object,
notify: true,
},
/**
* If both Cellular and Tether technologies exist, we combine the
* sections and set this to the device state for Tether.
* @type {!OncMojo.DeviceStateProperties|undefined}
*/
tetherDeviceState: Object,
/**
* Network state for the active network.
* @type {!OncMojo.NetworkStateProperties|undefined}
*/
activeNetworkState: Object,
/**
* List of all network state data for the network type.
* @type {!Array<!OncMojo.NetworkStateProperties>}
*/
networkStateList: {
type: Array,
value() {
return [];
},
},
/**
* Title line describing the network type to appear in the row's top
* line. If it is undefined, the title text is set to a default value.
* @type {string|undefined}
*/
networkTitleText: String,
/**
* Whether to show technology badge on mobile network icon.
* @private
*/
showTechnologyBadge_: {
type: Boolean,
value() {
return loadTimeData.valueExists('showTechnologyBadge') &&
loadTimeData.getBoolean('showTechnologyBadge');
},
},
/** @private {!chromeos.networkConfig.mojom.GlobalPolicy|undefined} */
globalPolicy: Object,
};
}
/*
* Returns the device enabled toggle element.
* @return {?CrToggleElement}
*/
getDeviceEnabledToggle() {
return this.shadowRoot.querySelector('#deviceEnabledButton');
}
/**
* @return {string}
* @private
*/
getNetworkStateText_() {
// If SIM Locked, show warning message instead of connection state.
if (this.shouldShowLockedWarningMessage_(this.deviceState)) {
return this.i18n('networkSimLockedSubtitle');
}
if (OncMojo.deviceIsInhibited(this.deviceState)) {
return this.i18n('internetDeviceBusy');
}
const stateText =
this.getConnectionStateText_(this.activeNetworkState, this.deviceState);
if (stateText) {
return stateText;
}
// No network state, use device state.
const deviceState = this.deviceState;
if (deviceState) {
if (deviceState.type === mojom.NetworkType.kTether) {
if (deviceState.deviceState === mojom.DeviceStateType.kUninitialized) {
return this.i18n('tetherEnableBluetooth');
}
}
// Enabled or enabling states.
if (deviceState.deviceState === mojom.DeviceStateType.kEnabled) {
return this.networkStateList.length > 0 ?
this.i18n('networkListItemNotConnected') :
this.i18n('networkListItemNoNetwork');
}
if (deviceState.deviceState === mojom.DeviceStateType.kEnabling) {
return this.i18n('internetDeviceEnabling');
}
}
// No device or unknown device state, use 'off'.
return this.i18n('deviceOff');
}
/**
* @param {!OncMojo.NetworkStateProperties|undefined} networkState
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {string}
* @private
*/
getConnectionStateText_(networkState, deviceState) {
if (!networkState || !networkState.guid) {
return '';
}
const connectionState = networkState.connectionState;
const name = OncMojo.getNetworkStateDisplayName(networkState);
if (OncMojo.connectionStateIsConnected(connectionState)) {
// Ethernet networks always have the display name 'Ethernet' so we use the
// state text 'Connected' to avoid repeating the label in the sublabel.
// See http://crbug.com/989907 for details.
return networkState.type === mojom.NetworkType.kEthernet ?
this.i18n('networkListItemConnected') :
name;
}
if (connectionState === mojom.ConnectionStateType.kConnecting) {
return name ? this.i18n('networkListItemConnectingTo', name) :
this.i18n('networkListItemConnecting');
}
return this.i18n('networkListItemNotConnected');
}
/**
* @param {!OncMojo.NetworkStateProperties} activeNetworkState
* @return {boolean}
* @private
*/
showPolicyIndicator_(activeNetworkState) {
return (activeNetworkState !== undefined &&
OncMojo.connectionStateIsConnected(
activeNetworkState.connectionState)) ||
this.isPolicySource(activeNetworkState.source) ||
this.isProhibitedVpn_();
}
/**
* @param {!OncMojo.NetworkStateProperties} activeNetworkState
* @return {!CrPolicyIndicatorType} Device policy indicator for VPN when
* disabled by policy and an indicator corresponding to the source of the
* active network state otherwise.
* @private
*/
getPolicyIndicatorType_(activeNetworkState) {
if (this.isProhibitedVpn_()) {
return this.getIndicatorTypeForSource(
chromeos.networkConfig.mojom.OncSource.kDevicePolicy);
}
return this.getIndicatorTypeForSource(activeNetworkState.source);
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
showSimInfo_(deviceState) {
if (!deviceState || deviceState.type !== mojom.NetworkType.kCellular) {
return false;
}
const {pSimSlots, eSimSlots} = getSimSlotCount(deviceState);
if (eSimSlots > 0) {
// Do not show simInfo if we are using an eSIM enabled device.
return false;
}
return this.simLocked_(deviceState);
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {string}
* @private
*/
getNetworkStateClass_(deviceState) {
if (this.shouldShowLockedWarningMessage_(deviceState)) {
return 'locked-warning-message';
}
return 'network-state';
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
shouldShowLockedWarningMessage_(deviceState) {
if (!deviceState || deviceState.type !== mojom.NetworkType.kCellular ||
!deviceState.simLockStatus) {
return false;
}
// If the device is eSIM capable, never show message.
const {eSimSlots} = getSimSlotCount(deviceState);
if (eSimSlots > 0) {
return false;
}
return !!deviceState.simLockStatus.lockType;
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
simLocked_(deviceState) {
if (!deviceState) {
return false;
}
if (!deviceState.simLockStatus) {
return false;
}
const simLockType = deviceState.simLockStatus.lockType;
return simLockType === 'sim-pin' || simLockType === 'sim-puk';
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean} True if the device is enabled or if it is a VPN or if
* we are in the state of inhibited. Note:
* This function will always return true for VPNs because VPNs can be
* disabled by policy only for built-in VPNs (OpenVPN & L2TP), but always
* enabled for other VPN providers. To know whether built-in VPNs are
* disabled, use builtInVpnProhibited_() instead.
* @private
*/
deviceIsEnabled_(deviceState) {
return !!deviceState &&
(deviceState.type === mojom.NetworkType.kVPN ||
deviceState.deviceState === mojom.DeviceStateType.kEnabled ||
OncMojo.deviceIsInhibited(deviceState));
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
enableToggleIsVisible_(deviceState) {
if (!deviceState) {
return false;
}
switch (deviceState.type) {
case mojom.NetworkType.kEthernet:
case mojom.NetworkType.kVPN:
return false;
case mojom.NetworkType.kTether:
return true;
case mojom.NetworkType.kWiFi:
return deviceState.deviceState !== mojom.DeviceStateType.kUninitialized;
case mojom.NetworkType.kCellular:
if (deviceState.deviceState === mojom.DeviceStateType.kUninitialized) {
return false;
}
// Toggle should be shown as long as we are not also showing the UI for
// unlocking the SIM.
return !this.showSimInfo_(deviceState);
}
assertNotReached();
return false;
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean}
* @private
*/
enableToggleIsEnabled_(deviceState) {
return this.enableToggleIsVisible_(deviceState) &&
deviceState.deviceState !== mojom.DeviceStateType.kProhibited &&
!OncMojo.deviceIsInhibited(deviceState) &&
!OncMojo.deviceStateIsIntermediate(deviceState.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 {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {string}
* @private
*/
getToggleA11yDescribedBy_(deviceState) {
// Use network state text to describe toggle for uninitialized tether
// device. This announces details about enabling bluetooth.
if (this.enableToggleIsVisible_(deviceState) &&
deviceState.type === mojom.NetworkType.kTether &&
deviceState.deviceState === mojom.DeviceStateType.kUninitialized) {
return 'networkState';
}
return '';
}
/**
* @return {boolean} True if VPNs are disabled by policy and the current
* device is VPN.
* @private
*/
isProhibitedVpn_() {
return !!this.deviceState &&
this.deviceState.type === mojom.NetworkType.kVPN &&
this.builtInVpnProhibited_(this.deviceState);
}
/**
* @param {!chromeos.networkConfig.mojom.VpnType} vpnType
* @return {boolean}
* @private
*/
isBuiltInVpnType_(vpnType) {
return vpnType === chromeos.networkConfig.mojom.VpnType.kL2TPIPsec ||
vpnType === chromeos.networkConfig.mojom.VpnType.kOpenVPN;
}
/**
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
* @return {boolean} True if at least one non-native VPN is configured.
* @private
*/
hasNonBuiltInVpn_(networkStateList) {
const nonBuiltInVpnIndex = networkStateList.findIndex((networkState) => {
return !this.isBuiltInVpnType_(networkState.typeState.vpn.type);
});
return nonBuiltInVpnIndex !== -1;
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @return {boolean} True if the built-in VPNs are disabled by policy.
* @private
*/
builtInVpnProhibited_(deviceState) {
return !!deviceState &&
deviceState.deviceState ===
chromeos.networkConfig.mojom.DeviceStateType.kProhibited;
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
* @return {boolean} True if there is any configured VPN for a non-disabled
* VPN provider. Note: Only built-in VPN providers can be disabled by
* policy at the moment.
* @private
*/
anyVpnExists_(deviceState, networkStateList) {
return this.hasNonBuiltInVpn_(networkStateList) ||
(!this.builtInVpnProhibited_(deviceState) &&
networkStateList.length > 0);
}
/**
* @param {!OncMojo.NetworkStateProperties|undefined} activeNetworkState
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
* @return {boolean}
* @private
*/
shouldShowDetails_(activeNetworkState, deviceState, networkStateList) {
if (!!deviceState && deviceState.type === mojom.NetworkType.kVPN) {
return this.anyVpnExists_(deviceState, networkStateList);
}
return this.deviceIsEnabled_(deviceState) &&
(!!activeNetworkState.guid || networkStateList.length > 0);
}
/**
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
* @return {boolean}
* @private
*/
shouldShowSubpage_(deviceState, networkStateList) {
if (!deviceState) {
return false;
}
const type = deviceState.type;
if (type === mojom.NetworkType.kTether ||
(type === mojom.NetworkType.kCellular && this.tetherDeviceState)) {
// The "Mobile data" subpage should always be shown if Tether is
// available, even if there are currently no associated networks.
return true;
}
if (type === mojom.NetworkType.kCellular) {
if (OncMojo.deviceIsInhibited(deviceState)) {
// The "Mobile data" subpage should be shown if the device state is
// inhibited.
return true;
}
// When network type is Cellular, always show "Mobile data" subpage, when
// at least one eSIM or pSIM slot is available
const {pSimSlots, eSimSlots} = getSimSlotCount(deviceState);
if (eSimSlots > 0 || pSimSlots > 0) {
return true;
}
}
if (type === mojom.NetworkType.kVPN) {
return this.anyVpnExists_(deviceState, networkStateList);
}
let minlen;
if (type === mojom.NetworkType.kWiFi) {
// WiFi subpage includes 'Known Networks' so always show, even if the
// technology is still enabling / scanning, or none are visible.
minlen = 0;
} else {
// By default, only show the subpage if there are 2+ networks
minlen = 2;
}
return networkStateList.length >= minlen;
}
/**
* This handles clicking the network summary item row. Clicking this row can
* lead to toggling device enablement or showing the corresponding networks
* list or showing details about a network or doing nothing based on the
* device and networks states.
* @param {!Event} event The enable button event.
* @private
*/
onShowDetailsTap_(event) {
if (!this.deviceIsEnabled_(this.deviceState)) {
if (this.enableToggleIsEnabled_(this.deviceState)) {
const type = this.deviceState.type;
const deviceEnabledToggledEvent =
new CustomEvent('device-enabled-toggled', {
bubbles: true,
composed: true,
detail: {enabled: true, type: type},
});
this.dispatchEvent(deviceEnabledToggledEvent);
}
} else if (this.shouldShowSubpage_(
this.deviceState, this.networkStateList)) {
const showNetworksEvent = new CustomEvent('show-networks', {
bubbles: true,
composed: true,
detail: this.deviceState.type,
});
this.dispatchEvent(showNetworksEvent);
} else if (this.shouldShowDetails_(
this.activeNetworkState, this.deviceState,
this.networkStateList)) {
if (this.activeNetworkState.guid) {
const showDetailEvent = new CustomEvent('show-detail', {
bubbles: true,
composed: true,
detail: this.activeNetworkState,
});
this.dispatchEvent(showDetailEvent);
} else if (this.networkStateList.length > 0) {
const showDetailEvent = new CustomEvent('show-detail', {
bubbles: true,
composed: true,
detail: this.networkStateList[0],
});
this.dispatchEvent(showDetailEvent);
}
}
event.stopPropagation();
}
/**
* @param {!OncMojo.NetworkStateProperties} activeNetworkState
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
* @return {boolean}
* @private
*/
isItemActionable_(activeNetworkState, deviceState, networkStateList) {
// The boolean logic here matches onShowDetailsTap_ method that handles the
// item click event.
if (!this.deviceIsEnabled_(this.deviceState)) {
// When device is disabled, tapping the item flips the enable toggle. So
// the item is actionable only when the toggle is enabled.
return this.enableToggleIsEnabled_(this.deviceState);
}
// Item is actionable if tapping should show either networks subpage or the
// network details page.
return this.shouldShowSubpage_(this.deviceState, this.networkStateList) ||
this.shouldShowDetails_(
activeNetworkState, deviceState, networkStateList);
}
/**
* @param {!OncMojo.NetworkStateProperties} activeNetworkState
* @param {!OncMojo.DeviceStateProperties|undefined} deviceState
* @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
* @return {boolean}
* @private
*/
showArrowButton_(activeNetworkState, deviceState, networkStateList) {
// If SIM info is shown on the right side of the item, no arrow should be
// shown.
if (this.showSimInfo_(deviceState)) {
return false;
}
if (!this.deviceIsEnabled_(deviceState)) {
return false;
}
return this.shouldShowSubpage_(deviceState, networkStateList) ||
this.shouldShowDetails_(
activeNetworkState, deviceState, networkStateList);
}
/**
* Event triggered when the enable button is toggled.
* @param {!Event} event
* @private
*/
onDeviceEnabledChange_(event) {
assert(this.deviceState);
const deviceIsEnabled = this.deviceIsEnabled_(this.deviceState);
const deviceEnabledToggledEvent =
new CustomEvent('device-enabled-toggled', {
bubbles: true,
composed: true,
detail: {enabled: !deviceIsEnabled, type: this.deviceState.type},
});
this.dispatchEvent(deviceEnabledToggledEvent);
// Set the device state to enabling or disabling until updated.
this.deviceState.deviceState = deviceIsEnabled ?
mojom.DeviceStateType.kDisabling :
mojom.DeviceStateType.kEnabling;
}
/**
* @return {string}
* @private
*/
getTitleText_() {
return this.networkTitleText ||
this.getNetworkTypeString_(this.activeNetworkState.type);
}
/**
* Make sure events in embedded components do not propagate to onDetailsTap_.
* @param {!Event} event
* @private
*/
doNothing_(event) {
event.stopPropagation();
}
/**
* @param {!mojom.NetworkType} type
* @return {string}
* @private
*/
getNetworkTypeString_(type) {
// The shared Cellular/Tether subpage is referred to as "Mobile".
// TODO(khorimoto): Remove once Cellular/Tether are split into their own
// sections.
if (type === mojom.NetworkType.kCellular ||
type === mojom.NetworkType.kTether) {
type = mojom.NetworkType.kMobile;
}
return this.i18n('OncType' + OncMojo.getNetworkTypeString(type));
}
}
customElements.define(NetworkSummaryItemElement.is, NetworkSummaryItemElement);