Sms7BitEncodingTranslator
Xia Ying [Tue, 29 Jul 2014 20:33:44 +0000 (15:33 -0500)]
Bug: 16197894
Change-Id: I389248343c4ee621a1e8980481987742623bcbe7

src/java/android/telephony/SmsManager.java
src/java/android/telephony/SmsMessage.java
src/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java [new file with mode: 0644]
src/java/com/android/internal/telephony/cdma/SmsMessage.java
src/java/com/android/internal/telephony/gsm/SmsMessage.java

index 8d9e734..ffc6329 100644 (file)
@@ -808,7 +808,7 @@ public final class SmsManager {
      *
      * @hide
      */
-    boolean isImsSmsSupported() {
+    public boolean isImsSmsSupported() {
         boolean boSupported = false;
         try {
             ISms iccISms = getISmsService();
@@ -833,7 +833,7 @@ public final class SmsManager {
      *
      * @hide
      */
-    String getImsSmsFormat() {
+    public String getImsSmsFormat() {
         String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN;
         try {
             ISms iccISms = getISmsService();
index 81a4121..a154477 100644 (file)
@@ -26,6 +26,7 @@ import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
 
 import java.lang.Math;
 import java.util.ArrayList;
@@ -363,8 +364,16 @@ public class SmsMessage {
             }
         }
 
+        String newMsgBody = null;
+        Resources r = Resources.getSystem();
+        if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+            newMsgBody  = Sms7BitEncodingTranslator.translate(text);
+        }
+        if (TextUtils.isEmpty(newMsgBody)) {
+            newMsgBody = text;
+        }
         int pos = 0;  // Index in code units.
-        int textLen = text.length();
+        int textLen = newMsgBody.length();
         ArrayList<String> result = new ArrayList<String>(ted.msgCount);
         while (pos < textLen) {
             int nextPos = 0;  // Counts code units.
@@ -374,7 +383,7 @@ public class SmsMessage {
                     nextPos = pos + Math.min(limit, textLen - pos);
                 } else {
                     // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
-                    nextPos = GsmAlphabet.findGsmSeptetLimitIndex(text, pos, limit,
+                    nextPos = GsmAlphabet.findGsmSeptetLimitIndex(newMsgBody, pos, limit,
                             ted.languageTable, ted.languageShiftTable);
                 }
             } else {  // Assume unicode.
@@ -385,7 +394,7 @@ public class SmsMessage {
                           nextPos + " >= " + textLen + ")");
                 break;
             }
-            result.add(text.substring(pos, nextPos));
+            result.add(newMsgBody.substring(pos, nextPos));
             pos = nextPos;
         }
         return result;
diff --git a/src/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/src/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
new file mode 100644 (file)
index 0000000..6cb1f14
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.Rlog;
+import android.os.Build;
+import android.util.SparseIntArray;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.telephony.cdma.sms.UserData;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class Sms7BitEncodingTranslator {
+    private static final String TAG = "Sms7BitEncodingTranslator";
+    private static final boolean DBG = Build.IS_DEBUGGABLE ;
+    private static boolean mIs7BitTranslationTableLoaded = false;
+    private static SparseIntArray mTranslationTable = null;
+    private static SparseIntArray mTranslationTableCommon = null;
+    private static SparseIntArray mTranslationTableGSM = null;
+    private static SparseIntArray mTranslationTableCDMA = null;
+
+    // Parser variables
+    private static final String XML_START_TAG = "SmsEnforce7BitTranslationTable";
+    private static final String XML_TRANSLATION_TYPE_TAG = "TranslationType";
+    private static final String XML_CHARACTOR_TAG = "Character";
+    private static final String XML_FROM_TAG = "from";
+    private static final String XML_TO_TAG = "to";
+
+    /**
+     * Translates each message character that is not supported by GSM 7bit
+     * alphabet into a supported one
+     *
+     * @param message
+     *            message to be translated
+     * @param throwsException
+     *            if true and some error occurs during translation, an exception
+     *            is thrown; otherwise a null String is returned
+     * @return translated message or null if some error occur
+     */
+    public static String translate(CharSequence message) {
+        if (message == null) {
+            Rlog.w(TAG, "Null message can not be translated");
+            return null;
+        }
+
+        int size = message.length();
+        if (size <= 0) {
+            return "";
+        }
+
+        if (!mIs7BitTranslationTableLoaded) {
+            mTranslationTableCommon = new SparseIntArray();
+            mTranslationTableGSM = new SparseIntArray();
+            mTranslationTableCDMA = new SparseIntArray();
+            load7BitTranslationTableFromXml();
+            mIs7BitTranslationTableLoaded = true;
+        }
+
+        if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
+                (mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
+                (mTranslationTableCDMA != null && mTranslationTableCDMA.size() > 0)) {
+            char[] output = new char[size];
+            for (int i = 0; i < size; i++) {
+                output[i] = translateIfNeeded(message.charAt(i));
+            }
+
+            return String.valueOf(output);
+        }
+
+        return null;
+    }
+
+    /**
+     * Translates a single character into its corresponding acceptable one, if
+     * needed, based on GSM 7-bit alphabet
+     *
+     * @param c
+     *            character to be translated
+     * @return original character, if it's present on GSM 7-bit alphabet; a
+     *         corresponding character, based on the translation table or white
+     *         space, if no mapping is found in the translation table for such
+     *         character
+     */
+    private static char translateIfNeeded(char c) {
+        if (noTranslationNeeded(c)) {
+            if (DBG) {
+                Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c));
+            }
+            return c;
+        }
+
+        /*
+         * Trying to translate unicode to Gsm 7-bit alphabet; If c is not
+         * present on translation table, c does not belong to Unicode Latin-1
+         * (Basic + Supplement), so we don't know how to translate it to a Gsm
+         * 7-bit character! We replace c for an empty space and advises the user
+         * about it.
+         */
+        int translation = -1;
+
+        if (mTranslationTableCommon != null) {
+            translation = mTranslationTableCommon.get(c, -1);
+        }
+
+        if (translation == -1) {
+            if (useCdmaFormatForMoSms()) {
+                if (mTranslationTableCDMA != null) {
+                    translation = mTranslationTableCDMA.get(c, -1);
+                }
+            } else {
+                if (mTranslationTableGSM != null) {
+                    translation = mTranslationTableGSM.get(c, -1);
+                }
+            }
+        }
+
+        if (translation != -1) {
+            if (DBG) {
+                Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to "
+                        + Integer.toHexString(translation) + " (" + (char) translation + ")");
+            }
+            return (char) translation;
+        } else {
+            if (DBG) {
+                Rlog.w(TAG, "No translation found for " + Integer.toHexString(c)
+                        + "! Replacing for empty space");
+            }
+            return ' ';
+        }
+    }
+
+    private static boolean noTranslationNeeded(char c) {
+        if (useCdmaFormatForMoSms()) {
+            return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1;
+        }
+        else {
+            return GsmAlphabet.isGsmSeptets(c);
+        }
+    }
+
+    private static boolean useCdmaFormatForMoSms() {
+        if (!SmsManager.getDefault().isImsSmsSupported()) {
+            // use Voice technology to determine SMS format.
+            return TelephonyManager.getDefault().getCurrentPhoneType()
+                    == PhoneConstants.PHONE_TYPE_CDMA;
+        }
+        // IMS is registered with SMS support, check the SMS format supported
+        return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+    }
+
+    /**
+     * Load the whole translation table file from the framework resource
+     * encoded in XML.
+     */
+    private static void load7BitTranslationTableFromXml() {
+        XmlResourceParser parser = null;
+        Resources r = Resources.getSystem();
+
+        if (parser == null) {
+            if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file");
+            parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table);
+        }
+
+        try {
+            XmlUtils.beginDocument(parser, XML_START_TAG);
+            while (true)  {
+                XmlUtils.nextElement(parser);
+                String tag = parser.getName();
+                if (DBG) {
+                    Rlog.d(TAG, "tag: " + tag);
+                }
+                if (XML_TRANSLATION_TYPE_TAG.equals(tag)) {
+                    String type = parser.getAttributeValue(null, "Type");
+                    if (DBG) {
+                        Rlog.d(TAG, "type: " + type);
+                    }
+                    if (type.equals("common")) {
+                        mTranslationTable = mTranslationTableCommon;
+                    } else if (type.equals("gsm")) {
+                        mTranslationTable = mTranslationTableGSM;
+                    } else if (type.equals("cdma")) {
+                        mTranslationTable = mTranslationTableCDMA;
+                    } else {
+                        Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type);
+                    }
+                } else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) {
+                    int from = parser.getAttributeUnsignedIntValue(null,
+                            XML_FROM_TAG, -1);
+                    int to = parser.getAttributeUnsignedIntValue(null,
+                            XML_TO_TAG, -1);
+                    if ((from != -1) && (to != -1)) {
+                        if (DBG) {
+                            Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from)
+                                    .toUpperCase() + " -> " + Integer.toHexString(to)
+                                    .toUpperCase());
+                        }
+                        mTranslationTable.put (from, to);
+                    } else {
+                        Rlog.d(TAG, "Invalid translation table file format");
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded");
+        } catch (Exception e) {
+            Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e);
+        } finally {
+            if (parser instanceof XmlResourceParser) {
+                ((XmlResourceParser)parser).close();
+            }
+        }
+    }
+}
index 5c99a43..a3af3a5 100644 (file)
@@ -24,6 +24,8 @@ import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.telephony.Rlog;
 import android.util.Log;
