chromium/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.ts

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Common page for reading and writing preferences from
 * the background context (background page or options page).
 */
import {BridgeHelper} from '/common/bridge_helper.js';
import {LocalStorage} from '/common/local_storage.js';
import {TestImportManager} from '/common/testing/test_import_manager.js';

import {BridgeConstants} from '../common/bridge_constants.js';
import {Msgs} from '../common/msgs.js';
import {SettingsManager} from '../common/settings_manager.js';
import {Personality} from '../common/tts_types.js';

import {EventStreamLogger} from './logging/event_stream_logger.js';
import {LogUrlWatcher} from './logging/log_url_watcher.js';
import {Output} from './output/output.js';
import {TtsBackground} from './tts_background.js';

const Action = BridgeConstants.ChromeVoxPrefs.Action;
const TARGET = BridgeConstants.ChromeVoxPrefs.TARGET;

/**
 * Handles access and setting of preferences, regardless of whether they live in
 * system Settings or the ChromeVox Options page.
 */
export class ChromeVoxPrefs {
  static instance?: ChromeVoxPrefs;

  static darkScreen = false;
  /**
   * This indicates whether or not the sticky mode pref is toggled on.
   * Use ChromeVoxPrefs.isStickyModeOn() to test if sticky mode is enabled
   * either through the pref or due to being temporarily toggled on.
   */
  static isStickyPrefOn = false;
  /**
   * If set to true or false, this value overrides ChromeVoxPrefs.isStickyPrefOn
   * temporarily - in order to temporarily enable sticky mode while doing
   * 'read from here' or to temporarily disable it while using a widget.
   */
  static stickyOverride: boolean | null = null;

  /**
   * Whether the screen is darkened.
   *
   * Starts each session as false, since the display will be on whenever
   * ChromeVox starts.
   */
  private static darkScreen_ = false;

  constructor() {
    // Default per session sticky to off.
    LocalStorage.set('sticky', false);
  }

  /**
   * Merge the default values of all known prefs with what's found in
   * LocalStorage.
   */
  static init(): void {
    ChromeVoxPrefs.instance = new ChromeVoxPrefs();

    ChromeVoxPrefs.isStickyPrefOn = LocalStorage.getBoolean('sticky');

    // Set the default value of any pref that isn't already in LocalStorage.
    for (const pref in DEFAULT_PREFS) {
      if (LocalStorage.get(pref) === undefined) {
        LocalStorage.set(pref, DEFAULT_PREFS[pref]);
      }
    }

    // TODO(b/314203187): Not null asserted, check that this is correct.
    BridgeHelper.registerHandler(
        TARGET, Action.GET_PREFS, () => ChromeVoxPrefs.instance!.getPrefs());
    BridgeHelper.registerHandler(
        TARGET, Action.GET_STICKY_PREF, () => ChromeVoxPrefs.isStickyPrefOn);
    BridgeHelper.registerHandler(
        TARGET, Action.SET_LOGGING_PREFS,
        (key: LoggingPrefs, value: boolean) =>
            ChromeVoxPrefs.instance!.setLoggingPrefs(key, value));
    BridgeHelper.registerHandler(
        TARGET, Action.SET_PREF,
        (key: string, value: Object | string | number | boolean) =>
            ChromeVoxPrefs.instance!.setPref(key, value));
  }

  /**
   * Get the prefs (not including keys).
   * @return A map of all prefs except the key map from LocalStorage and
   *     SettingsManager.
   */
  getPrefs(): {[key: string]: any} {
    let prefs: {[key: string]: any} = {};
    for (const pref in DEFAULT_PREFS) {
      prefs[pref] = LocalStorage.get(pref);
    }
    for (const pref of SettingsManager.PREFS) {
      prefs[pref] = SettingsManager.get(pref);
    }
    prefs = {...prefs, ...SettingsManager.getEventStreamFilters()};
    return prefs;
  }

  /** Set the value of a pref. */
  setPref(key: string, value: Object | string | number | boolean): void {
    if (SettingsManager.EVENT_STREAM_FILTERS.includes(key)) {
      SettingsManager.setEventStreamFilter(key, Boolean(value));
      return;
    }
    if (SettingsManager.PREFS.includes(key)) {
      SettingsManager.set(key, value);
      return;
    }
    if (LocalStorage.get(key) !== value) {
      LocalStorage.set(key, value);
    }
  }

  /** Set the value of a pref of logging options. */
  setLoggingPrefs(key: LoggingPrefs, value: boolean): void {
    SettingsManager.set(key, value);
    if (key === 'enableSpeechLogging') {
      TtsBackground.console.setEnabled(value);
    } else if (key === 'enableEventStreamLogging') {
      EventStreamLogger.instance.updateAllFilters(value);
    }
    this.enableOrDisableLogUrlWatcher();
  }

  /**
   * Returns whether sticky mode is on, taking both the global sticky mode
   * pref and the temporary sticky mode override into account.
   */
  static isStickyModeOn(): boolean {
    if (ChromeVoxPrefs.stickyOverride !== null) {
      return ChromeVoxPrefs.stickyOverride;
    } else {
      return ChromeVoxPrefs.isStickyPrefOn;
    }
  }

  /**
   * Sets the value of the sticky mode pref, as well as updating the listeners
   * and announcing.
   */
  setAndAnnounceStickyPref(value: boolean): void {
    chrome.accessibilityPrivate.setKeyboardListener(true, value);
    new Output()
        .withInitialSpeechProperties(Personality.ANNOTATION)
        .withString(
            value ? Msgs.getMsg('sticky_mode_enabled') :
                    Msgs.getMsg('sticky_mode_disabled'))
        .go();
    this.setPref('sticky', value);
    ChromeVoxPrefs.isStickyPrefOn = value;
  }

  get darkScreen(): boolean {
    return ChromeVoxPrefs.darkScreen_;
  }

  set darkScreen(newVal: boolean) {
    ChromeVoxPrefs.darkScreen_ = newVal;
  }

  enableOrDisableLogUrlWatcher(): void {
    for (const pref of Object.values(LoggingPrefs)) {
      if (SettingsManager.getBoolean(pref)) {
        LogUrlWatcher.create();
        return;
      }
    }
    LogUrlWatcher.destroy();
  }
}

export enum LoggingPrefs {
  SPEECH = 'enableSpeechLogging',
  BRAILLE = 'enableBrailleLogging',
  EARCON = 'enableEarconLogging',
  EVENT = 'enableEventStreamLogging',
}

// Local to module.

/**
 * The default value of all preferences in LocalStorage except the key map.
 *
 * TODO(b/262786141): Move each of these to SettingsManager.
 */
const DEFAULT_PREFS: {[key: string]: boolean | number} = {
  'brailleCaptions': false,
  'earcons': true,
  'sticky': false,
  'typingEcho': 0,
};

TestImportManager.exportForTesting(ChromeVoxPrefs);