chromium/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.ts

// 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 '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/polymer/v3_0/paper-tooltip/paper-tooltip.js';
import './base_page.js';
import './shimless_rma_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 {assert} from 'chrome://resources/js/assert.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {createCustomEvent, OPEN_LOGS_DIALOG, OpenLogsDialogEvent} from './events.js';
import {getShimlessRmaService} from './mojo_interface_provider.js';
import {PowerCableStateObserverReceiver, ShimlessRmaServiceInterface, ShutdownMethod} from './shimless_rma.mojom-webui.js';
import {executeThenTransitionState, focusPageTitle} from './shimless_rma_util.js';
import {getTemplate} from './wrapup_repair_complete_page.html.js';

declare global {
  interface HTMLElementEventMap {
    [OPEN_LOGS_DIALOG]: OpenLogsDialogEvent;
  }
}

/**
 * @fileoverview
 * 'wrapup-repair-complete-page' is the main landing page for the shimless rma
 * process.
 */

const WrapupRepairCompletePageBase = I18nMixin(PolymerElement);

/**
 * Supported options for finishing RMA.
 */
enum FinishRmaOption {
  SHUTDOWN = 'shutdown',
  REBOOT = 'reboot',
}

export class WrapupRepairCompletePage extends WrapupRepairCompletePageBase {
  static get is() {
    return 'wrapup-repair-complete-page' as const;
  }

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

  static get properties() {
    return {
      /**
       * Set by shimless_rma.ts.
       */
      allButtonsDisabled: {
        reflectToAttribute: true,
        type: Boolean,
      },

      /**
       * Keeps the shutdown and reboot buttons disabled after the response from
       * the service to prevent successive shutdown or reboot attempts.
       */
      shutdownButtonsDisabled: {
        type: Boolean,
        value: false,
      },

      /**
       * Assume plugged in is true until first observation.
       */
      pluggedIn: {
        reflectToAttribute: true,
        type: Boolean,
        value: true,
      },

      selectedFinishRmaOption: {
        type: String,
        value: '',
      },

      /**
       * This variable needs to remain public because the unit tests need to
       * check its value.
       * TODO(b:315002705): Make this property protected and add a test for it.
       */
      batteryTimeoutID: {
        type: Number,
        value: -1,
      },

      /**
       * This variable needs to remain public because the unit tests need to
       * set it to 0.
       * TODO(b:315002705): Make this property protected and add a test for it.
       */
      batteryTimeoutInMs: {
        type: Number,
        value: 5000,
      },
    };
  }

  allButtonsDisabled: boolean;
  batteryTimeoutID: number;
  batteryTimeoutInMs: number;
  protected shutdownButtonsDisabled: boolean;
  protected pluggedIn: boolean;
  protected selectedFinishRmaOption: string;
  private shimlessRmaService: ShimlessRmaServiceInterface =
      getShimlessRmaService();
  powerCableStateReceiver: PowerCableStateObserverReceiver;

  constructor() {
    super();

    this.powerCableStateReceiver = new PowerCableStateObserverReceiver(this);

    this.shimlessRmaService.observePowerCableState(
        this.powerCableStateReceiver.$.bindNewPipeAndPassRemote());
  }

  override ready() {
    super.ready();

    focusPageTitle(this);
  }

  protected onDiagnosticsButtonClick(): void {
    this.shimlessRmaService.launchDiagnostics();
  }

  protected onShutDownButtonClick(e: Event): void {
    e.preventDefault();
    this.selectedFinishRmaOption = FinishRmaOption.SHUTDOWN;
    this.shimlessRmaService.getPowerwashRequired().then(
        (result: {powerwashRequired: boolean}) => {
          this.handlePowerwash(result.powerwashRequired);
        });
  }

  /**
   * Handles the response to getPowerwashRequired from the backend.
   */
  private handlePowerwash(powerwashRequired: boolean): void {
    if (powerwashRequired) {
      const dialog: CrDialogElement|null =
          this.shadowRoot!.querySelector('#powerwashDialog');
      assert(dialog);
      if (!dialog.open) {
        dialog.showModal();
      }
    } else {
      this.shutDownOrReboot();
    }
  }

  private shutDownOrReboot(): void {
    // Keeps the buttons disabled until the device is shutdown.
    this.shutdownButtonsDisabled = true;

    if (this.selectedFinishRmaOption === FinishRmaOption.SHUTDOWN) {
      this.endRmaAndShutdown();
    } else {
      this.endRmaAndReboot();
    }
  }

  /**
   * Sends a shutdown request to the backend.
   */
  private endRmaAndShutdown(): void {
    executeThenTransitionState(
        this, () => this.shimlessRmaService.endRma(ShutdownMethod.kShutdown));
  }

  protected getPowerwashDescriptionString(): string {
    return this.selectedFinishRmaOption === FinishRmaOption.SHUTDOWN ?
        this.i18n('powerwashDialogShutdownDescription') :
        this.i18n('powerwashDialogRebootDescription');
  }

