Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,111 @@
/**
* BluetoothAdapterManager:
* - BluetoothAdapterManager is an Observable that wraps the platform
* Bluetooth object for construct default adapter.
* - It has only one observable properties: defaultAdapter
* BluetoothAdapterManager only update state and does not involve in any UI
* logic.
*
* @module BluetoothAdapterManager
*/
define(function(require) {
'use strict';
var NavigatorBluetooth = require('modules/navigator/mozBluetooth');
var Observable = require('modules/mvvm/observable');
var _debug = false;
var debug = function() {};
if (_debug) {
debug = function btam_debug(msg) {
console.log('--> [BluetoothAdapterManager]: ' + msg);
};
}
/**
* @alias module:modules/bluetooth/BluetoothAdapterManager
* @requires module:modules/navigator/mozBluetooth
* @requires module:modules/mvvm/observable
* @return {BluetoothAdapterManager}
*/
var BluetoothAdapterManager = {
/**
* The default adapter used to connect to the remote bluetooth devices.
*
* @readonly
* @memberOf BluetoothAdapterManager
* @type {Object}
*/
defaultAdapter: null,
/**
* Init Bluetooth module.
*
* @access private
* @memberOf BluetoothAdapterManager
*/
_init: function btam__init() {
// Early return while there is no navigator.mozBluetooth module.
if (!NavigatorBluetooth) {
return;
}
// watch BluetoothManager event
this._watchMozBluetoothAttributechanged();
// init default adapter
this._initDefaultAdapter();
},
/**
* Watch 'attributechanged' event from mozBluetooth for updating default
* adapter information.
*
* 'attributechanged' event description:
* A handler to trigger when bluetooth manager's only property
* defaultAdapter has changed.
*
* @access private
* @memberOf BluetoothAdapterManager
*/
_watchMozBluetoothAttributechanged:
function btam__watchMozBluetoothAttributechanged() {
NavigatorBluetooth.addEventListener('attributechanged', (evt) => {
for (var i in evt.attrs) {
switch (evt.attrs[i]) {
case 'defaultAdapter':
// Default adapter attribute change.
// Usually, it means that we reach new default adapter.
this.defaultAdapter = NavigatorBluetooth.defaultAdapter;
debug('_watchMozBluetoothAttributechanged(): ' +
'this.defaultAdapter = ' + this.defaultAdapter);
break;
default:
break;
}
}
});
},
/* Reach default adapter from platform Bluetooth.
*
* If the property is accessed on platform in the first time, it will
* trigger Gecko::Bluetooth to get default adapter from the chipset.
* So the value would be null.
* Then we will receive 'attributechanged' event later.
*
* @access private
* @memberOf BluetoothAdapterManager
*/
_initDefaultAdapter: function btam__initDefaultAdapter() {
this.defaultAdapter = NavigatorBluetooth.defaultAdapter;
debug('_initDefaultAdapter(): this.defaultAdapter = ' +
this.defaultAdapter);
}
};
// Create the observable object using the prototype.
var bluetoothAdapterManager = Observable(BluetoothAdapterManager);
bluetoothAdapterManager._init();
return bluetoothAdapterManager;
});

View file

@ -0,0 +1,198 @@
/**
* BluetoothClassOfDeviceMapper:
* - BluetoothClassOfDeviceMapper is a mapper that helps settings/bluetooth
* apps to decide the icon type. Since platform exposed the 'number' type
* for class of device(Major Service Classes, Major Device Classes,
* Minor Device Classes), the helper will map the input number to specific
* device type. And the decision is referenced from Baseband.
* (Assigned numbers for Baseband identifies the Inquiry Access codes
* and Class of Device/Service (CoD) fields.)[3]
* According to the specifications described, we define the device type in
* Gaia side entirely. And the naming of device type we given is also
* mapping to CSS style sheet.
*
* - [1] Bluetooth API v1, BluetoothDevice icon property:
* https://developer.mozilla.org/en-US/docs/Web/API/BluetoothDevice.icon
* - [2] Bluetooth API v2, BluetoothClassOfDevice object:
* https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/
* BluetoothClassOfDevice
* - [3] Bluetooth Class of Device Reference:
* https://www.bluetooth.org/en-us/specification/assigned-numbers/baseband
* - We can update the mapper if the specifications Baseband is updated.
*
* @module BluetoothClassOfDeviceMapper
*/
define(function() {
'use strict';
var _debug = false;
var debug = function() {};
if (_debug) {
debug = function btcodm_debug(msg) {
console.log('--> [BluetoothCODMapper]: ' + msg);
};
}
var MinorDeviceClasses = {
Computer: {
0: 'computer', // Uncategorized, code for device not assigned
1: 'computer', // Desktop workstation
2: 'computer', // Server-class computer
3: 'computer', // Laptop
4: 'pda', // Handheld PC/PDA (clamshell)
5: 'pda', // Palm-size PC/PDA
6: 'computer', // Wearable computer (watch size)
7: 'computer', // Tablet
reserved: 'computer' // All other values reserved. Default type
// 'computer' given for reserved case.
},
Phone: {
0: 'phone', // Uncategorized, code for device not assigned
1: 'phone', // Cellular
2: 'phone', // Cordless
3: 'phone', // Smartphone
4: 'modem', // Wired modem or voice gateway
5: 'phone', // Common ISDN access
reserved: 'phone' // All other values reserved. Default type
// 'phone' given for reserved case.
},
AudioVideo: {
0: 'audio-card', // Uncategorized, code not assigned
1: 'audio-card', // Wearable Headset Device
2: 'audio-card', // Hands-free Device
4: 'audio-input-microphone', // Microphone
5: 'audio-card', // Loudspeaker
6: 'audio-card', // Headphones
7: 'audio-card', // Portable Audio
8: 'audio-card', // Car audio
9: 'audio-card', // Set-top box
10: 'audio-card', // HiFi Audio Device
11: 'camera-video', // VCR
12: 'camera-video', // Video Camera
13: 'camera-video', // Camcorder
14: 'video-display', // Video Monitor
15: 'video-display', // Video Display and Loudspeaker
16: 'video-display', // Video Conferencing
18: 'audio-card', // Gameing/Toy
reserved: 'audio-card' // All other values reserved. Default type
// 'audio-card' given for reserved case.
},
Peripheral: {
1: 'input-gaming', // Joystick
2: 'input-gaming', // Gamepad
16: 'input-keyboard', // Keyboard
17: 'input-keyboard', // Keyboard
18: 'input-keyboard', // Keyboard
19: 'input-keyboard', // Keyboard
20: 'input-keyboard', // Keyboard
21: 'input-keyboard', // Keyboard
22: 'input-keyboard', // Keyboard
23: 'input-keyboard', // Keyboard
24: 'input-keyboard', // Keyboard
25: 'input-keyboard', // Keyboard
26: 'input-keyboard', // Keyboard
27: 'input-keyboard', // Keyboard
28: 'input-keyboard', // Keyboard
29: 'input-keyboard', // Keyboard
30: 'input-keyboard', // Keyboard
31: 'input-keyboard', // Keyboard
32: 'input-mouse', // Pointing device
33: 'input-mouse', // Pointing device
34: 'input-mouse', // Pointing device
35: 'input-mouse', // Pointing device
36: 'input-mouse', // Pointing device
37: 'input-tablet', // Digitizer tablet
38: 'input-mouse', // Pointing device
39: 'input-mouse', // Pointing device
40: 'input-mouse', // Pointing device
41: 'input-mouse', // Pointing device
42: 'input-mouse', // Pointing device
43: 'input-mouse', // Pointing device
44: 'input-mouse', // Pointing device
45: 'input-mouse', // Pointing device
46: 'input-mouse', // Pointing device
47: 'input-mouse' // Pointing device
},
Imaging: {
4: 'video-display', // Display, bit: XXX1XX
8: 'camera-photo', // Camera, bit: XX1XXX
16: 'scanner', // Scanner, bit: X1XXXX
32: 'printer' // Printer, bit: 1XXXXX
}
};
var MajorDeviceClasses = {
1: MinorDeviceClasses.Computer,
2: MinorDeviceClasses.Phone,
3: 'network-wireless', // LAN/Network Access Point Major Class
4: MinorDeviceClasses.AudioVideo,
5: MinorDeviceClasses.Peripheral,
6: MinorDeviceClasses.Imaging
};
var BluetoothClassOfDeviceMapper = {
/**
* The string indicating which kind of icon could be used to represent
* the device.
*
* @access public
* @memberOf BluetoothClassOfDeviceMapper
* @param {BluetoothClassOfDevice} cod
* @return {String}
*/
getDeviceType: function btcodm_getDeviceType(cod) {
debug('cod.majorDeviceClass = ' + cod.majorDeviceClass);
debug('cod.majorServiceClass = ' + cod.majorServiceClass);
debug('cod.minorDeviceClass = ' + cod.minorDeviceClass);
// Given an empty string to be default type.
// Then, we won't show any icon for empty type.
var deviceType = '';
var majorDeviceClass = MajorDeviceClasses[cod.majorDeviceClass];
debug('majorDeviceClass = ' + majorDeviceClass);
if (typeof(majorDeviceClass) === 'object') {
// drop in other Major Class
deviceType = majorDeviceClass[cod.minorDeviceClass] ||
majorDeviceClass.reserved || '';
debug('return in minor class, type = ' + deviceType);
return deviceType;
} else if (typeof(majorDeviceClass) === 'string') {
// drop in LAN/Network Access Point Major Class
debug('return in major class, type = network-wireless');
return majorDeviceClass;
} else if (cod.majorServiceClass & 0x100) {
// Not in any Major Device Class which is defined in Gaia.
// Ex: Wearable, Toy, Health.
// Since there is no icon to display wearable, toy, health devices,
// file a bug 1163479[2] to define them for specification needed.
// But some of these devices probably service 'Audio' per Bluetooth
// specification[1].
// Property 'type' may be missed due to CoD of major class is TOY(0x08).
// But we need to assign 'type' as 'audio-card' if service class
// is 'Audio'. This is for PTS test case TC_AG_COD_BV_02_I.
// As HFP specification defines that service class is 'Audio' can
// be considered as HFP HF.
// [1]: HFP_SPEC_V16.pdf: A device implementing the HF role of HFP shall
// set the "Audio" bit in the Service Class field.
// [2]: Bug 1163479 - [Gaia][Bluetooth] Device icon definition for
// wearable, toy, health(Major Device Class) devices.
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1163479)
// Major Service Class: Audio(Speaker, Microphone, Headset service, ...)
deviceType = 'audio-card';
debug('return in major service class, type = audio-card');
return deviceType;
} else {
// Not in any Class which is defined in Gaia.
debug('not mapping in any class, return type = ' + deviceType);
return deviceType;
}
}
};
return BluetoothClassOfDeviceMapper;
});

