Bug 790192 - Part 1/2: support strict GSM SMS 7-Bit encoding, r=marshall_law
authorVicamo Yang <vyang@mozilla.com>
Wed, 03 Oct 2012 16:37:19 +0800
changeset 109134 6f1ea4ac1aa48a3939bb9ee1cce5ac205072cab3
parent 109133 c7a62be5fdd0e9a1cc39d9eaeee321a5f1469482
child 109135 97d7f66e2b301e1133e9704a04dcbbf1f43f4d02
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersmarshall_law
bugs790192
milestone18.0a1
Bug 790192 - Part 1/2: support strict GSM SMS 7-Bit encoding, r=marshall_law
b2g/app/b2g.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/ril_consts.js
dom/system/gonk/ril_worker.js
modules/libpref/src/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -382,16 +382,17 @@ pref("browser.link.open_newwindow.restri
 pref("dom.mozBrowserFramesEnabled", true);
 
 pref("dom.ipc.tabs.disabled", false);
 
 pref("dom.ipc.browser_frames.oop_by_default", false);
 
 // Temporary permission hack for WebSMS
 pref("dom.sms.enabled", true);
+pref("dom.sms.strict7BitEncoding", false); // Disabled by default.
 
 // Temporary permission hack for WebContacts
 pref("dom.mozContacts.enabled", true);
 
 // WebAlarms
 pref("dom.mozAlarms.enabled", true);
 
 // WebSettings
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -1622,39 +1622,47 @@ RadioInterfaceLayer.prototype = {
    * Calculate encoded length using specified locking/single shift table
    *
    * @param message
    *        message string to be encoded.
    * @param langTable
    *        locking shift table string.
    * @param langShiftTable
    *        single shift table string.
+   * @param strict7BitEncoding
+   *        Optional. Enable Latin characters replacement with corresponding
+   *        ones in GSM SMS 7-bit default alphabet.
    *
    * @return encoded length in septets.
    *
    * @note that the algorithm used in this function must match exactly with
    * GsmPDUHelper#writeStringAsSeptets.
    */
-  _countGsm7BitSeptets: function _countGsm7BitSeptets(message, langTable, langShiftTable) {
+  _countGsm7BitSeptets: function _countGsm7BitSeptets(message, langTable, langShiftTable, strict7BitEncoding) {
     let length = 0;
     for (let msgIndex = 0; msgIndex < message.length; msgIndex++) {
-      let septet = langTable.indexOf(message.charAt(msgIndex));
+      let c = message.charAt(msgIndex);
+      if (strict7BitEncoding) {
+        c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c;
+      }
+
+      let septet = langTable.indexOf(c);
 
       // According to 3GPP TS 23.038, section 6.1.1 General notes, "The
       // characters marked '1)' are not used but are displayed as a space."
       if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) {
         continue;
       }
 
       if (septet >= 0) {
         length++;
         continue;
       }
 
-      septet = langShiftTable.indexOf(message.charAt(msgIndex));
+      septet = langShiftTable.indexOf(c);
       if (septet < 0) {
         return -1;
       }
 
       // According to 3GPP TS 23.038 B.2, "This code represents a control
       // character and therefore must not be used for language specific
       // characters."
       if (septet == RIL.PDU_NL_RESERVED_CONTROL) {
@@ -1673,35 +1681,39 @@ RadioInterfaceLayer.prototype = {
   },
 
   /**
    * Calculate user data length of specified message string encoded in GSM 7Bit
    * alphabets.
    *
    * @param message
    *        a message string to be encoded.
+   * @param strict7BitEncoding
+   *        Optional. Enable Latin characters replacement with corresponding
+   *        ones in GSM SMS 7-bit default alphabet.
    *
    * @return null or an options object with attributes `dcs`,
    *         `userDataHeaderLength`, `encodedFullBodyLength`, `langIndex`,
    *         `langShiftIndex`, `segmentMaxSeq` set.
    *
    * @see #_calculateUserDataLength().
    */
-  _calculateUserDataLength7Bit: function _calculateUserDataLength7Bit(message) {
+  _calculateUserDataLength7Bit: function _calculateUserDataLength7Bit(message, strict7BitEncoding) {
     let options = null;
     let minUserDataSeptets = Number.MAX_VALUE;
     for (let i = 0; i < this.enabledGsmTableTuples.length; i++) {
       let [langIndex, langShiftIndex] = this.enabledGsmTableTuples[i];
 
       const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
       const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
 
       let bodySeptets = this._countGsm7BitSeptets(message,
                                                   langTable,
-                                                  langShiftTable);
+                                                  langShiftTable,
+                                                  strict7BitEncoding);
       if (bodySeptets < 0) {
         continue;
       }
 
       let headerLen = 0;
       if (langIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) {
         headerLen += 3; // IEI + len + langIndex
       }
@@ -1734,16 +1746,17 @@ RadioInterfaceLayer.prototype = {
 
       options = {
         dcs: RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET,
         encodedFullBodyLength: bodySeptets,
         userDataHeaderLength: headerLen,
         langIndex: langIndex,
         langShiftIndex: langShiftIndex,
         segmentMaxSeq: segments,
+        strict7BitEncoding: strict7BitEncoding
       };
     }
 
     return options;
   },
 
   /**
    * Calculate user data length of specified message string encoded in UCS2.
@@ -1781,16 +1794,19 @@ RadioInterfaceLayer.prototype = {
     };
   },
 
   /**
    * Calculate user data length and its encoding.
    *
    * @param message
    *        a message string to be encoded.
+   * @param strict7BitEncoding
+   *        Optional. Enable Latin characters replacement with corresponding
+   *        ones in GSM SMS 7-bit default alphabet.
    *
    * @return an options object with some or all of following attributes set:
    *
    * @param dcs
    *        Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
    *        constants.
    * @param fullBody
    *        Original unfragmented text message.
@@ -1804,18 +1820,18 @@ RadioInterfaceLayer.prototype = {
    *        Table index used for normal 7-bit encoded character lookup.
    * @param langShiftIndex
    *        Table index used for escaped 7-bit encoded character lookup.
    * @param segmentMaxSeq
    *        Max sequence number of a multi-part messages, or 1 for single one.
    *        This number might not be accurate for a multi-part message until
    *        it's processed by #_fragmentText() again.
    */
-  _calculateUserDataLength: function _calculateUserDataLength(message) {
-    let options = this._calculateUserDataLength7Bit(message);
+  _calculateUserDataLength: function _calculateUserDataLength(message, strict7BitEncoding) {
+    let options = this._calculateUserDataLength7Bit(message, strict7BitEncoding);
     if (!options) {
       options = this._calculateUserDataLengthUCS2(message);
     }
 
     if (options) {
       options.fullBody = message;
     }
 
@@ -1829,34 +1845,42 @@ RadioInterfaceLayer.prototype = {
    * @param text
    *        text string to be fragmented.
    * @param langTable
    *        locking shift table string.
    * @param langShiftTable
    *        single shift table string.
    * @param headerLen
    *        Length of prepended user data header.
+   * @param strict7BitEncoding
+   *        Optional. Enable Latin characters replacement with corresponding
+   *        ones in GSM SMS 7-bit default alphabet.
    *
    * @return an array of objects. See #_fragmentText() for detailed definition.
    */
-  _fragmentText7Bit: function _fragmentText7Bit(text, langTable, langShiftTable, headerLen) {
+  _fragmentText7Bit: function _fragmentText7Bit(text, langTable, langShiftTable, headerLen, strict7BitEncoding) {
     const headerSeptets = Math.ceil((headerLen ? headerLen + 1 : 0) * 8 / 7);
     const segmentSeptets = RIL.PDU_MAX_USER_DATA_7BIT - headerSeptets;
     let ret = [];
     let begin = 0, len = 0;
     for (let i = 0, inc = 0; i < text.length; i++) {
-      let septet = langTable.indexOf(text.charAt(i));
+      let c = text.charAt(i);
+      if (strict7BitEncoding) {
+        c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c;
+      }
+
+      let septet = langTable.indexOf(c);
       if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) {
         continue;
       }
 
       if (septet >= 0) {
         inc = 1;
       } else {
-        septet = langShiftTable.indexOf(text.charAt(i));
+        septet = langShiftTable.indexOf(c);
         if (septet < 0) {
           throw new Error("Given text cannot be encoded with GSM 7-bit Alphabet!");
         }
 
         if (septet == RIL.PDU_NL_RESERVED_CONTROL) {
           continue;
         }
 
@@ -1917,59 +1941,76 @@ RadioInterfaceLayer.prototype = {
    * attributes `body`, substring for this segment, `encodedBodyLength`,
    * length of the encoded segment body in septets.
    *
    * @param text
    *        Text string to be fragmented.
    * @param options
    *        Optional pre-calculated option object. The output array will be
    *        stored at options.segments if there are multiple segments.
+   * @param strict7BitEncoding
+   *        Optional. Enable Latin characters replacement with corresponding
+   *        ones in GSM SMS 7-bit default alphabet.
    *
    * @return Populated options object.
    */
-  _fragmentText: function _fragmentText(text, options) {
+  _fragmentText: function _fragmentText(text, options, strict7BitEncoding) {
     if (!options) {
-      options = this._calculateUserDataLength(text);
+      options = this._calculateUserDataLength(text, strict7BitEncoding);
     }
 
     if (options.segmentMaxSeq <= 1) {
       options.segments = null;
       return options;
     }
 
     if (options.dcs == RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
       const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[options.langIndex];
       const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[options.langShiftIndex];
       options.segments = this._fragmentText7Bit(options.fullBody,
                                                 langTable, langShiftTable,
-                                                options.userDataHeaderLength);
+                                                options.userDataHeaderLength,
+                                                options.strict7BitEncodingEncoding);
     } else {
       options.segments = this._fragmentTextUCS2(options.fullBody,
                                                 options.userDataHeaderLength);
     }
 
     // Re-sync options.segmentMaxSeq with actual length of returning array.
     options.segmentMaxSeq = options.segments.length;
 
     return options;
   },
 
   getNumberOfMessagesForText: function getNumberOfMessagesForText(text) {
-    return this._fragmentText(text).segmentMaxSeq;
+    let strict7BitEncoding;
+    try {
+      strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding");
+    } catch (e) {
+      strict7BitEncoding = false;
+    }
+    return this._fragmentText(text, null, strict7BitEncoding).segmentMaxSeq;
   },
 
   sendSMS: function sendSMS(number, message, requestId, processId) {
-    let options = this._calculateUserDataLength(message);
+    let strict7BitEncoding;
+    try {
+      strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding");
+    } catch (e) {
+      strict7BitEncoding = false;
+    }
+
+    let options = this._calculateUserDataLength(message, strict7BitEncoding);
     options.rilMessageType = "sendSMS";
     options.number = number;
     options.requestId = requestId;
     options.processId = processId;
     options.requestStatusReport = true;
 
-    this._fragmentText(message, options);
+    this._fragmentText(message, options, strict7BitEncoding);
     if (options.segmentMaxSeq > 1) {
       options.segmentRef16Bit = this.segmentRef16Bit;
       options.segmentRef = this.nextSegmentRef;
     }
 
     // Keep current SMS message info for sent/delivered notifications
     this.createSmsEnvelope(options);
 
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -1644,16 +1644,28 @@ const PDU_NL_SINGLE_SHIFT_TABLES = [
   + "                "
 ];
 
 // Special SMS Message Indication constants
 const PDU_MWI_STORE_TYPE_BIT     = 0x80;
 const PDU_MWI_STORE_TYPE_DISCARD = 0x00;
 const PDU_MWI_STORE_TYPE_STORE   = 0x80;
 
+const GSM_SMS_STRICT_7BIT_CHARMAP = {
+  "\u00C1": "\u0041", // Á(\u00C1) => A(\u0041)
+  "\u00E1": "\u0061", // á(\u00E1) => a(\u0061)
+  "\u00CD": "\u0049", // Í(\u00CD) => I(\u0049)
+  "\u00ED": "\u0069", // í(\u00ED) => i(\u0069)
+  "\u00D3": "\u004F", // Ó(\u00D3) => O(\u004F)
+  "\u00F3": "\u006F", // ó(\u00F3) => o(\u006F)
+  "\u00DA": "\u0055", // Ú(\u00DA) => U(\u0055)
+  "\u00FA": "\u0075", // ú(\u00FA) => u(\u0075)
+  "\u00E7": "\u00C7"  // ç(\u00E7) => Ç(\u00C7)
+};
+
 const RADIOTECH_FAMILY_3GPP = 1;  // GSM, WCDMA, LTE
 const RADIOTECH_FAMILY_3GPP2 = 2; // CDMA, EVDO
 
 const DATACALL_RADIOTECHNOLOGY_CDMA = 0;
 const DATACALL_RADIOTECHNOLOGY_GSM = 1;
 
 const DATACALL_AUTH_NONE = 0;
 const DATACALL_AUTH_PAP = 1;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -5022,35 +5022,40 @@ let GsmPDUHelper = {
        * The fill bits in the last octet may happen to form a full septet and
        * be appended at the end of result string.
        */
       ret = ret.slice(0, length);
     }
     return ret;
   },
 
-  writeStringAsSeptets: function writeStringAsSeptets(message, paddingBits, langIndex, langShiftIndex) {
+  writeStringAsSeptets: function writeStringAsSeptets(message, paddingBits, langIndex, langShiftIndex, strict7BitEncoding) {
     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
 
     let dataBits = paddingBits;
     let data = 0;
     for (let i = 0; i < message.length; i++) {
-      let septet = langTable.indexOf(message[i]);
+      let c = message.charAt(i);
+      if (strict7BitEncoding) {
+        c = GSM_SMS_STRICT_7BIT_CHARMAP[c] || c;
+      }
+
+      let septet = langTable.indexOf(c);
       if (septet == PDU_NL_EXTENDED_ESCAPE) {
         continue;
       }
 
       if (septet >= 0) {
         data |= septet << dataBits;
         dataBits += 7;
       } else {
-        septet = langShiftTable.indexOf(message[i]);
+        septet = langShiftTable.indexOf(c);
         if (septet == -1) {
-          throw new Error(message[i] + " not in 7 bit alphabet "
+          throw new Error("'" + c + "' is not in 7 bit alphabet "
                           + langIndex + ":" + langShiftIndex + "!");
         }
 
         if (septet == PDU_NL_RESERVED_CONTROL) {
           continue;
         }
 
         data |= PDU_NL_EXTENDED_ESCAPE << dataBits;
@@ -5985,16 +5990,17 @@ let GsmPDUHelper = {
     }
     let address = options.number;
     let body = options.body;
     let dcs = options.dcs;
     let userDataHeaderLength = options.userDataHeaderLength;
     let encodedBodyLength = options.encodedBodyLength;
     let langIndex = options.langIndex;
     let langShiftIndex = options.langShiftIndex;
+    let strict7BitEncoding = options.strict7BitEncoding;
 
     // SMS-SUBMIT Format:
     //
     // PDU Type - 1 octet
     // Message Reference - 1 octet
     // DA - Destination Address - 2 to 12 octets
     // PID - Protocol Identifier - 1 octet
     // DCS - Data Coding Scheme - 1 octet
@@ -6107,17 +6113,18 @@ let GsmPDUHelper = {
     }
 
     if (headerOctets) {
       this.writeUserDataHeader(options);
     }
 
     switch (dcs) {
       case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
-        this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex);
+        this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex,
+                                  strict7BitEncoding);
         break;
       case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
         // Unsupported.
         break;
       case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
         this.writeUCS2String(body);
         break;
     }
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3699,16 +3699,19 @@ pref("dom.vibrator.enabled", true);
 pref("dom.vibrator.max_vibrate_ms", 10000);
 pref("dom.vibrator.max_vibrate_list_len", 128);
 
 // Battery API
 pref("dom.battery.enabled", true);
 
 // WebSMS
 pref("dom.sms.enabled", false);
+// Enable Latin characters replacement with corresponding ones in GSM SMS
+// 7-bit default alphabet.
+pref("dom.sms.strict7BitEncoding", false);
 
 // WebContacts
 pref("dom.mozContacts.enabled", false);
 
 // WebAlarms
 pref("dom.mozAlarms.enabled", false);
 
 // WebSettings