Bug 793111 - Part 2 - getting PLMN list and deciding required carrier name component r=vicamo
authorPatrick Wang <kk1fff@patrickz.net>
Fri, 02 Nov 2012 19:34:41 +0800
changeset 123591 afd9eb3d5e5ab23d9c1b7c6dbda91fedef6c03db
parent 123590 71d28da5c50e9b50f4b9619038d599504f75a7f3
child 123592 985e380010e329be7b3ef0f19d7fbe19020d9d63
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvicamo
bugs793111
milestone20.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 793111 - Part 2 - getting PLMN list and deciding required carrier name component r=vicamo
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/ril_consts.js
dom/system/gonk/ril_worker.js
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -1503,16 +1503,18 @@ RadioInterfaceLayer.prototype = {
     let oldIcc = this.rilContext.icc;
     this.rilContext.icc = message;
    
     let iccInfoChanged = !oldIcc ||
                          oldIcc.iccid != message.iccid ||
                          oldIcc.mcc != message.mcc || 
                          oldIcc.mnc != message.mnc ||
                          oldIcc.spn != message.spn ||
+                         oldIcc.isDisplayNetworkNameRequired != message.isDisplayNetworkNameRequired ||
+                         oldIcc.isDisplaySpnRequired != message.isDisplaySpnRequired ||
                          oldIcc.msisdn != message.msisdn;
     if (!iccInfoChanged) {
       return;
     }
     // RIL:IccInfoChanged corresponds to a DOM event that gets fired only
     // when the MCC or MNC codes have changed.
     this._sendTargetMessage("mobileconnection", "RIL:IccInfoChanged", message);
   },
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -400,16 +400,17 @@ this.ICC_COMMAND_READ_RECORD = 0xb2;
 this.ICC_COMMAND_GET_RESPONSE = 0xc0;
 this.ICC_COMMAND_UPDATE_BINARY = 0xd6;
 this.ICC_COMMAND_UPDATE_RECORD = 0xdc;
 
 // ICC constants, GSM SIM file ids from TS 51.011
 this.ICC_EF_ICCID  = 0x2fe2;
 this.ICC_EF_IMG    = 0x4f20;
 this.ICC_EF_PBR    = 0x4f30;
+this.ICC_EF_PLMNsel = 0x6f30; // PLMN for SIM
 this.ICC_EF_SST    = 0x6f38;
 this.ICC_EF_UST    = 0x6f38; // For USIM
 this.ICC_EF_ADN    = 0x6f3a;
 this.ICC_EF_FDN    = 0x6f3b;
 this.ICC_EF_SMS    = 0x6f3c;
 this.ICC_EF_MSISDN = 0x6f40;
 this.ICC_EF_SPN    = 0x6f46;
 this.ICC_EF_SDN    = 0x6f49;
@@ -558,16 +559,20 @@ this.COMPREHENSIONTLV_TAG_CAUSE = 0x1a;
 this.COMPREHENSIONTLV_TAG_LOCATION_STATUS = 0x1b;
 this.COMPREHENSIONTLV_TAG_TRANSACTION_ID = 0x1c;
 this.COMPREHENSIONTLV_TAG_EVENT_LIST = 0x19;
 this.COMPREHENSIONTLV_TAG_ICON_ID = 0x1e;
 this.COMPREHENSIONTLV_TAG_ICON_ID_LIST = 0x1f;
 this.COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE = 0x2b;
 this.COMPREHENSIONTLV_TAG_URL = 0x31;
 
+// Tags for Service Provider Display Information TLV
+this.SPDI_TAG_SPDI = 0xa3;
+this.SPDI_TAG_PLMN_LIST = 0x80;
+
 // Device identifiers, see TS 11.14, clause 12.7
 this.STK_DEVICE_ID_KEYPAD = 0x01;
 this.STK_DEVICE_ID_DISPLAY = 0x02;
 this.STK_DEVICE_ID_EARPIECE = 0x03;
 this.STK_DEVICE_ID_SIM = 0x81;
 this.STK_DEVICE_ID_ME = 0x82;
 this.STK_DEVICE_ID_NETWORK = 0x83;
 
