Bug 1114935 - Part 2: Add Gonk Implementation of nsIIccService. r=echen
authorBevis Tseng <btseng@mozilla.com>
Tue, 06 Jan 2015 13:32:08 +0800
changeset 265016 689af3a45b4ea08e4548a5027c9dca8206dd27d5
parent 265015 67573eb8d30845feaad566a591f1531789b4be37
child 265017 0e50f55bbd038b83ead01be05bfb5f00f0f629f5
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersechen
bugs1114935
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1114935 - Part 2: Add Gonk Implementation of nsIIccService. r=echen
b2g/installer/package-manifest.in
dom/icc/gonk/IccService.js
dom/icc/gonk/IccService.manifest
dom/icc/moz.build
dom/system/gonk/RadioInterfaceLayer.js
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -478,16 +478,18 @@
 @RESPATH@/components/ResourceStatsManager.js
 @RESPATH@/components/ResourceStatsManager.manifest
 #endif // MOZ_WIDGET_GONK
 
 ; RIL
 #if defined(MOZ_WIDGET_GONK) && defined(MOZ_B2G_RIL)
 @RESPATH@/components/CellBroadcastService.js
 @RESPATH@/components/CellBroadcastService.manifest
+@BINPATH@/components/IccService.js
+@BINPATH@/components/IccService.manifest
 @RESPATH@/components/MmsService.js
 @RESPATH@/components/MmsService.manifest
 @RESPATH@/components/MobileMessageDatabaseService.js
 @RESPATH@/components/MobileMessageDatabaseService.manifest
 #ifndef DISABLE_MOZ_RIL_GEOLOC
 @RESPATH@/components/MobileConnectionService.js
 @RESPATH@/components/MobileConnectionService.manifest
 @RESPATH@/components/RadioInterfaceLayer.js
