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

// Copyright 2020 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 the Phone Hub notification access setup flow that, when
 * successfully completed, enables the feature that allows a user's phone
 * notifications to be mirrored on their Chromebook.
 */

import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/ash/common/cr_elements/localized_link/localized_link.js';
import '../os_settings_icons.html.js';
import '../settings_shared.css.js';

import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
import {MultiDeviceFeature} from './multidevice_constants.js';
import {getTemplate} from './multidevice_notification_access_setup_dialog.html.js';

/**
 * Numerical values should not be changed because they must stay in sync with
 * notification_access_setup_operation.h, with the exception of
 * CONNECTION_REQUESTED.
 */
export enum NotificationAccessSetupOperationStatus {
  CONNECTION_REQUESTED = 0,
  CONNECTING = 1,
  TIMED_OUT_CONNECTING = 2,
  CONNECTION_DISCONNECTED = 3,
  SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE = 4,
  COMPLETED_SUCCESSFULLY = 5,
  NOTIFICATION_ACCESS_PROHIBITED = 6,
}

export interface SettingsMultideviceNotificationAccessSetupDialogElement {
  $: {
    dialog: CrDialogElement,
  };
}

const SettingsMultideviceNotificationAccessSetupDialogElementBase =
    WebUiListenerMixin(I18nMixin(PolymerElement));

