chromium/ash/webui/common/resources/network/cellular_utils.js

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '//resources/ash/common/network/onc_mojo.js';

import {MojoInterfaceProviderImpl} from '//resources/ash/common/network/mojo_interface_provider.js';
import {OncMojo} from '//resources/ash/common/network/onc_mojo.js';
import {ApnProperties, ApnType, DeviceStateProperties, FilterType, ManagedProperties, NetworkStateProperties, NO_LIMIT} from '//resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
import {ConnectionStateType, NetworkType} from '//resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';

/**
 * TODO(b/162365553): Implement Edit mode.
 * @enum {string}
 */
export const ApnDetailDialogMode = {
  CREATE: 'create',
  EDIT: 'edit',
  VIEW: 'view',
};

/**
 * @typedef {{
 *   apn: !ApnProperties,
 *   mode: !ApnDetailDialogMode,
 * }}
 */
export let ApnEventData;

/**
 * Checks if the device has a cellular network with connectionState not
 * kNotConnected.
 * @return {!Promise<boolean>}
 */
export function hasActiveCellularNetwork() {
  const networkConfig =
      MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
  return networkConfig
      .getNetworkStateList({
        filter: FilterType.kActive,
        networkType: NetworkType.kCellular,
        limit: NO_LIMIT,
      })
      .then((response) => {
        return response.result.some(network => {
          return network.connectionState !== ConnectionStateType.kNotConnected;
        });
      });
}

/**
 * Returns number of phyical SIM and eSIM slots on the current device
 * @param {!DeviceStateProperties|undefined}
 *     deviceState
 * @return {!{pSimSlots: number, eSimSlots: number}}
 */
export function getSimSlotCount(deviceState) {
  let pSimSlots = 0;
  let eSimSlots = 0;

  if (!deviceState || !deviceState.simInfos) {
    return {pSimSlots, eSimSlots};
  }

  for (const simInfo of deviceState.simInfos) {
    if (simInfo.eid) {
      eSimSlots++;
      continue;
    }
    pSimSlots++;
  }

  return {pSimSlots, eSimSlots};
}

/**
 * Checks if the device is currently connected to a WiFi, Ethernet or Tether
 * network.
 * @return {!Promise<boolean>}
 */
export function isConnectedToNonCellularNetwork() {
  const networkConfig =
      MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
  return networkConfig
      .getNetworkStateList({
        filter: FilterType.kActive,
        networkType: NetworkType.kAll,
        limit: NO_LIMIT,
      })
      .then((response) => {
        // Filter for connected non-cellular networks.
        return response.result.some(network => {
          return network.connectionState === ConnectionStateType.kOnline &&
              network.type !== NetworkType.kCellular;
        });
      });
}

/**
 * Determines if the current network is on the active sim slot.
 * @param {?NetworkStateProperties} networkState
 * @param {?DeviceStateProperties} deviceState
 */
export function isActiveSim(networkState, deviceState) {
  if (!networkState || networkState.type !== NetworkType.kCellular) {
    return false;
  }

  const iccid = networkState.typeState.cellular.iccid;
  if (!iccid || !deviceState || !deviceState.simInfos) {
    return false;
  }
  const isActiveSim = deviceState.simInfos.find(simInfo => {
    return simInfo.iccid === iccid && simInfo.isPrimary;
  });
  return !!isActiveSim;
}

/**
 * Returns true if all significant DeviceState fields match. Ignores
 * |scanning| which can be noisy and is handled separately.
 * @param {!OncMojo.DeviceStateProperties} a
 * @param {!OncMojo.DeviceStateProperties} b
 * @return {boolean}
 */
export function deviceStatesMatch(a, b) {
  return a.type === b.type && a.macAddress === b.macAddress &&
      a.simAbsent === b.simAbsent && a.deviceState === b.deviceState &&
      a.managedNetworkAvailable === b.managedNetworkAvailable &&
      OncMojo.ipAddressMatch(a.ipv4Address, b.ipv4Address) &&
      OncMojo.ipAddressMatch(a.ipv6Address, b.ipv6Address) &&
      OncMojo.simLockStatusMatch(a.simLockStatus, b.simLockStatus) &&
      OncMojo.simInfosMatch(a.simInfos, b.simInfos) &&
      a.inhibitReason === b.inhibitReason;
}