View file

@ -0,0 +1,953 @@
/**
* BluetoothConnectionManager:
* BluetoothConnectionManager only update state and does not involve in any UI
* logic.
*
* @module BluetoothConnectionManager
*/
define(function(require) {
'use strict';
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
var AsyncStorage = require('shared/async_storage');
var _debug = false;
var debug = function() {};
if (_debug) {
debug = function btam_debug(msg) {
console.log('--> [BluetoothConnectionManager]: ' + msg);
};
}
/**
* @alias module:modules/bluetooth/BluetoothConnectionManager
* @requires module:modules/bluetooth/bluetooth_adapter_manager
* @requires module:shared/async_storage
* @return {BluetoothConnectionManager}
*/
var BluetoothConnectionManager = {
/**
* The profiles of connected device that we are defined here.
*
* @public
* @memberOf BluetoothConnectionManager
* @type {Object}
*/
Profiles: {
'hfp': 0x111E, // Handsfree
'a2dp': 0x110D // Advanced Audio Distribution Devices
},
/**
* The address of device that we are trying to connect.
*
* @public
* @memberOf BluetoothConnectionManager
* @type {String}
*/
connectingAddress: null,
/**
* A object that we cache the connected devices information(address, device,
* connectedProfiles). It will be inited while default adapter is ready.
* And these information is coming from profile events.
* Each connected device is hashed by device address.
*
* EX:
* _connectedDevicesInfo = {
* 'AA:BB:CC:00:11:22': {
* 'device': DeviceObject,
* 'connectedProfiles': {
* 'hfp': true,
* 'a2dp': false
* }
* }
* };
*
* @private
* @memberOf BluetoothConnectionManager
* @type {Object}
*/
_connectedDevicesInfo: {},
/**
* An instance to maintain that we have created a promise to get connected
* devices.
*
* @access public
* @memberOf BluetoothConnectionManager
* @return {Promise}
*/
_getConnectedDevicesPromise: null,
/**
* The object maintains listeners' callback per property name.
* Each listener would be called as following definition.
* 'connecting' - be called when device is connecting.
* 'connected': - be called when device is connected.
* 'disconnected': - be called when device is disconnected.
* 'profileChanged': - be called when profile is changed.
*
* @memberOf BluetoothConnectionManager
* @type {Object}
*/
_listeners: {
'connecting': [],
'connected': [],
'disconnected': [],
'profileChanged': []
},
/**
* Default adapter of Bluetooth.
*
* @access private
* @memberOf BluetoothConnectionManager
* @type {Object} BluetoothAdapter
*/
_defaultAdapter: null,
/**
* Init BluetoothConnectionManager module.
*
* @access private
* @memberOf BluetoothConnectionManager
*/
_init: function btcm__init() {
// Observe 'defaultAdapter' property for reaching default adapter.
AdapterManager.observe('defaultAdapter',
this._onDefaultAdapterChanged.bind(this));
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
},
/**
* 'defaultAdapter' change event handler from adapter manager for
* updating adapter immediately.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} BluetoothAdapter newAdapter
* @param {Object} BluetoothAdapter oldAdapter
*/
_onDefaultAdapterChanged:
function btcm__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._unwatchProfilesStatuschanged(oldAdapter);
this._unwatchDefaultAdapterOnattributechanged(oldAdapter);
}
if (newAdapter) {
// watch event since the new adapter is ready to access
this._watchProfilesStatuschanged(newAdapter);
this._watchDefaultAdapterOnattributechanged(newAdapter);
// restore connection
if (newAdapter.state === 'enabled') {
this._restoreConnection();
}
} else {
// reset properties only
this._resetConnectionInfo();
}
},
/**
* Return the cache of connected devices in ConnectionManager.
*
* @access public
* @memberOf BluetoothConnectionManager
* @return {Promise}
*/
getConnectedDevices: function btcm_getConnectedDevices() {
if (!this._getConnectedDevicesPromise) {
this._getConnectedDevicesPromise =
this._initConnectedDevicesInfo().then(() => {
debug('getConnectedDevices(): resolved with latest cache = ' +
JSON.stringify(this._connectedDevicesInfo));
return this._connectedDevicesInfo;
}, (reason) => {
debug('getConnectedDevices(): rejected with reason = ' + reason);
this._getConnectedDevicesPromise = null;
});
}
return this._getConnectedDevicesPromise;
},
/**
* Init cache of connected device and save it in cache.
*
* @access private
* @memberOf BluetoothConnectionManager
* @return {Promise}
*/
_initConnectedDevicesInfo: function btcm__initConnectedDevicesInfo() {
if (!this._defaultAdapter) {
return Promise.reject('default adapter is not existed!!');
}
// Init connection status and profile from platform.
// Then, save these connected device information in cache.
return this._getConnectedDevicesFromPlatform().then(
(connectedDevicesByProfile) => {
this._constructDeviceItemsMap(connectedDevicesByProfile);
}, (reason) => {
debug('_initConnectedDevicesInfo(): rejected in ' +
'_getConnectedDevicesFromPlatform');
return Promise.reject(reason);
});
},
/**
* The method will update each device item in maintaining map.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} connectedDevices
*/
_constructDeviceItemsMap:
function btcm__constructDeviceItemsMap(connectedDevices) {
debug('_constructDeviceItemsMap(): connectedDevices = ' +
JSON.stringify(connectedDevices));
if (!connectedDevices) {
// Return empty object while there is no any connected devices.
debug('_constructDeviceItemsMap(): early return with empty object');
return;
}
Object.keys(this.Profiles).map((profileID) => {
connectedDevices[profileID].forEach((connectedDevice) => {
var connectionDeviceInfo = {
address: connectedDevice.address,
connected: true,
profileID: profileID,
device: connectedDevice
};
debug('_constructDeviceItemsMap(): connectionDeviceInfo = ' +
JSON.stringify(connectionDeviceInfo));
this._initConnectedDevicesCache(connectionDeviceInfo);
});
});
},
/**
* Init the cache which is maintained for connection devices.
* And the input is gotten from platform API adapter.getConnectedDevices().
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} options
* @param {String} options.address - address of the device
* @param {Boolean} options.connected - is connected or not
* @param {Object} options.profileID - profile ID of the connection type
* @param {Object} options.device - connect device, Bluetooth Object
*/
_initConnectedDevicesCache:
function btcm__initConnectedDevicesCache(options) {
debug('_initConnectedDevicesCache(): options = ' +
JSON.stringify(options));
// hash by device address
var info = this._connectedDevicesInfo[options.address];
if (info) {
// Already have profiles, update it for other profile.
info.connectedProfiles[options.profileID] = options.connected;
} else {
// Not have profiles yet, create for it.
// If options.device is existed, save the connected device.
// Otherwise, given null in this property.
var connectedDevice = (options.device) ? options.device : null;
info = {
'device': connectedDevice,
'connectedProfiles': {}
};
info.connectedProfiles[options.profileID] = options.connected;
}
// Save the device/profile in map.
this._connectedDevicesInfo[options.address] = info;
// If there is no profile connected,
// remove the device item from cache since it is already disconnected.
var dataToCheckConnectedProfile = {
address: options.address,
connectedDevices: this._connectedDevicesInfo
};
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
delete this._connectedDevicesInfo[options.address];
}
// Return the latest cache which is just updated here.
debug('_initConnectedDevicesCache(): this._connectedDevicesInfo = ' +
JSON.stringify(this._connectedDevicesInfo));
return this._connectedDevicesInfo;
},
/**
* Update the cache which is maintained for connection devices.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} options
* @param {String} options.address - address of the device
* @param {Boolean} options.connected - is connected or not
* @param {Object} options.profileID - profile ID of the connection type
* @param {Object} options.device - connect device, Bluetooth Object
*/
_updateConnectedDevices:
function btcm__updateConnectedDevices(options) {
return this.getConnectedDevices().then((connectedDevicesInfo) => {
debug('_updateConnectedDevices(): connectedDevicesInfo = ' +
JSON.stringify(connectedDevicesInfo));
// hash by device address
var info =
(connectedDevicesInfo) ? connectedDevicesInfo[options.address] : null;
if (info) {
// Already have profiles, update it for other profile.
info.connectedProfiles[options.profileID] = options.connected;
} else {
// Not have profiles yet, create for it.
// If options.device is existed, save the connected device.
// Otherwise, given null in this property.
var connectedDevice = (options.device) ? options.device : null;
info = {
'device': connectedDevice,
'connectedProfiles': {}
};
info.connectedProfiles[options.profileID] = options.connected;
}
// Save the device/profile in map.
this._connectedDevicesInfo[options.address] = info;
// If there is no profile connected,
// remove the device item from cache since it is already disconnected.
var dataToCheckConnectedProfile = {
address: options.address,
connectedDevices: this._connectedDevicesInfo
};
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
delete this._connectedDevicesInfo[options.address];
}
// Return the latest cache which is just updated here.
debug('_updateConnectedDevices(): this._connectedDevicesInfo = ' +
JSON.stringify(this._connectedDevicesInfo));
return Promise.resolve(this._connectedDevicesInfo);
}, () => {
debug('_updateConnectedDevices(): rejected with some exception');
return Promise.reject('rejected with some exception');
});
},
/**
* Only reset properties since there is no available default adapter.
*
* @access private
* @memberOf BluetoothConnectionManager
*/
_resetConnectionInfo: function btcm__resetConnectionInfo() {
// Reset connection status.
this.connectingAddress = null;
// Clean up the instance to get connected devices
// while new adapter is ready.
this._getConnectedDevicesPromise = null;
},
/**
* Watch 'onattributechanged' event from default adapter for watching
* adapter enabled/disabled status.
*
* Description of 'onattributechanged' 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 BluetoothConnectionManager
* @param {Object} BluetoothAdapter adapter
*/
_watchDefaultAdapterOnattributechanged:
function btcm__watchDefaultAdapterOnattributechanged(adapter) {
adapter.addEventListener('attributechanged',
this._onAdapterAttributeChanged.bind(this, adapter));
},
/**
* Unwatch 'onattributechanged' event from default adapter since adapter is
* removed.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} BluetoothAdapter adapter
*/
_unwatchDefaultAdapterOnattributechanged:
function btcm__unwatchDefaultAdapterOnattributechanged(adapter) {
adapter.removeEventListener('attributechanged',
this._onAdapterAttributeChanged);
},
/**
* 'onattributechanged' event handler from default adapter for reaching
* adapter enabled/disabled status.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} BluetoothAdapter adapter
* @param {event} evt
*/
_onAdapterAttributeChanged:
function btcm__onAdapterAttributeChanged(adapter, evt) {
for (var i in evt.attrs) {
debug('_onAdapterAttributeChanged(): ' + evt.attrs[i]);
switch (evt.attrs[i]) {
case 'state':
if (adapter.state === 'enabled') {
// Restore connection while default adapter state is enabled.
this._restoreConnection();
}
break;
default:
break;
}
}
},
/**
* Watch every of profile events('onhfpstatuschanged','ona2dpstatuschanged')
* from default adapter for updating device connected status immediately.
*
* Description of 'onhfpstatuschanged' event:
* Specifies an event listener to receive hfpstatuschanged events.
* Those events occur when an HFP connection status changes.
*
* Description of 'ona2dpstatuschanged' event:
* Specifies an event listener to receive a2dpstatuschanged events.
* Those events occur when an A2DP connection status changes.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} BluetoothAdapter adapter
*/
_watchProfilesStatuschanged:
function btcm__watchProfilesStatuschanged(adapter) {
var eventName;
for (var profileID in this.Profiles) {
eventName = 'on' + profileID + 'statuschanged';
adapter[eventName] =
this._onProfileStatuschangeHandler.bind(this, profileID);
}
},
/**
* Unwatch every of profile events('onhfpstatuschanged',
* 'ona2dpstatuschanged') from default adapter since adapter is removed.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} BluetoothAdapter adapter
*/
_unwatchProfilesStatuschanged:
function btc__unwatchProfilesStatuschanged(adapter) {
var eventName;
for (var profileID in this.Profiles) {
eventName = 'on' + profileID + 'statuschanged';
adapter[eventName] = null;
}
},
/**
* 'onhfpstatuschanged', 'ona2dpstatuschanged' events handler from
* default adapter for updating device connected status.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {String} profileID
* @param {event} evt
*/
_onProfileStatuschangeHandler:
function btcm__onProfileStatuschangeHandler(profileID, evt) {
debug('_onProfileStatuschangeHandler(): profileID = ' + profileID +
', evt.address = ' + evt.address + ', evt.status = ' + evt.status);
var options = {
address: evt.address,
connected: evt.status,
profileID: profileID
};
// Update connection status.
this._updateConnectionStatus(options);
},
/**
* Query async storage to restore connection.
*
* @access private
* @memberOf BluetoothConnectionManager
*/
_restoreConnection: function btcm__restoreConnection() {
if (!this._defaultAdapter) {
return;
}
// Reconnect the one kept in the async storage.
AsyncStorage.getItem('device.connected', (address) => {
if (!address) {
return;
}
// Make sure the restore device is already connected or not.
this.getConnectedDevices().then((connectedDevices) => {
if (connectedDevices && connectedDevices[address]) {
// Do an early return since the restore device is connected already.
debug('_restoreConnection(): early return cause connected already');
return;
}
// Get the device which is wanted to connect from paired devices.
var restoreDevice = this._getPairedDeviceByAddress(address);
if (restoreDevice) {
this.connectingAddress = restoreDevice.address;
// Fire 'connecting' event.
var event = {
type: 'connecting',
detail: {
address: restoreDevice.address
}
};
this._emitEvent(event);
this._connect(restoreDevice).then(() => {
debug('_restoreConnection(): restore connection successfully');
}, (reason) => {
debug('_restoreConnection(): restore connection failed, ' +
'reason = ' + reason);
// No available profiles are connected. Reset connecting address.
this.connectingAddress = null;
// Then, fire 'disconnected' event.
event = {
type: 'disconnected',
detail: {
address: restoreDevice.address
}
};
this._emitEvent(event);
});
}
});
});
},
/**
* Record connected device so if Bluetooth is turned off and then on
* we can restore the connection.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {String} action - to set or remove item for recording connection
* @param {String} address - the address of connected device
*/
_recordConnection: function btcm__recordConnection(action, address) {
if (action === 'set') {
// record connected device so if Bluetooth is turned off and then on
// we can restore the connection
AsyncStorage.setItem('device.connected', address);
debug('_recordConnection(): set item');
} else if ((action === 'remove') &&
(this._defaultAdapter.state === 'enabled')) {
// Clean up the connected device from async storage
// which is recorded before.
// Only remove the record while Bluetooth state is enabled.
// Because the request also comes while Bluetooth is turned off.
AsyncStorage.removeItem('device.connected');
debug('_recordConnection(): remove item');
}
},
/**
* Update connection status.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} options
* @param {String} options.address - address of the device
* @param {Boolean} options.connected - is connected or not
* @param {Object} options.profileID - profile ID of the connection type
*/
_updateConnectionStatus: function btcm___updateConnectionStatus(options) {
debug('_updateConnectionStatus(): address = ' + options.address +
', connected = ' + options.connected +
', profileID = ' + options.profileID);
this.connectingAddress = null;
// Update the profile in the cache of connected device info.
this._updateConnectedDevices(options).then((connectedDevicesInfo) => {
debug('_updateConnectionStatus(): _updateConnectedDevices() ' +
'resolved with connectedDevicesInfo = ' +
JSON.stringify(connectedDevicesInfo));
// Prepare latest data to check connected profile.
var dataToCheckConnectedProfile = {
address: options.address,
connectedDevices: connectedDevicesInfo
};
// Fire 'connected'/'disconnected' event according to
// the connection profile. Then, record connection.
var event;
if (options.connected) {
// Fire 'connected' event.
event = {
type: 'connected',
detail: {
address: options.address
}
};
this._emitEvent(event);
// Record connection.
this._recordConnection('set', options.address);
} else {
// If there is no profile connected,
// we have to remove the record connection.
// And fire 'disconnected' event for outer modules.
if (!this._hasConnectedProfileByAddress(
dataToCheckConnectedProfile)) {
// Record connection. Only remove the record while Bluetooth state
// is enabled. Because the event also comes while Bluetooth is
// turned off.
if (this._defaultAdapter.state === 'enabled') {
this._recordConnection('remove');
}
// Fire 'disconnected' event.
event = {
type: 'disconnected',
detail: {
address: options.address
}
};
this._emitEvent(event);
}
}
// Fire 'profileChanged' event.
var newProfiles;
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
newProfiles = null;
} else {
newProfiles = connectedDevicesInfo[options.address].connectedProfiles;
}
event = {
type: 'profileChanged',
detail: {
address: options.address,
profiles: newProfiles
}
};
this._emitEvent(event);
}, (reason) => {
debug('_updateConnectionStatus(): miss to update in rejected case, ' +
'reason = ' + reason);
});
},
/**
* It provides a convenient function for panel to connect with a device.
* And the panel no need to care about connected device currently.
* The method will disconnect current connected device first.
* Then, it will start connecting a paired device with the device's adapter.
*
* @access public
* @memberOf BluetoothConnectionManager
* @param {BluetoothDevice} device
* @return {Promise}
*/
connect: function btcm_connect(device) {
if (!this._defaultAdapter) {
return Promise.reject('default adapter is not existed!!');
}
// Disconnect current connected device first.
debug('connect(): Want to connect device address = ' + device.address +
', name = ' + device.name);
var connectedDevices = [];
return this.getConnectedDevices().then((connectedDevicesInfo) => {
for (var address in connectedDevicesInfo) {
if (connectedDevicesInfo[address].device !== null) {
connectedDevices.push(connectedDevicesInfo[address].device);
debug('connect(): push device cache in queue = ' +
JSON.stringify(connectedDevices));
} else {
var regetPairedDevice = this._getPairedDeviceByAddress(address);
connectedDevices.push(regetPairedDevice);
debug('connect(): push _getPairedDeviceByAddress in queue = ' +
JSON.stringify(regetPairedDevice));
}
}
debug('connect(): Will disconnect these connected devices = ' +
JSON.stringify(connectedDevices));
// Disconnect these connected device before
// service to connect with new device.
return Promise.all(connectedDevices.map((connectedDevice) => {
return this.disconnect(connectedDevice);
})).then(() => {
// All connected devices is disconnected.
// We can start to connect the new request.
debug('connect(): Start to connect with wanted device address = ' +
device.address);
return this._connect(device).then(() => {
debug('connect(): Resolved');
}, (reason) => {
debug('connect(): reason = ' + reason);
return Promise.reject(reason);
});
});
});
},
/**
* The method will connect the input device with the device's adapter.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {BluetoothDevice} device
* @returns {Promise}
*/
_connect: function btcm__connect(device) {
if (!this._defaultAdapter) {
return Promise.reject('default adapter is not existed!!');
}
// Save the connecting address
this.connectingAddress = device.address;
// Fire 'connecting' event.
var event = {
type: 'connecting',
detail: {
address: device.address
}
};
this._emitEvent(event);
debug('_connect(): before start to connect, stop discovery first');
// Note on Bluedroid stack, discovery has to be stopped before connect
// (i.e., call stopDiscovery() before connect())
// otherwise stack callbacks with connect failure.
return this._defaultAdapter.stopDiscovery().then(() => {
debug('_connect(): start connecting device, ' +
'address = ' + device.address);
return this._defaultAdapter.connect(device).then(() => {
debug('_connect(): resolve, already connected with address = ' +
device.address);
}, () => {
// No available profiles are connected. Reset connecting address.
this.connectingAddress = null;
// Fire 'disconnected' event.
event = {
type: 'disconnected',
detail: {
address: device.address
}
};
this._emitEvent(event);
debug('_connect(): reject with connection failed');
return Promise.reject('connection failed');
});
}, () => {
// Cannot connect with the device since stopDiscovery failed.
this.connectingAddress = null;
// Fire 'disconnected' event.
event = {
type: 'disconnected',
detail: {
address: device.address
}
};
this._emitEvent(event);
debug('_connect(): reject with stop discovery failed');
return Promise.reject('stop discovery failed');
});
},
/**
* The method will disconnect the input device with the device's adapter.
*
* @access public
* @memberOf BluetoothConnectionManager
* @param {BluetoothDevice} device
* @returns {Promise}
*/
disconnect: function btcm_disconnect(device) {
if (!this._defaultAdapter) {
return Promise.reject('default adapter is not existed!!');
}
return this._defaultAdapter.disconnect(device).then(() => {
debug('disconnect(): onsuccess(): resolve');
}, () => {
debug('disconnect(): onerror(): reject with disconnect failed');
return Promise.reject('disconnect failed');
});
},
/**
* The method will get all connected devices profiles
* which we are interested in. Profile: HFP, A2DP.
*
* @access private
* @memberOf BluetoothConnectionManager
* @returns {Promise} resolve: connectedDevices
* @returns {Promise} reject: reason
*/
_getConnectedDevicesFromPlatform:
function btcm__getConnectedDevicesFromPlatform() {
// Get connected device via profiles HFP, A2DP
return Promise.all(Object.keys(this.Profiles).map((profile) => {
return this._getConnectedDevicesByProfile(this.Profiles[profile]);
})).then((connectedDevices) => {
// Update each connected devices in map.
var collectedConnectedDevicesByProfile = {};
Object.keys(this.Profiles).forEach((profile, index) => {
collectedConnectedDevicesByProfile[profile] = connectedDevices[index];
});
debug('_getConnectedDevicesFromPlatform(): ' +
'collectedConnectedDevicesByProfile = ' +
JSON.stringify(collectedConnectedDevicesByProfile));
return Promise.resolve(collectedConnectedDevicesByProfile);
});
},
/**
* The method will get connected device by inputed profile.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {String} profileID
* @returns {Promise} resolve: the connected devices in array
* @returns {Promise} reject: reason
*/
_getConnectedDevicesByProfile:
function btcm__getConnectedDevicesByProfile(profileID) {
if (!this._defaultAdapter) {
debug('_getConnectedDevicesByProfile(): reject with no adapter');
return Promise.reject('default adapter is not existed!!');
}
if (this._defaultAdapter.state === 'disabled') {
debug('_getConnectedDevicesByProfile(): resolve with empty array ' +
'since it is impossible to connect with any device');
return Promise.reject('getConnectedDevices in disabled state');
}
return this._defaultAdapter.getConnectedDevices(profileID).then(
(connectedDevice) => {
debug('_getConnectedDevicesByProfile(): resolved with ' +
'connectedDevice = ' + JSON.stringify(connectedDevice));
return Promise.resolve(connectedDevice || []);
}, (reason) => {
debug('_getConnectedDevicesByProfile(): rejected with ' +
'reason = ' + reason);
return Promise.reject(reason);
});
},
/**
* Get device from paired devices by address.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {String} address
*/
_getPairedDeviceByAddress:
function btcm__getPairedDeviceByAddress(address) {
if (!this._defaultAdapter) {
return null;
}
var pairedDevices = this._defaultAdapter.getPairedDevices();
if (pairedDevices.length === 0) {
return null;
} else {
for (var i in pairedDevices) {
if (pairedDevices[i].address === address) {
return pairedDevices[i];
}
}
return null;
}
},
/**
* Find out there is any profile still connected.
*
* @access private
* @memberOf BluetoothConnectionManager
* @param {Object} options
* @param {String} options.connectedDevices - connected devices info
* @param {String} options.address - the address of device
*/
_hasConnectedProfileByAddress:
function btcm__hasConnectedProfileByAddress(options) {
if (!options.connectedDevices[options.address]) {
return false;
}
var hasConnectedProfile = false;
for (var profileID in this.Profiles) {
var connectedProfiles =
options.connectedDevices[options.address].connectedProfiles;
if (connectedProfiles && (connectedProfiles[profileID] === true)) {
hasConnectedProfile = true;
}
}
return hasConnectedProfile;
},
/**
* A function to emit event to each registered listener by the input type.
*
* @memberOf BluetoothConnectionManager
* @param {Object} options
* @param {String} options.type - type of event name
* @param {Object} options.detail - the object pass to the listener
*/
_emitEvent: function btcm__emitEvent(options) {
this._listeners[options.type].forEach(function(listener) {
listener(options);
});
},
/**
* The method will provide event listener for outer modules to regist.
*
* @access public
* @memberOf BluetoothConnectionManager
* @param {String} eventName
* @param {Function} callback
*/
addEventListener: function btcm_addEventListener(eventName, callback) {
if (callback && (this._listeners.hasOwnProperty(eventName))) {
this._listeners[eventName].push(callback);
}
},
/**
* The method will provide event listener for outer modules to un-regist.
*
* @access public
* @memberOf BluetoothConnectionManager
* @param {String} eventName
* @param {Function} callback
*/
removeEventListener:
function btcm_removeEventListener(eventName, callback) {
if (callback && (this._listeners.hasOwnProperty(eventName))) {
var index = this._listeners[eventName].indexOf(callback);
if (index >= 0) {
this._listeners[eventName].splice(index, 1);
}
}
}
};
BluetoothConnectionManager._init();
return BluetoothConnectionManager;
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
/**
* BluetoothDevice:
* - BluetoothDevice is an Observable that wraps the platform
* BluetoothDevice object.
* - It has some observable properties: name, paired, cod
* BluetoothDevice only update device information and does not involve in any
* UI logic.
*
* @module BluetoothDevice
*/
define(function(require) {
'use strict';
var BtClassOfDeviceMapper = require('modules/bluetooth/bluetooth_cod_mapper');
var Observable = require('modules/mvvm/observable');
var _debug = false;
var debug = function() {};
if (_debug) {
debug = function btd_debug(msg) {
console.log('--> [BluetoothDevice]: ' + msg);
};
}
/**
* Provide a function to update connection info.
*
* @access public
* @memberOf BluetoothDevice
* @param {Object} options
* @param {Boolean} options.connectionStatus - is connected or not
* @param {Object} options.profiles - profiles of the connection type
*/
var updateConnectionInfo = function(options) {
debug('updateConnectionInfo(): this.address = ' + this.address +
', this.name = ' + this.name +
', options = ' + JSON.stringify(options));
if (options.connectionStatus) {
this.connectionStatus = options.connectionStatus;
}
if (options.profiles) {
this.profiles = Object.create(options.profiles);
}
debug('updateConnectionInfo(): this.connectionStatus = ' +
this.connectionStatus + ', this.profiles = ' +
JSON.stringify(this.profiles));
};
/**
* Provide a function to update description text.
*
* @access public
* @memberOf BluetoothDevice
*/
var updateDescriptionText = function() {
debug('updateDescriptionText():');
// Define description for found device.
if (this.paired === false) {
this.descriptionText = 'tapToConnect';
} else if (this.paired === 'pairing') {
this.descriptionText = 'pairing';
} else if (this.paired === true) {
this.descriptionText = 'paired';
// Define description for paired device.
if (this.connectionStatus === 'connecting') {
this.descriptionText = 'connecting';
} else if (this.connectionStatus === 'connected') {
if (this.profiles) {
var hfpConnected = this.profiles.hfp;
var a2dpConnected = this.profiles.a2dp;
if (hfpConnected && a2dpConnected) {
this.descriptionText = 'connectedWithDeviceMedia';
} else if (hfpConnected) {
this.descriptionText = 'connectedWithDevice';
} else if (a2dpConnected) {
this.descriptionText = 'connectedWithMedia';
} else {
this.descriptionText = 'connectedWithNoProfileInfo';
}
} else {
this.descriptionText = 'connectedWithNoProfileInfo';
}
} else if (this.connectionStatus === 'disconnected') {
this.descriptionText = 'disconnected';
}
}
debug('updateDescriptionText(): this.descriptionText = ' +
this.descriptionText);
};
/**
* @class BluetoothDevice
* @requires module:modules/mvvm/observable
* @param {BluetoothDevice} device
* @return {Observable} observableBluetoothDevice
*/
return function ctor_bluetooth_device(device) {
var type = BtClassOfDeviceMapper.getDeviceType(device.cod);
var connectionStatus = device.connectionStatus || 'disconnected';
var observableBluetoothDevice = Observable({
name: device.name,
paired: device.paired,
address: device.address,
type: type,
connectionStatus: connectionStatus,
profiles: null,
descriptionText: '',
get data() {return device;},
updateConnectionInfo: updateConnectionInfo,
updateDescriptionText: updateDescriptionText
});
/**
* Observe 'paired', 'connectionStatus', and 'profiles' properties changed
* event in init function. Once these properties changed, we can update
* corrected description for the device.
*/
observableBluetoothDevice._init = function btd__init() {
this.observe('paired', this.updateDescriptionText.bind(this));
this.observe('connectionStatus', this.updateDescriptionText.bind(this));
this.observe('profiles', this.updateDescriptionText.bind(this));
};
/**
* watch 'onattributechanged' event
* A function to receive device which is just found via discovery handler.
* And set a function to handle 'onattributechanged' event.
*/
device.onattributechanged = function btd_onDeviceAttributeChanged(evt) {
for (var i in evt.attrs) {
debug('onDeviceAttributeChanged(): ' + evt.attrs[i]);
switch (evt.attrs[i]) {
case 'name':
observableBluetoothDevice.name = device.name;
break;
case 'paired':
debug('onDeviceAttributeChanged(): ' +
'device.paired = ' + device.paired);
observableBluetoothDevice.paired = device.paired;
break;
case 'cod':
debug('onDeviceAttributeChanged(): ' +
'device.cod = ' + device.cod);
observableBluetoothDevice.type =
BtClassOfDeviceMapper.getDeviceType(device.cod);
break;
default:
break;
}
}
};
observableBluetoothDevice._init();
return observableBluetoothDevice;
};
});