new file mode 100644
--- /dev/null
+++ b/dom/icc/gonk/IccService.js
@@ -0,0 +1,437 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"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");
+
+var RIL = {};
+Cu.import("resource://gre/modules/ril_consts.js", RIL);
+
+const GONK_ICCSERVICE_CONTRACTID = "@mozilla.org/icc/gonkiccservice;1";
+const GONK_ICCSERVICE_CID = Components.ID("{df854256-9554-11e4-a16c-c39e8d106c26}");
+
+const NS_XPCOM_SHUTDOWN_OBSERVER_ID      = "xpcom-shutdown";
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID  = "nsPref:changed";
+
+const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
+const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
+
+XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
+                                   "@mozilla.org/ril;1",
+                                   "nsIRadioInterfaceLayer");
+
+let DEBUG = RIL.DEBUG_RIL;
+function debug(s) {
+  dump("IccService: " + s);
+}
+
+function IccInfo() {}
+IccInfo.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIIccInfo]),
+
+  // nsIIccInfo
+
+  iccType: null,
+  iccid: null,
+  mcc: null,
+  mnc: null,
+  spn: null,
+  isDisplayNetworkNameRequired: false,
+  isDisplaySpnRequired: false
+};
+
+function GsmIccInfo() {}
+GsmIccInfo.prototype = {
+  __proto__: IccInfo.prototype,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIGsmIccInfo,
+                                         Ci.nsIIccInfo]),
+
+  // nsIGsmIccInfo
+
+  msisdn: null
+};
+
+function CdmaIccInfo() {}
+CdmaIccInfo.prototype = {
+  __proto__: IccInfo.prototype,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICdmaIccInfo,
+                                         Ci.nsIIccInfo]),
+
+  // nsICdmaIccInfo
+
+  mdn: null,
+  prlVersion: 0
+};
+
+function IccService() {
+  this._iccs = [];
+
+  let numClients = gRadioInterfaceLayer.numRadioInterfaces;
+  for (let i = 0; i < numClients; i++) {
+    this._iccs.push(new Icc(gRadioInterfaceLayer.getRadioInterface(i)));
+  }
+
+  this._updateDebugFlag();
+
+  Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false);
+  Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+}
+IccService.prototype = {
+  classID: GONK_ICCSERVICE_CID,
+
+  classInfo: XPCOMUtils.generateCI({classID: GONK_ICCSERVICE_CID,
+                                    contractID: GONK_ICCSERVICE_CONTRACTID,
+                                    classDescription: "IccService",
+                                    interfaces: [Ci.nsIIccService,
+                                                 Ci.nsIGonkIccService],
+                                    flags: Ci.nsIClassInfo.SINGLETON}),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIIccService,
+                                         Ci.nsIGonkIccService,
+                                         Ci.nsIObserver]),
+
+  // An array of Icc instances.
+  _iccs: null,
+
+  _updateDebugFlag: function() {
+    try {
+      DEBUG = DEBUG ||
+              Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
+    } catch (e) {}
+  },
+
+  /**
+   * nsIIccService interface.
+   */
+  getIccByServiceId: function(aServiceId) {
+    let icc = this._iccs[aServiceId];
+    if (!icc) {
+      throw Cr.NS_ERROR_UNEXPECTED;
+    }
+
+    return icc;
+  },
+
+  /**
+   * nsIGonkIccService interface.
+   */
+  notifyCardStateChanged: function(aServiceId, aCardState) {
+    if (DEBUG) {
+      debug("notifyCardStateChanged for service Id: " + aServiceId +
+            ", CardState: " + aCardState);
+    }
+
+    this.getIccByServiceId(aServiceId)._updateCardState(aCardState);
+  },
+
+  notifyIccInfoChanged: function(aServiceId, aIccInfo) {
+    if (DEBUG) {
+      debug("notifyIccInfoChanged for service Id: " + aServiceId +
+            ", IccInfo: " + JSON.stringify(aIccInfo));
+    }
+
+    this.getIccByServiceId(aServiceId)._updateIccInfo(aIccInfo);
+  },
+
+  notifyImsiChanged: function(aServiceId, aImsi) {
+    if (DEBUG) {
+      debug("notifyImsiChanged for service Id: " + aServiceId +
+            ", Imsi: " + aImsi);
+    }
+
+    let icc = this.getIccByServiceId(aServiceId);
+    icc._imsi = aImsi;
+  },
+
+  /**
+   * nsIObserver interface.
+   */
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
+        if (aData === kPrefRilDebuggingEnabled) {
+          this._updateDebugFlag();
+        }
+        break;
+      case NS_XPCOM_SHUTDOWN_OBSERVER_ID:
+        Services.prefs.removeObserver(kPrefRilDebuggingEnabled, this);
+        Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+        break;
+    }
+  }
+};
+
+function Icc(aRadioInterface) {
+  this._radioInterface = aRadioInterface;
+  this._listeners = [];
+}
+Icc.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIIcc]),
+
+  _radioInterface: null,
+  _imsi: null,
+  _listeners: null,
+
+  _updateCardState: function(aCardState) {
+    if (this.cardState != aCardState) {
+      this.cardState = aCardState;
+    }
+
+    this._deliverListenerEvent("notifyCardStateChanged");
+  },
+
+  // An utility function to copy objects.
+  _updateInfo: function(aSrcInfo, aDestInfo) {
+    for (let key in aSrcInfo) {
+      aDestInfo[key] = aSrcInfo[key];
+    }
+  },
+
+  /**
+   * We need to consider below cases when update iccInfo:
+   * 1. Should clear iccInfo to null if there is no card detected.
+   * 2. Need to create corresponding object based on iccType.
+   */
+  _updateIccInfo: function(aIccInfo) {
+    // Card is not detected, clear iccInfo to null.
+    if (!aIccInfo || !aIccInfo.iccid) {
+      if (this.iccInfo) {
+        if (DEBUG) {
+          debug("Card is not detected, clear iccInfo to null.");
+        }
+        this.iccInfo = null;
+        this._deliverListenerEvent("notifyIccInfoChanged");
+      }
+      return;
+    }
+
+    // If iccInfo is null, new corresponding object based on iccType.
+    if (!this.iccInfo ||
+        this.iccInfo.iccType != aIccInfo.iccType) {
+      if (aIccInfo.iccType === "ruim" || aIccInfo.iccType === "csim") {
+        this.iccInfo = new CdmaIccInfo();
+      } else if (aIccInfo.iccType === "sim" || aIccInfo.iccType === "usim") {
+        this.iccInfo = new GsmIccInfo();
+      } else {
+        this.iccInfo = new IccInfo();
+      }
+    }
+
+    this._updateInfo(aIccInfo, this.iccInfo);
+
+    this._deliverListenerEvent("notifyIccInfoChanged");
+
+    // Update lastKnownSimMcc.
+    if (aIccInfo.mcc) {
+      try {
+        Services.prefs.setCharPref("ril.lastKnownSimMcc",
+                                   aIccInfo.mcc.toString());
+      } catch (e) {}
+    }
+  },
+
+  _deliverListenerEvent: function(aName, aArgs) {
+    let listeners = this._listeners.slice();
+    for (let listener of listeners) {
+      if (this._listeners.indexOf(listener) === -1) {
+        continue;
+      }
+      let handler = listener[aName];
+      if (typeof handler != "function") {
+        throw new Error("No handler for " + aName);
+      }
+      try {
+        handler.apply(listener, aArgs);
+      } catch (e) {
+        if (DEBUG) {
+          debug("listener for " + aName + " threw an exception: " + e);
+        }
+      }
+    }
+  },
+
+  _modifyCardLock: function(aOperation, aOptions, aCallback) {
+    this._radioInterface.sendWorkerMessage(aOperation,
+                                           aOptions,
+                                           (aResponse) => {
+      if (aResponse.errorMsg) {
+        let retryCount =
+          (aResponse.retryCount !== undefined) ? aResponse.retryCount : -1;
+        aCallback.notifyCardLockError(aResponse.errorMsg, retryCount);
+        return;
+      }
+
+      aCallback.notifySuccess();
+    });
+  },
+
+  /**
+   * Helper to match the MVNO pattern with IMSI.
+   *
+   * Note: Characters 'x' and 'X' in MVNO are skipped and not compared.
+   *       E.g., if the aMvnoData passed is '310260x10xxxxxx', then the
+   *       function returns true only if imsi has the same first 6 digits, 8th
+   *       and 9th digit.
+   *
+   * @param aMvnoData
+   *        MVNO pattern.
+   * @param aImsi
+   *        IMSI of this ICC.
+   *
+   * @return true if matched.
+   */
+  _isImsiMatches: function(aMvnoData, aImsi) {
+    // This should not be an error, but a mismatch.
+    if (aMvnoData.length > aImsi.length) {
+      return false;
+    }
+
+    for (let i = 0; i < aMvnoData.length; i++) {
+      let c = aMvnoData[i];
+      if ((c !== 'x') && (c !== 'X') && (c !== aImsi[i])) {
+        return false;
+      }
+    }
+    return true;
+  },
+
+  /**
+   * nsIIcc interface.
+   */
+  iccInfo: null,
+  cardState: Ci.nsIIcc.CARD_STATE_UNKNOWN,
+
+  registerListener: function(aListener) {
+    if (this._listeners.indexOf(aListener) >= 0) {
+      throw Cr.NS_ERROR_UNEXPECTED;
+    }
+
+    this._listeners.push(aListener);
+  },
+
+  unregisterListener: function(aListener) {
+    let index = this._listeners.indexOf(aListener);
+    if (index >= 0) {
+      this._listeners.splice(index, 1);
+    }
+  },
+
+  getCardLockEnabled: function(aLockType, aCallback) {
+    this._radioInterface.sendWorkerMessage("iccGetCardLockEnabled",
+                                           { lockType: aLockType },
+                                           (aResponse) => {
+      if (aResponse.errorMsg) {
+        aCallback.notifyError(aResponse.errorMsg);
+        return;
+      }
+
+      aCallback.notifySuccessWithBoolean(aResponse.enabled);
+    });
+  },
+
+  unlockCardLock: function(aLockType, aPassword, aNewPin, aCallback) {
+    this._modifyCardLock("iccUnlockCardLock",
+                         { lockType: aLockType,
+                           password: aPassword,
+                           newPin: aNewPin },
+                         aCallback);
+  },
+
+  setCardLockEnabled: function(aLockType, aPassword, aEnabled, aCallback) {
+    this._modifyCardLock("iccSetCardLockEnabled",
+                         { lockType: aLockType,
+                           password: aPassword,
+                           enabled: aEnabled },
+                         aCallback);
+  },
+
+  changeCardLockPassword: function(aLockType, aPassword, aNewPassword, aCallback) {
+    this._modifyCardLock("iccChangeCardLockPassword",
+                         { lockType: aLockType,
+                           password: aPassword,
+                           newPassword: aNewPassword, },
+                         aCallback);
+  },
+
+  getCardLockRetryCount: function(aLockType, aCallback) {
+    this._radioInterface.sendWorkerMessage("iccGetCardLockRetryCount",
+                                           { lockType: aLockType },
+                                           (aResponse) => {
+      if (aResponse.errorMsg) {
+        aCallback.notifyError(aResponse.errorMsg);
+        return;
+      }
+
+      aCallback.notifyGetCardLockRetryCount(aResponse.retryCount);
+    });
+  },
+
+  matchMvno: function(aMvnoType, aMvnoData, aCallback) {
+    if (!aMvnoData) {
+      aCallback.notifyError(RIL.GECKO_ERROR_INVALID_PARAMETER);
+      return;
+    }
+
+    switch (aMvnoType) {
+      case Ci.nsIIcc.CARD_MVNO_TYPE_IMSI:
+        let imsi = this._imsi;
+        if (!imsi) {
+          aCallback.notifyError(RIL.GECKO_ERROR_GENERIC_FAILURE);
+          break;
+        }
+        aCallback.notifySuccessWithBoolean(
+          this._isImsiMatches(aMvnoData, imsi));
+        break;
+      case Ci.nsIIcc.CARD_MVNO_TYPE_SPN:
+        let spn = this.iccInfo && this.iccInfo.spn;
+        if (!spn) {
+          aCallback.notifyError(RIL.GECKO_ERROR_GENERIC_FAILURE);
+          break;
+        }
+        aCallback.notifySuccessWithBoolean(spn == aMvnoData);
+        break;
+      case Ci.nsIIcc.CARD_MVNO_TYPE_GID:
+        this._radioInterface.sendWorkerMessage("getGID1",
+                                               null,
+                                               (aResponse) => {
+          let gid = aResponse.gid1;
+          let mvnoDataLength = aMvnoData.length;
+
+          if (!gid) {
+            aCallback.notifyError(RIL.GECKO_ERROR_GENERIC_FAILURE);
+          } else if (mvnoDataLength > gid.length) {
+            aCallback.notifySuccessWithBoolean(false);
+          } else {
+            let result =
+              gid.substring(0, mvnoDataLength).toLowerCase() ==
+              aMvnoData.toLowerCase();
+            aCallback.notifySuccessWithBoolean(result);
+          }
+        });
+        break;
+      default:
+        aCallback.notifyError(RIL.GECKO_ERROR_MODE_NOT_SUPPORTED);
+        break;
+    }
+  },
+
+  getServiceStateEnabled: function(aService, aCallback) {
+    this._radioInterface.sendWorkerMessage("getIccServiceState",
+                                           { service: aService },
+                                           (aResponse) => {
+      if (aResponse.errorMsg) {
+        aCallback.notifyError(aResponse.errorMsg);
+        return;
+      }
+
+      aCallback.notifySuccessWithBoolean(aResponse.result);
+    });
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IccService]);
new file mode 100644
--- /dev/null
+++ b/dom/icc/gonk/IccService.manifest
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+component {df854256-9554-11e4-a16c-c39e8d106c26} IccService.js
+contract @mozilla.org/icc/gonkiccservice;1 {df854256-9554-11e4-a16c-c39e8d106c26}
\ No newline at end of file
--- a/dom/icc/moz.build
+++ b/dom/icc/moz.build
@@ -21,16 +21,20 @@ UNIFIED_SOURCES += [
     'IccListener.cpp',
     'IccManager.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
     EXTRA_JS_MODULES += [
         'gonk/StkProactiveCmdFactory.jsm',
     ]
+    EXTRA_COMPONENTS += [
+        'gonk/IccService.js',
+        'gonk/IccService.manifest',
+    ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -122,16 +122,20 @@ function updateDebugFlag() {
   DEBUG = RIL.DEBUG_RIL || debugPref;
 }
 updateDebugFlag();
 
 function debug(s) {
   dump("-*- RadioInterfaceLayer: " + s + "\n");
 }
 
+XPCOMUtils.defineLazyServiceGetter(this, "gIccService",
+                                   "@mozilla.org/icc/gonkiccservice;1",
+                                   "nsIGonkIccService");
+
 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
                                    "@mozilla.org/mobilemessage/mobilemessageservice;1",
                                    "nsIMobileMessageService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsService",
                                    "@mozilla.org/sms/gonksmsservice;1",
                                    "nsIGonkSmsService");
 
@@ -881,18 +885,18 @@ IccInfo.prototype = {
 
   // nsIIccInfo
 
   iccType: null,
   iccid: null,
   mcc: null,
   mnc: null,
   spn: null,
-  isDisplayNetworkNameRequired: null,
-  isDisplaySpnRequired: null
+  isDisplayNetworkNameRequired: false,
+  isDisplaySpnRequired: false
 };
 
 function GsmIccInfo() {}
 GsmIccInfo.prototype = {
   __proto__: IccInfo.prototype,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIGsmIccInfo,
                                          Ci.nsIIccInfo]),
 