/**
 * @param {!NetworkType} type
 * @param {!Array<!DeviceStateProperties>} devices
 * @param {?OncMojo.DeviceStateProperties} deviceState
 * @return {{deviceState: OncMojo.DeviceStateProperties,
 *           shouldGetNetworkDetails: boolean}}
 */
export function processDeviceState(type, devices, deviceState) {
  const newDeviceState = devices.find(device => device.type === type) || null;
  let shouldGetNetworkDetails = false;
  if (!deviceState || !newDeviceState) {
    deviceState = /**@type {?OncMojo.DeviceStateProperties}*/ (newDeviceState);
    shouldGetNetworkDetails = !!deviceState;
  } else if (!deviceStatesMatch(deviceState, newDeviceState)) {
    // Only request a network state update if the deviceState changed.
    shouldGetNetworkDetails =
        deviceState.deviceState !== newDeviceState.deviceState;
    deviceState = /**@type {?OncMojo.DeviceStateProperties}*/ (newDeviceState);
  } else if (deviceState.scanning !== newDeviceState.scanning) {
    // Update just the scanning state to avoid interrupting other parts of
    // the UI (e.g. custom IP addresses or nameservers).
    deviceState.scanning = newDeviceState.scanning;
    // Cellular properties are not updated while scanning (since they
    // may be invalid), so request them on scan completion.
    if (type === NetworkType.kCellular) {
      shouldGetNetworkDetails = true;
    }
  } else if (type === NetworkType.kCellular) {
    // If there are no device state property changes but type is
    // cellular, then always fetch network details. This is because
    // for cellular networks, some shill device level properties are
    // represented at network level in ONC.
    shouldGetNetworkDetails = true;
  }
  return {deviceState, shouldGetNetworkDetails};
}

/**
 * Returns whether or not the network associated with |managedProperties| is
 * carrier locked.
 * @param {?OncMojo.DeviceStateProperties} deviceState
 * @param {ManagedProperties|undefined} managedProperties
 */
export function isCarrierLockedActiveSim(managedProperties, deviceState) {
  if (!deviceState || deviceState.type !== NetworkType.kCellular) {
    return false;
  }
  if (!managedProperties) {
    return false;
  }
  const networkState =
      OncMojo.managedPropertiesToNetworkState(managedProperties);

  if (!isActiveSim(networkState, deviceState)) {
    return false;
  }
  const simLockStatus = deviceState.simLockStatus;
  if (!simLockStatus) {
    return false;
  }
  return simLockStatus.lockType === 'network-pin';
}

/**
 * Returns whether or not the network associated with |managedProperties| should
 * allow modification of its properties via the UI.
 * @param {?OncMojo.DeviceStateProperties} deviceState
 * @param {ManagedProperties|undefined} managedProperties
 */
export function shouldDisallowNetworkModifications(
    deviceState, managedProperties) {
  if (!deviceState || deviceState.type !== NetworkType.kCellular) {
    return false;
  }
  // If device is carrier locked, all the settings should be
  // disabled for non compatible SIMs.
  if (isCarrierLockedActiveSim(managedProperties, deviceState)) {
    return true;
  }
  // If this is a cellular device and inhibited, state cannot be changed, so
  // the page's inputs should be disabled.
  return OncMojo.deviceIsInhibited(deviceState);
}

/**
 * Returns the display name for |apn|.
 * @param {function(string)} i18nFunction
 * @param {!ApnProperties} apn
 */
export function getApnDisplayName(i18nFunction, apn) {
  const name = apn.name || apn.accessPointName;
  if (name) {
    return name;
  }

  // If APN has no name, it's an APN detected by the modem (b/295588352).
  return i18nFunction('apnNameModem');
}

/**
 * Returns true if the |apn| can be used as a attach APN.
 * @param {ApnProperties} apn
 * @return {boolean}
 */
export function isAttachApn(apn) {
  return !!apn.apnTypes && apn.apnTypes.includes(ApnType.kAttach);
}

/**
 * Returns true if the |apn| can be used as a default APN.
 * @param {ApnProperties} apn
 * @return {boolean}
 */
export function isDefaultApn(apn) {
  return !!apn.apnTypes && apn.apnTypes.includes(ApnType.kDefault);
}