View file

@ -0,0 +1,242 @@
'use strict';
define(function(require) {
var elements = new WeakMap();
var ObservableArray = require('modules/mvvm/observable_array');
/*
* A ListView takes an ObservableArray or an ordinary array, and generate/
* manipulate the corresponding DOM elements of the content in the array using
* the given template function. If the array is an ObservableArray, ListView
* updates the DOM elements accordingly when the array is manipulated.
*/
var ListView = function(root, observableArray, templateFunc) {
var _observableArray = null;
var _root = root;
var _templateFunc = templateFunc;
var _enabled = true;
// keep track of observeables so we can unobserve
var observerMap = new WeakMap();
function releaseObservers(element) {
var observable = observerMap.get(element);
if (!observable || typeof observable !== 'object') {
return;
}
var observers = observerMap.get(observable);
if (observers) {
observers.forEach(function eachObserver(observer) {
observable.unobserve(observer);
});
observerMap.delete(observable);
observerMap.delete(element);
}
}
// helper to observe something and automatically remove old observers
function observeAndCall(observable, map) {
var observers = observerMap.get(observable) || [];
if (observerMap.has(observable)) {
observers = observerMap.get(observable);
}
observers = Object.keys(map).map(function eachProperty(property) {
observable.observe(property, map[property]);
map[property]();
return map[property];
});
observerMap.set(observable, observers);
}
var _helper = {
observeAndCall: observeAndCall
};
var _handleEvent = function(event) {
// Ignore the change event when the list view is not enabled.
if (!_enabled) {
return;
}
var data = event.data;
switch (event.type) {
case 'insert':
_insert(data.index, data.items);
break;
case 'remove':
_remove(data.index, data.count);
break;
case 'replace':
_replace(data.index, data.newValue);
break;
case 'reset':
_reset(data.items || []);
break;
default:
break;
}
};
var _insert = function(index, items) {
if (items.length <= 0) {
return;
}
// add DOM elements
var referenceElement = _root.children[index];
for (var i = items.length - 1; i >= 0; i--) {
var curElement = _templateFunc(items[i], undefined, _helper);
observerMap.set(curElement, items[i]);
_root.insertBefore(curElement, referenceElement);
referenceElement = curElement;
}
};
var _remove = function(index, count) {
if (count === 0) {
return;
}
// remove DOM elements
if (count >= _root.childElementCount) {
// clear all
while (_root.firstElementChild) {
releaseObservers(_root.firstElementChild);
_root.removeChild(_root.firstElementChild);
}
} else {
var nextElement = _root.children[index];
for (var i = 0; i < count; i++) {
if (nextElement) {
var temp = nextElement.nextElementSibling;
releaseObservers(nextElement);
_root.removeChild(nextElement);
nextElement = temp;
} else {
break;
}
}
}
};
var _replace = function(index, value) {
var element = _root.children[index];
if (element) {
releaseObservers(element);
var newElement = _templateFunc(value, element, _helper);
observerMap.set(element, value);
if (newElement !== element) {
_root.insertBefore(newElement, element);
_root.removeChild(element);
}
}
};
var _reset = function(items) {
var itemCount = items.length;
var elementCount = _root.childElementCount;
if (itemCount === 0) {
_remove(0, elementCount);
} else if (itemCount <= elementCount) {
items.forEach(function(item, index) {
_replace(index, item);
});
// remove extra elements
_remove(itemCount, elementCount - itemCount);
} else {
var slicedItems = items.slice(0, elementCount);
var remainingItems = items.slice(elementCount);
slicedItems.forEach(function(item, index) {
_replace(index, item);
});
// add extra elements
_insert(elementCount, remainingItems);
}
};
var _enabledChanged = function() {
if (_enabled) {
_reset(_observableArray.array);
}
};
var view = {
set: function lv_set(newArray) {
if (_observableArray) {
_observableArray.unobserve(_handleEvent);
}
if (!newArray) {
// clear all existing items
if (_observableArray) {
_remove(0, _observableArray.length);
_observableArray = null;
}
return;
}
// If new array is an ordinary array, convert it to an ObservableArray.
if (Array.isArray(newArray)) {
newArray = ObservableArray(newArray);
}
_observableArray = newArray;
// An ObservableArray fires the four events for notifying its changes.
// Register to these events for updating UI.
_observableArray.observe('insert', _handleEvent);
_observableArray.observe('remove', _handleEvent);
_observableArray.observe('replace', _handleEvent);
_observableArray.observe('reset', _handleEvent);
if (this.enabled) {
_reset(_observableArray.array);
}
},
destroy: function() {
// unobserve from array and null everything out
if (_observableArray) {
_observableArray.unobserve(_handleEvent);
}
_remove(0, _observableArray.length);
elements.delete(_root);
_root = null;
_observableArray = null;
_templateFunc = null;
_enabled = false;
},
get element() {
return _root;
},
set enabled(value) {
if (_enabled !== value) {
_enabled = value;
_enabledChanged();
}
},
get enabled() {
return _enabled;
}
};
if (elements.has(_root)) {
// destroy old ListView if we setup a second one
elements.get(_root).destroy();
}
elements.set(_root, view);
// emtpy element at creation time
_root.innerHTML = '';
view.set(observableArray);
return view;
};
return ListView;
});