export class SettingsMultideviceNotificationAccessSetupDialogElement extends
    SettingsMultideviceNotificationAccessSetupDialogElementBase {
  static get is() {
    return 'settings-multidevice-notification-access-setup-dialog' as const;
  }

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

  static get properties() {
    return {
      /**
       * A null |setupState_| indicates that the operation has not yet started.
       */
      setupState_: {
        type: Number,
        value: null,
      },

      title_: {
        type: String,
        computed: 'getTitle_(setupState_)',
      },

      description_: {
        type: String,
        computed: 'getDescription_(setupState_)',
      },

      hasNotStartedSetupAttempt_: {
        type: Boolean,
        computed: 'computeHasNotStartedSetupAttempt_(setupState_)',
        reflectToAttribute: true,
      },

      isSetupAttemptInProgress_: {
        type: Boolean,
        computed: 'computeIsSetupAttemptInProgress_(setupState_)',
        reflectToAttribute: true,
      },

      didSetupAttemptFail_: {
        type: Boolean,
        computed: 'computeDidSetupAttemptFail_(setupState_)',
        reflectToAttribute: true,
      },

      hasCompletedSetupSuccessfully_: {
        type: Boolean,
        computed: 'computeHasCompletedSetupSuccessfully_(setupState_)',
        reflectToAttribute: true,
      },

      isNotificationAccessProhibited_: {
        type: Boolean,
        computed: 'computeIsNotificationAccessProhibited_(setupState_)',
      },

      shouldShowSetupInstructionsSeparately_: {
        type: Boolean,
        computed: 'computeShouldShowSetupInstructionsSeparately_(' +
            'setupState_)',
        reflectToAttribute: true,
      },
    };
  }

  private browserProxy_: MultiDeviceBrowserProxy;
  private description_: string;
  private didSetupAttemptFail_: boolean;
  private hasCompletedSetupSuccessfully_: boolean;
  private hasNotStartedSetupAttempt_: boolean;
  private isNotificationAccessProhibited_: boolean;
  private isSetupAttemptInProgress_: boolean;
  private setupState_: NotificationAccessSetupOperationStatus|null;
  private shouldShowSetupInstructionsSeparately_: boolean;
  private title_: string;

  constructor() {
    super();

    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
  }

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

    this.addWebUiListener(
        'settings.onNotificationAccessSetupStatusChanged',
        this.onSetupStateChanged_.bind(this));
    this.$.dialog.showModal();
  }

  private onSetupStateChanged_(
      setupState: NotificationAccessSetupOperationStatus): void {
    this.setupState_ = setupState;
    if (this.setupState_ ===
        NotificationAccessSetupOperationStatus.COMPLETED_SUCCESSFULLY) {
      this.browserProxy_.setFeatureEnabledState(
          MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS, true);
    }
  }

  private computeHasNotStartedSetupAttempt_(): boolean {
    return this.setupState_ === null;
  }

  private computeIsSetupAttemptInProgress_(): boolean {
    return this.setupState_ ===
        NotificationAccessSetupOperationStatus
            .SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE ||
        this.setupState_ ===
        NotificationAccessSetupOperationStatus.CONNECTING ||
        this.setupState_ ===
        NotificationAccessSetupOperationStatus.CONNECTION_REQUESTED;
  }

  private computeHasCompletedSetupSuccessfully_(): boolean {
    return this.setupState_ ===
        NotificationAccessSetupOperationStatus.COMPLETED_SUCCESSFULLY;
  }

  private computeIsNotificationAccessProhibited_(): boolean {
    return this.setupState_ ===
        NotificationAccessSetupOperationStatus.NOTIFICATION_ACCESS_PROHIBITED;
  }

  private computeDidSetupAttemptFail_(): boolean {
    return this.setupState_ ===
        NotificationAccessSetupOperationStatus.TIMED_OUT_CONNECTING ||
        this.setupState_ ===
        NotificationAccessSetupOperationStatus.CONNECTION_DISCONNECTED ||
        this.setupState_ ===
        NotificationAccessSetupOperationStatus.NOTIFICATION_ACCESS_PROHIBITED;
  }

  /**
   * @return Whether to show setup instructions in its own section.
   */
  private computeShouldShowSetupInstructionsSeparately_(): boolean {
    return this.setupState_ === null ||
        this.setupState_ ===
        NotificationAccessSetupOperationStatus.CONNECTION_REQUESTED ||
        this.setupState_ ===
        NotificationAccessSetupOperationStatus
            .SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE ||
        this.setupState_ === NotificationAccessSetupOperationStatus.CONNECTING;
  }

  private attemptNotificationSetup_(): void {
    this.browserProxy_.attemptNotificationSetup();
    this.setupState_ =
        NotificationAccessSetupOperationStatus.CONNECTION_REQUESTED;
  }

  private onCancelClicked_(): void {
    this.browserProxy_.cancelNotificationSetup();
    this.$.dialog.close();
  }

  private onDoneOrCloseButtonClicked_(): void {
    this.$.dialog.close();
  }

  private getTitle_(): string {
    if (this.setupState_ === null) {
      return this.i18n('multideviceNotificationAccessSetupAckTitle');
    }

    const Status = NotificationAccessSetupOperationStatus;
    switch (this.setupState_) {
      case Status.CONNECTION_REQUESTED:
      case Status.CONNECTING:
        return this.i18n('multideviceNotificationAccessSetupConnectingTitle');
      case Status.SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE:
        return this.i18n(
            'multideviceNotificationAccessSetupAwaitingResponseTitle');
      case Status.COMPLETED_SUCCESSFULLY:
        return this.i18n('multideviceNotificationAccessSetupCompletedTitle');
      case Status.TIMED_OUT_CONNECTING:
        return this.i18n(
            'multideviceNotificationAccessSetupCouldNotEstablishConnectionTitle');
      case Status.CONNECTION_DISCONNECTED:
        return this.i18n(
            'multideviceNotificationAccessSetupConnectionLostWithPhoneTitle');
      case Status.NOTIFICATION_ACCESS_PROHIBITED:
        return this.i18n(
            'multideviceNotificationAccessSetupAccessProhibitedTitle');
      default:
        return '';
    }
  }

  /**
   * @return A description about the connection attempt state.
   */
  private getDescription_(): TrustedHTML|string {
    if (this.setupState_ === null) {
      return this.i18n('multideviceNotificationAccessSetupAckSummary');
    }

    const Status = NotificationAccessSetupOperationStatus;
    switch (this.setupState_) {
      case Status.COMPLETED_SUCCESSFULLY:
        return this.i18n('multideviceNotificationAccessSetupCompletedSummary');
      case Status.TIMED_OUT_CONNECTING:
        return this.i18n(
            'multideviceNotificationAccessSetupEstablishFailureSummary');
      case Status.CONNECTION_DISCONNECTED:
        return this.i18n(
            'multideviceNotificationAccessSetupMaintainFailureSummary');
      case Status.NOTIFICATION_ACCESS_PROHIBITED:
        return this.i18nAdvanced(
            'multideviceNotificationAccessSetupAccessProhibitedSummary');
      case Status.SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE:
        return this.i18n(
            'multideviceNotificationAccessSetupAwaitingResponseSummary');

      // Only setup instructions will be shown.
      case Status.CONNECTION_REQUESTED:
      case Status.CONNECTING:
      default:
        return '';
    }
  }

  private shouldShowCancelButton_(): boolean {
    return this.setupState_ !==
        NotificationAccessSetupOperationStatus.COMPLETED_SUCCESSFULLY &&
        this.setupState_ !==
        NotificationAccessSetupOperationStatus.NOTIFICATION_ACCESS_PROHIBITED;
  }

  private shouldShowTryAgainButton_(): boolean {
    return this.setupState_ ===
        NotificationAccessSetupOperationStatus.TIMED_OUT_CONNECTING ||
        this.setupState_ ===
        NotificationAccessSetupOperationStatus.CONNECTION_DISCONNECTED;
  }
}

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

customElements.define(
    SettingsMultideviceNotificationAccessSetupDialogElement.is,
    SettingsMultideviceNotificationAccessSetupDialogElement);