chromium/chrome/browser/resources/ash/settings/multidevice_page/multidevice_feature_mixin.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 Polymer behavior for dealing with MultiDevice features. It is
 * intended to facilitate passing data between elements in the MultiDevice page
 * cleanly and concisely. It includes some constants and utility methods.
 */
import {I18nMixin, I18nMixinInterface} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {isRevampWayfindingEnabled} from '../common/load_time_booleans.js';
import {Constructor} from '../common/types.js';

import {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessStatus} from './multidevice_constants.js';

export interface MultiDeviceFeatureMixinInterface extends I18nMixinInterface {
  pageContentData: MultiDevicePageContentData;
  MultiDeviceFeature: MultiDeviceFeature;
  getFeatureName(feature: MultiDeviceFeature): string;
  getFeatureState(feature: MultiDeviceFeature): MultiDeviceFeatureState|null;
  getFeatureSummaryHtml(feature: MultiDeviceFeature): TrustedHTML|string;
  getIconName(feature: MultiDeviceFeature): string;
  isFeatureAllowedByPolicy(feature: MultiDeviceFeature): boolean;
  isFeatureStateEditable(feature: MultiDeviceFeature): boolean;
  isFeatureSupported(feature: MultiDeviceFeature): boolean;
  isHostSet(): boolean;
  isPhoneHubAppsAccessProhibited(): boolean;
  isPhoneHubAppsSetupRequired(): boolean;
  isPhoneHubCameraRollSetupRequired(): boolean;
  isPhoneHubNotificationAccessProhibited(): boolean;
  isPhoneHubNotificationsSetupRequired(): boolean;
  isPhoneHubOn(): boolean;
  isPhoneHubSubFeature(feature: MultiDeviceFeature): boolean;
  isSuiteAllowedByPolicy(): boolean;
  isSuiteOn(): boolean;
}