View file

@ -0,0 +1,102 @@
'use strict';
define(function() {
function unobserve(_eventHandlers, prop, handler) {
// arguments in reverse order to support .bind(handler) for the
// unbind from all case
function removeHandler(handler, prop) {
var handlers = _eventHandlers[prop];
if (!handlers) {
return;
}
var index = handlers.indexOf(handler);
if (index >= 0) {
handlers.splice(index, 1);
}
}
if (typeof prop === 'function') {
// (handler) -- remove from every key in _eventHandlers
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
} else if (handler) {
// (prop, handler) -- remove handler from the specific prop
removeHandler(handler, prop);
} else if (prop in _eventHandlers) {
// (prop) -- otherwise remove all handlers for property
_eventHandlers[prop] = [];
}
}
/*
* An Observable is able to notify its property change. It is initialized by
* an ordinary object.
*/
function Observable(obj) {
var _eventHandlers = {};
var _observable = {
observe: function o_observe(p, handler) {
/*
* We should check if _observable[_p] exists. Since _observable[_p] is
* created along with _eventHandlers[p], here we simply check
* _eventHandlers[p].
*/
var handlers = _eventHandlers[p];
if (handlers) {
handlers.push(handler);
}
},
/**
* unobserve([prop], handler) - remove handler from observeable callbacks
*/
unobserve: unobserve.bind(null, _eventHandlers)
};
var _getFunctionTemplate = function(p) {
return function() {
return _observable['_' + p];
};
};
var _setFunctionTemplate = function(p) {
return function(value) {
var oldValue = _observable['_' + p];
if (oldValue !== value) {
_observable['_' + p] = value;
var handlers = _eventHandlers[p];
handlers.forEach(function(handler) {
handler(value, oldValue);
});
}
};
};
/*
* Iterate all properties in the object and create corresponding getter and
* setter for them.
*/
for (var p in obj) {
// If p is a function, simply add it to the observable.
if (typeof obj[p] === 'function') {
_observable[p] = obj[p];
continue;
}
_eventHandlers[p] = [];
Object.defineProperty(_observable, '_' + p, {
value: obj[p],
writable: true
});
Object.defineProperty(_observable, p, {
enumerable: true,
get: _getFunctionTemplate(p),
set: _setFunctionTemplate(p)
});
}
return _observable;
}
return Observable;
});