+import android.text.TextUtils;
+import android.content.res.Resources;
 
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsConstants;
@@ -38,6 +40,7 @@ import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.util.BitwiseInputStream;
 import com.android.internal.util.HexDump;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
 
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -458,7 +461,15 @@ public class SmsMessage extends SmsMessageBase {
      */
     public static TextEncodingDetails calculateLength(CharSequence messageBody,
             boolean use7bitOnly) {
-        return BearerData.calcTextEncodingDetails(messageBody, use7bitOnly);
+        CharSequence newMsgBody = null;
+        Resources r = Resources.getSystem();
+        if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+            newMsgBody  = Sms7BitEncodingTranslator.translate(messageBody);
+        }
+        if (TextUtils.isEmpty(newMsgBody)) {
+            newMsgBody = messageBody;
+        }
+        return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly);
     }
 
     /**
index 236c3b1..a1f029f 100644 (file)
@@ -20,6 +20,7 @@ import android.telephony.PhoneNumberUtils;
 import android.text.format.Time;
 import android.telephony.Rlog;
 import android.content.res.Resources;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.EncodeException;
 import com.android.internal.telephony.GsmAlphabet;
@@ -27,6 +28,7 @@ import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
 
 import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
@@ -783,11 +785,19 @@ public class SmsMessage extends SmsMessageBase {
      */
     public static TextEncodingDetails calculateLength(CharSequence msgBody,
             boolean use7bitOnly) {
-        TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(msgBody, use7bitOnly);
+        CharSequence newMsgBody = null;
+        Resources r = Resources.getSystem();
+        if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+            newMsgBody  = Sms7BitEncodingTranslator.translate(msgBody);
+        }
+        if (TextUtils.isEmpty(newMsgBody)) {
+            newMsgBody = msgBody;
+        }
+        TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
         if (ted == null) {
             ted = new TextEncodingDetails();
-            int octets = msgBody.length() * 2;
-            ted.codeUnitCount = msgBody.length();
+            int octets = newMsgBody.length() * 2;
+            ted.codeUnitCount = newMsgBody.length();
             if (octets > MAX_USER_DATA_BYTES) {
                 // If EMS is not supported, break down EMS into single segment SMS
                 // and add page info " x/y".