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,802 @@
/* 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();
}
});
}());