View file

@ -0,0 +1,153 @@
'use strict';
define(function() {
function unobserve(_eventHandlers, prop, handler) {
// arguments in reverse order to support .bind(handler) for the
// unbind from all case
function removeHandler(handler, prop) {
var handlers = _eventHandlers[prop];
if (!handlers) {
return;
}
var index = handlers.indexOf(handler);
if (index >= 0) {
handlers.splice(index, 1);
}
}
if (typeof prop === 'function') {
// (handler) -- remove from every key in _eventHandlers
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
} else if (handler) {
// (prop, handler) -- remove handler from the specific prop
removeHandler(handler, prop);
} else if (prop in _eventHandlers) {
// (prop) -- otherwise remove all handlers for property
_eventHandlers[prop] = [];
}
}
/*
* An ObservableArray is able to notify its change through four basic
* operations including 'insert', 'remove', 'replace', 'reset'. It is
* initialized by an ordinary array.
*/
function ObservableArray(array) {
var _array = array || [];
var _eventHandlers = {
'insert': [],
'remove': [],
'replace': [],
'reset': []
};
var _notify = function(eventType, data) {
var handlers = _eventHandlers[eventType];
handlers.forEach(function(handler) {
handler({
type: eventType,
data: data
});
});
};
return {
get length() {
return _array.length;
},
get array() {
return _array;
},
forEach: function oa_foreach(func) {
_array.forEach(func);
},
observe: function oa_observe(eventType, handler) {
var handlers = _eventHandlers[eventType];
if (handlers) {
handlers.push(handler);
}
},
unobserve: unobserve.bind(null, _eventHandlers),
push: function oa_push(item) {
_array.push(item);
_notify('insert', {
index: _array.length - 1,
count: 1,
items: [item]
});
},
pop: function oa_pop() {
if (!_array.length) {
return;
}
var item = _array.pop();
_notify('remove', {
index: _array.length,
count: 1
});
return item;
},
splice: function oa_splice(index, count) {
if (arguments.length < 2) {
return;
}
var addedItems = Array.prototype.slice.call(arguments, 2);
_array.splice.apply(_array, arguments);
if (count) {
_notify('remove', {
index: index,
count: count
});
}
if (addedItems.length) {
_notify('insert', {
index: index,
count: addedItems.length,
items: addedItems
});
}
},
set: function oa_set(index, value) {
if (index < 0 || index >= _array.length) {
return;
}
var oldValue = _array[index];
_array[index] = value;
_notify('replace', {
index: index,
oldValue: oldValue,
newValue: value
});
},
get: function oa_get(index) {
return _array[index];
},
reset: function oa_reset(array) {
_array = array;
_notify('reset', {
items: _array
});
}
};
}
return ObservableArray;
});

