gecko-dev/dom/nfc/gonk/Nfc.js

671 lines
20 KiB
JavaScript

/* Copyright 2012 Mozilla Foundation and Mozilla contributors
*
* 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.
*/
/* Copyright © 2013, Deutsche Telekom, Inc. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
"@mozilla.org/settingsService;1",
"nsISettingsService");
XPCOMUtils.defineLazyGetter(this, "NFC", function () {
let obj = {};
Cu.import("resource://gre/modules/nfc_consts.js", obj);
return obj;
});
Cu.import("resource://gre/modules/systemlibs.js");
const NFC_ENABLED = libcutils.property_get("ro.moz.nfc.enabled", "false") === "true";
// set to true in nfc_consts.js to see debug messages
let DEBUG = NFC.DEBUG_NFC;
let debug;
function updateDebug() {
if (DEBUG || NFC.DEBUG_NFC) {
debug = function (s) {
dump("-*- Nfc: " + s + "\n");
};
} else {
debug = function (s) {};
}
};
updateDebug();
const NFC_CONTRACTID = "@mozilla.org/nfc;1";
const NFC_CID =
Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");
const NFC_IPC_MSG_NAMES = [
"NFC:AddEventListener",
"NFC:QueryInfo"
];
const NFC_IPC_NFC_PERM_MSG_NAMES = [
"NFC:ReadNDEF",
"NFC:Connect",
"NFC:Close",
"NFC:WriteNDEF",
"NFC:MakeReadOnly",
"NFC:Format",
];
const NFC_IPC_NFC_SHARE_PERM_MSG_NAMES = [
"NFC:SendFile",
"NFC:RegisterPeerReadyTarget",
"NFC:UnregisterPeerReadyTarget"
];
const NFC_IPC_MANAGER_PERM_MSG_NAMES = [
"NFC:CheckP2PRegistration",
"NFC:NotifyUserAcceptedP2P",
"NFC:NotifySendFileStatus",
"NFC:ChangeRFState"
];
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
return {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
Ci.nsIObserver]),
nfc: null,
// Manage registered Peer Targets
peerTargets: {},
eventListeners: [],
init: function init(nfc) {
this.nfc = nfc;
if (!NFC.DEBUG_NFC) {
let lock = gSettingsService.createLock();
lock.get(NFC.SETTING_NFC_DEBUG, this.nfc);
Services.obs.addObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED, false);
}
Services.obs.addObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN, false);
this._registerMessageListeners();
},
_shutdown: function _shutdown() {
this.nfc.shutdown();
this.nfc = null;
Services.obs.removeObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED);
Services.obs.removeObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN);
this._unregisterMessageListeners();
},
_registerMessageListeners: function _registerMessageListeners() {
ppmm.addMessageListener("child-process-shutdown", this);
for (let message of NFC_IPC_MSG_NAMES) {
ppmm.addMessageListener(message, this);
}
for (let message of NFC_IPC_NFC_PERM_MSG_NAMES) {
ppmm.addMessageListener(message, this);
}
for (let message of NFC_IPC_NFC_SHARE_PERM_MSG_NAMES) {
ppmm.addMessageListener(message, this);
}
for (let message of NFC_IPC_MANAGER_PERM_MSG_NAMES) {
ppmm.addMessageListener(message, this);
}
},
_unregisterMessageListeners: function _unregisterMessageListeners() {
ppmm.removeMessageListener("child-process-shutdown", this);
for (let message of NFC_IPC_MSG_NAMES) {
ppmm.removeMessageListener(message, this);
}
for (let message of NFC_IPC_NFC_PERM_MSG_NAMES) {
ppmm.removeMessageListener(message, this);
}
for (let message of NFC_IPC_NFC_SHARE_PERM_MSG_NAMES) {
ppmm.removeMessageListener(message, this);
}
for (let message of NFC_IPC_MANAGER_PERM_MSG_NAMES) {
ppmm.removeMessageListener(message, this);
}
ppmm = null;
},
registerPeerReadyTarget: function registerPeerReadyTarget(target, appId) {
if (!this.peerTargets[appId]) {
this.peerTargets[appId] = target;
}
},
unregisterPeerReadyTarget: function unregisterPeerReadyTarget(appId) {
if (this.peerTargets[appId]) {
delete this.peerTargets[appId];
}
},
removePeerTarget: function removePeerTarget(target) {
Object.keys(this.peerTargets).forEach((appId) => {
if (this.peerTargets[appId] === target) {
delete this.peerTargets[appId];
}
});
},
notifyDOMEvent: function notifyDOMEvent(target, options) {
if (!target) {
dump("invalid target");
return;
}
target.sendAsyncMessage("NFC:DOMEvent", options);
},
addEventListener: function addEventListener(target) {
if (this.eventListeners.indexOf(target) != -1) {
return;
}
this.eventListeners.push(target);
},
removeEventListener: function removeEventListener(target) {
let index = this.eventListeners.indexOf(target);
if (index !== -1) {
this.eventListeners.splice(index, 1);
}
},
checkP2PRegistration: function checkP2PRegistration(message) {
let target = this.peerTargets[message.data.appId];
let sessionToken = SessionHelper.getCurrentP2PToken();
let isValid = (sessionToken != null) && (target != null);
let respMsg = { requestId: message.data.requestId };
if (!isValid) {
respMsg.errorMsg = this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_P2P_REG_INVALID);
}
// Notify the content process immediately of the status
message.target.sendAsyncMessage(message.name + "Response", respMsg);
},
notifyUserAcceptedP2P: function notifyUserAcceptedP2P(appId) {
let target = this.peerTargets[appId];
let sessionToken = SessionHelper.getCurrentP2PToken();
let isValid = (sessionToken != null) && (target != null);
if (!isValid) {
debug("Peer already lost or " + appId + " is not a registered PeerReadytarget");
return;
}
this.notifyDOMEvent(target, {event: NFC.PEER_EVENT_READY,
sessionToken: sessionToken});
},
onTagFound: function onTagFound(message) {
message.event = NFC.TAG_EVENT_FOUND;
for (let target of this.eventListeners) {
this.notifyDOMEvent(target, message);
}
delete message.event;
},
onTagLost: function onTagLost(sessionToken) {
for (let target of this.eventListeners) {
this.notifyDOMEvent(target, {event: NFC.TAG_EVENT_LOST,
sessionToken: sessionToken});
}
},
onPeerEvent: function onPeerEvent(eventType, sessionToken) {
for (let target of this.eventListeners) {
this.notifyDOMEvent(target, { event: eventType,
sessionToken: sessionToken });
}
},
onRFStateChange: function onRFStateChange(rfState) {
for (let target of this.eventListeners) {
this.notifyDOMEvent(target, { event: NFC.RF_EVENT_STATE_CHANGE,
rfState: rfState});
}
},
/**
* nsIMessageListener interface methods.
*/
receiveMessage: function receiveMessage(message) {
DEBUG && debug("Received message from content process: " + JSON.stringify(message));
if (message.name == "child-process-shutdown") {
this.removePeerTarget(message.target);
this.nfc.removeTarget(message.target);
this.removeEventListener(message.target);
return null;
}
if (NFC_IPC_MSG_NAMES.indexOf(message.name) != -1) {
// Do nothing.
} else if (NFC_IPC_NFC_PERM_MSG_NAMES.indexOf(message.name) != -1) {
if (!message.target.assertPermission("nfc")) {
debug("Nfc Peer message " + message.name +
" from a content process with no 'nfc' privileges.");
return null;
}
} else if (NFC_IPC_NFC_SHARE_PERM_MSG_NAMES.indexOf(message.name) != -1) {
if (!message.target.assertPermission("nfc-share")) {
debug("Nfc Peer message " + message.name +
" from a content process with no 'nfc-share' privileges.");
return null;
}
} else if (NFC_IPC_MANAGER_PERM_MSG_NAMES.indexOf(message.name) != -1) {
if (!message.target.assertPermission("nfc-manager")) {
debug("NFC message " + message.name +
" from a content process with no 'nfc-manager' privileges.");
return null;
}
} else {
debug("Ignoring unknown message type: " + message.name);
return null;
}
switch (message.name) {
case "NFC:AddEventListener":
this.addEventListener(message.target);
return null;
case "NFC:RegisterPeerReadyTarget":
this.registerPeerReadyTarget(message.target, message.data.appId);
return null;
case "NFC:UnregisterPeerReadyTarget":
this.unregisterPeerReadyTarget(message.data.appId);
return null;
case "NFC:CheckP2PRegistration":
this.checkP2PRegistration(message);
return null;
case "NFC:NotifyUserAcceptedP2P":
this.notifyUserAcceptedP2P(message.data.appId);
return null;
case "NFC:NotifySendFileStatus":
// Upon receiving the status of sendFile operation, send the response
// to appropriate content process.
message.data.type = "NotifySendFileStatusResponse";
if (message.data.status) {
message.data.errorMsg =
this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_SEND_FILE_FAILED);
}
this.nfc.sendNfcResponse(message.data);
return null;
default:
return this.nfc.receiveMessage(message);
}
},
/**
* nsIObserver interface methods.
*/
observe: function observe(subject, topic, data) {
switch (topic) {
case NFC.TOPIC_MOZSETTINGS_CHANGED:
if ("wrappedJSObject" in subject) {
subject = subject.wrappedJSObject;
}
if (subject) {
this.nfc.handle(subject.key, subject.value);
}
break;
case NFC.TOPIC_XPCOM_SHUTDOWN:
this._shutdown();
break;
}
},
};
});
let SessionHelper = {
tokenMap: {},
registerSession: function registerSession(id, isP2P) {
if (this.tokenMap[id]) {
return this.tokenMap[id].token;
}
this.tokenMap[id] = {
token: UUIDGenerator.generateUUID().toString(),
isP2P: isP2P
};
return this.tokenMap[id].token;
},
unregisterSession: function unregisterSession(id) {
if (this.tokenMap[id]) {
delete this.tokenMap[id];
}
},
getToken: function getToken(id) {
return this.tokenMap[id] ? this.tokenMap[id].token : null;
},
getCurrentP2PToken: function getCurrentP2PToken() {
for (let id in this.tokenMap) {
if (this.tokenMap[id] && this.tokenMap[id].isP2P) {
return this.tokenMap[id].token;
}
}
return null;
},
getId: function getId(token) {
for (let id in this.tokenMap) {
if (this.tokenMap[id].token == token) {
return id;
}
}
return 0;
},
isP2PSession: function isP2PSession(id) {
return (this.tokenMap[id] != null) && this.tokenMap[id].isP2P;
}
};
function Nfc() {
debug("Starting Nfc Service");
let nfcService = Cc["@mozilla.org/nfc/service;1"].getService(Ci.nsINfcService);
if (!nfcService) {
debug("No nfc service component available!");
return;
}
nfcService.start(this);
this.nfcService = nfcService;
gMessageManager.init(this);
this.targetsByRequestId = {};
}
Nfc.prototype = {
classID: NFC_CID,
classInfo: XPCOMUtils.generateCI({classID: NFC_CID,
classDescription: "Nfc",
interfaces: [Ci.nsINfcService]}),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsINfcGonkEventListener]),
rfState: null,
nfcService: null,
targetsByRequestId: null,
/**
* Send arbitrary message to Nfc service.
*
* @param nfcMessageType
* A text message type.
* @param message [optional]
* An optional message object to send.
*/
sendToNfcService: function sendToNfcService(nfcMessageType, message) {
message = message || {};
message.type = nfcMessageType;
this.nfcService.sendCommand(message);
},
sendNfcResponse: function sendNfcResponse(message) {
let target = this.targetsByRequestId[message.requestId];
if (!target) {
debug("No target for requestId: " + message.requestId);
return;
}
delete this.targetsByRequestId[message.requestId];
target.sendAsyncMessage("NFC:" + message.type, message);
},
/**
* Send Error response to content. This is used only
* in case of discovering an error in message received from
* content process.
*
* @param message
* An nsIMessageListener's message parameter.
*/
sendNfcErrorResponse: function sendNfcErrorResponse(message, errorCode) {
if (!message.target) {
return;
}
let nfcMsgType = message.name + "Response";
message.data.errorMsg = this.getErrorMessage(errorCode);
message.target.sendAsyncMessage(nfcMsgType, message.data);
},
getErrorMessage: function getErrorMessage(errorCode) {
return NFC.NFC_ERROR_MSG[errorCode];
},
/**
* Process the incoming message from the NFC Service.
*/
onEvent: function onEvent(event) {
let message = Cu.cloneInto(event, this);
DEBUG && debug("Received message from NFC Service: " + JSON.stringify(message));
switch (message.type) {
case "InitializedNotification":
// Do nothing.
break;
case "TechDiscoveredNotification":
message.type = "techDiscovered";
// Update the upper layers with a session token (alias)
message.sessionToken =
SessionHelper.registerSession(message.sessionId, message.isP2P);
// Do not expose the actual session to the content
let sessionId = message.sessionId;
delete message.sessionId;
if (SessionHelper.isP2PSession(sessionId)) {
gMessageManager.onPeerEvent(NFC.PEER_EVENT_FOUND, message.sessionToken);
} else {
gMessageManager.onTagFound(message);
}
gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
break;
case "TechLostNotification":
message.type = "techLost";
// Update the upper layers with a session token (alias)
message.sessionToken = SessionHelper.getToken(message.sessionId);
if (SessionHelper.isP2PSession(message.sessionId)) {
gMessageManager.onPeerEvent(NFC.PEER_EVENT_LOST, message.sessionToken);
} else {
gMessageManager.onTagLost(message.sessionToken);
}
SessionHelper.unregisterSession(message.sessionId);
// Do not expose the actual session to the content
delete message.sessionId;
gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
break;
case "HCIEventTransactionNotification":
this.notifyHCIEventTransaction(message);
break;
case "ChangeRFStateResponse":
this.sendNfcResponse(message);
if (!message.errorMsg) {
this.rfState = message.rfState;
gMessageManager.onRFStateChange(this.rfState);
}
break;
case "ConnectResponse": // Fall through.
case "CloseResponse":
case "ReadNDEFResponse":
case "MakeReadOnlyResponse":
case "FormatResponse":
case "WriteNDEFResponse":
this.sendNfcResponse(message);
break;
default:
throw new Error("Don't know about this message type: " + message.type);
}
},
// HCI Event Transaction
notifyHCIEventTransaction: function notifyHCIEventTransaction(message) {
delete message.type;
/**
* FIXME:
* GSMA 6.0 7.4 UI Application triggering requirements
* This specifies the need for the following parameters to be derived and
* sent. One unclear spec is what the URI format "secure:0" refers to, given
* SEName can be something like "SIM1" or "SIM2".
*
* 1) Mime-type - Secure Element application dependent
* 2) URI, of the format: nfc://secure:0/<SEName>/<AID>
* - SEName reflects the originating SE. It must be compliant with
* SIMAlliance Open Mobile APIs
* - AID reflects the originating UICC applet identifier
* 3) Data - Data payload of the transaction notification, if any.
*/
gSystemMessenger.broadcastMessage("nfc-hci-event-transaction", message);
},
/**
* Process a message from the gMessageManager.
*/
receiveMessage: function receiveMessage(message) {
if (["NFC:ChangeRFState",
"NFC:SendFile",
"NFC:QueryInfo"].indexOf(message.name) == -1) {
// Update the current sessionId before sending to the NFC service.
message.data.sessionId = SessionHelper.getId(message.data.sessionToken);
}
switch (message.name) {
case "NFC:ChangeRFState":
this.sendToNfcService("changeRFState", message.data);
break;
case "NFC:ReadNDEF":
this.sendToNfcService("readNDEF", message.data);
break;
case "NFC:WriteNDEF":
message.data.isP2P = SessionHelper.isP2PSession(message.data.sessionId);
this.sendToNfcService("writeNDEF", message.data);
break;
case "NFC:MakeReadOnly":
this.sendToNfcService("makeReadOnly", message.data);
break;
case "NFC:Format":
this.sendToNfcService("format", message.data);
break;
case "NFC:Connect":
this.sendToNfcService("connect", message.data);
break;
case "NFC:Close":
this.sendToNfcService("close", message.data);
break;
case "NFC:SendFile":
// Chrome process is the arbitrator / mediator between
// system app (content process) that issued nfc 'sendFile' operation
// and system app that handles the system message :
// 'nfc-manager-send-file'. System app subsequently handover's
// the data to alternate carrier's (BT / WiFi) 'sendFile' interface.
// Notify system app to initiate BT send file operation
gSystemMessenger.broadcastMessage("nfc-manager-send-file",
message.data);
break;
case "NFC:QueryInfo":
return {rfState: this.rfState};
default:
debug("UnSupported : Message Name " + message.name);
return null;
}
this.targetsByRequestId[message.data.requestId] = message.target;
return null;
},
removeTarget: function removeTarget(target) {
Object.keys(this.targetsByRequestId).forEach((requestId) => {
if (this.targetsByRequestId[requestId] === target) {
delete this.targetsByRequestId[requestId];
}
});
},
/**
* nsISettingsServiceCallback
*/
handle: function handle(name, result) {
switch (name) {
case NFC.SETTING_NFC_DEBUG:
DEBUG = result;
updateDebug();
break;
}
},
/**
* nsIObserver interface methods.
*/
observe: function(subject, topic, data) {
if (topic != "profile-after-change") {
debug("Should receive 'profile-after-change' only, received " + topic);
}
},
shutdown: function shutdown() {
this.nfcService.shutdown();
this.nfcService = null;
}
};
if (NFC_ENABLED) {
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Nfc]);
}