chromium/ash/webui/common/resources/multidevice_setup/start_setup_page.js

// 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.

import './icons.html.js';
import './mojo_api.js';
import './multidevice_setup_shared.css.js';
import './ui_page.js';
import '//resources/ash/common/cr.m.js';
import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
import '//resources/polymer/v3_0/iron-media-query/iron-media-query.js';
import 'chrome://resources/cros_components/lottie_renderer/lottie-renderer.js';

import {loadTimeData} from '//resources/ash/common/load_time_data.m.js';
import {WebUIListenerBehavior} from '//resources/ash/common/web_ui_listener_behavior.js';
import {Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {LottieRenderer} from 'chrome://resources/cros_components/lottie_renderer/lottie-renderer.js';
import {ConnectivityStatus} from 'chrome://resources/mojo/chromeos/ash/services/device_sync/public/mojom/device_sync.mojom-webui.js';
import {HostDevice} from 'chrome://resources/mojo/chromeos/ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom-webui.js';

import {MojoInterfaceProvider, MojoInterfaceProviderImpl} from './mojo_api.js';
import {MultiDeviceSetupDelegate} from './multidevice_setup_delegate.js';
import {getTemplate} from './start_setup_page.html.js';
import {UiPageContainerBehavior} from './ui_page_container_behavior.js';

/**
 * The multidevice setup animation for dynamic colors.
 * @type {string}
 */
const MULTIDEVICE_ANIMATION_JELLY_URL =
    'chrome://resources/ash/common/multidevice_setup/multidevice_setup_animation.json';

Polymer({
  _template: getTemplate(),
  is: 'start-setup-page',

  properties: {
    /* The localized loadTimeData string for the
     * StartSetupPage header text, dependent on whether
     * a user is on OOBE and previously connected their phone during Quick
     * Start.
     * */
    headerTextId: {
      type: String,
      value: 'startSetupPageHeader',
      notify: true,
    },

    /** Overridden from UiPageContainerBehavior. */
    forwardButtonTextId: {
      type: String,
      value: 'accept',
    },

    /** Overridden from UiPageContainerBehavior. */
    cancelButtonTextId: {
      type: String,
      computed: 'getCancelButtonTextId_(delegate)',
    },

    /**
     * Array of objects representing all potential MultiDevice hosts.
     *
     * @type {!Array<!HostDevice>}
     */
    devices: {
      type: Array,
      value: () => [],
      observer: 'devicesChanged_',
    },

    /**
     * Unique identifier for the currently selected host device. This uses the
     * device's Instance ID if it is available; otherwise, the device's legacy
     * device ID is used.
     * TODO(crbug.com/40105247): When v1 DeviceSync is turned off, only
     * use Instance ID since all devices are guaranteed to have one.
     *
     * Undefined if the no list of potential hosts has been received from mojo
     * service.
     *
     * @type {string|undefined}
     */
    selectedInstanceIdOrLegacyDeviceId: {
      type: String,
      notify: true,
    },

    /**
     * Delegate object which performs differently in OOBE vs. non-OOBE mode.
     * @type {!MultiDeviceSetupDelegate}
     */
    delegate: Object,

    /** @private */
    wifiSyncEnabled_: {
      type: Boolean,
      value() {
        return loadTimeData.valueExists('wifiSyncEnabled') &&
            loadTimeData.getBoolean('wifiSyncEnabled');
      },
    },

    /** @private */
    phoneHubEnabled_: {
      type: Boolean,
      value() {
        return loadTimeData.valueExists('phoneHubEnabled') &&
            loadTimeData.getBoolean('phoneHubEnabled');
      },
    },

    /**
     * ID of phone a user used to complete Quick Start earlier in OOBE flow.
     * @private {string|undefined}
     */
    quickStartPhoneInstanceId_: String,

    /**
     * Provider of an interface to the MultiDeviceSetup Mojo service.
     * @private {!MojoInterfaceProvider}
     */
    mojoInterfaceProvider_: Object,
  },

  behaviors: [
    UiPageContainerBehavior,
    WebUIListenerBehavior,
  ],

  /** @override */
  created() {
    this.mojoInterfaceProvider_ = MojoInterfaceProviderImpl.getInstance();
  },

  /** @override */
  attached() {
    this.addWebUIListener(
        'multidevice_setup.initializeSetupFlow',
        () => this.initializeSetupFlow_());
  },

  /**
   * This will play or stop the screen's lottie animation.
   * @param {boolean} enabled Whether the animation should play or not.
   */
  setPlayAnimation(enabled) {
    if (enabled) {
      this.$.multideviceSetupAnimation.play();
    } else {
      this.$.multideviceSetupAnimation.pause();
    }
  },

  /**
   * If the user used Quick Start, this method retrieves and sets the ID of the
   * phone a user used to complete the flow earlier in OOBE.
   * @private
   */
  initializeSetupFlow_() {
    this.mojoInterfaceProvider_.getMojoServiceRemote()
        .getQuickStartPhoneInstanceID()
        .then(({qsPhoneInstanceId}) => {
          if (!qsPhoneInstanceId) {
            return;
          }

          this.quickStartPhoneInstanceId_ = qsPhoneInstanceId;
        })
        .catch((error) => {
          console.warn('Mojo service failure: ' + error);
        });
  },

  /**
   * @param {!MultiDeviceSetupDelegate} delegate
   * @return {string} The cancel button text ID, dependent on OOBE vs. non-OOBE.
   * @private
   */
  getCancelButtonTextId_(delegate) {
    return this.delegate.getStartSetupCancelButtonTextId();
  },

  /**
   * @param {!Array<!HostDevice>} devices
   * @return {string} Label for devices selection content.
   * @private
   */
  getDeviceSelectionHeader_(devices) {
    switch (devices.length) {
      case 0:
        return '';
      case 1:
        return this.i18n('startSetupPageSingleDeviceHeader');
      default:
        return this.i18n('startSetupPageMultipleDeviceHeader');
    }
  },

  /**
   * @param {!Array<!HostDevice>} devices
   * @return {boolean} True if there are more than one potential host devices.
   * @private
   */
  doesDeviceListHaveMultipleElements_(devices) {
    return devices.length > 1;
  },

  /**
   * @param {!Array<!HostDevice>} devices
   * @return {boolean} True if there is exactly one potential host device.
   * @private
   */
  doesDeviceListHaveOneElement_(devices) {
    return devices.length === 1;
  },

  /**
   * @param {!Array<!HostDevice>} devices
   * @return {string} Name of the first device in device list if there are any.
   *     Returns an empty string otherwise.
   * @private
   */
  getFirstDeviceNameInList_(devices) {
    return devices[0] ? this.devices[0].remoteDevice.deviceName : '';
  },

  /**
   * @param {!ConnectivityStatus} connectivityStatus
   * @return {string} The classes to bind to the device name option.
   * @private
   */
  getDeviceOptionClass_(connectivityStatus) {
    return connectivityStatus === ConnectivityStatus.kOffline ?
        'offline-device-name' :
        '';
  },

  /**
   * @param {!HostDevice} device
   * @return {string} Name of the device, with connectivity status information.
   * @private
   */
  getDeviceNameWithConnectivityStatus_(device) {
    return device.connectivityStatus === ConnectivityStatus.kOffline ?
        this.i18n(
            'startSetupPageOfflineDeviceOption',
            device.remoteDevice.deviceName) :
        device.remoteDevice.deviceName;
  },

  /**
   * @param {!HostDevice} device
   * @return {string} Returns a unique identifier for the input device, using
   *     the device's Instance ID if it is available; otherwise, the device's
   *     legacy device ID is used.
   *     TODO(crbug.com/40105247): When v1 DeviceSync is turned off, only
   *     use Instance ID since all devices are guaranteed to have one.
   * @private
   */
  getInstanceIdOrLegacyDeviceId_(device) {
    if (device.remoteDevice.instanceId) {
      return device.remoteDevice.instanceId;
    }

    return device.remoteDevice.deviceId;
  },

  /** @private */
  devicesChanged_() {
    if (this.devices.length > 0) {
      if (this.quickStartPhoneInstanceId_ &&
          this.moveDeviceToFront_(this.quickStartPhoneInstanceId_)) {
        // Adjust the title to reflect that the Quick Start phone was moved to
        // top of list.
        this.headerTextId = 'startSetupPageAfterQuickStartHeader';
      }

      this.selectedInstanceIdOrLegacyDeviceId =
          this.getInstanceIdOrLegacyDeviceId_(this.devices[0]);
    }
  },

  /**
   * Checks if the devices list contains a phone matching the provided
   * device_id. If so, that phone is moved to the front of the devices list.
   * @param {string} device_id
   * @return {boolean} Whether the device matching the provided ID is moved to
   *     the front of the devices list. Returns false if no matching device is
   *     found. Returns true and moves the matching device to index 0 if a
   *     matching device is found.
   * @private
   */
  moveDeviceToFront_(device_id) {
    const matchingDeviceIdx = this.devices.findIndex(
        device => this.getInstanceIdOrLegacyDeviceId_(device) === device_id);

    if (matchingDeviceIdx === -1) {
      return false;
    }

    // Move device located at the matchingDeviceIdx to the front of the
    // devices list.
    this.devices.unshift(this.devices.splice(matchingDeviceIdx, 1)[0]);
    return true;
  },

  /** @private */
  onDeviceDropdownSelectionChanged_() {
    this.selectedInstanceIdOrLegacyDeviceId = this.$.deviceDropdown.value;
  },

  /**
   * Wrapper for i18nAdvanced for binding to location updates in OOBE.
   * @param {string} locale The language code (e.g. en, es) for the current
   *     display language for CrOS. As with I18nBehavior.i18nDynamic(), the
   *     parameter is not used directly but is provided to allow HTML binding
   *     without passing an unexpected argument to I18nBehavior.i18nAdvanced().
   * @param {string} textId The loadTimeData ID of the string to be translated.
   * @private
   */
  i18nAdvancedDynamic_(locale, textId) {
    return this.i18nAdvanced(textId);
  },

  /**
   * Returns the URL for the asset that defines the multidevice setup page's
   * animation
   * @return {string}
   * @private
   */
  getAnimationUrl_() {
    return MULTIDEVICE_ANIMATION_JELLY_URL;
  },
});