View file

@ -0,0 +1,12 @@
/**
* Wraps navigator.bluetooth for replacing it in unit tests more easily.
*/
define(function() {
'use strict';
if (navigator.mozBluetooth) {
return navigator.mozBluetooth;
} else {
return null;
}
});

View file

@ -0,0 +1,430 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global NotificationHelper */
define(function(require) {
'use strict';
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
var BtContext = require('modules/bluetooth/bluetooth_context');
var PairExpiredDialog = require('views/pair_expired_dialog');
var _debug = false;
var debug = function() {};
if (_debug) {
debug = function btam_debug(msg) {
console.log('--> [PairManager]: ' + msg);
};
}
/*
* PairManager is responsible for:
* 1. Handling system message 'bluetooth-pairing-request' while there is an
* incoming/outgoing pairing request.
* 2. Handling dom event from BluetoothPairingListener while there is an
* incoming/outgoing pairing request.
* 3. Handling dom event 'ondeviceunpaired' while some remote devices
* request for canceling an overdue pairing request. The reason could be
* authentication fails, remote device down, and internal error happens.
*/
var PairManager = {
/**
* Default adapter of Bluetooth.
*
* @access private
* @memberOf PairManger
* @type {Object} BluetoothAdapter
*/
_defaultAdapter: null,
/**
* Indicate if the pair request is expired
* @type {Boolean}
*/
_isExpired: false,
init: function() {
// Observe 'defaultAdapter' property for reaching default adapter.
AdapterManager.observe('defaultAdapter',
this._onDefaultAdapterChanged.bind(this));
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
// Watch pairing events.
this._watchOndisplaypasskeyreq();
this._watchOnenterpincodereq();
this._watchOnpairingconfirmationreq();
this._watchOnpairingconsentreq();
this._watchOnpairingaborted();
navigator.mozSetMessageHandler('bluetooth-pairing-request',
this._onRequestPairingFromSystemMessage.bind(this)
);
// Observe screen lockscreen locked/unlocked state for show pending
// pairing request immediately.
navigator.mozSettings.addObserver('lockscreen.locked',
function gotScreenLockedChanged(event) {
this.showPendingPairing(event.settingValue);
}.bind(this));
// Observe Bluetooth 'enabled' property from hardware side.
// Then, close pairing dialog immediately.
BtContext.observe('enabled', (enabled) => {
if (!enabled) {
this.onBluetoothDisabled();
}
});
},
/**
* 'defaultAdapter' change event handler from adapter manager for
* updating it immediately.
*
* @access private
* @memberOf PairManager
* @param {Object} BluetoothAdapter newAdapter
*/
_onDefaultAdapterChanged: function(newAdapter) {
// save default adapter
this._defaultAdapter = newAdapter;
},
/**
* Watch 'ondisplaypasskeyreq' dom event for pairing.
* A handler to trigger when a remote bluetooth device requests to display
* passkey on the screen during pairing process.
*
* @access private
* @memberOf PairManager
*/
_watchOndisplaypasskeyreq: function() {
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
debug('_watchOndisplaypasskeyreq() no adapter or pairingReqs');
return;
}
this._defaultAdapter.pairingReqs.addEventListener('displaypasskeyreq',
(evt) => {
debug('_watchOndisplaypasskeyreq()');
this._handlePairingRequest({
method: 'displaypasskey',
evt: evt
});
});
},
/**
* Watch 'onenterpincodereq' dom event for pairing.
* A handler to trigger when a remote bluetooth device requests user enter
* PIN code during pairing process.
*
* @access private
* @memberOf PairManager
*/
_watchOnenterpincodereq: function() {
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
debug('_watchOnenterpincodereq() no adapter or pairingReqs');
return;
}
this._defaultAdapter.pairingReqs.addEventListener('enterpincodereq',
(evt) => {
debug('_watchOnenterpincodereq()');
this._handlePairingRequest({
method: 'enterpincode',
evt: evt
});
});
},
/**
* Watch 'onpairingconfirmationreq' dom event for pairing.
* A handler to trigger when a remote bluetooth device requests user
* confirm passkey during pairing process. Applications may prompt passkey
* to user for confirmation, or confirm the passkey for user proactively.
*
* @access private
* @memberOf PairManager
*/
_watchOnpairingconfirmationreq: function() {
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
debug('_watchOnpairingconfirmationreq() no adapter or pairingReqs');
return;
}
this._defaultAdapter.pairingReqs.addEventListener(
'pairingconfirmationreq', (evt) => {
debug('_watchOnpairingconfirmationreq: evt = ' + JSON.stringify(evt));
this._handlePairingRequest({
method: 'confirmation',
evt: evt
});
});
},
/**
* Watch 'onpairingconsentreq' dom event for pairing.
* A handler to trigger when a remote bluetooth device requests user
* confirm pairing during pairing process. Applications may prompt user
* for confirmation or confirm for user proactively.
*
* @access private
* @memberOf PairManager
*/
_watchOnpairingconsentreq: function() {
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
debug('_watchOnpairingconsentreq() no adapter or pairingReqs');
return;
}
this._defaultAdapter.pairingReqs.addEventListener('pairingconsentreq',
(evt) => {
debug('_watchOnpairingconsentreq: evt = ' + JSON.stringify(evt));
this._handlePairingRequest({
method: 'consent',
evt: evt
});
});
},
/**
* Watch 'onpairingaborted' dom event for pairing aborted.
* A handler to trigger when pairing fails due to one of
* following conditions:
* - authentication fails
* - remote device down (bluetooth ACL becomes disconnected)
* - internal error happens
*
* @access private
* @memberOf PairManager
*/
_watchOnpairingaborted: function() {
if (!this._defaultAdapter) {
return;
}
this._defaultAdapter.addEventListener('pairingaborted',
this._onPairingAborted.bind(this));
},
/**
* A handler to handle 'onpairingaborted' event while it's coming.
*
* @access private
* @memberOf PairManager
*/
_onPairingAborted: function(evt) {
debug('_onPairingAborted(): evt = ' + JSON.stringify(evt));
// if the attention screen still open, close it
if (this.childWindow) {
this.childWindow.Pairview.closeInput();
this.childWindow.close();
}
// If "onpairingaborted" event is coming, and there is a pending pairing
// request, we have reset the object. Because the callback is useless
// since the older pairing request is expired.
// The moment is fit to notify user that the later pairing request
// is timeout or canceled. According to user story, do nothing here.
// Clear up the pending pairing request only.
if (this.pendingPairing) {
this.pendingPairing = null;
}
// Close pairing expired dialog if it is showing.
if (PairExpiredDialog.isVisible) {
PairExpiredDialog.close();
// Have to close Bluetooth app after the dialog is closed.
window.close();
}
},
/**
* Receive the system message event for launch Bluetooth app here.
*
* @access private
* @memberOf PairManager
*/
_onRequestPairingFromSystemMessage: function() {
debug('onRequestPairingFromSystemMessage():');
},
/**
* It is used to handle each pairing request from different pairing methods.
*
* @memberOf PairManager
* @access private
* @param {Object} pairingInfo
* @param {Object} pairingInfo.method - method of this pairing request
* @param {Object} pairingInfo.evt - DOM evt of this pairing request
*/
_handlePairingRequest: function(pairingInfo) {
debug('_onRequestPairing():' +
' pairingInfo.method = ' + pairingInfo.method +
' pairingInfo.evt = ' + pairingInfo.evt);
var req = navigator.mozSettings.createLock().get('lockscreen.locked');
req.onsuccess = () => {
if (req.result['lockscreen.locked']) {
// notify user that we are receiving pairing request
this.fireNotification(pairingInfo);
} else {
// We have clear up pending one before show the new pairing requst.
// Becasue the pending pairing request is no longer usefull.
this.cleanPendingPairing();
// show pair view directly while lock screen is unlocked
this.showPairview(pairingInfo);
}
};
req.onerror = () => {
// We have clear up pending one before show the new pairing requst.
// Becasue the pending pairing request is no longer usefull.
this.cleanPendingPairing();
// fallback to default value 'unlocked'
this.showPairview(pairingInfo);
};
},
fireNotification: function(pairingInfo) {
// Once we received a pairing request in screen locked mode,
// overwrite the object with the latest pairing request. Because the
// older pairing request might be timeout and useless now.
this.pendingPairing = {
showPairviewCallback: this.showPairview.bind(this, pairingInfo)
};
// Prepare notification toast.
var titleL10n = 'bluetooth-pairing-request-now-title';
var bodyL10n = pairingInfo.evt.deviceName ?
{ raw: pairingInfo.evt.deviceName } :
'unnamed-device';
var iconUrl =
'app://bluetooth.gaiamobile.org/style/images/icon_bluetooth.png';
// We always use tag "pairing-request" to manage these notifications.
var notificationId = 'pairing-request';
return NotificationHelper.send(titleL10n, {
bodyL10n: bodyL10n,
icon: iconUrl,
tag: notificationId,
closeOnClick: false
}).then(notification => {
// set onclick handler for the notification
notification.onclick =
this.pairingRequestExpiredNotificationHandler.bind(this,
notification);
});
},
// According to user story, it won't notify user again
// while the pending pairing request is just timeout or canceled.
// So we have to set onclick handler in this moment.
// The handler will pop out a pairing request expired prompt only.
pairingRequestExpiredNotificationHandler: function(notification) {
var req = navigator.mozSettings.createLock().get('lockscreen.locked');
req.onsuccess = () => {
// Avoid to do nothting while the notification toast is showing
// and a user is able to trigger onclick event. Make sure screen
// is unlocked, then show the prompt in bluetooth app.
if (!req.result['lockscreen.locked']) {
// Clean the pairing request notficiation which is expired.
notification.close();
navigator.mozApps.getSelf().onsuccess = (evt) => {
var app = evt.target.result;
// launch bluetooth app to foreground for showing the prompt
if (PairExpiredDialog.isVisible) {
app.launch();
}
// show an alert with the overdue message
if (!PairExpiredDialog.isVisible && this._isExpired) {
debug('show expired dialog');
PairExpiredDialog.showConfirm(function() {
// Have to close Bluetooth app after the dialog is closed.
window.close();
});
}
};
}
};
},
// If there is a pending pairing request while a user just unlocks screen,
// we will show pair view immediately. Then, we clear up the notification.
// If pendingPairing object is not exist, it means pair request is expired.
showPendingPairing: function(screenLocked) {
debug('showPendingPairing when screenLocked:' + screenLocked);
if (!screenLocked) {
if (this.pendingPairing) {
this._isExpired = false;
// show pair view from the callback function
if (this.pendingPairing.showPairviewCallback) {
this.pendingPairing.showPairviewCallback();
}
this.cleanPendingPairing();
} else {
this._isExpired = true;
}
}
},
cleanPendingPairing: function() {
debug('cleanPendingPairing(): has pendingPairing = ' +
(this.pendingPairing));
// Clear up the pending pairing request
if (this.pendingPairing) {
this.pendingPairing = null;
}
// Clear up the pairing request from notification.
this.cleanNotifications();
},
// Clean all notifications which are fired with tag 'pairing-request'.
cleanNotifications: function() {
Notification.get().then(function(notifications) {
if (notifications) {
notifications.forEach(function(notification) {
// Compare tags, as the tag is based on the "pairing-request" and
// we only have one notification for the request. Plus, there
// is no "id" field on the notification.
if (notification.tag === 'pairing-request' &&
notification.close) {
notification.close();
}
});
}
});
},
showPairview: function(pairingInfo) {
debug('showPairview(): pairingInfo = ' + pairingInfo);
var protocol = window.location.protocol;
var host = window.location.host;
this.childWindow = window.open(protocol + '//' + host + '/onpair.html',
'pair_screen', 'attention');
this.childWindow.onload = () => {
this.childWindow.Pairview.init(pairingInfo.method, pairingInfo.evt);
};
},
onBluetoothDisabled: function() {
debug('onBluetoothDisabled():');
// if the attention screen still open, close it
if (this.childWindow) {
this.childWindow.Pairview.closeInput();
this.childWindow.close();
}
// Since Bluetooth is off, close itself.
window.close();
}
};
return PairManager;
});