@@ -1911,16 +1915,18 @@ RadioInterface.prototype = {
         // gRadioEnabledController should know the radio state for each client,
         // so notify gRadioEnabledController here.
         gRadioEnabledController.notifyRadioStateChanged(this.clientId,
                                                         message.radioState);
         break;
       case "cardstatechange":
         this.rilContext.cardState = message.cardState;
         gRadioEnabledController.receiveCardState(this.clientId);
+        gIccService.notifyCardStateChanged(this.clientId,
+                                           this.rilContext.cardState);
         gMessageManager.sendIccMessage("RIL:CardStateChanged",
                                        this.clientId, message);
         break;
       case "sms-received":
         this.handleSmsReceived(message);
         break;
       case "cellbroadcast-received":
         this.handleCellbroadcastMessageReceived(message);
@@ -1928,16 +1934,17 @@ RadioInterface.prototype = {
       case "nitzTime":
         this.handleNitzTime(message);
         break;
       case "iccinfochange":
         this.handleIccInfoChange(message);
         break;
       case "iccimsi":
         this.rilContext.imsi = message.imsi;
+        gIccService.notifyImsiChanged(this.clientId, this.rilContext.imsi);
         break;
       case "iccmbdn":
         this.handleIccMbdn(message);
         break;
       case "iccmwis":
         this.handleIccMwis(message.mwi);
         break;
       case "stkcommand":
@@ -2240,24 +2247,17 @@ RadioInterface.prototype = {
       this.updateInfo(message, this.rilContext.iccInfo);
     }
 
     // RIL:IccInfoChanged corresponds to a DOM event that gets fired only
     // when iccInfo has changed.
     gMessageManager.sendIccMessage("RIL:IccInfoChanged",
                                    this.clientId,
                                    message.iccid ? message : null);
-
-    // Update lastKnownSimMcc.
-    if (message.mcc) {
-      try {
-        Services.prefs.setCharPref("ril.lastKnownSimMcc",
-                                   message.mcc.toString());
-      } catch (e) {}
-    }
+    gIccService.notifyIccInfoChanged(this.clientId, this.rilContext.iccInfo);
 
     // Update lastKnownHomeNetwork.
     if (message.mcc && message.mnc) {
       let lastKnownHomeNetwork = message.mcc + "-" + message.mnc;
       // Append spn information if available.
       if (message.spn) {
         lastKnownHomeNetwork += "-" + message.spn;
       }