@@ -902,25 +907,29 @@ this.STK_SUPPORTED_TERMINAL_PROFILE = [
  * (U)SIM Services.
  *
  * @see 3GPP TS 51.011 10.3.7 (SIM) and 3GPP TS 31.102 4.2.8 (USIM).
  */
 this.GECKO_ICC_SERVICES = {
   sim: {
     ADN: 2,
     FDN: 3,
+    PLMNSEL: 7,
+    SPN: 17,
     SDN: 18,
     DATA_DOWNLOAD_SMS_PP: 26,
     BDN: 31
   },
   usim: {
     FDN: 2,
     SDN: 4,
     BDN: 6,
-    DATA_DOWNLOAD_SMS_PP: 28
+    SPN: 19,
+    DATA_DOWNLOAD_SMS_PP: 28,
+    SPDI: 51
   }
 };
 
 /**
  * GSM PDU constants
  */
 
 // PDU TYPE-OF-ADDRESS
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -699,16 +699,21 @@ let RIL = {
     /**
      * Strings
      */
     this.IMEI = null;
     this.IMEISV = null;
     this.SMSC = null;
 
     /**
+     * ICC information that is not exposed to Gaia.
+     */
+    this.iccInfoPrivate = {};
+
+    /**
      * ICC information, such as MSISDN, IMSI, ...etc.
      */
     this.iccInfo = {};
 
     /**
      * Application identification for apps in ICC.
      */
     this.aid = null;
@@ -1166,30 +1171,109 @@ let RIL = {
   /**
    * Fetch ICC records.
    */
   fetchICCRecords: function fetchICCRecords() {
     this.getICCID();
     this.getIMSI();
     this.getMSISDN();
     this.getAD();
-    this.getSPN();
     this.getSST();
     this.getMBDN();
   },
 
   /**
    * Update the ICC information to RadioInterfaceLayer.
    */
   _handleICCInfoChange: function _handleICCInfoChange() {
     this.iccInfo.rilMessageType = "iccinfochange";
     this.sendDOMMessage(this.iccInfo);
   },
 
   /**
+   * This will compute the spnDisplay field of the network.
+   * See TS 22.101 Annex A and TS 51.011 10.3.11 for details.
+   *
+   * @return True if some of iccInfo is changed in by this function.
+   */
+  updateDisplayCondition: function updateDisplayCondition() {
+    // If EFspn isn't existed in SIM or it haven't been read yet, we should
+    // just set isDisplayNetworkNameRequired = true and
+    // isDisplaySpnRequired = false
+    let iccInfo = this.iccInfo;
+    let iccInfoPriv = this.iccInfoPrivate;
+    let iccSpn = iccInfoPriv.SPN;
+    let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired;
+    let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired;
+
+    if (!iccSpn) {
+      iccInfo.isDisplayNetworkNameRequired = true;
+      iccInfo.isDisplaySpnRequired = false;
+    } else {
+      let operatorMnc = this.operator.mnc;
+      let operatorMcc = this.operator.mcc;
+
+      // First detect if we are on HPLMN or one of the PLMN
+      // specified by the SIM card.
+      let isOnMatchingPlmn = false;
+
+      // If the current network is the one defined as mcc/mnc
+      // in SIM card, it's okay.
+      if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) {
+        isOnMatchingPlmn = true;
+      }
+
+      // Test to see if operator's mcc/mnc match mcc/mnc of PLMN.
+      if (!isOnMatchingPlmn && iccInfoPriv.PLMN) {
+        let iccPlmn = iccInfoPriv.PLMN; // PLMN list
+        for (let plmn in iccPlmn) {
+          let plmnMcc = iccPlmn[plmn].mcc;
+          let plmnMnc = iccPlmn[plmn].mnc;
+          isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc);
+          if (isOnMatchingPlmn) {
+            break;
+          }
+        }
+      }
+
+      if (isOnMatchingPlmn) {
+        // The first bit of display condition tells us if we should display
+        // registered PLMN.
+        if (DEBUG) debug("updateDisplayCondition: PLMN is HPLMN or PLMN is in PLMN list");
+        if (iccSpn.spnDisplayCondition & 0x01) {
+          iccInfo.isDisplayNetworkNameRequired = true;
+          iccInfo.isDisplaySpnRequired = false;
+        } else {
+          iccInfo.isDisplayNetworkNameRequired = false;
+          iccInfo.isDisplaySpnRequired = false;
+        }
+      } else {
+        // The second bit of display condition tells us if we should display
+        // registered PLMN.
+        if (DEBUG) debug("updateICCDisplayName: PLMN isn't HPLMN and PLMN isn't in PLMN list");
+        if (iccSpn.spnDisplayCondition & 0x02) {
+          iccInfo.isDisplayNetworkNameRequired = false;
+          iccInfo.isDisplaySpnRequired = false;
+        } else {
+          iccInfo.isDisplayNetworkNameRequired = false;
+          iccInfo.isDisplaySpnRequired = true;
+        }
+      }
+    }
+
+    if (DEBUG) {
+      debug("updateDisplayCondition: isDisplayNetworkNameRequired = " + iccInfo.isDisplayNetworkNameRequired);
+      debug("updateDisplayCondition: isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired);
+    }
+
+    return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) ||
+            (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired));
+  },
+
+  /**
    * Get EF_phase.
    * This EF is only available in SIM.
    */
   getICCPhase: function getICCPhase() {
     function callback() {
       let length = Buf.readUint32();
 
       let phase = GsmPDUHelper.readHexOctet();
@@ -1340,22 +1424,30 @@ let RIL = {
    */
   getSPN: function getSPN() {
     function callback() {
       let length = Buf.readUint32();
       // Each octet is encoded into two chars.
       // Minus 1 because first is used to store display condition
       let len = (length / 2) - 1;
       let spnDisplayCondition = GsmPDUHelper.readHexOctet();
-      this.iccInfo.spn = GsmPDUHelper.readAlphaIdentifier(len);
+      let spn = GsmPDUHelper.readAlphaIdentifier(len);
       Buf.readStringDelimiter(length);
 
       if (DEBUG) {
-        debug("SPN: spn=" + this.iccInfo.spn + ", spnDisplayCondition=" + spnDisplayCondition);
+        debug("SPN: spn = " + spn +
+              ", spnDisplayCondition = " + spnDisplayCondition);
       }
+
+      this.iccInfoPrivate.SPN = {
+        spn : spn,
+        spnDisplayCondition : spnDisplayCondition,
+      };
+      this.iccInfo.spn = spn;
+      this.updateDisplayCondition();
       this._handleICCInfoChange();
     }
 
     this.iccIO({
       command:   ICC_COMMAND_GET_RESPONSE,
       fileId:    ICC_EF_SPN,
       pathId:    this._getPathIdForICCRecord(ICC_EF_SPN),
       p1:        0, // For GET_RESPONSE, p1 = 0
@@ -1364,16 +1456,114 @@ let RIL = {
       data:      null,
       pin2:      null,
       type:      EF_TYPE_TRANSPARENT,
       callback:  callback,
     });
   },
 
   /**
+   * Read the PLMNsel (Public Land Mobile Network) from the ICC.
+   *
+   * See ETSI TS 100.977 section 10.3.4 EF_PLMNsel
+   */
+  getPLMNSelector: function getPLMNSelector() {
+    function callback() {
+      if (DEBUG) debug("PLMN: [PLMN Selector] Process PLMN Selector");
+
+      let length = Buf.readUint32();
+      this.iccInfoPrivate.PLMN = this.readPLMNEntries(length/6);
+      Buf.readStringDelimiter(length);
+
+      if (DEBUG) debug("PLMN: [PLMN Selector] " + JSON.stringify(this.iccInfoPrivate.PLMN));
+
+      if (this.updateDisplayCondition()) {
+        this._handleICCInfoChange();
+      }
+    }
+
+    // PLMN List is Service 7 in SIM, EF_PLMNsel
+    this.iccIO({
+      command:   ICC_COMMAND_GET_RESPONSE,
+      fileId:    ICC_EF_PLMNsel,
+      pathId:    this._getPathIdForICCRecord(ICC_EF_PLMNsel),
+      p1:        0, // For GET_RESPONSE, p1 = 0
+      p2:        0, // For GET_RESPONSE, p2 = 0
+      p3:        GET_RESPONSE_EF_SIZE_BYTES,
+      data:      null,
+      pin2:      null,
+      type:      EF_TYPE_TRANSPARENT,
+      callback:  callback,
+    });
+  },
+
+  /**
+   * Read the SPDI (Service Provider Display Information) from the ICC.
+   *
+   * See TS 131.102 section 4.2.66
+   */
+  getSPDI: function getSPDI() {
+    function callback() {
+      if (DEBUG) debug("PLMN: [SPDI] Process SPDI callback");
+      let length = Buf.readUint32();
+      let tlvTag;
+      let tlvLen;
+      let readLen = 0;
+      let endLoop = false;
+      this.iccInfoPrivate.PLMN = null;
+      while ((readLen < length) && !endLoop) {
+        tlvTag = GsmPDUHelper.readHexOctet();
+        tlvLen = GsmPDUHelper.readHexOctet();
+        readLen += 2; // For tag and length.
+        switch (tlvTag) {
+        case SPDI_TAG_SPDI:
+          // The value part itself is a TLV.
+          continue;
+        case SPDI_TAG_PLMN_LIST:
+          // This PLMN list is what we want.
+          this.iccInfoPrivate.PLMN = readPLMNEntries(tlvLen/6);
+          readLen += tlvLen;
+          endLoop = true;
+          break;
+        default:
+          // We don't care about its content if its tag is not SPDI nor
+          // PLMN_LIST.
+          GsmPDUHelper.readHexOctetArray(tlvLen);
+          readLen += tlvLen;
+        }
+      }
+
+      // Consume unread octets.
+      if (length - readLen > 0) {
+        GsmPDUHelper.readHexOctetArray(length - readLen);
+      }
+      Buf.readStringDelimiter(length);
+
+      if (DEBUG) debug("PLMN: [SPDI] " + JSON.stringify(this.iccInfoPrivate.PLMN));
+      if (this.updateDisplayCondition()) {
+        this._handleICCInfoChange();
+      }
+    }
+
+    // PLMN List is Servive 51 in USIM, EF_SPDI
+    this.iccIO({
+      command:   ICC_COMMAND_GET_RESPONSE,
+      fileId:    ICC_EF_SPDI,
+      pathId:    this._getPathIdForICCRecord(ICC_EF_SPDI),
+      p1:        0, // For GET_RESPONSE, p1 = 0
+      p2:        0, // For GET_RESPONSE, p2 = 0
+      p3:        GET_RESPONSE_EF_SIZE_BYTES,
+      data:      null,
+      pin2:      null,
+      type:      EF_TYPE_TRANSPARENT,
+      callback:  callback,
+    });
+  },
+
+  /**
    * Get whether specificed (U)SIM service is available.
    *
    * @param geckoService
    *        Service name like "ADN", "BDN", etc.
    *
    * @return true if the service is enabled, false otherwise.
    */
   isICCServiceAvailable: function isICCServiceAvailable(geckoService) {
@@ -1443,16 +1633,34 @@ let RIL = {
 
       if (DEBUG) {
         let str = "";
         for (let i = 0; i < this.iccInfo.sst.length; i++) {
           str += this.iccInfo.sst[i] + ", ";
         }
         debug("SST: " + str);
       }
+
+      // Fetch SPN and PLMN list, if some of them are available.
+      if (this.isICCServiceAvailable("SPN")) {
+        if (DEBUG) debug("SPN: SPN is available");
+        this.getSPN();
+      } else {
+        if (DEBUG) debug("SPN: SPN service is not available");
+      }
+
+      if (this.isICCServiceAvailable("PLMNSEL")) {
+        if (DEBUG) debug("PLMN: PLMNSEL available.");
+        this.getPLMNSelector();
+      } else if (this.isICCServiceAvailable("SPDI")) {
+        if (DEBUG) debug("PLMN: SPDI available.");
+        this.getSPDI();
+      } else {
+        if (DEBUG) debug("PLMN: Both PLMNSEL/SPDI not available");
+      }
     }
 
     // ICC_EF_UST has the same value with ICC_EF_SST.
     this.iccIO({
       command:   ICC_COMMAND_GET_RESPONSE,
       fileId:    ICC_EF_SST,
       pathId:    this._getPathIdForICCRecord(ICC_EF_SST),
       p1:        0, // For GET_RESPONSE, p1 = 0
@@ -1681,16 +1889,83 @@ let RIL = {
       if (tlvs[i].tag == tag) {
         return tlvs[i];
       }
     }
     return null;
   },
 
   /**
+   *  Read the list of PLMN (Public Land Mobile Network) entries
+   *  We cannot directly rely on readSwappedNibbleBcdToString(),
+   *  since it will no correctly handle some corner-cases that are
+   *  not a problem in our case (0xFF 0xFF 0xFF).
+   *
+   *  @param length The number of PLMN records.
+   *  @return An array of string corresponding to the PLMNs.
+   */
+  readPLMNEntries: function readPLMNEntries(length) {
+    let plmnList = [];
+    // each PLMN entry has 3 byte
+    debug("readPLMNEntries: PLMN entries length = " + length);
+    let index = 0;
+    while (index < length) {
+      // Unused entries will be 0xFFFFFF, according to EF_SPDI
+      // specs (TS 131 102, section 4.2.66)
+      try {
+        let plmn = [GsmPDUHelper.readHexOctet(),
+                    GsmPDUHelper.readHexOctet(),
+                    GsmPDUHelper.readHexOctet()];
+        if (DEBUG) debug("readPLMNEntries: Reading PLMN entry: [" + index +
+                         "]: '" + plmn + "'");
+        if (plmn[0] != 0xFF &&
+            plmn[1] != 0xFF &&
+            plmn[2] != 0xFF) {
+          let semiOctets = [];
+          for (let i = 0; i < plmn.length; i++) {
+            semiOctets.push((plmn[idx] & 0xF0) >> 4);
+            semiOctets.push(plmn[idx] & 0x0F);
+          }
+
+          // According to TS 24.301, 9.9.3.12, the semi octets is arranged
+          // in format:
+          // Byte 1: MCC[2] | MCC[1]
+          // Byte 2: MNC[3] | MCC[3]
+          // Byte 3: MNC[2] | MNC[1]
+          // Therefore, we need to rearrage them.
+          let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
+                          semiOctets[5], semiOctets[4], semiOctets[2]];
+          let buf = "";
+          let plmnEntry = {};
+          for (let i = 0; i < reformat.length; i++) {
+            if (reformat[i] != 0xF) {
+              buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
+            }
+            if (i === 2) {
+              // 0-2: MCC
+              plmnEntry.mcc = parseInt(buf);
+              buf = "";
+            } else if (i === 5) {
+              // 3-5: MNC
+              plmnEntry.mnc = parseInt(buf);
+            }
+          }
+          if (DEBUG) debug("readPLMNEntries: PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc);
+          plmnList.push(plmnEntry);
+        }
+      } catch (e) {
+        if (DEBUG) debug("readPLMNEntries: PLMN entry " + index + " is invalid.");
+        break;
+      }
+      index ++;
+    }
+    return plmnList;
+  },
+
+  /**
    * Get UICC Phonebook.
    *
    * @params contactType
    *         "ADN" or "FDN".
    */
   getICCContacts: function getICCContacts(options) {
     if (!this.appType) {
       options.rilMessageType = "icccontacts";
@@ -3077,28 +3352,30 @@ let RIL = {
       case CARD_APPTYPE_SIM:
         switch (fileId) {
           case ICC_EF_FDN:
           case ICC_EF_MSISDN:
             return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
 
           case ICC_EF_AD:
           case ICC_EF_MBDN:
+          case ICC_EF_PLMNsel:
           case ICC_EF_SPN:
           case ICC_EF_SST:
             return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
         }
       case CARD_APPTYPE_USIM:
         switch (fileId) {
           case ICC_EF_AD:
           case ICC_EF_FDN:
           case ICC_EF_MBDN:
           case ICC_EF_UST:
           case ICC_EF_MSISDN:
           case ICC_EF_SPN:
+          case ICC_EF_SPDI:
             return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
 
           default:
             // The file ids in USIM phone book entries are decided by the
 	    // card manufacturer. So if we don't match any of the cases
 	    // above and if its a USIM return the phone book path.
             return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
         }
@@ -3433,17 +3710,19 @@ let RIL = {
 
       if (networkTuple) {
         try {
           this._processNetworkTuple(networkTuple, this.operator);
         } catch (e) {
           debug("Error processing operator tuple: " + e);
         }
       }
-
+      if (this.updateDisplayCondition()) {
+        this._handleICCInfoChange();
+      }
       this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
     }
   },
 
   /**
    * Helpers for processing call state and handle the active call.
    */
   _processCalls: function _processCalls(newCalls) {