// 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.
/**
* @fileoverview
* UI element to show a list of discovered Bluetooth devices and initiate
* pairing to a device.
*/
import './bluetooth_base_page.js';
import './bluetooth_pairing_device_item.js';
import '//resources/ash/common/cr_elements/cr_shared_style.css.js';
import '//resources/polymer/v3_0/iron-list/iron-list.js';
import '//resources/ash/common/cr_elements/localized_link/localized_link.js';
import {CrScrollableMixin} from '//resources/ash/common/cr_elements/cr_scrollable_mixin.js';
import {I18nMixin} from '//resources/ash/common/cr_elements/i18n_mixin.js';
import {BluetoothDeviceProperties} from '//resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
import {afterNextRender, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {getTemplate} from './bluetooth_pairing_device_selection_page.html.js';
import {ButtonBarState, ButtonState, DeviceItemState} from './bluetooth_types.js';
const SettingsBluetoothPairingDeviceSelectionPageElementBase =
I18nMixin(CrScrollableMixin(PolymerElement));
export class SettingsBluetoothPairingDeviceSelectionPageElement extends
SettingsBluetoothPairingDeviceSelectionPageElementBase {
static get is() {
return 'bluetooth-pairing-device-selection-page' as const;
}
static get template() {
return getTemplate();
}
static get properties() {
return {
devices: {
type: Array,
value: [],
observer: 'onDevicesChanged_',
},
/**
* Id of a device who's pairing attempt failed.
*/
failedPairingDeviceId: {
type: String,
value: '',
},
devicePendingPairing: {
type: Object,
value: null,
observer: 'onDevicePendingPairingChanged_',
},
isBluetoothEnabled: {
type: Boolean,
value: false,
},
/**
* Flag indicating whether links should be displayed or not. In some
* cases, such as the user being in OOBE or the login screen, links will
* not work and should not be displayed.
*/
shouldOmitLinks: {
type: Boolean,
value: false,
},
buttonBarState_: {
type: Object,
value: {
cancel: ButtonState.ENABLED,
pair: ButtonState.HIDDEN,
},
},
/**
* Used by FocusRowBehavior to track the last focused element on a row.
*/
lastFocused_: Object,
/**
* Used by FocusRowBehavior to track if the list has been blurred.
*/
listBlurred_: Boolean,
};
}
devices: BluetoothDeviceProperties[];
failedPairingDeviceId: string;
devicePendingPairing: BluetoothDeviceProperties|null;
isBluetoothEnabled: boolean;
shouldOmitLinks: boolean;
private buttonBarState_: ButtonBarState;
private lastFocused_?: HTMLElement;
private listBlurred_: boolean;
private lastSelectedDevice_: BluetoothDeviceProperties | null;
constructor() {
super();
/**
* The last device that was selected for pairing
*/
this.lastSelectedDevice_ = null;
}
/**
* Attempts to focus the item corresponding to |lastSelectedDevice_|.
*/
attemptFocusLastSelectedItem(): void {
if (!this.lastSelectedDevice_) {
return;
}
const index = this.devices.findIndex(
device => device.id === this.lastSelectedDevice_!.id);
if (index < 0) {
return;
}
afterNextRender(this, ()=> {
const items =
this.shadowRoot!.querySelectorAll('bluetooth-pairing-device-item');
if (index >= items.length) {
return;
}
items[index].focus();
});
}
private onDevicesChanged_(): void {
// CrScrollableBehaviorInterface method required for list items to be
// properly rendered when devices updates. This is because iron-list size
// is not fixed, if this is not called iron-list container would not be
// properly sized.
this.updateScrollableContents();
}
private onDevicePendingPairingChanged_(): void {
// If |devicePendingPairing_| has changed to a defined value, it was the
// last selected device. |devicePendingPairing_| gets reset to null whenever
// we move back to this page after a pairing attempt fails or cancels. In
// this case, do not reset |lastSelectedDevice_| because we want to hold
// onto the device that was last attempted to be paired with.
if (!this.devicePendingPairing) {
return;
}
this.lastSelectedDevice_ = this.devicePendingPairing;
}
private shouldShowDeviceList_(): boolean {
return this.isBluetoothEnabled && this.devices && this.devices.length > 0;
}
private getDeviceListTitle_(): string {
if (!this.isBluetoothEnabled) {
return this.i18n('bluetoothDisabled');
}
// Case where device list becomes empty (device is turned off)
// and then device pairing fails.
// Note: |devices| always updates before pairing result
// returns.
if (!this.devicePendingPairing && !this.shouldShowDeviceList_()) {
return this.i18n('bluetoothNoAvailableDevices');
}
// When pairing succeeds there is a brief moement where |devices| is empty
// (device is removed from discovered list) but because of b/216522777 we
// still want to show available devices header, we check for
// |devicePendingPairing| which will have a value since it is only reset
// after pairing fails.
if (this.shouldShowDeviceList_() || this.devicePendingPairing) {
return this.i18n('bluetoothAvailableDevices');
}
return this.i18n('bluetoothNoAvailableDevices');
}
private getDeviceItemState_(device: BluetoothDeviceProperties): DeviceItemState {
if (!device) {
return DeviceItemState.DEFAULT;
}
if (device.id === this.failedPairingDeviceId) {
return DeviceItemState.FAILED;
}
if (this.devicePendingPairing &&
device.id === this.devicePendingPairing.id) {
return DeviceItemState.PAIRING;
}
return DeviceItemState.DEFAULT;
}
}
declare global {
interface HTMLElementTagNameMap {
[SettingsBluetoothPairingDeviceSelectionPageElement.is]:
SettingsBluetoothPairingDeviceSelectionPageElement;
}
}
customElements.define(
SettingsBluetoothPairingDeviceSelectionPageElement.is,
SettingsBluetoothPairingDeviceSelectionPageElement);