dom/system/gonk/Nfc.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors
     2  *
     3  * Licensed under the Apache License, Version 2.0 (the "License");
     4  * you may not use this file except in compliance with the License.
     5  * You may obtain a copy of the License at
     6  *
     7  *     http://www.apache.org/licenses/LICENSE-2.0
     8  *
     9  * Unless required by applicable law or agreed to in writing, software
    10  * distributed under the License is distributed on an "AS IS" BASIS,
    11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  * See the License for the specific language governing permissions and
    13  * limitations under the License.
    14  */
    16 /* Copyright © 2013, Deutsche Telekom, Inc. */
    18 "use strict";
    20 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    22 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    23 Cu.import("resource://gre/modules/Services.jsm");
    25 let NFC = {};
    26 Cu.import("resource://gre/modules/nfc_consts.js", NFC);
    28 Cu.import("resource://gre/modules/systemlibs.js");
    29 const NFC_ENABLED = libcutils.property_get("ro.moz.nfc.enabled", "false") === "true";
    31 // set to true in nfc_consts.js to see debug messages
    32 let DEBUG = NFC.DEBUG_NFC;
    34 let debug;
    35 if (DEBUG) {
    36   debug = function (s) {
    37     dump("-*- Nfc: " + s + "\n");
    38   };
    39 } else {
    40   debug = function (s) {};
    41 }
    43 const NFC_CONTRACTID = "@mozilla.org/nfc;1";
    44 const NFC_CID =
    45   Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");
    47 const NFC_IPC_MSG_NAMES = [
    48   "NFC:SetSessionToken"
    49 ];
    51 const NFC_IPC_READ_PERM_MSG_NAMES = [
    52   "NFC:ReadNDEF",
    53   "NFC:GetDetailsNDEF",
    54   "NFC:Connect",
    55   "NFC:Close",
    56 ];
    58 const NFC_IPC_WRITE_PERM_MSG_NAMES = [
    59   "NFC:WriteNDEF",
    60   "NFC:MakeReadOnlyNDEF",
    61   "NFC:SendFile",
    62   "NFC:RegisterPeerTarget",
    63   "NFC:UnregisterPeerTarget"
    64 ];
    66 const NFC_IPC_MANAGER_PERM_MSG_NAMES = [
    67   "NFC:CheckP2PRegistration",
    68   "NFC:NotifyUserAcceptedP2P",
    69   "NFC:NotifySendFileStatus",
    70   "NFC:StartPoll",
    71   "NFC:StopPoll",
    72   "NFC:PowerOff"
    73 ];
    75 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
    76                                    "@mozilla.org/parentprocessmessagemanager;1",
    77                                    "nsIMessageBroadcaster");
    78 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
    79                                    "@mozilla.org/system-message-internal;1",
    80                                    "nsISystemMessagesInternal");
    81 XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager",
    82                                    "@mozilla.org/telephony/system-worker-manager;1",
    83                                    "nsISystemWorkerManager");
    84 XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
    85                                     "@mozilla.org/uuid-generator;1",
    86                                     "nsIUUIDGenerator");
    87 XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
    88   return {
    89     QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
    90                                            Ci.nsIObserver]),
    92     nfc: null,
    94     // Manage message targets in terms of sessionToken. Only the authorized and
    95     // registered contents can receive related messages.
    96     targetsBySessionTokens: {},
    97     sessionTokens: [],
    99     // Manage registered Peer Targets
   100     peerTargetsMap: {},
   101     currentPeerAppId: null,
   103     init: function init(nfc) {
   104       this.nfc = nfc;
   106       Services.obs.addObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN, false);
   107       this._registerMessageListeners();
   108     },
   110     _shutdown: function _shutdown() {
   111       this.nfc = null;
   113       Services.obs.removeObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN);
   114       this._unregisterMessageListeners();
   115     },
   117     _registerMessageListeners: function _registerMessageListeners() {
   118       ppmm.addMessageListener("child-process-shutdown", this);
   120       for (let msgname of NFC_IPC_MSG_NAMES) {
   121         ppmm.addMessageListener(msgname, this);
   122       }
   124       for (let msgname of NFC_IPC_READ_PERM_MSG_NAMES) {
   125         ppmm.addMessageListener(msgname, this);
   126       }
   128       for (let msgname of NFC_IPC_WRITE_PERM_MSG_NAMES) {
   129         ppmm.addMessageListener(msgname, this);
   130       }
   132       for (let msgname of NFC_IPC_MANAGER_PERM_MSG_NAMES) {
   133         ppmm.addMessageListener(msgname, this);
   134       }
   135     },
   137     _unregisterMessageListeners: function _unregisterMessageListeners() {
   138       ppmm.removeMessageListener("child-process-shutdown", this);
   140       for (let msgname of NFC_IPC_MSG_NAMES) {
   141         ppmm.removeMessageListener(msgname, this);
   142       }
   144       for (let msgname of NFC_IPC_READ_PERM_MSG_NAMES) {
   145         ppmm.removeMessageListener(msgname, this);
   146       }
   148       for (let msgname of NFC_IPC_WRITE_PERM_MSG_NAMES) {
   149         ppmm.removeMessageListener(msgname, this);
   150       }
   152       for (let msgname of NFC_IPC_MANAGER_PERM_MSG_NAMES) {
   153         ppmm.removeMessageListener(msgname, this);
   154       }
   156       ppmm = null;
   157     },
   159     _registerMessageTarget: function _registerMessageTarget(sessionToken, target) {
   160       let targets = this.targetsBySessionTokens[sessionToken];
   161       if (!targets) {
   162         targets = this.targetsBySessionTokens[sessionToken] = [];
   163         let list = this.sessionTokens;
   164         if (list.indexOf(sessionToken) == -1) {
   165           list.push(sessionToken);
   166         }
   167       }
   169       if (targets.indexOf(target) != -1) {
   170         debug("Already registered this target!");
   171         return;
   172       }
   174       targets.push(target);
   175       debug("Registered :" + sessionToken + " target: " + target);
   176     },
   178     _unregisterMessageTarget: function _unregisterMessageTarget(sessionToken, target) {
   179       if (sessionToken == null) {
   180         // Unregister the target for every sessionToken when no sessionToken is specified.
   181         for (let session of this.sessionTokens) {
   182           this._unregisterMessageTarget(session, target);
   183         }
   184         return;
   185       }
   187       // Unregister the target for a specified sessionToken.
   188       let targets = this.targetsBySessionTokens[sessionToken];
   189       if (!targets) {
   190         return;
   191       }
   193       if (target == null) {
   194         debug("Unregistered all targets for the " + sessionToken + " targets: " + targets);
   195         targets = [];
   196         let list = this.sessionTokens;
   197         if (sessionToken !== null) {
   198           let index = list.indexOf(sessionToken);
   199           if (index > -1) {
   200             list.splice(index, 1);
   201           }
   202         }
   203         return;
   204       }
   206       let index = targets.indexOf(target);
   207       if (index != -1) {
   208         targets.splice(index, 1);
   209       }
   210     },
   212     _sendTargetMessage: function _sendTargetMessage(sessionToken, message, options) {
   213       let targets = this.targetsBySessionTokens[sessionToken];
   214       if (!targets) {
   215         return;
   216       }
   218       for (let target of targets) {
   219         target.sendAsyncMessage(message, options);
   220       }
   221     },
   223     registerPeerTarget: function registerPeerTarget(msg) {
   224       let appInfo = msg.json;
   225       // Sanity check on PeerEvent
   226       if (!this.isValidPeerEvent(appInfo.event)) {
   227         return;
   228       }
   229       let targets = this.peerTargetsMap;
   230       let targetInfo = targets[appInfo.appId];
   231       // If the application Id is already registered
   232       if (targetInfo) {
   233         // If the event is not registered
   234         if (targetInfo.event !== appInfo.event) {
   235           // Update the event field ONLY
   236           targetInfo.event |= appInfo.event;
   237         }
   238         // Otherwise event is already registered, return!
   239         return;
   240       }
   241       // Target not registered yet! Add to the target map
   243       // Registered targetInfo target consists of 2 fields (values)
   244       // target : Target to notify the right content for peer notifications
   245       // event  : NFC_PEER_EVENT_READY (0x01) Or NFC_PEER_EVENT_LOST (0x02)
   246       let newTargetInfo = { target : msg.target,
   247                             event  : appInfo.event };
   248       targets[appInfo.appId] = newTargetInfo;
   249     },
   251     unregisterPeerTarget: function unregisterPeerTarget(msg) {
   252       let appInfo = msg.json;
   253       // Sanity check on PeerEvent
   254       if (!this.isValidPeerEvent(appInfo.event)) {
   255         return;
   256       }
   257       let targets = this.peerTargetsMap;
   258       let targetInfo = targets[appInfo.appId];
   259       if (targetInfo) {
   260         // Application Id registered and the event exactly matches.
   261         if (targetInfo.event === appInfo.event) {
   262           // Remove the target from the list of registered targets
   263           delete targets[appInfo.appId]
   264         }
   265         else {
   266           // Otherwise, update the event field ONLY, by removing the event flag
   267           targetInfo.event &= ~appInfo.event;
   268         }
   269       }
   270     },
   272     removePeerTarget: function removePeerTarget(target) {
   273       let targets = this.peerTargetsMap;
   274       Object.keys(targets).forEach((appId) => {
   275         let targetInfo = targets[appId];
   276         if (targetInfo && targetInfo.target === target) {
   277           // Remove the target from the list of registered targets
   278           delete targets[appId];
   279         }
   280       });
   281     },
   283     isRegisteredP2PTarget: function isRegisteredP2PTarget(appId, event) {
   284       let targetInfo = this.peerTargetsMap[appId];
   285       // Check if it is a registered target for the 'event'
   286       return ((targetInfo != null) && (targetInfo.event & event !== 0));
   287     },
   289     notifyPeerEvent: function notifyPeerEvent(appId, event) {
   290       let targetInfo = this.peerTargetsMap[appId];
   291       // Check if the application id is a registeredP2PTarget
   292       if (this.isRegisteredP2PTarget(appId, event)) {
   293         targetInfo.target.sendAsyncMessage("NFC:PeerEvent", {
   294           event: event,
   295           sessionToken: this.nfc.sessionTokenMap[this.nfc._currentSessionId]
   296         });
   297         return;
   298       }
   299       debug("Application ID : " + appId + " is not a registered target" +
   300                              "for the event " + event + " notification");
   301     },
   303     isValidPeerEvent: function isValidPeerEvent(event) {
   304       // Valid values : 0x01, 0x02 Or 0x03
   305       return ((event === NFC.NFC_PEER_EVENT_READY) ||
   306               (event === NFC.NFC_PEER_EVENT_LOST)  ||
   307               (event === (NFC.NFC_PEER_EVENT_READY | NFC.NFC_PEER_EVENT_LOST)));
   308     },
   310     /**
   311      * nsIMessageListener interface methods.
   312      */
   314     receiveMessage: function receiveMessage(msg) {
   315       debug("Received '" + msg.name + "' message from content process");
   316       if (msg.name == "child-process-shutdown") {
   317         // By the time we receive child-process-shutdown, the child process has
   318         // already forgotten its permissions so we need to unregister the target
   319         // for every permission.
   320         this._unregisterMessageTarget(null, msg.target);
   321         this.removePeerTarget(msg.target);
   322         return null;
   323       }
   325       if (NFC_IPC_MSG_NAMES.indexOf(msg.name) != -1) {
   326         // Do nothing.
   327       } else if (NFC_IPC_READ_PERM_MSG_NAMES.indexOf(msg.name) != -1) {
   328         if (!msg.target.assertPermission("nfc-read")) {
   329           debug("Nfc message " + msg.name +
   330                 " from a content process with no 'nfc-read' privileges.");
   331           return null;
   332         }
   333       } else if (NFC_IPC_WRITE_PERM_MSG_NAMES.indexOf(msg.name) != -1) {
   334         if (!msg.target.assertPermission("nfc-write")) {
   335           debug("Nfc Peer message  " + msg.name +
   336                 " from a content process with no 'nfc-write' privileges.");
   337           return null;
   338         }
   339       } else if (NFC_IPC_MANAGER_PERM_MSG_NAMES.indexOf(msg.name) != -1) {
   340         if (!msg.target.assertPermission("nfc-manager")) {
   341           debug("NFC message " + message.name +
   342                 " from a content process with no 'nfc-manager' privileges.");
   343           return null;
   344         }
   345       } else {
   346         debug("Ignoring unknown message type: " + msg.name);
   347         return null;
   348       }
   350       switch (msg.name) {
   351         case "NFC:SetSessionToken":
   352           this._registerMessageTarget(this.nfc.sessionTokenMap[this.nfc._currentSessionId], msg.target);
   353           debug("Registering target for this SessionToken : " +
   354                 this.nfc.sessionTokenMap[this.nfc._currentSessionId]);
   355           return null;
   356         case "NFC:RegisterPeerTarget":
   357           this.registerPeerTarget(msg);
   358           return null;
   359         case "NFC:UnregisterPeerTarget":
   360           this.unregisterPeerTarget(msg);
   361           return null;
   362         case "NFC:CheckP2PRegistration":
   363           // Check if the application id is a valid registered target.
   364           // (It should have registered for NFC_PEER_EVENT_READY).
   365           let isRegistered = this.isRegisteredP2PTarget(msg.json.appId,
   366                                                         NFC.NFC_PEER_EVENT_READY);
   367           // Remember the current AppId if registered.
   368           this.currentPeerAppId = (isRegistered) ? msg.json.appId : null;
   369           let status = (isRegistered) ? NFC.GECKO_NFC_ERROR_SUCCESS :
   370                                         NFC.GECKO_NFC_ERROR_GENERIC_FAILURE;
   371           // Notify the content process immediately of the status
   372           msg.target.sendAsyncMessage(msg.name + "Response", {
   373             status: status,
   374             requestId: msg.json.requestId
   375           });
   376           return null;
   377         case "NFC:NotifyUserAcceptedP2P":
   378           // Notify the 'NFC_PEER_EVENT_READY' since user has acknowledged
   379           this.notifyPeerEvent(msg.json.appId, NFC.NFC_PEER_EVENT_READY);
   380           return null;
   381         case "NFC:NotifySendFileStatus":
   382           // Upon receiving the status of sendFile operation, send the response
   383           // to appropriate content process.
   384           this.sendNfcResponseMessage(msg.name + "Response", msg.json);
   385           return null;
   386         default:
   387           return this.nfc.receiveMessage(msg);
   388       }
   389     },
   391     /**
   392      * nsIObserver interface methods.
   393      */
   395     observe: function observe(subject, topic, data) {
   396       switch (topic) {
   397         case NFC.TOPIC_XPCOM_SHUTDOWN:
   398           this._shutdown();
   399           break;
   400       }
   401     },
   403     sendNfcResponseMessage: function sendNfcResponseMessage(message, data) {
   404       this._sendTargetMessage(this.nfc.sessionTokenMap[this.nfc._currentSessionId], message, data);
   405     },
   406   };
   407 });
   409 function Nfc() {
   410   debug("Starting Worker");
   411   this.worker = new ChromeWorker("resource://gre/modules/nfc_worker.js");
   412   this.worker.onerror = this.onerror.bind(this);
   413   this.worker.onmessage = this.onmessage.bind(this);
   415   gMessageManager.init(this);
   417   // Maps sessionId (that are generated from nfcd) with a unique guid : 'SessionToken'
   418   this.sessionTokenMap = {};
   419   this.targetsByRequestId = {};
   421   gSystemWorkerManager.registerNfcWorker(this.worker);
   422 }
   424 Nfc.prototype = {
   426   classID:   NFC_CID,
   427   classInfo: XPCOMUtils.generateCI({classID: NFC_CID,
   428                                     classDescription: "Nfc",
   429                                     interfaces: [Ci.nsIWorkerHolder]}),
   431   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder]),
   433   _currentSessionId: null,
   435   powerLevel: NFC.NFC_POWER_LEVEL_UNKNOWN,
   437   onerror: function onerror(event) {
   438     debug("Got an error: " + event.filename + ":" +
   439           event.lineno + ": " + event.message + "\n");
   440     event.preventDefault();
   441   },
   443   /**
   444    * Send arbitrary message to worker.
   445    *
   446    * @param nfcMessageType
   447    *        A text message type.
   448    * @param message [optional]
   449    *        An optional message object to send.
   450    */
   451   sendToWorker: function sendToWorker(nfcMessageType, message) {
   452     message = message || {};
   453     message.type = nfcMessageType;
   454     this.worker.postMessage(message);
   455   },
   457   /**
   458    * Send Error response to content.
   459    *
   460    * @param message
   461    *        An nsIMessageListener's message parameter.
   462    */
   463   sendNfcErrorResponse: function sendNfcErrorResponse(message) {
   464     if (!message.target) {
   465       return;
   466     }
   468     let nfcMsgType = message.name + "Response";
   469     message.target.sendAsyncMessage(nfcMsgType, {
   470       sessionId: message.json.sessionToken,
   471       requestId: message.json.requestId,
   472       status: NFC.GECKO_NFC_ERROR_GENERIC_FAILURE
   473     });
   474   },
   476   /**
   477    * Process the incoming message from the NFC worker
   478    */
   479   onmessage: function onmessage(event) {
   480     let message = event.data;
   481     debug("Received message from NFC worker: " + JSON.stringify(message));
   483     switch (message.type) {
   484       case "techDiscovered":
   485         this._currentSessionId = message.sessionId;
   487         // Check if the session token already exists. If exists, continue to use the same one.
   488         // If not, generate a new token.
   489         if (!this.sessionTokenMap[this._currentSessionId]) {
   490           this.sessionTokenMap[this._currentSessionId] = UUIDGenerator.generateUUID().toString();
   491         }
   492         // Update the upper layers with a session token (alias)
   493         message.sessionToken = this.sessionTokenMap[this._currentSessionId];
   494         // Do not expose the actual session to the content
   495         delete message.sessionId;
   497         gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
   498         break;
   499       case "techLost":
   500         gMessageManager._unregisterMessageTarget(this.sessionTokenMap[this._currentSessionId], null);
   502         // Update the upper layers with a session token (alias)
   503         message.sessionToken = this.sessionTokenMap[this._currentSessionId];
   504         // Do not expose the actual session to the content
   505         delete message.sessionId;
   507         gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
   508         // Notify 'PeerLost' to appropriate registered target, if any
   509         gMessageManager.notifyPeerEvent(this.currentPeerAppId, NFC.NFC_PEER_EVENT_LOST);
   510         delete this.sessionTokenMap[this._currentSessionId];
   511         this._currentSessionId = null;
   512         this.currentPeerAppId = null;
   513         break;
   514      case "ConfigResponse":
   515         let target = this.targetsByRequestId[message.requestId];
   516         if (!target) {
   517           debug("No target for requestId: " + message.requestId);
   518           return;
   519         }
   520         delete this.targetsByRequestId[message.requestId];
   522         if (message.status == NFC.GECKO_NFC_ERROR_SUCCESS) {
   523           this.powerLevel = message.powerLevel;
   524         }
   526         target.sendAsyncMessage("NFC:ConfigResponse", message);
   527         break;
   528       case "ConnectResponse": // Fall through.
   529       case "CloseResponse":
   530       case "GetDetailsNDEFResponse":
   531       case "ReadNDEFResponse":
   532       case "MakeReadOnlyNDEFResponse":
   533       case "WriteNDEFResponse":
   534         message.sessionToken = this.sessionTokenMap[this._currentSessionId];
   535         // Do not expose the actual session to the content
   536         delete message.sessionId;
   537         gMessageManager.sendNfcResponseMessage("NFC:" + message.type, message);
   538         break;
   539       default:
   540         throw new Error("Don't know about this message type: " + message.type);
   541     }
   542   },
   544   // nsINfcWorker
   545   worker: null,
   547   sessionTokenMap: null,
   549   targetsByRequestId: null,
   551   /**
   552    * Process a message from the content process.
   553    */
   554   receiveMessage: function receiveMessage(message) {
   555     debug("Received '" + JSON.stringify(message) + "' message from content process");
   557     // Handle messages without sessionToken.
   558     if (message.name == "NFC:StartPoll") {
   559       this.targetsByRequestId[message.json.requestId] = message.target;
   560       this.setConfig({powerLevel: NFC.NFC_POWER_LEVEL_ENABLED,
   561                       requestId: message.json.requestId});
   562       return null;
   563     } else if (message.name == "NFC:StopPoll") {
   564       this.targetsByRequestId[message.json.requestId] = message.target;
   565       this.setConfig({powerLevel: NFC.NFC_POWER_LEVEL_LOW,
   566                       requestId: message.json.requestId});
   567       return null;
   568     } else if (message.name == "NFC:PowerOff") {
   569       this.targetsByRequestId[message.json.requestId] = message.target;
   570       this.setConfig({powerLevel: NFC.NFC_POWER_LEVEL_DISABLED,
   571                       requestId: message.json.requestId});
   572       return null;
   573     }
   575     if (this.powerLevel != NFC.NFC_POWER_LEVEL_ENABLED) {
   576       debug("NFC is not enabled. current powerLevel:" + this.powerLevel);
   577       this.sendNfcErrorResponse(message);
   578       return null;
   579     }
   581     // Sanity check on sessionId
   582     if (message.json.sessionToken !== this.sessionTokenMap[this._currentSessionId]) {
   583       debug("Invalid Session Token: " + message.json.sessionToken +
   584             " Expected Session Token: " + this.sessionTokenMap[this._currentSessionId]);
   585       this.sendNfcErrorResponse(message);
   586       return null;
   587     }
   589     // Update the current sessionId before sending to the worker
   590     message.json.sessionId = this._currentSessionId;
   592     switch (message.name) {
   593       case "NFC:GetDetailsNDEF":
   594         this.sendToWorker("getDetailsNDEF", message.json);
   595         break;
   596       case "NFC:ReadNDEF":
   597         this.sendToWorker("readNDEF", message.json);
   598         break;
   599       case "NFC:WriteNDEF":
   600         this.sendToWorker("writeNDEF", message.json);
   601         break;
   602       case "NFC:MakeReadOnlyNDEF":
   603         this.sendToWorker("makeReadOnlyNDEF", message.json);
   604         break;
   605       case "NFC:Connect":
   606         this.sendToWorker("connect", message.json);
   607         break;
   608       case "NFC:Close":
   609         this.sendToWorker("close", message.json);
   610         break;
   611       case "NFC:SendFile":
   612         // Chrome process is the arbitrator / mediator between
   613         // system app (content process) that issued nfc 'sendFile' operation
   614         // and system app that handles the system message :
   615         // 'nfc-manager-send-file'. System app subsequently handover's
   616         // the data to alternate carrier's (BT / WiFi) 'sendFile' interface.
   618         // Notify system app to initiate BT send file operation
   619         gSystemMessenger.broadcastMessage("nfc-manager-send-file",
   620                                            message.json);
   621         break;
   622       default:
   623         debug("UnSupported : Message Name " + message.name);
   624         return null;
   625     }
   627     return null;
   628   },
   630   setConfig: function setConfig(prop) {
   631     this.sendToWorker("config", prop);
   632   }
   633 };
   635 if (NFC_ENABLED) {
   636   this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Nfc]);
   637 }

mercurial