Source: modules/bluetooth/bluetooth_connection_manager.js

  1. /**
  2. * BluetoothConnectionManager:
  3. * BluetoothConnectionManager only update state and does not involve in any UI
  4. * logic.
  5. *
  6. * @module BluetoothConnectionManager
  7. */
  8. define(function(require) {
  9. 'use strict';
  10. var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
  11. var AsyncStorage = require('shared/async_storage');
  12. var _debug = false;
  13. var Debug = function() {};
  14. if (_debug) {
  15. Debug = function btam_debug(msg) {
  16. console.log('--> [BluetoothConnectionManager]: ' + msg);
  17. };
  18. }
  19. /**
  20. * @alias module:modules/bluetooth/BluetoothConnectionManager
  21. * @requires module:modules/bluetooth/bluetooth_adapter_manager
  22. * @requires module:shared/async_storage
  23. * @return {BluetoothConnectionManager}
  24. */
  25. var BluetoothConnectionManager = {
  26. /**
  27. * The profiles of connected device that we are defined here.
  28. *
  29. * @public
  30. * @memberOf BluetoothConnectionManager
  31. * @type {Object}
  32. */
  33. Profiles: {
  34. 'hfp': 0x111E, // Handsfree
  35. 'a2dp': 0x110D // Advanced Audio Distribution Devices
  36. },
  37. /**
  38. * The address of device that we are trying to connect.
  39. *
  40. * @public
  41. * @memberOf BluetoothConnectionManager
  42. * @type {String}
  43. */
  44. connectingAddress: null,
  45. /**
  46. * A object that we cache the connected devices information(address, device,
  47. * connectedProfiles). It will be inited while default adapter is ready.
  48. * And these information is coming from profile events.
  49. * Each connected device is hashed by device address.
  50. *
  51. * EX:
  52. * _connectedDevicesInfo = {
  53. * 'AA:BB:CC:00:11:22': {
  54. * 'device': DeviceObject,
  55. * 'connectedProfiles': {
  56. * 'hfp': true,
  57. * 'a2dp': false
  58. * }
  59. * }
  60. * };
  61. *
  62. * @private
  63. * @memberOf BluetoothConnectionManager
  64. * @type {Object}
  65. */
  66. _connectedDevicesInfo: {},
  67. /**
  68. * An instance to maintain that we have created a promise to get connected
  69. * devices.
  70. *
  71. * @access public
  72. * @memberOf BluetoothConnectionManager
  73. * @return {Promise}
  74. */
  75. _getConnectedDevicesPromise: null,
  76. /**
  77. * The object maintains listeners' callback per property name.
  78. * Each listener would be called as following definition.
  79. * 'connecting' - be called when device is connecting.
  80. * 'connected': - be called when device is connected.
  81. * 'disconnected': - be called when device is disconnected.
  82. * 'profileChanged': - be called when profile is changed.
  83. *
  84. * @memberOf BluetoothConnectionManager
  85. * @type {Object}
  86. */
  87. _listeners: {
  88. 'connecting': [],
  89. 'connected': [],
  90. 'disconnected': [],
  91. 'profileChanged': []
  92. },
  93. /**
  94. * Default adapter of Bluetooth.
  95. *
  96. * @access private
  97. * @memberOf BluetoothConnectionManager
  98. * @type {BluetoothAdapter}
  99. */
  100. _defaultAdapter: null,
  101. /**
  102. * Init BluetoothConnectionManager module.
  103. *
  104. * @access private
  105. * @memberOf BluetoothConnectionManager
  106. */
  107. _init: function btcm__init() {
  108. // Observe 'defaultAdapter' property for reaching default adapter.
  109. AdapterManager.observe('defaultAdapter',
  110. this._onDefaultAdapterChanged.bind(this));
  111. this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
  112. },
  113. /**
  114. * 'defaultAdapter' change event handler from adapter manager for
  115. * updating adapter immediately.
  116. *
  117. * @access private
  118. * @memberOf BluetoothConnectionManager
  119. * @param {BluetoothAdapter} newAdapter
  120. * @param {BluetoothAdapter} oldAdapter
  121. */
  122. _onDefaultAdapterChanged:
  123. function btcm__onDefaultAdapterChanged(newAdapter, oldAdapter) {
  124. Debug('_onDefaultAdapterChanged(): newAdapter = ' + newAdapter);
  125. Debug('_onDefaultAdapterChanged(): oldAdapter = ' + oldAdapter);
  126. // save default adapter
  127. this._defaultAdapter = newAdapter;
  128. if (oldAdapter) {
  129. // unwatch event since the old adapter is no longer usefull
  130. this._unwatchProfilesStatuschanged(oldAdapter);
  131. this._unwatchDefaultAdapterOnattributechanged(oldAdapter);
  132. }
  133. if (newAdapter) {
  134. // watch event since the new adapter is ready to access
  135. this._watchProfilesStatuschanged(newAdapter);
  136. this._watchDefaultAdapterOnattributechanged(newAdapter);
  137. // restore connection
  138. if (newAdapter.state === 'enabled') {
  139. this._restoreConnection();
  140. }
  141. } else {
  142. // reset properties only
  143. this._resetConnectionInfo();
  144. }
  145. },
  146. /**
  147. * Return the cache of connected devices in ConnectionManager.
  148. *
  149. * @access public
  150. * @memberOf BluetoothConnectionManager
  151. * @return {Promise}
  152. */
  153. getConnectedDevices: function btcm_getConnectedDevices() {
  154. if (!this._getConnectedDevicesPromise) {
  155. this._getConnectedDevicesPromise =
  156. this._initConnectedDevicesInfo().then(() => {
  157. Debug('getConnectedDevices(): resolved with latest cache = ' +
  158. JSON.stringify(this._connectedDevicesInfo));
  159. return this._connectedDevicesInfo;
  160. }, (reason) => {
  161. Debug('getConnectedDevices(): rejected with reason = ' + reason);
  162. this._getConnectedDevicesPromise = null;
  163. });
  164. }
  165. return this._getConnectedDevicesPromise;
  166. },
  167. /**
  168. * Init cache of connected device and save it in cache.
  169. *
  170. * @access private
  171. * @memberOf BluetoothConnectionManager
  172. * @return {Promise}
  173. */
  174. _initConnectedDevicesInfo: function btcm__initConnectedDevicesInfo() {
  175. if (!this._defaultAdapter) {
  176. return Promise.reject('default adapter is not existed!!');
  177. }
  178. // Init connection status and profile from platform.
  179. // Then, save these connected device information in cache.
  180. return this._getConnectedDevicesFromPlatform().then(
  181. (connectedDevicesByProfile) => {
  182. this._constructDeviceItemsMap(connectedDevicesByProfile);
  183. }, (reason) => {
  184. Debug('_initConnectedDevicesInfo(): rejected in ' +
  185. '_getConnectedDevicesFromPlatform');
  186. return Promise.reject(reason);
  187. });
  188. },
  189. /**
  190. * The method will update each device item in maintaining map.
  191. *
  192. * @access private
  193. * @memberOf BluetoothConnectionManager
  194. * @param {Object} connectedDevices
  195. */
  196. _constructDeviceItemsMap:
  197. function btcm__constructDeviceItemsMap(connectedDevices) {
  198. Debug('_constructDeviceItemsMap(): connectedDevices = ' +
  199. JSON.stringify(connectedDevices));
  200. if (!connectedDevices) {
  201. // Return empty object while there is no any connected devices.
  202. Debug('_constructDeviceItemsMap(): early return with empty object');
  203. return;
  204. }
  205. Object.keys(this.Profiles).map((profileID) => {
  206. connectedDevices[profileID].forEach((connectedDevice) => {
  207. var connectionDeviceInfo = {
  208. address: connectedDevice.address,
  209. connected: true,
  210. profileID: profileID,
  211. device: connectedDevice
  212. };
  213. Debug('_constructDeviceItemsMap(): connectionDeviceInfo = ' +
  214. JSON.stringify(connectionDeviceInfo));
  215. this._initConnectedDevicesCache(connectionDeviceInfo);
  216. });
  217. });
  218. },
  219. /**
  220. * Init the cache which is maintained for connection devices.
  221. * And the input is gotten from platform API adapter.getConnectedDevices().
  222. *
  223. * @access private
  224. * @memberOf BluetoothConnectionManager
  225. * @param {Object} options
  226. * @param {String} options.address - address of the device
  227. * @param {Boolean} options.connected - is connected or not
  228. * @param {Object} options.profileID - profile ID of the connection type
  229. * @param {Object} options.device - connect device, Bluetooth Object
  230. */
  231. _initConnectedDevicesCache:
  232. function btcm__initConnectedDevicesCache(options) {
  233. Debug('_initConnectedDevicesCache(): options = ' +
  234. JSON.stringify(options));
  235. // hash by device address
  236. var info = this._connectedDevicesInfo[options.address];
  237. if (info) {
  238. // Already have profiles, update it for other profile.
  239. info.connectedProfiles[options.profileID] = options.connected;
  240. } else {
  241. // Not have profiles yet, create for it.
  242. // If options.device is existed, save the connected device.
  243. // Otherwise, given null in this property.
  244. var connectedDevice = (options.device) ? options.device : null;
  245. info = {
  246. 'device': connectedDevice,
  247. 'connectedProfiles': {}
  248. };
  249. info.connectedProfiles[options.profileID] = options.connected;
  250. }
  251. // Save the device/profile in map.
  252. this._connectedDevicesInfo[options.address] = info;
  253. // If there is no profile connected,
  254. // remove the device item from cache since it is already disconnected.
  255. var dataToCheckConnectedProfile = {
  256. address: options.address,
  257. connectedDevices: this._connectedDevicesInfo
  258. };
  259. if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
  260. delete this._connectedDevicesInfo[options.address];
  261. }
  262. // Return the latest cache which is just updated here.
  263. Debug('_initConnectedDevicesCache(): this._connectedDevicesInfo = ' +
  264. JSON.stringify(this._connectedDevicesInfo));
  265. return this._connectedDevicesInfo;
  266. },
  267. /**
  268. * Update the cache which is maintained for connection devices.
  269. *
  270. * @access private
  271. * @memberOf BluetoothConnectionManager
  272. * @param {Object} options
  273. * @param {String} options.address - address of the device
  274. * @param {Boolean} options.connected - is connected or not
  275. * @param {Object} options.profileID - profile ID of the connection type
  276. * @param {Object} options.device - connect device, Bluetooth Object
  277. */
  278. _updateConnectedDevices:
  279. function btcm__updateConnectedDevices(options) {
  280. return this.getConnectedDevices().then((connectedDevicesInfo) => {
  281. Debug('_updateConnectedDevices(): connectedDevicesInfo = ' +
  282. JSON.stringify(connectedDevicesInfo));
  283. // hash by device address
  284. var info =
  285. (connectedDevicesInfo) ? connectedDevicesInfo[options.address] : null;
  286. if (info) {
  287. // Already have profiles, update it for other profile.
  288. info.connectedProfiles[options.profileID] = options.connected;
  289. } else {
  290. // Not have profiles yet, create for it.
  291. // If options.device is existed, save the connected device.
  292. // Otherwise, given null in this property.
  293. var connectedDevice = (options.device) ? options.device : null;
  294. info = {
  295. 'device': connectedDevice,
  296. 'connectedProfiles': {}
  297. };
  298. info.connectedProfiles[options.profileID] = options.connected;
  299. }
  300. // Save the device/profile in map.
  301. this._connectedDevicesInfo[options.address] = info;
  302. // If there is no profile connected,
  303. // remove the device item from cache since it is already disconnected.
  304. var dataToCheckConnectedProfile = {
  305. address: options.address,
  306. connectedDevices: this._connectedDevicesInfo
  307. };
  308. if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
  309. delete this._connectedDevicesInfo[options.address];
  310. }
  311. // Return the latest cache which is just updated here.
  312. Debug('_updateConnectedDevices(): this._connectedDevicesInfo = ' +
  313. JSON.stringify(this._connectedDevicesInfo));
  314. return Promise.resolve(this._connectedDevicesInfo);
  315. }, () => {
  316. Debug('_updateConnectedDevices(): rejected with some exception');
  317. return Promise.reject('rejected with some exception');
  318. });
  319. },
  320. /**
  321. * Only reset properties since there is no available default adapter.
  322. *
  323. * @access private
  324. * @memberOf BluetoothConnectionManager
  325. */
  326. _resetConnectionInfo: function btcm__resetConnectionInfo() {
  327. // Reset connection status.
  328. this.connectingAddress = null;
  329. // Clean up the instance to get connected devices
  330. // while new adapter is ready.
  331. this._getConnectedDevicesPromise = null;
  332. },
  333. /**
  334. * Watch 'onattributechanged' event from default adapter for watching
  335. * adapter enabled/disabled status.
  336. *
  337. * Description of 'onattributechanged' event:
  338. * A handler to trigger when one of the local bluetooth adapter's properties
  339. * has changed. Note access to the changed property in this event handler
  340. * would get the updated value.
  341. *
  342. * @access private
  343. * @memberOf BluetoothConnectionManager
  344. * @param {BluetoothAdapter} adapter
  345. */
  346. _watchDefaultAdapterOnattributechanged:
  347. function btcm__watchDefaultAdapterOnattributechanged(adapter) {
  348. adapter.addEventListener('attributechanged',
  349. this._onAdapterAttributeChanged.bind(this, adapter));
  350. },
  351. /**
  352. * Unwatch 'onattributechanged' event from default adapter since adapter is
  353. * removed.
  354. *
  355. * @access private
  356. * @memberOf BluetoothConnectionManager
  357. * @param {BluetoothAdapter} adapter
  358. */
  359. _unwatchDefaultAdapterOnattributechanged:
  360. function btcm__unwatchDefaultAdapterOnattributechanged(adapter) {
  361. adapter.removeEventListener('attributechanged',
  362. this._onAdapterAttributeChanged);
  363. },
  364. /**
  365. * 'onattributechanged' event handler from default adapter for reaching
  366. * adapter enabled/disabled status.
  367. *
  368. * @access private
  369. * @memberOf BluetoothConnectionManager
  370. * @param {BluetoothAdapter} adapter
  371. * @param {event} evt
  372. */
  373. _onAdapterAttributeChanged:
  374. function btcm__onAdapterAttributeChanged(adapter, evt) {
  375. for (var i in evt.attrs) {
  376. Debug('_onAdapterAttributeChanged(): ' + evt.attrs[i]);
  377. switch (evt.attrs[i]) {
  378. case 'state':
  379. if (adapter.state === 'enabled') {
  380. // Restore connection while default adapter state is enabled.
  381. this._restoreConnection();
  382. }
  383. break;
  384. default:
  385. break;
  386. }
  387. }
  388. },
  389. /**
  390. * Watch every of profile events('onhfpstatuschanged','ona2dpstatuschanged')
  391. * from default adapter for updating device connected status immediately.
  392. *
  393. * Description of 'onhfpstatuschanged' event:
  394. * Specifies an event listener to receive hfpstatuschanged events.
  395. * Those events occur when an HFP connection status changes.
  396. *
  397. * Description of 'ona2dpstatuschanged' event:
  398. * Specifies an event listener to receive a2dpstatuschanged events.
  399. * Those events occur when an A2DP connection status changes.
  400. *
  401. * @access private
  402. * @memberOf BluetoothConnectionManager
  403. * @param {BluetoothAdapter} adapter
  404. */
  405. _watchProfilesStatuschanged:
  406. function btcm__watchProfilesStatuschanged(adapter) {
  407. var eventName;
  408. for (var profileID in this.Profiles) {
  409. eventName = 'on' + profileID + 'statuschanged';
  410. adapter[eventName] =
  411. this._onProfileStatuschangeHandler.bind(this, profileID);
  412. }
  413. },
  414. /**
  415. * Unwatch every of profile events('onhfpstatuschanged',
  416. * 'ona2dpstatuschanged') from default adapter since adapter is removed.
  417. *
  418. * @access private
  419. * @memberOf BluetoothConnectionManager
  420. * @param {BluetoothAdapter} adapter
  421. */
  422. _unwatchProfilesStatuschanged:
  423. function btc__unwatchProfilesStatuschanged(adapter) {
  424. var eventName;
  425. for (var profileID in this.Profiles) {
  426. eventName = 'on' + profileID + 'statuschanged';
  427. adapter[eventName] = null;
  428. }
  429. },
  430. /**
  431. * 'onhfpstatuschanged', 'ona2dpstatuschanged' events handler from
  432. * default adapter for updating device connected status.
  433. *
  434. * @access private
  435. * @memberOf BluetoothConnectionManager
  436. * @param {String} profileID
  437. * @param {event} evt
  438. */
  439. _onProfileStatuschangeHandler:
  440. function btcm__onProfileStatuschangeHandler(profileID, evt) {
  441. Debug('_onProfileStatuschangeHandler(): profileID = ' + profileID +
  442. ', evt.address = ' + evt.address + ', evt.status = ' + evt.status);
  443. var options = {
  444. address: evt.address,
  445. connected: evt.status,
  446. profileID: profileID
  447. };
  448. // Update connection status.
  449. this._updateConnectionStatus(options);
  450. },
  451. /**
  452. * Query async storage to restore connection.
  453. *
  454. * @access private
  455. * @memberOf BluetoothConnectionManager
  456. */
  457. _restoreConnection: function btcm__restoreConnection() {
  458. if (!this._defaultAdapter) {
  459. return;
  460. }
  461. // Reconnect the one kept in the async storage.
  462. AsyncStorage.getItem('device.connected', (address) => {
  463. if (!address) {
  464. return;
  465. }
  466. // Make sure the restore device is already connected or not.
  467. this.getConnectedDevices().then((connectedDevices) => {
  468. if (connectedDevices && connectedDevices[address]) {
  469. // Do an early return since the restore device is connected already.
  470. Debug('_restoreConnection(): early return cause connected already');
  471. return;
  472. }
  473. // Get the device which is wanted to connect from paired devices.
  474. var restoreDevice = this._getPairedDeviceByAddress(address);
  475. if (restoreDevice) {
  476. this.connectingAddress = restoreDevice.address;
  477. // Fire 'connecting' event.
  478. var event = {
  479. type: 'connecting',
  480. detail: {
  481. address: restoreDevice.address
  482. }
  483. };
  484. this._emitEvent(event);
  485. this._connect(restoreDevice).then(() => {
  486. Debug('_restoreConnection(): restore connection successfully');
  487. }, (reason) => {
  488. Debug('_restoreConnection(): restore connection failed, ' +
  489. 'reason = ' + reason);
  490. // No available profiles are connected. Reset connecting address.
  491. this.connectingAddress = null;
  492. // Then, fire 'disconnected' event.
  493. event = {
  494. type: 'disconnected',
  495. detail: {
  496. address: restoreDevice.address
  497. }
  498. };
  499. this._emitEvent(event);
  500. });
  501. }
  502. });
  503. });
  504. },
  505. /**
  506. * Record connected device so if Bluetooth is turned off and then on
  507. * we can restore the connection.
  508. *
  509. * @access private
  510. * @memberOf BluetoothConnectionManager
  511. * @param {String} action - to set or remove item for recording connection
  512. * @param {String} address - the address of connected device
  513. */
  514. _recordConnection: function btcm__recordConnection(action, address) {
  515. if (action === 'set') {
  516. // record connected device so if Bluetooth is turned off and then on
  517. // we can restore the connection
  518. AsyncStorage.setItem('device.connected', address);
  519. Debug('_recordConnection(): set item');
  520. } else if ((action === 'remove') &&
  521. (this._defaultAdapter.state === 'enabled')) {
  522. // Clean up the connected device from async storage
  523. // which is recorded before.
  524. // Only remove the record while Bluetooth state is enabled.
  525. // Because the request also comes while Bluetooth is turned off.
  526. AsyncStorage.removeItem('device.connected');
  527. Debug('_recordConnection(): remove item');
  528. }
  529. },
  530. /**
  531. * Update connection status.
  532. *
  533. * @access private
  534. * @memberOf BluetoothConnectionManager
  535. * @param {Object} options
  536. * @param {String} options.address - address of the device
  537. * @param {Boolean} options.connected - is connected or not
  538. * @param {Object} options.profileID - profile ID of the connection type
  539. */
  540. _updateConnectionStatus: function btcm___updateConnectionStatus(options) {
  541. Debug('_updateConnectionStatus(): address = ' + options.address +
  542. ', connected = ' + options.connected +
  543. ', profileID = ' + options.profileID);
  544. this.connectingAddress = null;
  545. // Update the profile in the cache of connected device info.
  546. this._updateConnectedDevices(options).then((connectedDevicesInfo) => {
  547. Debug('_updateConnectionStatus(): _updateConnectedDevices() ' +
  548. 'resolved with connectedDevicesInfo = ' +
  549. JSON.stringify(connectedDevicesInfo));
  550. // Prepare latest data to check connected profile.
  551. var dataToCheckConnectedProfile = {
  552. address: options.address,
  553. connectedDevices: connectedDevicesInfo
  554. };
  555. // Fire 'connected'/'disconnected' event according to
  556. // the connection profile. Then, record connection.
  557. var event;
  558. if (options.connected) {
  559. // Fire 'connected' event.
  560. event = {
  561. type: 'connected',
  562. detail: {
  563. address: options.address
  564. }
  565. };
  566. this._emitEvent(event);
  567. // Record connection.
  568. this._recordConnection('set', options.address);
  569. } else {
  570. // If there is no profile connected,
  571. // we have to remove the record connection.
  572. // And fire 'disconnected' event for outer modules.
  573. if (!this._hasConnectedProfileByAddress(
  574. dataToCheckConnectedProfile)) {
  575. // Record connection. Only remove the record while Bluetooth state
  576. // is enabled. Because the event also comes while Bluetooth is
  577. // turned off.
  578. if (this._defaultAdapter.state === 'enabled') {
  579. this._recordConnection('remove');
  580. }
  581. // Fire 'disconnected' event.
  582. event = {
  583. type: 'disconnected',
  584. detail: {
  585. address: options.address
  586. }
  587. };
  588. this._emitEvent(event);
  589. }
  590. }
  591. // Fire 'profileChanged' event.
  592. var newProfiles;
  593. if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
  594. newProfiles = null;
  595. } else {
  596. newProfiles = connectedDevicesInfo[options.address].connectedProfiles;
  597. }
  598. event = {
  599. type: 'profileChanged',
  600. detail: {
  601. address: options.address,
  602. profiles: newProfiles
  603. }
  604. };
  605. this._emitEvent(event);
  606. }, (reason) => {
  607. Debug('_updateConnectionStatus(): miss to update in rejected case, ' +
  608. 'reason = ' + reason);
  609. });
  610. },
  611. /**
  612. * It provides a convenient function for panel to connect with a device.
  613. * And the panel no need to care about connected device currently.
  614. * The method will disconnect current connected device first.
  615. * Then, it will start connecting a paired device with the device's adapter.
  616. *
  617. * @access public
  618. * @memberOf BluetoothConnectionManager
  619. * @param {BluetoothDevice} device
  620. * @return {Promise}
  621. */
  622. connect: function btcm_connect(device) {
  623. if (!this._defaultAdapter) {
  624. return Promise.reject('default adapter is not existed!!');
  625. }
  626. // Disconnect current connected device first.
  627. Debug('connect(): Want to connect device address = ' + device.address +
  628. ', name = ' + device.name);
  629. var connectedDevices = [];
  630. return this.getConnectedDevices().then((connectedDevicesInfo) => {
  631. for (var address in connectedDevicesInfo) {
  632. if (connectedDevicesInfo[address].device !== null) {
  633. connectedDevices.push(connectedDevicesInfo[address].device);
  634. Debug('connect(): push device cache in queue = ' +
  635. JSON.stringify(connectedDevices));
  636. } else {
  637. var regetPairedDevice = this._getPairedDeviceByAddress(address);
  638. connectedDevices.push(regetPairedDevice);
  639. Debug('connect(): push _getPairedDeviceByAddress in queue = ' +
  640. JSON.stringify(regetPairedDevice));
  641. }
  642. }
  643. Debug('connect(): Will disconnect these connected devices = ' +
  644. JSON.stringify(connectedDevices));
  645. // Disconnect these connected device before
  646. // service to connect with new device.
  647. return Promise.all(connectedDevices.map((connectedDevice) => {
  648. return this.disconnect(connectedDevice);
  649. })).then(() => {
  650. // All connected devices is disconnected.
  651. // We can start to connect the new request.
  652. Debug('connect(): Start to connect with wanted device address = ' +
  653. device.address);
  654. return this._connect(device).then(() => {
  655. Debug('connect(): Resolved');
  656. }, (reason) => {
  657. Debug('connect(): reason = ' + reason);
  658. return Promise.reject(reason);
  659. });
  660. });
  661. });
  662. },
  663. /**
  664. * The method will connect the input device with the device's adapter.
  665. *
  666. * @access private
  667. * @memberOf BluetoothConnectionManager
  668. * @param {BluetoothDevice} device
  669. * @returns {Promise}
  670. */
  671. _connect: function btcm__connect(device) {
  672. if (!this._defaultAdapter) {
  673. return Promise.reject('default adapter is not existed!!');
  674. }
  675. // Save the connecting address
  676. this.connectingAddress = device.address;
  677. // Fire 'connecting' event.
  678. var event = {
  679. type: 'connecting',
  680. detail: {
  681. address: device.address
  682. }
  683. };
  684. this._emitEvent(event);
  685. Debug('_connect(): before start to connect, stop discovery first');
  686. // Note on Bluedroid stack, discovery has to be stopped before connect
  687. // (i.e., call stopDiscovery() before connect())
  688. // otherwise stack callbacks with connect failure.
  689. return this._defaultAdapter.stopDiscovery().then(() => {
  690. Debug('_connect(): start connecting device, ' +
  691. 'address = ' + device.address);
  692. return this._defaultAdapter.connect(device).then(() => {
  693. Debug('_connect(): resolve, already connected with address = ' +
  694. device.address);
  695. }, () => {
  696. // No available profiles are connected. Reset connecting address.
  697. this.connectingAddress = null;
  698. // Fire 'disconnected' event.
  699. event = {
  700. type: 'disconnected',
  701. detail: {
  702. address: device.address
  703. }
  704. };
  705. this._emitEvent(event);
  706. Debug('_connect(): reject with connection failed');
  707. return Promise.reject('connection failed');
  708. });
  709. }, () => {
  710. // Cannot connect with the device since stopDiscovery failed.
  711. this.connectingAddress = null;
  712. // Fire 'disconnected' event.
  713. event = {
  714. type: 'disconnected',
  715. detail: {
  716. address: device.address
  717. }
  718. };
  719. this._emitEvent(event);
  720. Debug('_connect(): reject with stop discovery failed');
  721. return Promise.reject('stop discovery failed');
  722. });
  723. },
  724. /**
  725. * The method will disconnect the input device with the device's adapter.
  726. *
  727. * @access public
  728. * @memberOf BluetoothConnectionManager
  729. * @param {BluetoothDevice} device
  730. * @returns {Promise}
  731. */
  732. disconnect: function btcm_disconnect(device) {
  733. if (!this._defaultAdapter) {
  734. return Promise.reject('default adapter is not existed!!');
  735. }
  736. return this._defaultAdapter.disconnect(device).then(() => {
  737. Debug('disconnect(): onsuccess(): resolve');
  738. }, () => {
  739. Debug('disconnect(): onerror(): reject with disconnect failed');
  740. return Promise.reject('disconnect failed');
  741. });
  742. },
  743. /**
  744. * The method will get all connected devices profiles
  745. * which we are interested in. Profile: HFP, A2DP.
  746. *
  747. * @access private
  748. * @memberOf BluetoothConnectionManager
  749. * @returns {Promise} resolve: connectedDevices
  750. * @returns {Promise} reject: reason
  751. */
  752. _getConnectedDevicesFromPlatform:
  753. function btcm__getConnectedDevicesFromPlatform() {
  754. // Get connected device via profiles HFP, A2DP
  755. return Promise.all(Object.keys(this.Profiles).map((profile) => {
  756. return this._getConnectedDevicesByProfile(this.Profiles[profile]);
  757. })).then((connectedDevices) => {
  758. // Update each connected devices in map.
  759. var collectedConnectedDevicesByProfile = {};
  760. Object.keys(this.Profiles).forEach((profile, index) => {
  761. collectedConnectedDevicesByProfile[profile] = connectedDevices[index];
  762. });
  763. Debug('_getConnectedDevicesFromPlatform(): ' +
  764. 'collectedConnectedDevicesByProfile = ' +
  765. JSON.stringify(collectedConnectedDevicesByProfile));
  766. return Promise.resolve(collectedConnectedDevicesByProfile);
  767. });
  768. },
  769. /**
  770. * The method will get connected device by inputed profile.
  771. *
  772. * @access private
  773. * @memberOf BluetoothConnectionManager
  774. * @param {String} profileID
  775. * @returns {Promise} resolve: the connected devices in array
  776. * @returns {Promise} reject: reason
  777. */
  778. _getConnectedDevicesByProfile:
  779. function btcm__getConnectedDevicesByProfile(profileID) {
  780. if (!this._defaultAdapter) {
  781. Debug('_getConnectedDevicesByProfile(): reject with no adapter');
  782. return Promise.reject('default adapter is not existed!!');
  783. }
  784. if (this._defaultAdapter.state === 'disabled') {
  785. Debug('_getConnectedDevicesByProfile(): resolve with empty array ' +
  786. 'since it is impossible to connect with any device');
  787. return Promise.reject('getConnectedDevices in disabled state');
  788. }
  789. return this._defaultAdapter.getConnectedDevices(profileID).then(
  790. (connectedDevice) => {
  791. Debug('_getConnectedDevicesByProfile(): resolved with ' +
  792. 'connectedDevice = ' + JSON.stringify(connectedDevice));
  793. return Promise.resolve(connectedDevice || []);
  794. }, (reason) => {
  795. Debug('_getConnectedDevicesByProfile(): rejected with ' +
  796. 'reason = ' + reason);
  797. return Promise.reject(reason);
  798. });
  799. },
  800. /**
  801. * Get device from paired devices by address.
  802. *
  803. * @access private
  804. * @memberOf BluetoothConnectionManager
  805. * @param {String} address
  806. */
  807. _getPairedDeviceByAddress:
  808. function btcm__getPairedDeviceByAddress(address) {
  809. if (!this._defaultAdapter) {
  810. return null;
  811. }
  812. var pairedDevices = this._defaultAdapter.getPairedDevices();
  813. if (pairedDevices.length === 0) {
  814. return null;
  815. } else {
  816. for (var i in pairedDevices) {
  817. if (pairedDevices[i].address === address) {
  818. return pairedDevices[i];
  819. }
  820. }
  821. return null;
  822. }
  823. },
  824. /**
  825. * Find out there is any profile still connected.
  826. *
  827. * @access private
  828. * @memberOf BluetoothConnectionManager
  829. * @param {Object} options
  830. * @param {String} options.connectedDevices - connected devices info
  831. * @param {String} options.address - the address of device
  832. */
  833. _hasConnectedProfileByAddress:
  834. function btcm__hasConnectedProfileByAddress(options) {
  835. if (!options.connectedDevices[options.address]) {
  836. return false;
  837. }
  838. var hasConnectedProfile = false;
  839. for (var profileID in this.Profiles) {
  840. var connectedProfiles =
  841. options.connectedDevices[options.address].connectedProfiles;
  842. if (connectedProfiles && (connectedProfiles[profileID] === true)) {
  843. hasConnectedProfile = true;
  844. }
  845. }
  846. return hasConnectedProfile;
  847. },
  848. /**
  849. * A function to emit event to each registered listener by the input type.
  850. *
  851. * @memberOf BluetoothConnectionManager
  852. * @param {Object} options
  853. * @param {String} options.type - type of event name
  854. * @param {Object} options.detail - the object pass to the listener
  855. */
  856. _emitEvent: function btcm__emitEvent(options) {
  857. this._listeners[options.type].forEach(function(listener) {
  858. listener(options);
  859. });
  860. },
  861. /**
  862. * The method will provide event listener for outer modules to regist.
  863. *
  864. * @access public
  865. * @memberOf BluetoothConnectionManager
  866. * @param {String} eventName
  867. * @param {Function} callback
  868. */
  869. addEventListener: function btcm_addEventListener(eventName, callback) {
  870. if (callback && (this._listeners.hasOwnProperty(eventName))) {
  871. this._listeners[eventName].push(callback);
  872. }
  873. },
  874. /**
  875. * The method will provide event listener for outer modules to un-regist.
  876. *
  877. * @access public
  878. * @memberOf BluetoothConnectionManager
  879. * @param {String} eventName
  880. * @param {Function} callback
  881. */
  882. removeEventListener:
  883. function btcm_removeEventListener(eventName, callback) {
  884. if (callback && (this._listeners.hasOwnProperty(eventName))) {
  885. var index = this._listeners[eventName].indexOf(callback);
  886. if (index >= 0) {
  887. this._listeners[eventName].splice(index, 1);
  888. }
  889. }
  890. }
  891. };
  892. BluetoothConnectionManager._init();
  893. return BluetoothConnectionManager;
  894. });