Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
802 lines
27 KiB
JavaScript
802 lines
27 KiB
JavaScript
/* Copyright © 2013, Deutsche Telekom, Inc. */
|
|
|
|
/* globals NDEFUtils, NfcConnectSystemDialog, LazyLoader,
|
|
NDEF, NfcUtils, NotificationHelper, BaseModule, Service */
|
|
'use strict';
|
|
|
|
/**
|
|
* NfcHandoverManager handles handovers from other Bluetooth devices according
|
|
* to the specification of the NFC Forum
|
|
* (Document: NFCForum-TS-ConnectionHandover_1_2.doc).
|
|
* @class NfcHandoverManager
|
|
*/
|
|
(function() {
|
|
var NfcHandoverManager = function() {};
|
|
|
|
NfcHandoverManager.STATES = [
|
|
'isHandoverInProgress'
|
|
];
|
|
|
|
NfcHandoverManager.SETTINGS = [
|
|
'nfc.debugging.enabled'
|
|
];
|
|
|
|
NfcHandoverManager.EVENTS = [
|
|
'bluetooth-enabled',
|
|
'bluetooth-disabled',
|
|
'nfc-transfer-started',
|
|
'nfc-transfer-completed'
|
|
];
|
|
|
|
BaseModule.create(NfcHandoverManager, {
|
|
name: 'NfcHandoverManager',
|
|
/**
|
|
* Flag which turns on debuging messages
|
|
* @type {boolean}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
DEBUG: false,
|
|
|
|
/**
|
|
* Default bluetooth adapter
|
|
* @type {Object}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_adapter: null,
|
|
|
|
/**
|
|
* Keeps a list of actions that need to be performed after
|
|
* Bluetooth is turned on.
|
|
* @type {Array}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
actionQueue: [],
|
|
|
|
/**
|
|
* Keeps a list of send file requests made via peer.sendFile(blob).
|
|
* It will be inspected in the handling of Handover Select messages
|
|
* to distinguish between static and negotiated handovers.
|
|
* @type {Object}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
sendFileQueue: [],
|
|
|
|
/**
|
|
* The length of the timeout in milliseconds to wait for an outstanding
|
|
* handover response.
|
|
* @type {Number}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
responseTimeoutMillis: 9000,
|
|
|
|
/**
|
|
* Set whenever a timeout is defined while waiting for an outstanding
|
|
* handover response.
|
|
* @type {Object}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
responseTimeoutFunction: null,
|
|
|
|
/**
|
|
* Set to true during a file transfer that was initiated by another device.
|
|
* @type {boolean}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
incomingFileTransferInProgress: false,
|
|
|
|
/**
|
|
* Remembers whether Bluetooth was already saved during an earlier
|
|
* file transfer.
|
|
* @type {boolean}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
bluetoothStatusSaved: false,
|
|
|
|
/**
|
|
* Remembers whether Bluetooth was enabled or automatically.
|
|
* @type {boolean}
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
bluetoothAutoEnabled: false,
|
|
|
|
/**
|
|
* Used to prevent triggering Settings multiple times.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
settingsNotified: false,
|
|
|
|
/**
|
|
* Logs message in logcat
|
|
* @param {String} msg debug messages
|
|
* @param {Object} opObject object to printed after doing JSON.stringfy
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_logVisibly: function _logVisibly(msg, optObject) {
|
|
var output = '[NfcHandoverManager]: ' + msg;
|
|
if (optObject) {
|
|
output += JSON.stringify(optObject);
|
|
}
|
|
console.log(output);
|
|
},
|
|
|
|
'_observe_nfc.debugging.enabled': function(enabled) {
|
|
this.DEBUG = enabled;
|
|
},
|
|
|
|
/**
|
|
* Initializes event and message handlers, initializes properties.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_start: function _start() {
|
|
this.incomingFileTransferInProgress = false;
|
|
this.bluetoothStatusSaved = false;
|
|
this.bluetoothAutoEnabled = false;
|
|
|
|
window.navigator.mozSetMessageHandler('nfc-manager-send-file',
|
|
(msg) => {
|
|
this.debug('In New event nfc-manager-send-file' +
|
|
JSON.stringify(msg));
|
|
this.handleFileTransfer(msg);
|
|
});
|
|
},
|
|
|
|
'_handle_nfc-transfer-completed': function(evt) {
|
|
this.transferComplete(evt);
|
|
},
|
|
|
|
'_handle_nfc-transfer-started': function(evt) {
|
|
this._transferStarted(evt);
|
|
},
|
|
|
|
'_handle_bluetooth-disabled': function() {
|
|
this.debug('bluetooth-disabled');
|
|
this._adapter = null;
|
|
this._clearBluetoothStatus();
|
|
},
|
|
|
|
'_handle_bluetooth-enabled': function() {
|
|
this.debug('bluetooth-enabled');
|
|
// wait for adapter is ready
|
|
Service.request('Bluetooth:adapter').then((adapter) => {
|
|
this._adapter = adapter;
|
|
this.settingsNotified = false;
|
|
this.debug('MAC address: ' + adapter.address);
|
|
this.debug('MAC name: ' + adapter.name);
|
|
this.debug('process queued actions');
|
|
/*
|
|
* Call all actions that have queued up while Bluetooth
|
|
* was turned on.
|
|
*/
|
|
for (var i = 0, len = this.actionQueue.length; i < len; i++) {
|
|
var action = this.actionQueue[i];
|
|
action.callback.apply(this, action.args);
|
|
}
|
|
this.actionQueue = [];
|
|
}).catch(() => {
|
|
this._logVisibly('event listener: Failed to get bluetooth adapter');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Save the on/off status of Bluetooth.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_saveBluetoothStatus: function _saveBluetoothStatus() {
|
|
if (!this.bluetoothStatusSaved) {
|
|
this.bluetoothStatusSaved = true;
|
|
this.bluetoothAutoEnabled = !Service.query('Bluetooth.isEnabled');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Restore the Bluetooth status.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_restoreBluetoothStatus: function _restoreBluetoothStatus() {
|
|
if (!this.isHandoverInProgress() &&
|
|
Service.query('BluetoothTransfer.isSendFileQueueEmpty')) {
|
|
if (this.bluetoothAutoEnabled) {
|
|
this.debug('Disabling Bluetooth');
|
|
this.publish('request-disable-bluetooth', this,
|
|
/* no prefix */ true);
|
|
this.bluetoothAutoEnabled = false;
|
|
}
|
|
this.bluetoothStatusSaved = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Forget a previously saved Bluetooth status.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_clearBluetoothStatus: function _clearBluetoothStatus() {
|
|
this.bluetoothStatusSaved = false;
|
|
},
|
|
|
|
/*
|
|
* Performs an action once Bluetooth is enabled. If Bluetooth is disabled,
|
|
* enable Bluetooth and queue the action. If Bluetooth is already enabled,
|
|
* request the bluetooth adapter and perform the action.
|
|
* @param {Object} action action to be performed
|
|
* @param {function} action.callback function to be executed
|
|
* @param {Array} action.args arguments for the function
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_doAction: function _doAction(action) {
|
|
var enabled = Service.query('Bluetooth.isEnabled');
|
|
if (enabled === undefined) {
|
|
this._logVisibly('Bluetooth is not available yet');
|
|
return;
|
|
}
|
|
|
|
if (!enabled) {
|
|
this.debug('Bluetooth: not yet enabled');
|
|
this.actionQueue.push(action);
|
|
if (this.settingsNotified === false) {
|
|
this.publish('request-enable-bluetooth', this,
|
|
/* no prefix */ true);
|
|
this.settingsNotified = true;
|
|
}
|
|
} else {
|
|
Service.request('Bluetooth:adapter').then((adapter) => {
|
|
this.debug('Got the adapter, start the action');
|
|
this._adapter = adapter;
|
|
action.callback.apply(this, action.args);
|
|
}).catch((err) => {
|
|
this._logVisibly('Failed to get bluetooth adapter');
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets the data about other device taking part in handover proces
|
|
* from NDEF message
|
|
* @param {Array} ndef NDEF message
|
|
* @returns {Object} ssp - object containing info about other devices
|
|
* @returns {string} ssp.mac - mac addres of other devices
|
|
* @returns {string} ssp.localName - local name if present in NDEF message,
|
|
* null otherwise
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_getBluetoothSSP: function _getBluetoothSSP(ndef) {
|
|
var handover = NDEFUtils.parseHandoverNDEF(ndef);
|
|
if (handover == null) {
|
|
// Bad handover message. Just ignore.
|
|
this.debug('Bad handover messsage');
|
|
return null;
|
|
}
|
|
var btsspRecord = NDEFUtils.searchForBluetoothAC(handover);
|
|
if (btsspRecord == null) {
|
|
// There is no Bluetooth Alternative Carrier record in the
|
|
// Handover Select message. Since we cannot handle WiFi Direct,
|
|
// just ignore.
|
|
this.debug('No BT AC');
|
|
return null;
|
|
}
|
|
return NDEFUtils.parseBluetoothSSP(btsspRecord);
|
|
},
|
|
|
|
/**
|
|
* Look for a paired device and invoke the appropriate callback function.
|
|
* @param {string} mac MAC address of the device
|
|
* @param {function} foundCb Found callback
|
|
* @param {function} notFoundCb Not found callback
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_findPairedDevice: function _findPairedDevice(mac, foundCb, notFoundCb) {
|
|
this.debug('_findPairedDevice');
|
|
Service.request('Bluetooth:getPairedDevices').then((devices) => {
|
|
this.debug('# devices: ' + devices.length);
|
|
for (var i = 0, len = devices.length; i < len; i++) {
|
|
var device = devices[i];
|
|
this.debug('Address: ' + device.address);
|
|
if (device.address.toLowerCase() === mac.toLowerCase()) {
|
|
this.debug('Found device ' + mac);
|
|
foundCb(device);
|
|
return;
|
|
}
|
|
}
|
|
if (notFoundCb) {
|
|
notFoundCb();
|
|
}
|
|
}).catch((err) => {
|
|
this._logVisibly('Cannot get paired devices from adapter: ' + err);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Connects via bluetooth to the paired device.
|
|
* @param {string} device Device to be paired
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_doConnect: function _doConnect(device) {
|
|
this.debug('doConnect with: ' + device.address);
|
|
var req = this._adapter.connect(device);
|
|
req.onsuccess = () => { this.debug('Connect succeeded'); };
|
|
req.onerror = () => { this.debug('Connect failed'); };
|
|
},
|
|
|
|
/**
|
|
* Performs bluetooth pairing with other device
|
|
* @param {string} mac MAC address of the peer device
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_doPairing: function _doPairing(mac) {
|
|
this.debug('doPairing: ' + mac);
|
|
var alreadyPaired = (device) => {
|
|
this._adapter.connect(device);
|
|
};
|
|
|
|
var notYetPaired = () => {
|
|
this.debug('Device not yet paired');
|
|
Service.request('Bluetooth:pair', mac).then(() => {
|
|
this.debug('Pairing succeeded');
|
|
this._clearBluetoothStatus();
|
|
/*
|
|
* Bug 979427:
|
|
* After pairing we connect to the remote device. The only thing we
|
|
* know here is the MAC address, but the defaultAdapter.connect()
|
|
* requires a BluetoothDevice argument. So we use _findPairedDevice()
|
|
* to map the MAC to a BluetoothDevice instance.
|
|
*/
|
|
this._findPairedDevice(mac, (device) => {
|
|
this._doConnect(device);
|
|
});
|
|
}).catch((err) => {
|
|
this.debug(err);
|
|
this._logVisibly('Pairing failed');
|
|
this._restoreBluetoothStatus();
|
|
});
|
|
};
|
|
|
|
this._findPairedDevice(mac, alreadyPaired, notYetPaired);
|
|
},
|
|
|
|
/**
|
|
* Show an error notification when file transfer failed.
|
|
* @param {String} msg Optional message.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_showFailedNotification: function _showFailedNotification(title, msg) {
|
|
var body = (msg !== undefined) ? msg : '';
|
|
var icon = 'style/bluetooth_transfer/images/icon_bluetooth.png';
|
|
NotificationHelper.send(title, {
|
|
body: body,
|
|
icon: icon
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Show 'send failed, try again' notification.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_showTryAgainNotification: function _showTryAgainNotification() {
|
|
var _ = navigator.mozL10n.get;
|
|
this._showFailedNotification('transferFinished-sentFailed-title',
|
|
_('transferFinished-try-again-description'));
|
|
},
|
|
|
|
/**
|
|
* This function will be called after a timeout when we did not receive the
|
|
* Hs record within three seconds. At this point we cancel the file
|
|
* transfer.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_cancelSendFileTransfer: function _cancelSendFileTransfer() {
|
|
this.debug('_cancelSendFileTransfer');
|
|
this.responseTimeoutFunction = null;
|
|
var job = this.sendFileQueue.pop();
|
|
job.onerror();
|
|
this._showFailedNotification('transferFinished-sentFailed-title',
|
|
job.blob.name);
|
|
this._restoreBluetoothStatus();
|
|
},
|
|
|
|
/**
|
|
* This function will be called after a timeout when we did not receive
|
|
* the Hs record within three seconds. At this point we cancel the file
|
|
* transfer.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_cancelIncomingFileTransfer: function _cancelIncomingFileTransfer() {
|
|
this.debug('_cancelIncomingFileTransfer');
|
|
this.responseTimeoutFunction = null;
|
|
this.incomingFileTransferInProgress = false;
|
|
this._showFailedNotification('transferFinished-receivedFailed-title');
|
|
this._restoreBluetoothStatus();
|
|
},
|
|
|
|
/**
|
|
* Performs bluetooth file transfer if this.sendFileRequest exists
|
|
* to other device
|
|
* @param {string} mac MAC address of the other device
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_doFileTransfer: function _doFileTransfer(mac) {
|
|
this.debug('doFileTransfer');
|
|
if (this.sendFileQueue.length === 0) {
|
|
// Nothing to do
|
|
this.debug('sendFileQueue empty');
|
|
return;
|
|
}
|
|
this.debug('Send blob to ' + mac);
|
|
var blob = this.sendFileQueue[0].blob;
|
|
this.publish('bluetooth-sendfile-via-handover', {
|
|
mac: mac,
|
|
blob: blob
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Performs tha actual handover request
|
|
* @param {Array} ndef NDEF message conating the handover request record
|
|
* @param {MozNFCPeer} MozNFCPeer object.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_doHandoverRequest: function _doHandoverRequest(ndef, nfcPeer) {
|
|
this.debug('doHandoverRequest');
|
|
if (this._getBluetoothSSP(ndef) == null) {
|
|
/*
|
|
* The handover request didn't contain a valid MAC address. Simply
|
|
* ignore the request.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (nfcPeer.isLost) {
|
|
this._logVisibly('NFC peer went away during doHandoverRequest');
|
|
this._showFailedNotification('transferFinished-receivedFailed-title');
|
|
this._restoreBluetoothStatus();
|
|
return;
|
|
}
|
|
|
|
var cps = Service.query('Bluetooth.isEnabled') ?
|
|
NDEF.CPS_ACTIVE : NDEF.CPS_ACTIVATING;
|
|
var mac = this._adapter.address;
|
|
var hs = NDEFUtils.encodeHandoverSelect(mac, cps);
|
|
nfcPeer.sendNDEF(hs).then(() => {
|
|
this.debug('sendNDEF(hs) succeeded');
|
|
this.incomingFileTransferInProgress = true;
|
|
}).catch((e) => {
|
|
this._logVisibly('sendNDEF(hs) failed : ' + e);
|
|
this._clearTimeout();
|
|
this._restoreBluetoothStatus();
|
|
});
|
|
|
|
this._clearTimeout();
|
|
this.responseTimeoutFunction =
|
|
setTimeout(this._cancelIncomingFileTransfer.bind(this),
|
|
this.responseTimeoutMillis);
|
|
},
|
|
|
|
/**
|
|
* Initiate a file transfer by sending a Handover Request to the
|
|
* remote device.
|
|
* @param {Object} msg message object
|
|
* @param {MozNFCPeer} msg.peer An instance of MozNFCPeer object.
|
|
* @param {Blob} msg.blob File to be sent.
|
|
* @param {String} msg.requestId Request ID.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_doInitiateFileTransfer: function _doInitiateFileTransfer(msg) {
|
|
this.debug('initiateFileTransfer');
|
|
/*
|
|
* Initiate a file transfer by sending a Handover Request to the
|
|
* remote device.
|
|
*/
|
|
var onsuccess = () => {
|
|
this._dispatchSendFileStatus(0, msg.requestId);
|
|
};
|
|
var onerror = () => {
|
|
this._dispatchSendFileStatus(1, msg.requestId);
|
|
};
|
|
|
|
if (msg.peer.isLost) {
|
|
this._logVisibly('NFC peer went away during initiateFileTransfer');
|
|
onerror();
|
|
this._restoreBluetoothStatus();
|
|
this._showFailedNotification('transferFinished-sentFailed-title',
|
|
msg.blob.name);
|
|
return;
|
|
}
|
|
var job = {nfcPeer: msg.peer, blob: msg.blob, requestId: msg.requestId,
|
|
onsuccess: onsuccess, onerror: onerror};
|
|
this.sendFileQueue.push(job);
|
|
var cps = Service.query('Bluetooth.isEnabled') ?
|
|
NDEF.CPS_ACTIVE : NDEF.CPS_ACTIVATING;
|
|
var mac = this._adapter.address;
|
|
var hr = NDEFUtils.encodeHandoverRequest(mac, cps);
|
|
|
|
msg.peer.sendNDEF(hr).then(() => {
|
|
this.debug('sendNDEF(hr) succeeded');
|
|
}).catch((e) => {
|
|
this.debug('sendNDEF(hr) failed : ' + e);
|
|
onerror();
|
|
this.sendFileQueue.pop();
|
|
this._clearTimeout();
|
|
this._restoreBluetoothStatus();
|
|
this._showFailedNotification('transferFinished-sentFailed-title',
|
|
msg.blob.name);
|
|
});
|
|
this._clearTimeout();
|
|
this.responseTimeoutFunction =
|
|
setTimeout(this._cancelSendFileTransfer.bind(this),
|
|
this.responseTimeoutMillis);
|
|
},
|
|
|
|
/**
|
|
* Clears timeout that handles the case an outstanding handover message
|
|
* has not been received within a certain timeframe.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_clearTimeout: function _clearTimeout() {
|
|
this.debug('_clearTimeout');
|
|
if (this.responseTimeoutFunction != null) {
|
|
// Clear the timeout that handles error
|
|
this.debug('clearing timeout');
|
|
clearTimeout(this.responseTimeoutFunction);
|
|
this.responseTimeoutFunction = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Dispatches status of file sending to mozNfc.
|
|
* @param {number} status status of file send operation
|
|
* @param {string} request ID of the operation
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_dispatchSendFileStatus:
|
|
function _dispatchSendFileStatus(status, requestId) {
|
|
this.debug('In dispatchSendFileStatus ' + status);
|
|
navigator.mozNfc.notifySendFileStatus(status, requestId);
|
|
},
|
|
|
|
/**
|
|
* Handles connection request by asking user for confirmation.
|
|
* @param {Object} btssp BT SSP (Secure Simple Pairing) data.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_onRequestConnect: function _onRequestConnect(btssp) {
|
|
var onconfirm = () => {
|
|
this.debug('Connect confirmed');
|
|
this._doAction({callback: this._doPairing, args: [btssp.mac]});
|
|
};
|
|
var onabort = () => {
|
|
this.debug('Connect aborted');
|
|
};
|
|
if (!this.nfcConnectSystemDialog) {
|
|
LazyLoader.load('js/system_nfc_connect_dialog.js').then(() => {
|
|
this.nfcConnectSystemDialog = new NfcConnectSystemDialog();
|
|
this.nfcConnectSystemDialog.show(btssp.localName, onconfirm, onabort);
|
|
}).catch((err) => {
|
|
console.error(err);
|
|
});
|
|
} else {
|
|
this.nfcConnectSystemDialog.show(btssp.localName, onconfirm, onabort);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check if a device is already paired and connected.
|
|
*
|
|
* BTv2 deprecate the device.connected property.
|
|
* connect stat should be retrieved by adapter.getConnectedDevices API
|
|
*
|
|
* @param {Object} btssp BT SSP record
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_checkConnected: function _checkConnected(btssp) {
|
|
if (!Service.query('Bluetooth.isEnabled')) {
|
|
this._onRequestConnect(btssp);
|
|
return;
|
|
}
|
|
if (this._adapter === null) {
|
|
this._logVisibly('No Bluetooth Adapter');
|
|
return;
|
|
}
|
|
var connected = false;
|
|
// Service Class Name: OBEXObjectPush, UUID: 0x1105
|
|
// Specification: Object Push Profile (OPP)
|
|
// NOTE: Used as both Service Class Identifier and Profile.
|
|
// Allowed Usage: Service Class/Profile
|
|
// https://www.bluetooth.org/en-us/specification/assigned-numbers/
|
|
// service-discovery
|
|
var serviceUuid = '0x1105';
|
|
var req = this._adapter.getConnectedDevices(serviceUuid);
|
|
req.onsuccess = () => {
|
|
if (req.result) {
|
|
this.debug('got connectedList');
|
|
var connectedList = req.result;
|
|
var length = connectedList.length;
|
|
for (var i = 0; i < length; i++) {
|
|
if (connectedList[i].address == btssp.mac) {
|
|
connected = true;
|
|
}
|
|
}
|
|
if (!connected) {
|
|
this._onRequestConnect(btssp);
|
|
}
|
|
} else {
|
|
this._logVisibly('Can not get connected device result.');
|
|
return;
|
|
}
|
|
};
|
|
req.onerror = () => {
|
|
this.debug('Can not check is device connected from adapter.');
|
|
this._onRequestConnect(btssp);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Handles simplified pairing record.
|
|
* @param {Array} ndef NDEF message containing simplified pairing record
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_handleSimplifiedPairingRecord: function _handlePairingRecord(ndef) {
|
|
this.debug('_handleSimplifiedPairingRecord');
|
|
var pairingRecord = ndef[0];
|
|
var btssp = NDEFUtils.parseBluetoothSSP(pairingRecord);
|
|
this.debug('Simplified pairing with: ' + btssp.mac);
|
|
this._checkConnected(btssp);
|
|
},
|
|
|
|
/**
|
|
* Handle NDEF Handover Select message.
|
|
* @param {Array} ndef NDEF message containing handover select record
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_handleHandoverSelect: function _handleHandoverSelect(ndef) {
|
|
this.debug('_handleHandoverSelect');
|
|
this._clearTimeout();
|
|
var btssp = this._getBluetoothSSP(ndef);
|
|
if (btssp == null) {
|
|
if (this.sendFileQueue.length !== 0) {
|
|
// We tried to send a file but the other device gave us an empty AC
|
|
// record. This will happen if the other device is currently
|
|
// transferring a file. Show a 'try later' notification.
|
|
this.debug('Other device is transferring file. Aborting');
|
|
var job = this.sendFileQueue.shift();
|
|
job.onerror();
|
|
this._showTryAgainNotification();
|
|
}
|
|
this._restoreBluetoothStatus();
|
|
return;
|
|
}
|
|
|
|
if (this.sendFileQueue.length !== 0) {
|
|
// This is the response to a file transfer request (negotiated handover)
|
|
this._doAction({callback: this._doFileTransfer, args: [btssp.mac]});
|
|
} else {
|
|
// This is a static handover
|
|
this._checkConnected(btssp);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handles NDEF Handover Request message.
|
|
* @param {Array} ndef NDEF message containing handover request record
|
|
* @param {MozNFCPeer} MozNFCPeer object.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_handleHandoverRequest: function _handleHandoverRequest(ndef, nfcPeer) {
|
|
this.debug('_handleHandoverRequest');
|
|
if (Service.query('BluetoothTransfer.isFileTransferInProgress')) {
|
|
// We don't allow concurrent file transfers
|
|
this.debug('This device is currently transferring a file. ' +
|
|
'Aborting via empty Hs');
|
|
var hs = NDEFUtils.encodeEmptyHandoverSelect();
|
|
nfcPeer.sendNDEF(hs);
|
|
return;
|
|
}
|
|
this._saveBluetoothStatus();
|
|
this._doAction({
|
|
callback: this._doHandoverRequest,
|
|
args: [ndef, nfcPeer]
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Checks if the first record of NDEF message is a handover record.
|
|
* If yes the NDEF message is handled according to handover record type.
|
|
* @param {Array} ndefMsg array of NDEF records
|
|
* @param {MozNFCPeer} MozNFCPeer object.
|
|
* @returns {boolean} true if handover record was found and handled, false
|
|
* if no handover record was found
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
tryHandover: function(ndefMsg, nfcPeer) {
|
|
this.debug('tryHandover: ', ndefMsg);
|
|
var nfcUtils = new NfcUtils();
|
|
if (!Array.isArray(ndefMsg) || !ndefMsg.length) {
|
|
return false;
|
|
}
|
|
|
|
var record = ndefMsg[0];
|
|
if (record.tnf === NDEF.TNF_WELL_KNOWN) {
|
|
if (nfcUtils.equalArrays(record.type, NDEF.RTD_HANDOVER_SELECT)) {
|
|
this._handleHandoverSelect(ndefMsg);
|
|
return true;
|
|
} else if (nfcUtils.equalArrays(
|
|
record.type, NDEF.RTD_HANDOVER_REQUEST)) {
|
|
this._handleHandoverRequest(ndefMsg, nfcPeer);
|
|
return true;
|
|
}
|
|
} else if ((record.tnf === NDEF.TNF_MIME_MEDIA) &&
|
|
nfcUtils.equalArrays(record.type, NDEF.MIME_BLUETOOTH_OOB)) {
|
|
this._handleSimplifiedPairingRecord(ndefMsg);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Trigger a file transfer with a remote device via BT.
|
|
* @param {Object} msg message object
|
|
* @param {MozNFCPeer} msg.peer An instance of MozNFCPeer object.
|
|
* @param {Blob} msg.blob File to be sent.
|
|
* @param {String} msg.requestId Request ID.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
handleFileTransfer: function handleFileTransfer(msg) {
|
|
this.debug('handleFileTransfer');
|
|
if (Service.query('BluetoothTransfer.isFileTransferInProgress')) {
|
|
// We don't allow concurrent file transfers
|
|
this.debug('This device is already transferring a file. Aborting');
|
|
this._dispatchSendFileStatus(1, msg.requestId);
|
|
this._showTryAgainNotification();
|
|
return;
|
|
}
|
|
this._saveBluetoothStatus();
|
|
this._doAction({
|
|
callback: this._doInitiateFileTransfer,
|
|
args: [msg]});
|
|
},
|
|
|
|
/**
|
|
* Returns true if a handover is in progress.
|
|
* @returns {boolean} true if handover is in progress.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
isHandoverInProgress: function isHandoverInProgress() {
|
|
return (this.sendFileQueue.length !== 0) ||
|
|
(this.incomingFileTransferInProgress === true);
|
|
},
|
|
|
|
/**
|
|
* BluetoothTransfer notifies us that a file transfer has started.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
_transferStarted: function bt__transferStarted() {
|
|
this._clearTimeout();
|
|
},
|
|
|
|
/**
|
|
* Tells NfcHandoverManager that a BT file transfer
|
|
* has been completed.
|
|
* @param details succeeded True if file transfer was successfull.
|
|
* @memberof NfcHandoverManager.prototype
|
|
*/
|
|
transferComplete: function transferComplete(evt) {
|
|
var details = evt.detail;
|
|
this.debug('transferComplete: ' + JSON.stringify(details));
|
|
if (!details.received && details.viaHandover) {
|
|
// Completed an outgoing send file request. Call onsuccess/onerror
|
|
var job = this.sendFileQueue.shift();
|
|
if (details.success) {
|
|
job.onsuccess();
|
|
} else {
|
|
job.onerror();
|
|
}
|
|
}
|
|
if (details.received) {
|
|
// We know that a file was received but we do not know if that
|
|
// file was sent via a NFC handover. Clearing the
|
|
// incomingFileTransferInProgress flag here could lead to an
|
|
// unavoidable race condition
|
|
this.incomingFileTransferInProgress = false;
|
|
}
|
|
this._restoreBluetoothStatus();
|
|
}
|
|
});
|
|
}());
|