chromium/chrome/browser/resources/ash/settings/multidevice_page/multidevice_tether_item.ts

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

/**
 * @fileoverview
 * This element provides a layer between the settings-multidevice-subpage
 * element and the internet_page folder's network-summary-item. It is
 * responsible for loading initial tethering network data from the
 * networkConfig mojo API as well as updating the data in real time. It
 * serves a role comparable to the internet_page's network-summary element.
 */

import 'chrome://resources/ash/common/network/network_icon.js';
import '../settings_shared.css.js';
import '../settings_vars.css.js';
import './multidevice_feature_item.js';

import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {CrosNetworkConfigInterface, FilterType, InhibitReason, NetworkStateProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
import {ConnectionStateType, DeviceStateType, NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {castExists} from '../assert_extras.js';
import {isRevampWayfindingEnabled} from '../common/load_time_booleans.js';
import {Constructor} from '../common/types.js';
import {routes} from '../router.js';

import {MultiDeviceFeatureMixin, MultiDeviceFeatureMixinInterface} from './multidevice_feature_mixin.js';
import {getTemplate} from './multidevice_tether_item.html.js';

const SettingsMultideviceTetherItemElementBase =
    mixinBehaviors(
        [
          NetworkListenerBehavior,
        ],
        MultiDeviceFeatureMixin(PolymerElement)) as
    Constructor<PolymerElement&MultiDeviceFeatureMixinInterface&
                NetworkListenerBehaviorInterface>;

class SettingsMultideviceTetherItemElement extends
    SettingsMultideviceTetherItemElementBase {
  static get is() {
    return 'settings-multidevice-tether-item' as const;
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      /**
       * The device state for tethering.
       */
      deviceState_: Object,

      /**
       * The network state for a potential tethering host phone. Note that there
       * is at most one because only one MultiDevice host phone is allowed on an
       * account at a given time.
       */
      activeNetworkState_: Object,

      /**
       * Alias for allowing Polymer bindings to routes.
       */
      routes: {
        type: Object,
        value: routes,
        readonly: true,
      },

      isRevampWayfindingEnabled_: {
        type: Boolean,
        value: () => {
          return isRevampWayfindingEnabled();
        },
      },

      /**
       * Whether to show technology badge on mobile network icon.
       */
      showTechnologyBadge_: {
        type: Boolean,
        value() {
          return loadTimeData.valueExists('showTechnologyBadge') &&
              loadTimeData.getBoolean('showTechnologyBadge');
        },
      },
    };
  }

  private activeNetworkState_: OncMojo.NetworkStateProperties|undefined;
  private deviceState_: OncMojo.DeviceStateProperties|undefined;
  private isRevampWayfindingEnabled_: boolean;
  private networkConfig_: CrosNetworkConfigInterface;
  private showTechnologyBadge_: boolean;

  constructor() {
    super();

    this.networkConfig_ =
        MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
  }

  override connectedCallback(): void {
    super.connectedCallback();

    this.updateTetherDeviceState_();
    this.updateTetherNetworkState_();
  }

  override focus(): void {
    this.shadowRoot!.querySelector(
                        'settings-multidevice-feature-item')!.focus();
  }

  /**
   * CrosNetworkConfigObserver impl
   * Note that any change to leading to a new active network will also trigger
   * onNetworkStateListChanged, triggering updateTetherNetworkState_ and
   * rendering this callback redundant. As a result, we return early if the
   * active network is not changed.
   */
  override onActiveNetworksChanged(networks: NetworkStateProperties[]): void {
    const guid = this.activeNetworkState_!.guid;
    if (!networks.find(network => network.guid === guid)) {
      return;
    }
    this.networkConfig_.getNetworkState(guid).then(response => {
      if (response.result) {
        this.activeNetworkState_ = response.result;
      }
    });
  }

  /** CrosNetworkConfigObserver impl */
  override onNetworkStateListChanged(): void {
    this.updateTetherNetworkState_();
  }

  /** CrosNetworkConfigObserver impl */
  override onDeviceStateListChanged(): void {
    this.updateTetherDeviceState_();
  }

  /**
   * Retrieves device states (OncMojo.DeviceStateProperties) and sets
   * this.deviceState_ to the retrieved Tether device state (or undefined if
   * there is none). Note that crosNetworkConfig.getDeviceStateList retrieves at
   * most one device per NetworkType so there will be at most one Tether device
   * state.
   */
  private updateTetherDeviceState_(): void {
    this.networkConfig_.getDeviceStateList().then(response => {
      const kTether = NetworkType.kTether;
      const deviceStates = response.result;
      const deviceState =
          deviceStates.find(deviceState => deviceState.type === kTether);
      this.deviceState_ = deviceState || {
        deviceState: DeviceStateType.kDisabled,
        inhibitReason: InhibitReason.kNotInhibited,
        managedNetworkAvailable: false,
        scanning: false,
        simAbsent: false,
        type: kTether,
      } as OncMojo.DeviceStateProperties;
    });
  }

  /**
   * Retrieves all Instant Tethering network states
   * (OncMojo.NetworkStateProperties). Note that there is at most one because
   * only one host is allowed on an account at a given time. Then it sets
   * this.activeNetworkState_ to that network if there is one or a dummy object
   * with an empty string for a GUID otherwise.
   */
  private updateTetherNetworkState_(): void {
    const kTether = NetworkType.kTether;
    const filter = {
      filter: FilterType.kVisible,
      limit: 1,
      networkType: kTether,
    };
    this.networkConfig_.getNetworkStateList(filter).then(response => {
      const networks = response.result;
      this.activeNetworkState_ =
          networks[0] || OncMojo.getDefaultNetworkState(kTether);
    });
  }

  /**
   * Returns an array containing the active network state if there is one
   * (note that if there is not GUID will be falsy).  Returns an empty array
   * otherwise.
   */
  private getNetworkStateList_(): NetworkStateProperties[] {
    return this.activeNetworkState_!.guid ?
        [castExists(this.activeNetworkState_)] :
        [];
  }

  private getTetherNetworkUrlSearchParams_(): URLSearchParams {
    return new URLSearchParams('type=Tether');
  }

  private getInstantTetheringDescription_(): string {
    const deviceState = this.deviceState_;
    // If the `deviceState` is enabled, the description depends on the
    // `connectionState`, otherwise return the disabled description directly.
    if (deviceState && deviceState.deviceState === DeviceStateType.kEnabled) {
      assert(deviceState.type === NetworkType.kTether);
      if (this.activeNetworkState_) {
        const connectionState = this.activeNetworkState_.connectionState;
        const deviceName = this.pageContentData.hostDeviceName || '';
        if (OncMojo.connectionStateIsConnected(connectionState)) {
          return this.i18n(
              'multideviceInstantTetheringItemConnectedDescription',
              deviceName);
        }
        if (connectionState === ConnectionStateType.kConnecting) {
          return this.i18n(
              'multideviceInstantTetheringItemConnectingDescription',
              deviceName);
        }
        if (connectionState === ConnectionStateType.kNotConnected) {
          return this.i18n(
              'multideviceInstantTetheringItemNoNetworkDescription',
              deviceName);
        }
      }
    }

    return this.i18n('multideviceInstantTetheringItemDisabledDescription');
  }
}

declare global {
  interface HTMLElementTagNameMap {
    [SettingsMultideviceTetherItemElement.is]:
        SettingsMultideviceTetherItemElement;
  }
}

customElements.define(
    SettingsMultideviceTetherItemElement.is,
    SettingsMultideviceTetherItemElement);