  protected onPowerwashButtonClick(e: Event): void {
    e.preventDefault();
    const dialog: CrDialogElement|null =
        this.shadowRoot!.querySelector('#powerwashDialog');
    assert(dialog);
    dialog.close();
    this.shutDownOrReboot();
  }

  protected onRebootButtonClick(e: Event): void {
    e.preventDefault();
    this.selectedFinishRmaOption = FinishRmaOption.REBOOT;
    this.shimlessRmaService.getPowerwashRequired().then(
        (result: {powerwashRequired: boolean}) => {
          this.handlePowerwash(result.powerwashRequired);
        });
  }

  /**
   * Sends a reboot request to the backend.
   */
  private endRmaAndReboot(): void {
    executeThenTransitionState(
        this, () => this.shimlessRmaService.endRma(ShutdownMethod.kReboot));
  }

  protected onRmaLogButtonClick(): void {
    this.dispatchEvent(createCustomEvent(OPEN_LOGS_DIALOG, {}));
  }

  protected onBatteryCutButtonClick(): void {
    const dialog: CrDialogElement|null =
        this.shadowRoot!.querySelector('#batteryCutoffDialog');
    assert(dialog);
    if (!dialog.open) {
      dialog.showModal();
    }

    // This is necessary because after the timeout "this" will be the window,
    // and not WrapupRepairCompletePage.
    const cutoffBattery = function(wrapupRepairCompletePage: HTMLElement|
                                   null): void {
      assert(wrapupRepairCompletePage);
      const dialog: CrDialogElement|null =
          wrapupRepairCompletePage.shadowRoot!.querySelector(
              '#batteryCutoffDialog');
      assert(dialog);
      dialog.close();
      executeThenTransitionState(
          wrapupRepairCompletePage,
          () => (wrapupRepairCompletePage as HTMLElement & {
                  shimlessRmaService: ShimlessRmaServiceInterface,
                }).shimlessRmaService.endRma(ShutdownMethod.kBatteryCutoff));
    };

    if (this.batteryTimeoutID === -1) {
      this.batteryTimeoutID =
          setTimeout(() => cutoffBattery(this), this.batteryTimeoutInMs);
    }
  }

  private cutoffBattery(): void {
    const dialog: CrDialogElement|null =
        this.shadowRoot!.querySelector('#batteryCutoffDialog');
    assert(dialog);
    dialog.close();
    executeThenTransitionState(
        this,
        () => this.shimlessRmaService.endRma(ShutdownMethod.kBatteryCutoff));
  }

  protected onCutoffShutdownButtonClick(): void {
    this.cutoffBattery();
  }

  protected closePowerwashDialog(): void {
    const dialog: CrDialogElement|null =
        this.shadowRoot!.querySelector('#powerwashDialog');
    assert(dialog);
    dialog.close();
  }

  protected onCutoffCancelClick(): void {
    this.cancelBatteryCutoff();
  }

  private cancelBatteryCutoff(): void {
    const batteryCutoffDialog: CrDialogElement|null =
        this.shadowRoot!.querySelector('#batteryCutoffDialog');
    assert(batteryCutoffDialog);
    batteryCutoffDialog.close();

    if (this.batteryTimeoutID !== -1) {
      clearTimeout(this.batteryTimeoutID);
      this.batteryTimeoutID = -1;
    }
  }

  /**
   * Implements PowerCableStateObserver.onPowerCableStateChanged()
   */
  onPowerCableStateChanged(pluggedIn: boolean): void {
    this.pluggedIn = pluggedIn;

    if (this.pluggedIn) {
      this.cancelBatteryCutoff();
    }

    const icon: HTMLElement|null =
        this.shadowRoot!.querySelector('#batteryCutoffIcon');
    assert(icon);
    icon.setAttribute(
        'icon',
        this.pluggedIn ? 'shimless-icon:battery-cutoff-disabled' :
                         'shimless-icon:battery-cutoff');
  }

  protected disableBatteryCutButton(): boolean {
    return this.pluggedIn || this.allButtonsDisabled;
  }

  protected getDiagnosticsIcon(): string {
    return this.allButtonsDisabled ? 'shimless-icon:diagnostics-disabled' :
                                     'shimless-icon:diagnostics';
  }

  protected getRmaLogIcon(): string {
    return this.allButtonsDisabled ? 'shimless-icon:rma-log-disabled' :
                                     'shimless-icon:rma-log';
  }

  protected getBatteryCutoffIcon(): string {
    return this.allButtonsDisabled ? 'shimless-icon:battery-cutoff-disabled' :
                                     'shimless-icon:battery-cutoff';
  }

  protected disableShutdownButtons(): boolean {
    return this.shutdownButtonsDisabled || this.allButtonsDisabled;
  }

  protected getRepairCompletedShutoffText(): string {
    return this.pluggedIn ?
        this.i18n('repairCompletedShutoffInstructionsText') :
        this.i18n('repairCompletedShutoffDescriptionText');
  }
}

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

customElements.define(WrapupRepairCompletePage.is, WrapupRepairCompletePage);