blob: d60e44f27549a795a8913ed387253bf0286be3a3 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'Settings page for managing bluetooth properties and devices. This page
* just provodes a summary and link to the subpage.
*/
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
import 'chrome://resources/cr_elements/icons.m.js';
import 'chrome://resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
import '../os_icons.js';
import '../../prefs/prefs.js';
import '../../settings_page/settings_animated_pages.js';
import '../../settings_page/settings_subpage.js';
import '../../settings_shared.css.js';
import './bluetooth_subpage.js';
import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {loadTimeData} from '../../i18n_setup.js';
import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
import {Route, Router} from '../../router.js';
import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
import {recordSettingChange} from '../metrics_recorder.js';
import {routes} from '../os_route.js';
import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
export const bluetoothApis = window['bluetoothApis'] || {
/**
* Set this to provide a fake implementation for testing.
* @type {Bluetooth}
*/
bluetoothApiForTest: null,
/**
* Set this to provide a fake implementation for testing.
* @type {BluetoothPrivate}
*/
bluetoothPrivateApiForTest: null,
};
/**
* @constructor
* @extends {PolymerElement}
* @implements {I18nBehaviorInterface}
* @implements {DeepLinkingBehaviorInterface}
* @implements {PrefsBehaviorInterface}
* @implements {RouteObserverBehaviorInterface}
*/
const SettingsBluetoothPageElementBase = mixinBehaviors(
[I18nBehavior, DeepLinkingBehavior, PrefsBehavior, RouteObserverBehavior],
PolymerElement);
/** @polymer */
class SettingsBluetoothPageElement extends SettingsBluetoothPageElementBase {
static get is() {
return 'settings-bluetooth-page';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/** Preferences state. */
prefs: {
type: Object,
notify: true,
},
/**
* Reflects the current state of the toggle buttons (in this page and the
* subpage). This will be set when the adapter state change or when the
* user changes the toggle.
* @private
*/
bluetoothToggleState_: {
type: Boolean,
observer: 'bluetoothToggleStateChanged_',
},
/**
* Set to true while an adapter state change is requested and the callback
* hasn't fired yet. One of the factor that determines whether to disable
* the toggle button.
* @private
*/
stateChangeInProgress_: {
type: Boolean,
value: false,
},
/**
* The cached bluetooth adapter state.
* @type {!chrome.bluetooth.AdapterState|undefined}
* @private
*/
adapterState_: {
type: Object,
notify: true,
},
/** @private {!Map<string, string>} */
focusConfig_: {
type: Object,
value() {
const map = new Map();
if (routes.BLUETOOTH_DEVICES) {
map.set(
routes.BLUETOOTH_DEVICES.path,
'#bluetoothDevices .subpage-arrow');
}
return map;
},
},
/**
* Interface for bluetooth calls. May be overridden by tests.
* @type {Bluetooth}
* @private
*/
bluetooth: {
type: Object,
value: chrome.bluetooth,
},
/**
* Interface for bluetoothPrivate calls. May be overridden by tests.
* @type {BluetoothPrivate}
* @private
*/
bluetoothPrivate: {
type: Object,
value: chrome.bluetoothPrivate,
},
/**
* Whether the user is a secondary user.
* @private
*/
isSecondaryUser_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('isSecondaryUser');
},
readOnly: true,
},
/**
* Email address for the primary user.
* @private
*/
primaryUserEmail_: {
type: String,
value() {
return loadTimeData.getString('primaryUserEmail');
},
readOnly: true,
},
/**
* Used by DeepLinkingBehavior to focus this page's deep links.
* @type {!Set<!Setting>}
*/
supportedSettingIds: {
type: Object,
value: () => new Set([Setting.kBluetoothOnOff]),
},
};
}
static get observers() {
return ['deviceListChanged_(deviceList_.*)'];
}
/** @override */
ready() {
super.ready();
if (bluetoothApis.bluetoothApiForTest) {
this.bluetooth = bluetoothApis.bluetoothApiForTest;
}
if (bluetoothApis.bluetoothPrivateApiForTest) {
this.bluetoothPrivate = bluetoothApis.bluetoothPrivateApiForTest;
}
}
/** @override */
connectedCallback() {
super.connectedCallback();
/**
* Listener for chrome.bluetooth.onAdapterStateChanged events.
* @type {function(!chrome.bluetooth.AdapterState)|undefined}
* @private
*/
this.bluetoothAdapterStateChangedListener_ =
this.onBluetoothAdapterStateChanged_.bind(this);
this.bluetooth.onAdapterStateChanged.addListener(
this.bluetoothAdapterStateChangedListener_);
// Request the inital adapter state.
this.bluetooth.getAdapterState(this.bluetoothAdapterStateChangedListener_);
}
/** @override */
disconnectedCallback() {
super.disconnectedCallback();
if (this.bluetoothAdapterStateChangedListener_) {
this.bluetooth.onAdapterStateChanged.removeListener(
this.bluetoothAdapterStateChangedListener_);
}
}
/**
* RouteObserverBehavior
* @param {!Route} newRoute
* @param {!Route=} oldRoute
* @protected
*/
currentRouteChanged(newRoute, oldRoute) {
// Does not apply to this page.
if (newRoute !== routes.BLUETOOTH) {
return;
}
this.attemptDeepLink();
}
/**
* @param {boolean} bluetoothToggleState
* @return {string}
* @private
*/
getIcon_(bluetoothToggleState) {
// Don't use |this.bluetoothToggleState_| here, since it has not been
// updated yet to the latest value.
if (!bluetoothToggleState) {
return 'os-settings:bluetooth-disabled';
}
return 'cr:bluetooth';
}
/**
* @param {boolean} enabled
* @param {string} onstr
* @param {string} offstr
* @return {string}
* @private
*/
getOnOffString_(enabled, onstr, offstr) {
return enabled ? onstr : offstr;
}
/**
* @return {boolean}
* @private
*/
isToggleEnabled_() {
return this.adapterState_ !== undefined && this.adapterState_.available &&
!this.stateChangeInProgress_;
}
/**
* Process bluetooth.onAdapterStateChanged events.
* @param {!chrome.bluetooth.AdapterState} state
* @private
*/
onBluetoothAdapterStateChanged_(state) {
this.adapterState_ = state;
if (this.isToggleEnabled_()) {
this.bluetoothToggleState_ = state.powered;
}
}
/**
* Listens for toggle change events (rather than state changes) to handle
* just user-triggered changes to the bluetoothToggleState_.
* @private
*/
onBluetoothToggledByUser_() {
// Record that the user manually enabled/disabled Bluetooth.
recordSettingChange(
Setting.kBluetoothOnOff, {boolValue: this.bluetoothToggleState_});
}
/** @private */
onTap_() {
if (!this.isToggleEnabled_()) {
return;
}
if (!this.bluetoothToggleState_) {
this.bluetoothToggleState_ = true;
} else {
this.openSubpage_();
}
}
/**
* @param {!Event} e
* @private
*/
onSubpageArrowTap_(e) {
this.openSubpage_();
e.stopPropagation();
}
/** @private */
bluetoothToggleStateChanged_() {
if (!this.adapterState_ || !this.isToggleEnabled_() ||
this.bluetoothToggleState_ === this.adapterState_.powered) {
return;
}
this.stateChangeInProgress_ = true;
this.bluetoothPrivate.setAdapterState(
{powered: this.bluetoothToggleState_}, () => {
// Restore the in-progress mark when the callback is called regardless
// of error or success.
this.stateChangeInProgress_ = false;
const error = chrome.runtime.lastError;
if (error && error !== 'Error setting adapter properties: powered') {
console.error('Error enabling bluetooth: ' + error.message);
return;
}
this.setPrefValue(
'ash.user.bluetooth.adapter_enabled',
this.bluetoothToggleState_);
});
}
/** @private */
openSubpage_() {
Router.getInstance().navigateTo(routes.BLUETOOTH_DEVICES);
}
}
customElements.define(
SettingsBluetoothPageElement.is, SettingsBluetoothPageElement);