chromium/chrome/browser/resources/ash/settings/multidevice_page/multidevice_feature_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
 * Item for an individual multidevice feature. These features appear in the
 * multidevice subpage to allow the user to individually toggle them as long as
 * the phone is enabled as a multidevice host. The feature items contain basic
 * information relevant to the individual feature, such as a route to the
 * feature's autonomous page if there is one.
 */

import 'chrome://resources/ash/common/cr_elements/localized_link/localized_link.js';
import 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import '../settings_shared.css.js';
import './multidevice_feature_toggle.js';

import {assert} from 'chrome://resources/js/assert.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {castExists} from '../assert_extras.js';
import {RouteOriginMixin} from '../common/route_origin_mixin.js';
import {Route, Router, routes} from '../router.js';

import {MultiDeviceFeature} from './multidevice_constants.js';
import {getTemplate} from './multidevice_feature_item.html.js';
import {MultiDeviceFeatureMixin} from './multidevice_feature_mixin.js';
import {recordSmartLockToggleMetric, SmartLockToggleLocation} from './multidevice_metrics_logger.js';

const SettingsMultideviceFeatureItemElementBase =
    MultiDeviceFeatureMixin(RouteOriginMixin(PolymerElement));

export class SettingsMultideviceFeatureItemElement extends
    SettingsMultideviceFeatureItemElementBase {
  static get is() {
    return 'settings-multidevice-feature-item' as const;
  }

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

  static get properties() {
    return {
      feature: Number,

      /**
       * If it is truthy, the item should be actionable and clicking on it
       * should navigate to the provided route. Otherwise, the item does not
       * have a subpage to navigate to.
       */
      subpageRoute: Object,

      /**
       * A tooltip to show over a help icon. If unset, no help icon is shown.
       */
      iconTooltip: String,

      /**
       * A Chrome icon asset to use as a help icon. The icon is not shown if
       * iconTooltip is unset. Defaults to cr:info-outline.
       */
      icon: {
        type: String,
        value: 'cr:info-outline',
      },

      /**
       * URLSearchParams for subpage route. No param is provided if it is
       * undefined.
       */
      subpageRouteUrlSearchParams: Object,

      /** Whether if the feature is a sub-feature */
      isSubFeature: {
        type: Boolean,
        value: false,
        reflectToAttribute: true,
      },

      /** Whether feature icon is present next to text in row */
      isFeatureIconHidden: {
        type: Boolean,
        value: false,
      },
    };
  }

  feature: MultiDeviceFeature;
  icon: string;
  iconTooltip: string;
  isSubFeature: boolean;
  isFeatureIconHidden: boolean;
  subpageRoute: Route|undefined;
  subpageRouteUrlSearchParams: URLSearchParams|undefined;

  constructor() {
    super();

    /** RouteOriginMixin override */
    this.route = routes.MULTIDEVICE_FEATURES;
  }

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

    this.addEventListener(
        'feature-toggle-clicked', this.onFeatureToggleClicked_);

    this.addFocusConfig(this.subpageRoute, '#subpageButton');
  }

  override focus(): void {
    const slot = castExists(this.shadowRoot!.querySelector<HTMLSlotElement>(
        'slot[name="feature-controller"]'));
    const elems = slot.assignedElements({flatten: true});
    assert(elems.length > 0);
    // Elems contains any elements that override the feature controller. If none
    // exist, contains the default toggle elem.
    (elems[0] as HTMLElement).focus();
  }

  private isRowClickable_(): boolean {
    return this.hasSubpageClickHandler_() ||
        this.isFeatureStateEditable(this.feature);
  }

  private hasSubpageClickHandler_(): boolean {
    return !!this.subpageRoute && this.isFeatureAllowedByPolicy(this.feature);
  }

  private shouldShowSeparator_(): boolean {
    return this.hasSubpageClickHandler_() || !!this.iconTooltip;
  }

  private handleItemClick_(event: Event): void {
    // We do not navigate away if the click was on a link.
    if ((event.composedPath()[0] as Element).tagName === 'A') {
      event.stopPropagation();
      return;
    }

    if (!this.hasSubpageClickHandler_()) {
      if (this.isFeatureStateEditable(this.feature)) {
        // Toggle the editable feature if the feature is editable and does not
        // link to a subpage.
        const toggleButton = castExists(this.shadowRoot!.querySelector(
            'settings-multidevice-feature-toggle'));
        toggleButton.toggleFeature();
      }
      return;
    }

    // Remove the search term when navigating to avoid potentially having any
    // visible search term reappear at a later time. See
    // https://crbug.com/989119.
    assert(this.subpageRoute);
    Router.getInstance().navigateTo(
        this.subpageRoute, this.subpageRouteUrlSearchParams,
        true /* opt_removeSearch */);
  }


  /**
   * The class name used for given multidevice feature item text container
   * Checks if icon is present next to text to determine if class 'middle'
   * applies
   */
  private getItemTextContainerClassName_(isFeatureIconHidden: boolean): string {
    return isFeatureIconHidden ? 'start' : 'middle';
  }

  /**
   * Intercept (but do not stop propagation of) the feature-toggle-clicked event
   * for the purpose of logging metrics.
   */
  private onFeatureToggleClicked_(
      event: CustomEvent<{feature: MultiDeviceFeature, enabled: boolean}>):
      void {
    const feature = event.detail.feature;
    const enabled = event.detail.enabled;

    if (feature === MultiDeviceFeature.SMART_LOCK) {
      const toggleLocation =
          Router.getInstance().currentRoute.contains(routes.LOCK_SCREEN) ?
          SmartLockToggleLocation.LOCK_SCREEN_SETTINGS :
          SmartLockToggleLocation.MULTIDEVICE_PAGE;

      recordSmartLockToggleMetric(toggleLocation, enabled);
    }
  }
}

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

customElements.define(
    SettingsMultideviceFeatureItemElement.is,
    SettingsMultideviceFeatureItemElement);