Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
457 lines
15 KiB
JavaScript
457 lines
15 KiB
JavaScript
/* Copyright © 2013, Deutsche Telekom, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* globals CustomEvent, MozActivity,
|
|
NfcUtils, NDEF, BaseModule, NfcIcon,
|
|
LazyLoader, Service */
|
|
|
|
'use strict';
|
|
|
|
(function(exports) {
|
|
|
|
const NFC_HW_EVENTS =
|
|
['enable', 'disable', 'enable-polling', 'disable-polling',
|
|
'hw-change-success', 'hw-change-failure'];
|
|
|
|
const NFC_HW_STATE_TABLE = {
|
|
'disabling': [null, null, null, null, 'disabled', 'enabled'],
|
|
'disabled': ['enabling', null, null, null, null, null],
|
|
'enabling': [null, null, null, null, 'enabled', 'disabled'],
|
|
'enabled': [null, 'disabling', 'polling-on', 'polling-off', null, null],
|
|
// enabled state in which NFC HW is polling for NFC tags/peers
|
|
'polling-on': [null, 'disabling', null, 'polling-off', null, null],
|
|
// enabled state with low power consumption, NFC HW is not actively
|
|
// polling for NFC tags/peers. Card emulation is active.
|
|
'polling-off': [null, 'disabling', 'polling-on', null, null, null]
|
|
};
|
|
|
|
/**
|
|
* NfcManager is responsible for NFC support. It controls NFC hardware
|
|
* state, detects NFC tags and triggers appropriate activities, detects NFC
|
|
* peers and takes part in NFC P2P sharing process (with ShrinkingUI),
|
|
* detects NFC Handover requests and passes them to NfcHandoverManager for
|
|
* handling.
|
|
* @class NfcManager
|
|
* @requires BaseModule
|
|
* @requires ScreenManager
|
|
* @requires MozActivity
|
|
* @requires NDEF
|
|
* @requires NfcUtils
|
|
*/
|
|
var NfcManager = function() {
|
|
};
|
|
|
|
NfcManager.SETTINGS = [
|
|
'nfc.enabled',
|
|
'nfc.debugging.enabled'
|
|
];
|
|
|
|
NfcManager.SUB_MODULES = [
|
|
'NfcHandoverManager'
|
|
];
|
|
|
|
NfcManager.STATES = [
|
|
'isActive'
|
|
];
|
|
|
|
NfcManager.EVENTS = [
|
|
'screenchange',
|
|
'lockscreen-appopened',
|
|
'lockscreen-appclosed'
|
|
];
|
|
|
|
BaseModule.create(NfcManager, {
|
|
name: 'NfcManager',
|
|
DEBUG: false,
|
|
|
|
/**
|
|
* Current NFC Hardware state
|
|
* @memberof NfcManager.prototype
|
|
* @type {String}
|
|
*/
|
|
_hwState: null,
|
|
|
|
/**
|
|
* Initializes NfcManager, sets up listeners and handlers
|
|
* @memberof NfcManager.prototype
|
|
*/
|
|
_start: function nm_start() {
|
|
this.debug('Starting NFC Manager');
|
|
this._hwState = 'disabled';
|
|
LazyLoader.load(['js/nfc_icon.js']).then(function() {
|
|
this.icon = new NfcIcon(this);
|
|
this.icon.start();
|
|
}.bind(this)).catch(function(err) {
|
|
console.error(err);
|
|
});
|
|
|
|
window.navigator.mozSetMessageHandler('nfc-manager-tech-discovered',
|
|
(msg) => this._handleTechDiscovered(msg));
|
|
window.navigator.mozSetMessageHandler('nfc-manager-tech-lost',
|
|
(msg) => this._handleTechLost(msg));
|
|
|
|
// reseting nfc.status to default state, as the device could've
|
|
// been restarted when HW change was in progress
|
|
this.writeSetting({ 'nfc.status':'disabled' });
|
|
},
|
|
|
|
/**
|
|
* Removes all listeners and handlers
|
|
* @memberof NfcManager.prototype
|
|
*/
|
|
_stop: function nm_stop() {
|
|
this.debug('Stopping NFC Manager');
|
|
|
|
window.navigator.mozSetMessageHandler('nfc-manager-tech-discovered',
|
|
null);
|
|
window.navigator.mozSetMessageHandler('nfc-manager-tech-lost', null);
|
|
},
|
|
|
|
'_observe_nfc.enabled': function(enabled) {
|
|
this._doNfcStateTransition(enabled ? 'enable' : 'disable');
|
|
},
|
|
|
|
'_observe_nfc.debugging.enabled': function(enabled) {
|
|
this.DEBUG = enabled;
|
|
},
|
|
|
|
_handle_screenchange: function(evt) {
|
|
var nfcEvt = Service.query('screenEnabled') &&
|
|
!Service.query('locked') ?
|
|
'enable-polling' : 'disable-polling';
|
|
this._doNfcStateTransition(nfcEvt);
|
|
},
|
|
|
|
'_handle_lockscreen-appopened': function(evt) {
|
|
this._doNfcStateTransition('disable-polling');
|
|
},
|
|
|
|
'_handle_lockscreen-appclosed': function(evt) {
|
|
this._doNfcStateTransition('enable-polling');
|
|
},
|
|
|
|
/**
|
|
* Returns if NFC is active or not, depending on the hardware state
|
|
* @memberof NfcManager.prototype
|
|
* returns {boolean} isActive
|
|
*/
|
|
isActive: function nm_isActive() {
|
|
return this._hwState === 'enabled' || this._hwState === 'polling-on' ||
|
|
this._hwState === 'polling-off';
|
|
},
|
|
|
|
/**
|
|
* Handler for nfc-manager-tech-discovered messages which originate from
|
|
* gecko. Basing on the first NDEF record tnf and type this method can use
|
|
* NfcHandoverManager to handle handover scenarios. Basing on the techList
|
|
* array it can either trigger P2P sharing scenario or create MozActivities
|
|
* for other apps to act upon.
|
|
* @memberof NfcManager.prototype
|
|
* @param {Object} msg gecko originated message
|
|
* @param {Array} msg.records NDEF records
|
|
* @param {string} msg.type set to 'techDiscovered'
|
|
*/
|
|
_handleTechDiscovered: function nm_handleTechDiscovered(msg) {
|
|
this.debug('Technology Discovered: ' + JSON.stringify(msg));
|
|
msg = msg || {};
|
|
msg.records = Array.isArray(msg.records) ? msg.records : [];
|
|
|
|
this.publish('nfc-tech-discovered', this, /* without prefix */ true);
|
|
window.navigator.vibrate([25, 50, 125]);
|
|
|
|
if (this.nfcHandoverManager.tryHandover(msg.records, msg.peer)) {
|
|
return;
|
|
}
|
|
|
|
if (msg.records.length) {
|
|
this._fireNDEFDiscovered(msg.records);
|
|
} else if (msg.peer) {
|
|
this._checkP2PRegistration();
|
|
} else {
|
|
this.debug('Got tag without NDEF records, ignoring.');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handler for nfc-manager-tech-lost messages
|
|
* @memberof NfcManager.prototype
|
|
* @param {Object} msg - tech lost message
|
|
*/
|
|
_handleTechLost: function nm_handleTechLost(msg) {
|
|
this.debug('Technology Lost: ' + JSON.stringify(msg));
|
|
|
|
window.navigator.vibrate([125, 50, 25]);
|
|
window.dispatchEvent(new CustomEvent('nfc-tech-lost'));
|
|
|
|
// Clean up P2P UI events
|
|
this._cleanP2PUI();
|
|
},
|
|
|
|
/**
|
|
* Performs NFC state transition. Checks in NFC HW State Table
|
|
* if NFC HW Event (argument) triggers a transition from current HW state
|
|
* to a different one. If transition exists, _hwState is changed to new
|
|
* state and state entry function is called.
|
|
* @memberof NfcManager.prototype
|
|
* @param {string} evt - NFC HW Event
|
|
*/
|
|
_doNfcStateTransition: function(evt) {
|
|
var evtIdx = NFC_HW_EVENTS.indexOf(evt);
|
|
var state = NFC_HW_STATE_TABLE[this._hwState][evtIdx];
|
|
if (!state) {
|
|
this.debug('no transition from ' + this._hwState + '[' + evt + ']');
|
|
return;
|
|
}
|
|
|
|
this.debug('state: ' + this._hwState + '[' + evt + ']' + ' -> ' + state);
|
|
this._hwState = state;
|
|
this._processNfcStateChange();
|
|
},
|
|
|
|
/**
|
|
* State entry function. Called after transitioning to new NFC HW state.
|
|
* Depending on _hwState it can trigger a new HW change request,
|
|
* change 'nfc.status' setting or update NFC icon.
|
|
* @memberof NfcManager.prototype
|
|
*/
|
|
_processNfcStateChange: function() {
|
|
var nfc = window.navigator.mozNfc;
|
|
var promise;
|
|
|
|
switch (this._hwState) {
|
|
case 'disabling':
|
|
this.writeSetting({ 'nfc.status': this._hwState });
|
|
promise = nfc.powerOff();
|
|
break;
|
|
case 'disabled':
|
|
this.writeSetting({ 'nfc.status': this._hwState });
|
|
this.icon && this.icon.update();
|
|
break;
|
|
case 'enabling':
|
|
this.writeSetting({ 'nfc.status': this._hwState });
|
|
promise = nfc.startPoll();
|
|
break;
|
|
case 'enabled':
|
|
this.writeSetting({ 'nfc.status': this._hwState });
|
|
this.icon && this.icon.update();
|
|
break;
|
|
case 'polling-on':
|
|
promise = nfc.startPoll();
|
|
break;
|
|
case 'polling-off':
|
|
promise = nfc.stopPoll();
|
|
break;
|
|
}
|
|
|
|
if (promise) {
|
|
promise.then(() => this._doNfcStateTransition('hw-change-success'))
|
|
.catch(() => this._doNfcStateTransition('hw-change-failure'));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Step 1 of system app fallback P2P sharing.
|
|
* Queries Gecko (via NFC dom) if currently visible app has registered
|
|
* onpeerready handler. If the result is true, shrinking-start event is
|
|
* dispatched to ShrinkingUI, which will trigger UI change asking the
|
|
* user to confirm sharing.
|
|
* @memberof NfcManager.prototype
|
|
*/
|
|
_checkP2PRegistration: function nm_checkP2PRegistration() {
|
|
var nfc = window.navigator.mozNfc;
|
|
var activeApp = this.service.query('getTopMostWindow');
|
|
var manifestURL = activeApp.manifestURL ||
|
|
this.service.manifestURL;
|
|
|
|
// Do not allow shrinking if we are on the private browser landing page.
|
|
if (activeApp.isPrivateBrowser() &&
|
|
activeApp.config.url.startsWith('app://')) {
|
|
return;
|
|
} else if (activeApp.isBrowser() && document.mozFullScreen) {
|
|
// If it is a browser and in fullscreen mode, don't share it.
|
|
return;
|
|
}
|
|
|
|
nfc.checkP2PRegistration(manifestURL).then(result => {
|
|
if (result) {
|
|
if (activeApp.isTransitioning() || activeApp.isSheetTransitioning()) {
|
|
return;
|
|
}
|
|
|
|
this._initP2PUI();
|
|
} else {
|
|
this.debug('CheckP2PRegistration failed');
|
|
this._cleanP2PUI();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* P2P UI clean up helper, notifies ShrinkingUI to stop shrinking animation
|
|
* and removes 'shrinking-sent' listener.
|
|
* @memberof NfcManager.prototype
|
|
*/
|
|
_cleanP2PUI: function() {
|
|
window.removeEventListener('shrinking-sent', this._handleShrinkingSent);
|
|
this.publish('shrinking-stop', this, /* without prefix */ true);
|
|
},
|
|
|
|
/**
|
|
* Notifies ShrinkingUI to start shrinking animation and starts listening
|
|
* for 'shrinking-sent' event.
|
|
* @memberof NfcManager.prototype
|
|
*/
|
|
_initP2PUI: function() {
|
|
this.publish('shrinking-start', this, /* without prefix */ true);
|
|
|
|
this._handleShrinkingSent = () => {
|
|
this._cleanP2PUI();
|
|
this._dispatchP2PUserResponse();
|
|
};
|
|
|
|
window.addEventListener('shrinking-sent', this._handleShrinkingSent);
|
|
},
|
|
|
|
/**
|
|
* Step 2 of system app fallback P2P sharing.
|
|
* Notifies Gecko to fire onpeerready handler of the currently visible app.
|
|
* @memberof NfcManager.prototype
|
|
*/
|
|
_dispatchP2PUserResponse: function nm_dispatchP2PUserResponse() {
|
|
var nfc = window.navigator.mozNfc;
|
|
var activeApp = this.service.query('getTopMostWindow');
|
|
var manifestURL = activeApp.manifestURL ||
|
|
this.service.manifestURL;
|
|
|
|
nfc.notifyUserAcceptedP2P(manifestURL);
|
|
},
|
|
|
|
/**
|
|
* Fires NDEF related activities to launch other apps to perform
|
|
* further actions with NDEF Message contents. If the first NDEF record
|
|
* contains a well know type additional parsing will be done in helper
|
|
* methods. In general the name of activity will be 'nfc-ndef-discovered',
|
|
* in some case other names may be used (e.g. 'dial' in case of tel uri)
|
|
* @memberof NfcManager.prototype
|
|
* @param {Array} records - NDEF Message
|
|
*/
|
|
_fireNDEFDiscovered: function nm_fireNDEFDiscovered(records) {
|
|
this.debug('_fireNDEFDiscovered: ' + JSON.stringify(records));
|
|
var smartPoster = this._getSmartPoster(records);
|
|
var record = smartPoster || records[0] || { tnf: NDEF.TNF_EMPTY };
|
|
|
|
var data = NDEF.payload.decode(record.tnf, record.type, record.payload);
|
|
var options = this._createNDEFActivityOptions(data);
|
|
|
|
if (data !== null) {
|
|
options.data.records = records;
|
|
}
|
|
|
|
this.debug('_fireNDEFDiscovered activity: ', JSON.stringify(options));
|
|
var activity = new MozActivity(options);
|
|
activity.onerror = () => {
|
|
this.debug('Firing nfc-ndef-discovered activity failed');
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Retrieves Smart Poster record from NDEF records array following
|
|
* the rule outlined in NFCForum-SmartPoster_RTD_1.0, 3.4:
|
|
* "If an NDEF message contains one or multiple URI [URI] records
|
|
* in addition to the Smart Poster record at the top level (i.e.,
|
|
* not nested), the Smart Poster record overrides them. The NDEF
|
|
* application MUST use only the Smart Poster record."
|
|
* @memberof NfcManager.prototype
|
|
* @param {Array} records - array of NDEF records
|
|
* @returns {Object} record - SmartPostr record or null
|
|
*/
|
|
_getSmartPoster: function nm_getSmartPoster(records) {
|
|
var nfcUtils = new NfcUtils();
|
|
if (!Array.isArray(records) || !records.length) {
|
|
return null;
|
|
}
|
|
|
|
var smartPosters = records.filter(function isSmartPoster(r) {
|
|
return nfcUtils.equalArrays(r.type, NDEF.RTD_SMART_POSTER);
|
|
});
|
|
|
|
if (smartPosters.length && records[0].tnf === NDEF.TNF_WELL_KNOWN &&
|
|
(nfcUtils.equalArrays(records[0].type, NDEF.RTD_URI) ||
|
|
nfcUtils.equalArrays(records[0].type, NDEF.RTD_SMART_POSTER))) {
|
|
return smartPosters[0];
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Basing on decoded payload from first record of NDEF message prepares
|
|
* activity options object which will be used to launch MozActivity
|
|
* @memberof NfcManager.prototype
|
|
* @param {Object} payload - decoded payload of first record from NDEF msg
|
|
* @returns {Object} options - object used to construct MozActivity
|
|
*/
|
|
_createNDEFActivityOptions: function nm_createNDEFActivityOptions(payload) {
|
|
var options = { name: 'nfc-ndef-discovered', data: {}};
|
|
if (payload === null) {
|
|
return options;
|
|
}
|
|
|
|
if (payload.type === 'uri') {
|
|
if (payload.uri.indexOf('tel:') === 0) {
|
|
// dial a number
|
|
options.name = 'dial';
|
|
options.data.type = 'webtelephony/number';
|
|
options.data.number = payload.uri.substring(4);
|
|
options.data.uri = payload.uri;
|
|
} else if (payload.uri.indexOf('mailto:') === 0) {
|
|
// create new mail
|
|
options.name = 'new';
|
|
options.data.type = 'mail';
|
|
options.data.url = payload.uri;
|
|
} else if (payload.uri.indexOf('http://') === 0 ||
|
|
payload.uri.indexOf('https://') === 0 ||
|
|
payload.uri.indexOf('data:text/html') === 0) {
|
|
// launch browser
|
|
options.name = 'view';
|
|
options.data.type = 'url';
|
|
options.data.url = payload.uri;
|
|
} else {
|
|
options.data = payload;
|
|
}
|
|
} else if (payload.type === 'smartposter' &&
|
|
(payload.uri.indexOf('http://') === 0 ||
|
|
payload.uri.indexOf('https://') === 0)) {
|
|
// smartposter adaptation for browser handling
|
|
options.name = 'view';
|
|
options.data = payload;
|
|
options.data.type = 'url';
|
|
options.data.url = payload.uri;
|
|
delete options.data.uri;
|
|
} else if (payload.type === 'text/vcard') {
|
|
// contact import
|
|
options.name = 'import';
|
|
options.data = payload;
|
|
} else {
|
|
options.data = payload;
|
|
}
|
|
|
|
if (options.name !== 'nfc-ndef-discovered') {
|
|
options.data.src = 'nfc';
|
|
}
|
|
|
|
return options;
|
|
}
|
|
});
|
|
}());
|