View file

@ -0,0 +1,349 @@
/*
* TransferManager is responsible for:
* - Handling system message 'activity' while there is an activity request
* incoming. Then, it will post result/error to requesting app.
* - Watching Bluetooth enabled/disabled state, then show/hide turn Bluetooth
* on dialog.
* - Watching default adapter existed or not. And will post error if we cannot
* get default adapter.
* - Watching 'devicePicked', 'cancelSelection' events from DevicePickerPanel
* module. Then, do corrected activity service flow regarding user decision.
*
* @module BluetoothContext
*/
define(function(require) {
'use strict';
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
var BtContext = require('modules/bluetooth/bluetooth_context');
var CannotTransferDialog = require('views/cannot_transfer_dialog');
var DevicePickerPanel = require('views/device_picker_panel');
var TurnBluetoothOnDialog = require('views/turn_bluetooth_on_dialog');
var _debug = false;
var debug = function() {};
if (_debug) {
debug = function bttm_debug(msg) {
console.log('--> [TransferManager]: ' + msg);
};
}
var TransferManager = {
/**
* An instance to access activity source.
*
* @access private
* @memberOf TransferManager
* @type {Object}
*/
_activity: null,
/**
* The init function only watches the 'activity' event from system message.
*
* @access private
* @memberOf TransferManager
*/
init: function bttm_init() {
// Watch 'activity' event from system message.
this._watchActivityRequest();
},
/**
* Watch 'activity' event with callback function from system message.
*
* @access private
* @memberOf TransferManager
*/
_watchActivityRequest: function bttm__watchActivityRequest() {
navigator.mozSetMessageHandler('activity',
this._activityHandler.bind(this));
},
/**
* Handle 'activity' request. Then make decision to service the request or
* reject it.
*
* @access private
* @memberOf TransferManager
* @param {Object} activityRequest
*/
_activityHandler: function bttm__activityHandler(activityRequest) {
debug('_activityHandler(): activityRequest = ' +
JSON.stringify(activityRequest));
if (activityRequest.source.data.blobs &&
activityRequest.source.data.blobs.length > 0) {
this._activity = activityRequest;
// Source info is enough to servie file transfer.
// Start to init and observe Bluetooth relative modules.
this._observeDefaultAdapter();
this._observeBluetoothEnabled();
this._watchEventFromDevicePicker();
DevicePickerPanel.visible = true;
} else {
debug('_activityHandler(): Cannot transfer without blobs data!');
this._cannotTransfer();
}
},
/**
* Show alert dialog. End the activity service while a user confirm to close
* the dialog.
*
* @access private
* @memberOf TransferManager
*/
_cannotTransfer: function bttm__cannotTransfer() {
debug('_cannotTransfer(): show alert dialog!!');
// show an alert with the overdue message
if (!CannotTransferDialog.isVisible) {
CannotTransferDialog.showConfirm().then(() => {
// Post error with reason then end the activity service.
this._endTransferWithReason('cancelled');
});
}
},
/**
* Post result/error to end the activity service.
*
* @access private
* @memberOf TransferManager
*/
_endTransferWithReason: function bttm__endTransferWithReason(reason) {
debug('_endTransferWithReason(): reason = ' + reason);
if (reason === 'transferred') {
this._activity.postResult(reason);
} else if (reason === 'cancelled') {
this._activity.postError(reason);
}
this._activity = null;
},
/**
* Observe and get default adapter from bluetooth adapter manager.
*
* @access private
* @memberOf TransferManager
*/
_observeDefaultAdapter: function bttm__observeDefaultAdapter() {
// Observe 'defaultAdapter' property for reaching default adapter.
AdapterManager.observe('defaultAdapter',
this._onDefaultAdapterChanged.bind(this));
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
},
/**
* Observe and get 'enabled' property from bluetooth context.
*
* @access private
* @memberOf TransferManager
*/
_observeBluetoothEnabled: function bttm__observeBluetoothEnabled() {
// Observe Bluetooth 'enabled' property from hardware side.
BtContext.observe('enabled', this._onBluetoothEnabledChanged.bind(this));
this._onBluetoothEnabledChanged(BtContext.enabled);
},
/**
* 'defaultAdapter' change event handler from adapter manager for
* updating it immediately.
*
* @access private
* @memberOf TransferManager
* @param {Object} BluetoothAdapter newAdapter
* @param {Object} BluetoothAdapter oldAdapter
*/
_onDefaultAdapterChanged:
function bttm__onDefaultAdapterChanged(newAdapter, oldAdapter) {
if (oldAdapter && (newAdapter === null)) {
debug('_onDefaultAdapterChanged(): Can not get bluetooth adapter!');
// Post error with reason then end the activity service.
this._endTransferWithReason('cancelled');
}
},
/**
* 'enabled' change event handler from bluetooth context for
* updating UI flow immediately.
*
* @access private
* @memberOf TransferManager
* @param {Boolean} enabled
*/
_onBluetoothEnabledChanged:
function bttm__onBluetoothEnabledChanged(enabled) {
debug('_onBluetoothEnabledChanged(): enabled = ' + enabled);
switch(enabled) {
case undefined:
break;
case true:
// Close the confirmation dialog
TurnBluetoothOnDialog.close();
break;
default:
TurnBluetoothOnDialog.showConfirm().then((result) => {
if (result === 'confirm') {
// Turn Bluetooth on.
BtContext.setEnabled(true);
} else if (result === 'cancel') {
// Post error with reason then end the activity service.
this._endTransferWithReason('cancelled');
}
});
break;
}
},
/**
* Watch 'devicePicked', 'cancelSelection' event from device picker panel.
*
* @access private
* @memberOf TransferManager
*/
_watchEventFromDevicePicker: function bttm__watchEventFromDevicePicker() {
debug('_watchEventFromDevicePicker():');
// Watch 'devicePicked' event for reaching device picked from user.
DevicePickerPanel.addEventListener('devicePicked',
this._onDevicePicked.bind(this));
// Watch 'cancelSelection' event for reaching cancel selection from user.
DevicePickerPanel.addEventListener('cancelSelection',
this._onCancelSelection.bind(this));
},
/**
* While the device is picked from user, let's send file(s) to the device.
*
* @access private
* @memberOf TransferManager
* @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
* @returns {Promise}
*/
_onDevicePicked: function bttm__onDevicePicked(event) {
// Send file to the device via address
debug('_onDevicePicked(): address of target device = ' +
event.detail.address);
var targetDeviceAddress = event.detail.address;
var blobs = this._activity.source.data.blobs;
// Produce sending files schedule.
// Then, post message to system app for sending files in queue.
var schedule = this._produceSendingFilesSchedule(blobs.length);
this._postMessageToSystemApp(schedule);
// Send each file via Bluetooth sendFile API
return Promise.all(blobs.map((blob, index) => {
/**
* Check blob.name because sendFile() API needs a "file"
* object and a filaname before sending it.
* If there is no filename in the blob, Bluetooth API will give a
* default name "Unknown.jpeg".
* So Bluetooth app has to find out the name via device storage.
*/
if (blob.name) {
// The blob has name, send the blob directly.
debug('blob is sending with name...');
return BtContext.sendFile(targetDeviceAddress, blob);
} else if (this._activity.source.data.filepaths) {
// The blob does not have name,
// browse the file via filepath from storage again.
var filepath = this._activity.source.data.filepaths[index];
var storage = navigator.getDeviceStorage('sdcard');
return storage.get(filepath).then((file) => {
debug('getFile succeed & file is sending... file = ' + file);
return BtContext.sendFile(targetDeviceAddress, file);
}, (error) => {
debug('getFile failed so that blob is sending without filename ' +
error && error.name);
return BtContext.sendFile(targetDeviceAddress, blob);
});
} else {
// The blob does not have name and filepath,
// pass the blob without name to send file directly.
debug('no filepath to get from device storage ' +
'so that blob is sending without filename');
return BtContext.sendFile(targetDeviceAddress, blob);
}
})).then(() => {
debug('all files are already sent out..');
// Post result with reason then end the activity service.
this._endTransferWithReason('transferred');
return Promise.resolve();
});
},
/**
* While a user cancel selection, let's post result then end the activity
* service.
*
* @access private
* @memberOf TransferManager
* @param {Object} event
* @param {String} event.type - type of event name
*/
_onCancelSelection: function bttm__onCancelSelection(event) {
debug('_onCancelSelection(): cancel from user decision');
// Post error with reason then end the activity service.
this._endTransferWithReason('cancelled');
},
/**
* A function is a role producer.
* Bluetooth app produce one message for each sending file request.
*
* @access private
* @memberOf TransferManager
* @param {Number} numberOfTasks - number of tasks
* @returns {Object}
*/
_produceSendingFilesSchedule:
function bttm__produceSendingFilesSchedule(numberOfTasks) {
// Construct a object to contain info of sending files schedule.
// And the info will be posted message to system app.
// The result of send files will be displayed on notification.
var sendingFilesSchedule = {
numberOfFiles: numberOfTasks,
numSuccessful: 0,
numUnsuccessful: 0
};
return sendingFilesSchedule;
},
/**
* A function to do inner app communcation.
*
* @access private
* @memberOf TransferManager
* @param {Object} message - the info of sending files schedule
*/
_postMessageToSystemApp:
function bttm__postMessageToSystemApp(message) {
// Set up Inter-App Communications
var getRequest = navigator.mozApps.getSelf();
getRequest.onsuccess = (evt) => {
var app = evt.target.result;
// If IAC doesn't exist, just bail out.
if (!app.connect) {
debug('_postMessageToSystemApp(): Cannot post message since no ' +
'connect function, message = ' + JSON.stringify(message));
return;
}
// Post message object to system app.
app.connect('bluetoothTransfercomms').then((ports) => {
ports.forEach((port) => {
port.postMessage(message);
debug('_postMessageToSystemApp(): sending files schedule = ' +
JSON.stringify(message));
});
});
};
}
};
return TransferManager;
});