chromium/chrome/browser/resources/bluetooth_internals/device_collection.js

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

/**
 * Javascript for DeviceCollection, served from
 *     chrome://bluetooth-internals/.
 */

import {assert} from 'chrome://resources/js/assert.js';

/**
 * Enum of connection status for a device.
 * @enum {number}
 */
export const ConnectionStatus = {
  DISCONNECTED: 0,
  CONNECTING: 1,
  CONNECTED: 2,
};

/**
 * Collection of devices. Extends EventTarget to notify observers when the
 * collection changes.
 */
export class DeviceCollection extends EventTarget {
  /**
   * @param {!Array<!DeviceInfo>} array The starting
   *     collection of devices.
   */
  constructor(array) {
    super();

    /** @private {!Array<!DeviceInfo>} */
    this.array_ = array;

    // Keep track of MAC addresses which were previously found via scan, but
    // are no longer being advertised or nearby. Used to inform isRemoved().
    /** @private {!Object<string, boolean>} */
    this.removedDevices_ = {};
  }

  /** @return {number} */
  get length() {
    return this.array_.length;
  }

  /**
   * @param {number}
   * @return {DeviceInfo}
   */
  item(index) {
    return (index < 0 || index > this.length) ? undefined : this.array_[index];
  }

  /**
   * Finds the Device in the collection with the matching address.
   * @param {string} address
   * @return {number} The index where the device was found.
   */
  getByAddress(address) {
    for (let i = 0; i < this.length; i++) {
      const device = this.array_[i];
      if (address === device.address) {
        return i;
      }
    }
    return -1;
  }

  /**
   * Adds or updates a Device with new DeviceInfo.
   * @param {!DeviceInfo} deviceInfo
   */
  addOrUpdate(deviceInfo) {
    this.removedDevices_[deviceInfo.address] = false;
    const oldDeviceIndex = this.getByAddress(deviceInfo.address);

    if (oldDeviceIndex !== -1) {
      // Update rssi if it's valid
      const oldDeviceInfo = this.array_[oldDeviceIndex];
      const rssi = (deviceInfo.rssi && deviceInfo.rssi.value) ||
          (oldDeviceInfo.rssi && oldDeviceInfo.rssi.value);

      // The rssi property may be null, so it must be re-assigned.
      Object.assign(oldDeviceInfo, deviceInfo);
      oldDeviceInfo.rssi = {value: rssi};
      this.dispatchEvent(new CustomEvent(
          'device-update',
          {bubbles: true, composed: true, detail: oldDeviceIndex}));
    } else {
      this.array_.push(deviceInfo);
      this.dispatchEvent(new CustomEvent('device-added', {
        bubbles: true,
        composed: true,
        detail: {index: this.length - 1, device: deviceInfo},
      }));
    }
  }

  /**
   * Marks the Device as removed.
   * @param {!DeviceInfo} deviceInfo
   */
  remove(deviceInfo) {
    const deviceIndex = this.getByAddress(deviceInfo.address);
    assert(deviceIndex !== -1, 'Device does not exist.');
    this.removedDevices_[deviceInfo.address] = true;
    this.dispatchEvent(new CustomEvent(
        'device-update', {bubbles: true, composed: true, detail: deviceIndex}));
  }

  /**
   * Return true if device was "removed" -- previously found via scan but
   * either no longer advertising or no longer nearby.
   * @param {!DeviceInfo} deviceInfo
   * @return {boolean}
   */
  isRemoved(deviceInfo) {
    return !!this.removedDevices_[deviceInfo.address];
  }

  resetForTest() {
    this.array_ = [];
    this.removedDevices_ = [];

    this.dispatchEvent(new CustomEvent(
        'devices-reset-for-test', {bubbles: true, composed: true}));
  }
}