1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony;
18 
19 import android.telephony.Rlog;
20 import android.os.Build;
21 import android.util.SparseIntArray;
22 import android.content.res.Resources;
23 import android.content.res.XmlResourceParser;
24 import android.telephony.SmsManager;
25 import android.telephony.TelephonyManager;
26 
27 import com.android.internal.util.XmlUtils;
28 import com.android.internal.telephony.cdma.sms.UserData;
29 
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
32 
33 public class Sms7BitEncodingTranslator {
34     private static final String TAG = "Sms7BitEncodingTranslator";
35     private static final boolean DBG = Build.IS_DEBUGGABLE ;
36     private static boolean mIs7BitTranslationTableLoaded = false;
37     private static SparseIntArray mTranslationTable = null;
38     private static SparseIntArray mTranslationTableCommon = null;
39     private static SparseIntArray mTranslationTableGSM = null;
40     private static SparseIntArray mTranslationTableCDMA = null;
41 
42     // Parser variables
43     private static final String XML_START_TAG = "SmsEnforce7BitTranslationTable";
44     private static final String XML_TRANSLATION_TYPE_TAG = "TranslationType";
45     private static final String XML_CHARACTOR_TAG = "Character";
46     private static final String XML_FROM_TAG = "from";
47     private static final String XML_TO_TAG = "to";
48 
49     /**
50      * Translates each message character that is not supported by GSM 7bit
51      * alphabet into a supported one
52      *
53      * @param message
54      *            message to be translated
55      * @param throwsException
56      *            if true and some error occurs during translation, an exception
57      *            is thrown; otherwise a null String is returned
58      * @return translated message or null if some error occur
59      */
translate(CharSequence message)60     public static String translate(CharSequence message) {
61         if (message == null) {
62             Rlog.w(TAG, "Null message can not be translated");
63             return null;
64         }
65 
66         int size = message.length();
67         if (size <= 0) {
68             return "";
69         }
70 
71         if (!mIs7BitTranslationTableLoaded) {
72             mTranslationTableCommon = new SparseIntArray();
73             mTranslationTableGSM = new SparseIntArray();
74             mTranslationTableCDMA = new SparseIntArray();
75             load7BitTranslationTableFromXml();
76             mIs7BitTranslationTableLoaded = true;
77         }
78 
79         if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
80                 (mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
81                 (mTranslationTableCDMA != null && mTranslationTableCDMA.size() > 0)) {
82             char[] output = new char[size];
83             boolean isCdmaFormat = useCdmaFormatForMoSms();
84             for (int i = 0; i < size; i++) {
85                 output[i] = translateIfNeeded(message.charAt(i), isCdmaFormat);
86             }
87 
88             return String.valueOf(output);
89         }
90 
91         return null;
92     }
93 
94     /**
95      * Translates a single character into its corresponding acceptable one, if
96      * needed, based on GSM 7-bit alphabet
97      *
98      * @param c
99      *            character to be translated
100      * @return original character, if it's present on GSM 7-bit alphabet; a
101      *         corresponding character, based on the translation table or white
102      *         space, if no mapping is found in the translation table for such
103      *         character
104      */
translateIfNeeded(char c, boolean isCdmaFormat)105     private static char translateIfNeeded(char c, boolean isCdmaFormat) {
106         if (noTranslationNeeded(c, isCdmaFormat)) {
107             if (DBG) {
108                 Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c));
109             }
110             return c;
111         }
112 
113         /*
114          * Trying to translate unicode to Gsm 7-bit alphabet; If c is not
115          * present on translation table, c does not belong to Unicode Latin-1
116          * (Basic + Supplement), so we don't know how to translate it to a Gsm
117          * 7-bit character! We replace c for an empty space and advises the user
118          * about it.
119          */
120         int translation = -1;
121 
122         if (mTranslationTableCommon != null) {
123             translation = mTranslationTableCommon.get(c, -1);
124         }
125 
126         if (translation == -1) {
127             if (isCdmaFormat) {
128                 if (mTranslationTableCDMA != null) {
129                     translation = mTranslationTableCDMA.get(c, -1);
130                 }
131             } else {
132                 if (mTranslationTableGSM != null) {
133                     translation = mTranslationTableGSM.get(c, -1);
134                 }
135             }
136         }
137 
138         if (translation != -1) {
139             if (DBG) {
140                 Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to "
141                         + Integer.toHexString(translation) + " (" + (char) translation + ")");
142             }
143             return (char) translation;
144         } else {
145             if (DBG) {
146                 Rlog.w(TAG, "No translation found for " + Integer.toHexString(c)
147                         + "! Replacing for empty space");
148             }
149             return ' ';
150         }
151     }
152 
noTranslationNeeded(char c, boolean isCdmaFormat)153     private static boolean noTranslationNeeded(char c, boolean isCdmaFormat) {
154         if (isCdmaFormat) {
155             return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1;
156         }
157         else {
158             return GsmAlphabet.isGsmSeptets(c);
159         }
160     }
161 
useCdmaFormatForMoSms()162     private static boolean useCdmaFormatForMoSms() {
163         if (!SmsManager.getDefault().isImsSmsSupported()) {
164             // use Voice technology to determine SMS format.
165             return TelephonyManager.getDefault().getCurrentPhoneType()
166                     == PhoneConstants.PHONE_TYPE_CDMA;
167         }
168         // IMS is registered with SMS support, check the SMS format supported
169         return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
170     }
171 
172     /**
173      * Load the whole translation table file from the framework resource
174      * encoded in XML.
175      */
load7BitTranslationTableFromXml()176     private static void load7BitTranslationTableFromXml() {
177         XmlResourceParser parser = null;
178         Resources r = Resources.getSystem();
179 
180         if (parser == null) {
181             if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file");
182             parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table);
183         }
184 
185         try {
186             XmlUtils.beginDocument(parser, XML_START_TAG);
187             while (true)  {
188                 XmlUtils.nextElement(parser);
189                 String tag = parser.getName();
190                 if (DBG) {
191                     Rlog.d(TAG, "tag: " + tag);
192                 }
193                 if (XML_TRANSLATION_TYPE_TAG.equals(tag)) {
194                     String type = parser.getAttributeValue(null, "Type");
195                     if (DBG) {
196                         Rlog.d(TAG, "type: " + type);
197                     }
198                     if (type.equals("common")) {
199                         mTranslationTable = mTranslationTableCommon;
200                     } else if (type.equals("gsm")) {
201                         mTranslationTable = mTranslationTableGSM;
202                     } else if (type.equals("cdma")) {
203                         mTranslationTable = mTranslationTableCDMA;
204                     } else {
205                         Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type);
206                     }
207                 } else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) {
208                     int from = parser.getAttributeUnsignedIntValue(null,
209                             XML_FROM_TAG, -1);
210                     int to = parser.getAttributeUnsignedIntValue(null,
211                             XML_TO_TAG, -1);
212                     if ((from != -1) && (to != -1)) {
213                         if (DBG) {
214                             Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from)
215                                     .toUpperCase() + " -> " + Integer.toHexString(to)
216                                     .toUpperCase());
217                         }
218                         mTranslationTable.put (from, to);
219                     } else {
220                         Rlog.d(TAG, "Invalid translation table file format");
221                     }
222                 } else {
223                     break;
224                 }
225             }
226             if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded");
227         } catch (Exception e) {
228             Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e);
229         } finally {
230             if (parser instanceof XmlResourceParser) {
231                 ((XmlResourceParser)parser).close();
232             }
233         }
234     }
235 }
236