Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
1043 lines
33 KiB
JavaScript
Executable file
1043 lines
33 KiB
JavaScript
Executable file
/**
|
|
* BluetoothContext:
|
|
* - BluetoothContext is an Observable that wraps the platform Bluetooth
|
|
* object.
|
|
* - BluetoothContext is a singleton that you can easily use it to fetch some
|
|
* shared data across different panels.
|
|
* - It has some observable properties: state, enabled, address, name,
|
|
* discoverable, discovering, numberOfPairedDevices, firstPairedDeviceName,
|
|
* hasPairedDevice.
|
|
* - It has two observable array: _pairedDevices, _remoteDevices.
|
|
* BluetoothContext only update state and does not involve in any UI logic.
|
|
*
|
|
* @module BluetoothContext
|
|
*/
|
|
define(function(require) {
|
|
'use strict';
|
|
|
|
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
|
|
var BtDevice = require('modules/bluetooth/bluetooth_device');
|
|
var ConnectionManager =
|
|
require('modules/bluetooth/bluetooth_connection_manager');
|
|
var Observable = require('modules/mvvm/observable');
|
|
var ObservableArray = require('modules/mvvm/observable_array');
|
|
var SettingsCache = require('modules/settings_cache');
|
|
|
|
var settings = navigator.mozSettings;
|
|
|
|
var _debug = false;
|
|
var debug = function() {};
|
|
if (_debug) {
|
|
debug = function btc_debug(msg) {
|
|
console.log('--> [BluetoothContext]: ' + msg);
|
|
};
|
|
}
|
|
|
|
var BluetoothContext = {
|
|
/**
|
|
* State of Bluetooth default adapter.
|
|
* This is an enum of BluetoothAdapterState.
|
|
* State: 'disabled', 'disabling', 'enabled', 'enabling'
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {String}
|
|
*/
|
|
state: 'disabled',
|
|
|
|
/**
|
|
* State of Bluetooth.
|
|
*
|
|
* Set the default state as undefined in bug 1191104 Since Bluetooth
|
|
* adapter API need sometime to respond for true Bluetooth state
|
|
*
|
|
* @readonly
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @type {Boolean}
|
|
*/
|
|
enabled: undefined,
|
|
|
|
/**
|
|
* The address of the device's adapter on the bluetooth micro-network.
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {String}
|
|
*/
|
|
address: null,
|
|
|
|
/**
|
|
* The human readable name of the device's adapter.
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {String}
|
|
*/
|
|
name: '',
|
|
|
|
/**
|
|
* Indicates if the device is discoverable (true) or not (false)
|
|
* by other bluetooth devices.
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {Boolean}
|
|
*/
|
|
discoverable: false,
|
|
|
|
/**
|
|
* Indicates if the device is in the process of discovering (true) or
|
|
* not (false) surrounding bluetooth devices.
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {Boolean}
|
|
*/
|
|
discovering: false,
|
|
|
|
/**
|
|
* Number of Bluetooth paired devices.
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {Number}
|
|
*/
|
|
numberOfPairedDevices: 0,
|
|
|
|
/**
|
|
* Device name of Bluetooth paired devices in the first sorting.
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {String}
|
|
*/
|
|
firstPairedDeviceName: '',
|
|
|
|
/**
|
|
* Indicates if the paired device is in the paired devices list (true) or
|
|
* not (false).
|
|
*
|
|
* @readonly
|
|
* @memberOf BluetoothContext
|
|
* @type {Boolean}
|
|
*/
|
|
hasPairedDevice: false,
|
|
|
|
/**
|
|
* Default adapter of Bluetooth.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @type {BluetoothAdapter}
|
|
*/
|
|
_defaultAdapter: null,
|
|
|
|
/**
|
|
* Init BluetoothContext module.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
*/
|
|
_init: function btc__init() {
|
|
// Observe 'defaultAdapter' property for reaching default adapter.
|
|
AdapterManager.observe('defaultAdapter',
|
|
this._onDefaultAdapterChanged.bind(this));
|
|
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
|
|
|
|
// Watch 'connecting' event for reaching connecting device.
|
|
ConnectionManager.addEventListener('connecting',
|
|
this._updateDeviceConnectionInfo.bind(this));
|
|
|
|
// Watch 'connected' event for reaching connected device.
|
|
ConnectionManager.addEventListener('connected',
|
|
this._updateDeviceConnectionInfo.bind(this));
|
|
|
|
// Watch 'disconnected' event for reaching disconnected device.
|
|
ConnectionManager.addEventListener('disconnected',
|
|
this._updateDeviceConnectionInfo.bind(this));
|
|
|
|
// Watch 'profileChange' event for reaching device connection profile.
|
|
ConnectionManager.addEventListener('profileChanged',
|
|
this._updateDeviceConnectionInfo.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Init properties from default adapter.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_initProperties: function btc__initProperties(adapter) {
|
|
// init observable properties
|
|
this._updateStatus(adapter.state);
|
|
this.address = adapter.address;
|
|
this.name = adapter.name;
|
|
this.discoverable = adapter.discoverable;
|
|
this.discovering = adapter.discovering;
|
|
},
|
|
|
|
/**
|
|
* Only reset properties since there is no available default adapter.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
*/
|
|
_resetProperties: function btc__resetProperties() {
|
|
this._updateStatus('disabled');
|
|
this.address = '';
|
|
this.name = '';
|
|
this.discoverable = false;
|
|
this.discovering = false;
|
|
},
|
|
|
|
/**
|
|
* Watch 'attributechanged' event from default adapter for updating
|
|
* enabled/disabled status immediately.
|
|
*
|
|
* Description of 'attributechanged' event:
|
|
* A handler to trigger when one of the local bluetooth adapter's properties
|
|
* has changed. Note access to the changed property in this event handler
|
|
* would get the updated value.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_watchDefaultAdapterAttributechanged:
|
|
function btc__watchDefaultAdapterAttributechanged(adapter) {
|
|
adapter.addEventListener('attributechanged',
|
|
this._onAdapterAttributeChanged.bind(this, adapter));
|
|
},
|
|
|
|
/**
|
|
* Unwatch 'attributechanged' event from default adapter since adapter is
|
|
* removed.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_unwatchDefaultAdapterAttributechanged:
|
|
function btc__unwatchDefaultAdapterAttributechanged(adapter) {
|
|
adapter.removeEventListener('attributechanged',
|
|
this._onAdapterAttributeChanged);
|
|
},
|
|
|
|
/**
|
|
* Watch 'ondevicepaired' event from default adapter for updating paired
|
|
* device immediately.
|
|
*
|
|
* Description of 'ondevicepaired' event:
|
|
* A handler to trigger when a remote device gets paired with local
|
|
* bluetooth adapter.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_watchDefaultAdapterOndevicepaired:
|
|
function btc__watchDefaultAdapterOndevicepaired(adapter) {
|
|
adapter.ondevicepaired =
|
|
this._onAdapterDevicepaired.bind(this, adapter);
|
|
},
|
|
|
|
/**
|
|
* Unwatch 'ondevicepaired' event from default adapter since adapter is
|
|
* removed.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_unwatchDefaultAdapterOndevicepaired:
|
|
function btc__unwatchDefaultAdapterOndevicepaired(adapter) {
|
|
adapter.ondevicepaired = null;
|
|
},
|
|
|
|
/**
|
|
* Watch 'ondeviceunpaired' event from default adapter for updating unpaired
|
|
* device immediately.
|
|
*
|
|
* Description of 'ondeviceunpaired' event:
|
|
* A handler to trigger when a remote device gets unpaired from local
|
|
* bluetooth adapter.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_watchDefaultAdapterOndeviceunpaired:
|
|
function btc__watchDefaultAdapterOndeviceunpaired(adapter) {
|
|
adapter.ondeviceunpaired =
|
|
this._onAdapterDeviceunpaired.bind(this, adapter);
|
|
},
|
|
|
|
/**
|
|
* Unwatch 'ondeviceunpaired' event from default adapter since adapter is
|
|
* removed.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_unwatchDefaultAdapterOndeviceunpaired:
|
|
function btc__unwatchDefaultAdapterOndeviceunpaired(adapter) {
|
|
adapter.ondeviceunpaired = null;
|
|
},
|
|
|
|
/**
|
|
* 'attributechanged' event handler from default adapter for updating
|
|
* latest BluetoothContext information.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
* @param {event} evt
|
|
*/
|
|
_onAdapterAttributeChanged:
|
|
function btc__onAdapterAttributeChanged(adapter, evt) {
|
|
for (var i in evt.attrs) {
|
|
debug('--> _onAdapterAttributeChanged(): ' + evt.attrs[i]);
|
|
switch (evt.attrs[i]) {
|
|
case 'state':
|
|
this._updateStatus(adapter.state);
|
|
if (adapter.state === 'enabled') {
|
|
// Init paired device information while Bluetooth is enabled.
|
|
this._refreshPairedDevicesInfo(adapter);
|
|
// Manually start discovery while the adapter state
|
|
// is already enabled.
|
|
this.startDiscovery();
|
|
}
|
|
break;
|
|
case 'address':
|
|
this.address = adapter.address;
|
|
break;
|
|
case 'name':
|
|
this.name = adapter.name;
|
|
break;
|
|
case 'discoverable':
|
|
this.discoverable = adapter.discoverable;
|
|
break;
|
|
case 'discovering':
|
|
this.discovering = adapter.discovering;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 'ondevicepaired' event handler from default adapter for updating paired
|
|
* device in remote/paired devices list.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
* @param {event} evt
|
|
*/
|
|
_onAdapterDevicepaired:
|
|
function btc__onAdapterDevicepaired(adapter, evt) {
|
|
debug('_onAdapterDevicepaired evt = ' + evt);
|
|
// have to get device object in this event handler
|
|
// Ex. evt.device --> device
|
|
|
|
// Remove the paired device from remote devices list.
|
|
this._removeItemFromList(this._remoteDevices, evt.device.address);
|
|
|
|
// Instead of adding the paired device in paired devices list,
|
|
// get paired devices from adapter directly.
|
|
this._refreshPairedDevicesInfo(adapter);
|
|
},
|
|
|
|
/**
|
|
* 'ondeviceunpaired' event handler from default adapter for updating
|
|
* unpaired device in remote/paired devices list.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
* @param {event} evt
|
|
*/
|
|
_onAdapterDeviceunpaired:
|
|
function btc__onAdapterDeviceunpaired(adapter, evt) {
|
|
debug('_onAdapterDeviceunpaired evt = ' + evt);
|
|
// have to get the device address in this event handler Ex. evt -> address
|
|
|
|
// Instead of removing the paired device from paired devices list,
|
|
// get paired devices from adapter directly.
|
|
this._refreshPairedDevicesInfo(adapter);
|
|
},
|
|
|
|
/**
|
|
* 'defaultAdapter' change event handler from adapter manager for
|
|
* watch/unwatch default adapter relative event immediately.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} newAdapter
|
|
* @param {BluetoothAdapter} oldAdapter
|
|
*/
|
|
_onDefaultAdapterChanged:
|
|
function btc__onDefaultAdapterChanged(newAdapter, oldAdapter) {
|
|
debug('_onDefaultAdapterChanged(): newAdapter = ' + newAdapter);
|
|
debug('_onDefaultAdapterChanged(): oldAdapter = ' + oldAdapter);
|
|
|
|
// save default adapter
|
|
this._defaultAdapter = newAdapter;
|
|
|
|
if (oldAdapter) {
|
|
// unwatch event since the old adapter is no longer usefull
|
|
this._unwatchDefaultAdapterAttributechanged(oldAdapter);
|
|
this._unwatchDefaultAdapterOndeviceunpaired(oldAdapter);
|
|
this._unwatchDefaultAdapterOndevicepaired(oldAdapter);
|
|
}
|
|
|
|
if (newAdapter) {
|
|
// watch event since the new adapter is ready to access
|
|
this._initProperties(newAdapter);
|
|
this._watchDefaultAdapterAttributechanged(newAdapter);
|
|
this._watchDefaultAdapterOndevicepaired(newAdapter);
|
|
this._watchDefaultAdapterOndeviceunpaired(newAdapter);
|
|
// init paired device information while Bluetooth is enabled
|
|
if (newAdapter.state === 'enabled') {
|
|
this._refreshPairedDevicesInfo(newAdapter);
|
|
}
|
|
} else {
|
|
// reset properties only
|
|
this._resetProperties();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Refresh paired devices information.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothAdapter} adapter
|
|
*/
|
|
_refreshPairedDevicesInfo: function btc__refreshPairedDevicesInfo(adapter) {
|
|
var pairedDevices = adapter.getPairedDevices();
|
|
debug('pairedDevices.length = ' + pairedDevices.length);
|
|
|
|
// reset paired devices list
|
|
this._pairedDevices.reset([]);
|
|
|
|
// refresh properties about paired devices
|
|
if (pairedDevices.length === 0) {
|
|
// reset the name
|
|
this.firstPairedDeviceName = '';
|
|
// update property 'hasPairedDevice'
|
|
this.hasPairedDevice = false;
|
|
} else {
|
|
// sort paired devices
|
|
pairedDevices.sort(function(a, b) {
|
|
return a.name > b.name;
|
|
});
|
|
// save paired devices in list
|
|
for (var i in pairedDevices) {
|
|
// create observable BtDevice
|
|
var observableBtDevice = BtDevice(pairedDevices[i]);
|
|
// push device in devices list with observable object
|
|
this._pairedDevices.push(observableBtDevice);
|
|
}
|
|
// set the name
|
|
this.firstPairedDeviceName = this._pairedDevices.get(0).name;
|
|
// update property 'hasPairedDevice'
|
|
this.hasPairedDevice = true;
|
|
// Update connection status and profile for these paired devices.
|
|
this._initConnectingDevices();
|
|
this._initConnectedDevices();
|
|
}
|
|
|
|
this.numberOfPairedDevices = pairedDevices.length;
|
|
},
|
|
|
|
/**
|
|
* Since we have three properties which are relative with hardware status,
|
|
* update them via this method here.
|
|
* Update status, enabled, settings key 'bluetooth.enabled'
|
|
* The input state would be {'disabled', 'disabling', 'enabled', 'enabling'}
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {String} state
|
|
*/
|
|
_updateStatus: function btc__updateStatus(state) {
|
|
// Wrapper state to enabled/disabled toggle state.
|
|
var enabled;
|
|
if (state === 'enabled' || state === 'disabling') {
|
|
enabled = true;
|
|
} else if (state === 'disabled' || state === 'enabling') {
|
|
enabled = false;
|
|
}
|
|
|
|
// Update state
|
|
this.state = state;
|
|
debug('_updateStatus(): set state = ' + state);
|
|
|
|
// Update enabled
|
|
this.enabled = enabled;
|
|
debug('_updateStatus(): set enabled = ' + enabled);
|
|
|
|
// Sync with settings key
|
|
this._syncWithSettingsKey(enabled);
|
|
},
|
|
|
|
/**
|
|
* The function provides to set booleans to update the 'bluetooth.enabled'
|
|
* settings key if the value is not sync.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {Boolean} enabled
|
|
*/
|
|
_syncWithSettingsKey: function btc__syncWithSettingsKey(enabled) {
|
|
SettingsCache.getSettings((results) => {
|
|
var btEnabled = results['bluetooth.enabled'];
|
|
if (btEnabled !== enabled) {
|
|
settings.createLock().set({'bluetooth.enabled': enabled});
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set Bluetooth enable/disable.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @param {Boolean} enabled
|
|
* @returns {Promise}
|
|
*/
|
|
setEnabled: function btc_setEnabled(enabled) {
|
|
debug('setEnabled(): request set enabled = ' + enabled);
|
|
if ((this.enabled === enabled) ||
|
|
(this.state === 'enabling') ||
|
|
(this.state === 'disabling')) {
|
|
return Promise.reject('state transition!!');
|
|
}
|
|
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
if (enabled) {
|
|
return this._defaultAdapter.enable().then(() => {
|
|
debug('setEnabled(): set enable successfully :)');
|
|
}, (reason) => {
|
|
debug('setEnabled(): set enable failed: reason = ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
} else {
|
|
return this._defaultAdapter.disable().then(() => {
|
|
debug('setEnabled(): set disable successfully :)');
|
|
}, (reason) => {
|
|
debug('setEnabled(): set disable failed: reason = ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set Bluetooth discoverable.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @param {Boolean} enabled
|
|
* @returns {Promise}
|
|
*/
|
|
setDiscoverable: function btc_setDiscoverable(enabled) {
|
|
if ((this.discoverable === enabled) || (this.state !== 'enabled')) {
|
|
return Promise.reject('same state or Bluetooth is disabled!!');
|
|
}
|
|
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
return this._defaultAdapter.setDiscoverable(enabled).then(() => {
|
|
debug('setDiscoverable(): set discoverable ' +
|
|
enabled + ' successfully :)');
|
|
}, (reason) => {
|
|
debug('setDiscoverable(): set discoverable failed: ' +
|
|
'reason = ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set adapter name.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @param {String} name
|
|
* @returns {Promise}
|
|
*/
|
|
setName: function btc_setName(name) {
|
|
if (name === this.name) {
|
|
return Promise.reject('same name!!');
|
|
}
|
|
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
return this._defaultAdapter.setName(name).then(() => {
|
|
debug('setName(): set name successfully :) name = ' +
|
|
this._defaultAdapter.name);
|
|
}, (reason) => {
|
|
debug('setName(): set name failed: reason = ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set adapter name by product model.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
*/
|
|
setNameByProductModel: function btc_setNameByProductModel() {
|
|
// Bug 847459: Default name of the bluetooth device is set by bluetoothd
|
|
// to the value of the Android ro.product.model property upon first
|
|
// start. In case the user gives an empty bluetooth device name, we want
|
|
// to revert to the original ro.product.model. Gecko exposes it under
|
|
// the deviceinfo.product_model setting.
|
|
SettingsCache.getSettings((results) => {
|
|
var productModel = results['deviceinfo.product_model'];
|
|
debug('setNameByProductModel(): productModel = ' + productModel);
|
|
this.setName(productModel);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* An observable array to maintain paired devices which are just found out.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @type {ObservableArray}
|
|
*/
|
|
_pairedDevices: ObservableArray([]),
|
|
|
|
/**
|
|
* An observable array to maintain remote devices which are just found out.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @type {ObservableArray}
|
|
*/
|
|
_remoteDevices: ObservableArray([]),
|
|
|
|
/**
|
|
* An handler to handle 'ondevicefound' event. Then, we can save and update
|
|
* found device in the remote devices array.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @type {Function}
|
|
*/
|
|
_discoveryHandler: null,
|
|
|
|
/**
|
|
* The method makes the device's adapter start seeking for remote devices.
|
|
* The discovery process may be terminated after discovering a period of
|
|
* time. If the startDiscovery operation succeeds, an onattributechanged
|
|
* event would be triggered before the Promise is resolved to indicate
|
|
* property discovering becomes true.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @type {Function}
|
|
* @returns {Promise}
|
|
*/
|
|
startDiscovery: function btc_startDiscovery() {
|
|
if ((this.discovering === true) || (this.state !== 'enabled')) {
|
|
return Promise.reject('same state or Bluetooth is disabled!!');
|
|
}
|
|
|
|
// clean up found devices list
|
|
this._remoteDevices.reset([]);
|
|
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
return this._defaultAdapter.startDiscovery().then((handle) => {
|
|
debug('startDiscovery(): startDiscovery successfully :)');
|
|
// Keep reference to handle in order to listen to
|
|
// ondevicefound event handler
|
|
this._setDiscoveryHandler(handle);
|
|
}, (reason) => {
|
|
debug('startDiscovery(): startDiscovery failed: ' +
|
|
'reason = ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* The method makes the device's adapter stop seeking for remote devices.
|
|
* This is an asynchronous method and its result is returned via a Promise.
|
|
* If the stopDiscovery operation succeeds, an onattributechanged would be
|
|
* triggered before the Promise is resolved to indicate property discovering
|
|
* becomes false. Note adapter may still receive
|
|
* BluetoothDiscoveryHandle.ondevicefound event until the Promise is
|
|
* resolved.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @type {Function}
|
|
* @returns {Promise}
|
|
*/
|
|
stopDiscovery: function btc_stopDiscovery() {
|
|
if (this.discovering === false) {
|
|
debug('stopDiscovery(): stopDiscovery successfully in same state :)');
|
|
return Promise.resolve('same state');
|
|
}
|
|
|
|
if (this.state !== 'enabled') {
|
|
return Promise.reject('Bluetooth is disabled!!');
|
|
}
|
|
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
return this._defaultAdapter.stopDiscovery().then(() => {
|
|
debug('stopDiscovery(): stopDiscovery successfully :)');
|
|
}, (reason) => {
|
|
debug('stopDiscovery(): stopDiscovery failed: reason = ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* A function to receive BluetoothDiscoveryHandle. And set a function to
|
|
* handle 'ondevicefound' event.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {BluetoothDiscoveryHandle} handle
|
|
*/
|
|
_setDiscoveryHandler: function btc__setDiscoveryHandler(handle) {
|
|
debug('_setDiscoveryHandler(): handle = ' + handle);
|
|
// make the code base easy to do unit test
|
|
this._discoveryHandler = handle;
|
|
this._discoveryHandler.ondevicefound = this._onDeviceFound.bind(this);
|
|
},
|
|
|
|
/**
|
|
* Handle 'ondevicefound' event to access remote device in list with
|
|
* observable object.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {Object} evt
|
|
*/
|
|
_onDeviceFound: function btc__onDeviceFound(evt) {
|
|
// save device
|
|
this._saveDevice(evt.device);
|
|
},
|
|
|
|
/**
|
|
* To distinguish between paired and unpaired.
|
|
* Then, save/update the device in list corresponding to the state of
|
|
* property paired.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {Observable} device
|
|
*/
|
|
_saveDevice: function btc__saveDevice(device) {
|
|
// Find the device is existed in devices list or not.
|
|
var existedDevice =
|
|
this._findDeviceByAddress({
|
|
paired: device.paired,
|
|
address: device.address
|
|
});
|
|
// If the device is not existed yet, create observable object
|
|
// for saving this device.
|
|
if (!existedDevice) {
|
|
// create observable BtDevice
|
|
var observableBtDevice = BtDevice(device);
|
|
// push device in devices list with observable object
|
|
var operatingDevices =
|
|
(device.paired) ? this.getPairedDevices() : this.getRemoteDevices();
|
|
operatingDevices.push(observableBtDevice);
|
|
} else {
|
|
// The device is existed, no need to do any thing here.
|
|
// set device in devices list
|
|
// operatingDevices.set(index, device);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Given an observable arrry and item address. The function will remove it,
|
|
* if it's existed in the array.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {ObservableArray} list
|
|
* @param {String} address
|
|
*/
|
|
_removeItemFromList: function btc__removeItemFromList(list, address) {
|
|
// check the device is existed or not in remote/paired devices array
|
|
var index =
|
|
list.array.findIndex(this._matchDeviceByAddress.bind(this, address));
|
|
if (index > -1) {
|
|
// The device is existed, remove it from observable list.
|
|
list.splice(index, 1);
|
|
debug('_removeItemFromList(): index = ' + index);
|
|
} else {
|
|
// The device is not existed, no need to do any thing here.
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Given paired, address properties to find out device element
|
|
* from remote/paired devices list.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {Object} options
|
|
* @param {String} options.paired - is paired or not
|
|
* @param {String} options.address - the address of the device
|
|
* @return {Object} device
|
|
*/
|
|
_findDeviceByAddress:
|
|
function btc__findDeviceByAddress(options) {
|
|
// Distinguish to find the specific device in remote/paired devices list.
|
|
var operatingDevices =
|
|
(options.paired) ? this.getPairedDevices() : this.getRemoteDevices();
|
|
// Check the device is existed or not in remote/paired devices array.
|
|
var index = operatingDevices.array.findIndex(
|
|
this._matchDeviceByAddress.bind(this, options.address));
|
|
|
|
if (index > -1) {
|
|
return operatingDevices.get(index);
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Given address to find out device element from array.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {String} address
|
|
* @param {BluetoothDevice} btDevice
|
|
* @return {Boolean}
|
|
*/
|
|
_matchDeviceByAddress:
|
|
function btc__matchDeviceByAddress(address, btDevice) {
|
|
return btDevice.address && (btDevice.address === address);
|
|
},
|
|
|
|
/**
|
|
* Return paired devices list which is maintained in BluetoothContext.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @return {ObservableArray}
|
|
*/
|
|
getPairedDevices: function btc_getPairedDevices() {
|
|
return this._pairedDevices;
|
|
},
|
|
|
|
/**
|
|
* Return remote devices list which is maintained in BluetoothContext.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @return {ObservableArray}
|
|
*/
|
|
getRemoteDevices: function btc_getRemoteDevices() {
|
|
return this._remoteDevices;
|
|
},
|
|
|
|
/**
|
|
* The method starts pairing a remote device with the device's adapter.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @param {String} address
|
|
* @returns {Promise}
|
|
*/
|
|
pair: function btc_pair(address) {
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
// Note on Bluedroid stack, discovery has to be stopped before pairing
|
|
// (i.e., call stopDiscovery() before pair()) otherwise stack callbacks
|
|
// with pairing failure.
|
|
return this.stopDiscovery().then(() => {
|
|
return this._defaultAdapter.pair(address).then(() => {
|
|
debug('pair(): Resolved with void value');
|
|
}, (reason) => {
|
|
debug('pair(): Reject with this reason: ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
}, (reason) => {
|
|
return Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* The method starts unpairs a remote device with the device's adapter.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @param {String} address
|
|
* @returns {Promise}
|
|
*/
|
|
unpair: function btc_unpair(address) {
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
return this._defaultAdapter.unpair(address).then(() => {
|
|
debug('unpair(): Resolved with void value');
|
|
}, (reason) => {
|
|
debug('unpair(): Reject with this reason: ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* The method starts sending file to a remote device with the device's
|
|
* adapter.
|
|
*
|
|
* @access public
|
|
* @memberOf BluetoothContext
|
|
* @param {String} address - address of target device
|
|
* @param {Object} blob - blob(file) to send
|
|
* @returns {Promise}
|
|
*/
|
|
sendFile: function btc_sendFile(address, blob) {
|
|
if (!this._defaultAdapter) {
|
|
return Promise.reject('default adapter is not existed!!');
|
|
}
|
|
|
|
return this._defaultAdapter.sendFile(address, blob).then(() => {
|
|
debug('sendFile(): Resolved with void value');
|
|
}, (reason) => {
|
|
debug('sendFile(): Reject with this reason: ' + reason);
|
|
return Promise.reject(reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Init the connecting device which is browsed in paired devices list.
|
|
* Get connection info from ConnectionManager.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
*/
|
|
_initConnectingDevices: function btc__initConnectingDevices() {
|
|
// Init the paired device connection status for connecting device.
|
|
if (!ConnectionManager.connectingAddress) {
|
|
return;
|
|
}
|
|
|
|
var existedDevice =
|
|
this._findDeviceByAddress({
|
|
paired: true,
|
|
address: ConnectionManager.connectingAddress
|
|
});
|
|
if (existedDevice) {
|
|
// The connecting device is existed. Init connection status for it.
|
|
var options = {
|
|
connectionStatus: 'connecting'
|
|
};
|
|
debug('_initConnectingDevices(): options = ' + JSON.stringify(options));
|
|
existedDevice.updateConnectionInfo(options);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Init the connected device which is browsed in paired devices list.
|
|
* Get connection info from ConnectionManager.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
*/
|
|
_initConnectedDevices: function btc__initConnectedDevices() {
|
|
// Init the connection status of paired device for connected device.
|
|
ConnectionManager.getConnectedDevices().then((connectedDevices) => {
|
|
for (var address in connectedDevices) {
|
|
var existedDevice =
|
|
this._findDeviceByAddress({paired: true, address: address});
|
|
|
|
if (existedDevice) {
|
|
// The connected device is existed.
|
|
// Init connection status/profiles for it.
|
|
var options = {
|
|
connectionStatus: 'connected',
|
|
profiles: connectedDevices[address].connectedProfiles
|
|
};
|
|
debug('_initConnectedDevices(): address = ' + address +
|
|
', options = ' + JSON.stringify(options));
|
|
existedDevice.updateConnectionInfo(options);
|
|
}
|
|
}
|
|
}, (reason) => {
|
|
debug('_initConnectedDevices(): getConnectedDevices(): failed, ' +
|
|
'reason = ' + reason);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Device 'connecting', 'connected', 'disconnected', and 'profiles'
|
|
* properties are changed from ConnectionManager operation.
|
|
* Update device properties of connection info via event 'type', 'detail'.
|
|
*
|
|
* @access private
|
|
* @memberOf BluetoothContext
|
|
* @param {Object} event
|
|
* @param {String} event.type - type of event name
|
|
* @param {Object} event.detail - device info in this object
|
|
* @param {Object} event.detail.address - address of device
|
|
* @param {Object} event.detail.profiles - connection profiles of device
|
|
*/
|
|
_updateDeviceConnectionInfo:
|
|
function btc__updateDeviceConnectionInfo(event) {
|
|
debug('_updateDeviceConnectionInfo(): event = ' + JSON.stringify(event));
|
|
var existedDevice =
|
|
this._findDeviceByAddress(
|
|
{paired: true, address: event.detail.address});
|
|
if (existedDevice) {
|
|
// The device is existed, update device info by event type.
|
|
var options;
|
|
switch (event.type) {
|
|
case 'connecting':
|
|
case 'connected':
|
|
case 'disconnected':
|
|
options = {
|
|
connectionStatus: event.type
|
|
};
|
|
break;
|
|
case 'profileChanged':
|
|
options = {
|
|
profiles: event.detail.profiles
|
|
};
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
debug('_updateDeviceConnectionInfo(): options = ' +
|
|
JSON.stringify(options));
|
|
existedDevice.updateConnectionInfo(options);
|
|
} else {
|
|
// If the device is not existed yet, do nothing here.
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create the observable object using the prototype.
|
|
var bluetoothContext = Observable(BluetoothContext);
|
|
bluetoothContext._init();
|
|
return bluetoothContext;
|
|
});
|