export const MultiDeviceFeatureMixin = dedupingMixin(
    <T extends Constructor<PolymerElement>>(superClass: T): T&
    Constructor<MultiDeviceFeatureMixinInterface> => {
      const superClassBase = I18nMixin(superClass);

      class MultiDeviceFeatureMixinInternal extends superClassBase implements
          MultiDeviceFeatureMixinInterface {
        static get properties() {
          return {
            pageContentData: Object,

            /**
             * Enum defined in multidevice_constants.js. Defined as a property
             * to be used in Polymer HTML bindings and mirror the enum.
             */
            MultiDeviceFeature: {
              type: Object,
              value: MultiDeviceFeature,
            },
          };
        }

        // eslint-disable-next-line @typescript-eslint/naming-convention
        MultiDeviceFeature: MultiDeviceFeature;
        pageContentData: MultiDevicePageContentData;

        /**
         * Whether the gatekeeper pref for the whole Better Together feature
         * suite is on.
         */
        isSuiteOn(): boolean {
          return !!this.pageContentData &&
              this.pageContentData.betterTogetherState ===
              MultiDeviceFeatureState.ENABLED_BY_USER;
        }

        /**
         * Whether the gatekeeper pref for the whole Better Together feature
         * suite is allowed by policy.
         */
        isSuiteAllowedByPolicy(): boolean {
          return !!this.pageContentData &&
              this.pageContentData.betterTogetherState !==
              MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
        }

        /**
         * Whether the instant hotspot rebrand feature flag is enabled
         */
        isInstantHotspotRebrandEnabled(): boolean {
          return loadTimeData.getBoolean('isInstantHotspotRebrandEnabled');
        }

        /**
         * Whether an individual feature is allowed by policy.
         */
        isFeatureAllowedByPolicy(feature: MultiDeviceFeature): boolean {
          return this.getFeatureState(feature) !==
              MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
        }

        isFeatureSupported(feature: MultiDeviceFeature): boolean {
          return ![MultiDeviceFeatureState.NOT_SUPPORTED_BY_CHROMEBOOK,
                   MultiDeviceFeatureState.NOT_SUPPORTED_BY_PHONE,
          ].includes(this.getFeatureState(feature)!);
        }

        /**
         * Whether the top-level Phone Hub feature is enabled.
         */
        isPhoneHubOn(): boolean {
          return this.getFeatureState(MultiDeviceFeature.PHONE_HUB) ===
              MultiDeviceFeatureState.ENABLED_BY_USER;
        }

        isPhoneHubSubFeature(feature: MultiDeviceFeature): boolean {
          return [
            MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL,
            MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
            MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION,
            MultiDeviceFeature.ECHE,
          ].includes(feature);
        }

        /**
         * @return Whether or not Phone Hub notification access is
         *     prohibited (i.e., due to the user having a work profile).
         */
        isPhoneHubNotificationAccessProhibited(): boolean {
          return this.pageContentData &&
              this.pageContentData.notificationAccessStatus ===
              PhoneHubFeatureAccessStatus.PROHIBITED;
        }

        /**
         * @return Whether or not Phone Hub apps access is prohibited
         * (i.e., due to the apps streaming policy of the phone is disabled).
         */
        isPhoneHubAppsAccessProhibited(): boolean {
          return this.pageContentData &&
              this.pageContentData.appsAccessStatus ===
              PhoneHubFeatureAccessStatus.PROHIBITED;
        }

        /**
         * Whether Camera Roll requires user action to finish set up.
         */
        isPhoneHubCameraRollSetupRequired(): boolean {
          return this.isFeatureSupported(
                     MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL) &&
              this.pageContentData.cameraRollAccessStatus ===
              PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED &&
              this.isFeatureAllowedByPolicy(
                  MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL);
        }

        /**
         * Whether Apps requires user action to finish set up.
         */
        isPhoneHubAppsSetupRequired(): boolean {
          return this.isFeatureSupported(MultiDeviceFeature.ECHE) &&
              this.pageContentData.isPhoneHubPermissionsDialogSupported &&
              this.pageContentData.appsAccessStatus ===
              PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED &&
              this.isFeatureAllowedByPolicy(MultiDeviceFeature.ECHE);
        }

        /**
         * Whether Notifications requires user action to finish set up.
         */
        isPhoneHubNotificationsSetupRequired(): boolean {
          return this.pageContentData.notificationAccessStatus ===
              PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED &&
              this.isFeatureAllowedByPolicy(
                  MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS);
        }

        /**
         * Whether the user is prevented from attempted to change a given
         * feature. In the UI this corresponds to a disabled toggle.
         */
        isFeatureStateEditable(feature: MultiDeviceFeature): boolean {
          // The suite is off and the toggle corresponds to an individual
          // feature (as opposed to the full suite).
          if (feature !== MultiDeviceFeature.BETTER_TOGETHER_SUITE &&
              !this.isSuiteOn()) {
            return false;
          }

          // Cannot edit Phone Hub sub-feature toggles if the top-level Phone
          // Hub feature is not enabled.
          if (this.isPhoneHubSubFeature(feature) && !this.isPhoneHubOn()) {
            return false;
          }

          // Cannot edit the Phone Hub notification toggle if notification
          // access is prohibited.
          if (feature === MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS &&
              this.isPhoneHubNotificationAccessProhibited()) {
            return false;
          }

          // Cannot edit the Phone Hub apps toggle if apps access is
          // prohibited.
          if (feature === MultiDeviceFeature.ECHE &&
              this.isPhoneHubAppsAccessProhibited()) {
            return false;
          }

          return [
            MultiDeviceFeatureState.DISABLED_BY_USER,
            MultiDeviceFeatureState.ENABLED_BY_USER,
          ].includes(this.getFeatureState(feature)!);
        }

        /**
         * The localized string representing the name of the feature.
         */
        getFeatureName(feature: MultiDeviceFeature): string {
          switch (feature) {
            case MultiDeviceFeature.BETTER_TOGETHER_SUITE:
              return this.i18n('multideviceSetupItemHeading');
            case MultiDeviceFeature.INSTANT_TETHERING:
              return this.isInstantHotspotRebrandEnabled() ?
                  this.i18n('multideviceInstantHotspotItemTitle') :
                  this.i18n('multideviceInstantTetheringItemTitle');
            case MultiDeviceFeature.SMART_LOCK:
              return this.i18n('multideviceSmartLockItemTitle');
            case MultiDeviceFeature.PHONE_HUB:
              return this.i18n('multidevicePhoneHubItemTitle');
            case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
              return this.i18n('multidevicePhoneHubCameraRollItemTitle');
            case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
              return this.i18n('multidevicePhoneHubNotificationsItemTitle');
            case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
              return this.i18n('multidevicePhoneHubTaskContinuationItemTitle');
            case MultiDeviceFeature.WIFI_SYNC:
              return this.i18n('multideviceWifiSyncItemTitle');
            case MultiDeviceFeature.ECHE:
              return this.i18n('multidevicePhoneHubAppsItemTitle');
            default:
              return '';
          }
        }

        /**
         * The full icon name used provided by the containing iron-iconset-svg
         * (i.e. [iron-iconset-svg name]:[SVG <g> tag id]) for a given feature.
         */
        getIconName(feature: MultiDeviceFeature): string {
          const deviceIcon = isRevampWayfindingEnabled() ?
              'os-settings:connected-devices-android-phone' :
              'os-settings:multidevice-better-together-suite';

          switch (feature) {
            case MultiDeviceFeature.BETTER_TOGETHER_SUITE:
              return deviceIcon;
            case MultiDeviceFeature.SMART_LOCK:
              return 'os-settings:multidevice-smart-lock';
            case MultiDeviceFeature.PHONE_HUB:
            case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
            case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
            case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
            case MultiDeviceFeature.ECHE:
              return deviceIcon;
            case MultiDeviceFeature.WIFI_SYNC:
              return 'os-settings:multidevice-wifi-sync';
            default:
              return '';
          }
        }

        /**
         * The localized string providing a description or useful status
         * information concerning a given feature.
         */
        getFeatureSummaryHtml(feature: MultiDeviceFeature): TrustedHTML|string {
          switch (feature) {
            case MultiDeviceFeature.SMART_LOCK:
              return this.i18nAdvanced('multideviceSmartLockItemSummary');
            case MultiDeviceFeature.INSTANT_TETHERING:
              return this.i18nAdvanced(
                  'multideviceInstantTetheringItemSummary');
            case MultiDeviceFeature.PHONE_HUB:
              return this.i18nAdvanced('multidevicePhoneHubItemSummary');
            case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
              return this.i18nAdvanced(
                  'multidevicePhoneHubCameraRollItemSummary');
            case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
              return this.i18nAdvanced(
                  'multidevicePhoneHubNotificationsItemSummary');
            case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
              return this.i18nAdvanced(
                  'multidevicePhoneHubTaskContinuationItemSummary');
            case MultiDeviceFeature.WIFI_SYNC:
              return this.i18nAdvanced('multideviceWifiSyncItemSummary');
            case MultiDeviceFeature.ECHE:
              return this.i18nAdvanced('multidevicePhoneHubAppsItemSummary');
            default:
              return '';
          }
        }

        /**
         * Extracts the MultiDeviceFeatureState enum value describing the given
         * feature from this.pageContentData. Returns null if the feature is not
         * an accepted value (e.g. testing fake).
         */
        getFeatureState(feature: MultiDeviceFeature): MultiDeviceFeatureState
            |null {
          if (!this.pageContentData) {
            return null;
          }

          switch (feature) {
            case MultiDeviceFeature.BETTER_TOGETHER_SUITE:
              return this.pageContentData.betterTogetherState;
            case MultiDeviceFeature.INSTANT_TETHERING:
              return this.pageContentData.instantTetheringState;
            case MultiDeviceFeature.SMART_LOCK:
              return this.pageContentData.smartLockState;
            case MultiDeviceFeature.PHONE_HUB:
              return this.pageContentData.phoneHubState;
            case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
              return this.pageContentData.phoneHubCameraRollState;
            case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
              return this.pageContentData.phoneHubNotificationsState;
            case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
              return this.pageContentData.phoneHubTaskContinuationState;
            case MultiDeviceFeature.WIFI_SYNC:
              return this.pageContentData.wifiSyncState;
            case MultiDeviceFeature.ECHE:
              return this.pageContentData.phoneHubAppsState;
            default:
              return null;
          }
        }

        /**
         * Whether a host phone has been set by the user (not necessarily
         * verified).
         */
        isHostSet(): boolean {
          return [
            MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
            MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
            MultiDeviceSettingsMode.HOST_SET_VERIFIED,
          ].includes(this.pageContentData.mode);
        }
      }

      return MultiDeviceFeatureMixinInternal;
    });