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.compat.annotation.UnsupportedAppUsage;
20 import android.content.res.Resources;
21 import android.content.res.XmlResourceParser;
22 import android.util.SparseIntArray;
23 
24 import com.android.internal.telephony.cdma.sms.UserData;
25 import com.android.internal.telephony.util.TelephonyUtils;
26 import com.android.internal.telephony.util.XmlUtils;
27 import com.android.telephony.Rlog;
28 
29 public class Sms7BitEncodingTranslator {
30     private static final String TAG = "Sms7BitEncodingTranslator";
31     @UnsupportedAppUsage
32     private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
33     private static boolean mIs7BitTranslationTableLoaded = false;
34     private static SparseIntArray mTranslationTable = null;
35     @UnsupportedAppUsage
36     private static SparseIntArray mTranslationTableCommon = null;
37     @UnsupportedAppUsage
38     private static SparseIntArray mTranslationTableGSM = null;
39     @UnsupportedAppUsage
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 alphabet into a supported
51      * one.
52      *
53      * @param message message to be translated.
54      * @param isCdmaFormat true if cdma format should be used.
55      * @return translated message or null if some error occur.
56      */
translate(CharSequence message, boolean isCdmaFormat)57     public static String translate(CharSequence message, boolean isCdmaFormat) {
58         if (message == null) {
59             Rlog.w(TAG, "Null message can not be translated");
60             return null;
61         }
62 
63         int size = message.length();
64         if (size <= 0) {
65             return "";
66         }
67 
68         ensure7BitTranslationTableLoaded();
69 
70         if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
71                 (mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
72                 (mTranslationTableCDMA != null && mTranslationTableCDMA.size() > 0)) {
73             char[] output = new char[size];
74             for (int i = 0; i < size; i++) {
75                 output[i] = translateIfNeeded(message.charAt(i), isCdmaFormat);
76             }
77 
78             return String.valueOf(output);
79         }
80 
81         return null;
82     }
83 
84     /**
85      * Translates a single character into its corresponding acceptable one, if
86      * needed, based on GSM 7-bit alphabet
87      *
88      * @param c
89      *            character to be translated
90      * @return original character, if it's present on GSM 7-bit alphabet; a
91      *         corresponding character, based on the translation table or white
92      *         space, if no mapping is found in the translation table for such
93      *         character
94      */
translateIfNeeded(char c, boolean isCdmaFormat)95     private static char translateIfNeeded(char c, boolean isCdmaFormat) {
96         if (noTranslationNeeded(c, isCdmaFormat)) {
97             if (DBG) {
98                 Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c));
99             }
100             return c;
101         }
102 
103         /*
104          * Trying to translate unicode to Gsm 7-bit alphabet; If c is not
105          * present on translation table, c does not belong to Unicode Latin-1
106          * (Basic + Supplement), so we don't know how to translate it to a Gsm
107          * 7-bit character! We replace c for an empty space and advises the user
108          * about it.
109          */
110         int translation = -1;
111 
112         ensure7BitTranslationTableLoaded();
113 
114         if (mTranslationTableCommon != null) {
115             translation = mTranslationTableCommon.get(c, -1);
116         }
117 
118         if (translation == -1) {
119             if (isCdmaFormat) {
120                 if (mTranslationTableCDMA != null) {
121                     translation = mTranslationTableCDMA.get(c, -1);
122                 }
123             } else {
124                 if (mTranslationTableGSM != null) {
125                     translation = mTranslationTableGSM.get(c, -1);
126                 }
127             }
128         }
129 
130         if (translation != -1) {
131             if (DBG) {
132                 Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to "
133                         + Integer.toHexString(translation) + " (" + (char) translation + ")");
134             }
135             return (char) translation;
136         } else {
137             if (DBG) {
138                 Rlog.w(TAG, "No translation found for " + Integer.toHexString(c)
139                         + "! Replacing for empty space");
140             }
141             return ' ';
142         }
143     }
144 
noTranslationNeeded(char c, boolean isCdmaFormat)145     private static boolean noTranslationNeeded(char c, boolean isCdmaFormat) {
146         if (isCdmaFormat) {
147             return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1;
148         }
149         else {
150             return GsmAlphabet.isGsmSeptets(c);
151         }
152     }
153 
ensure7BitTranslationTableLoaded()154     private static void ensure7BitTranslationTableLoaded() {
155         synchronized (Sms7BitEncodingTranslator.class) {
156             if (!mIs7BitTranslationTableLoaded) {
157                 mTranslationTableCommon = new SparseIntArray();
158                 mTranslationTableGSM = new SparseIntArray();
159                 mTranslationTableCDMA = new SparseIntArray();
160                 load7BitTranslationTableFromXml();
161                 mIs7BitTranslationTableLoaded = true;
162             }
163         }
164     }
165 
166     /**
167      * Load the whole translation table file from the framework resource
168      * encoded in XML.
169      */
load7BitTranslationTableFromXml()170     private static void load7BitTranslationTableFromXml() {
171         XmlResourceParser parser = null;
172         Resources r = Resources.getSystem();
173 
174         if (parser == null) {
175             if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file");
176             parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table);
177         }
178 
179         try {
180             XmlUtils.beginDocument(parser, XML_START_TAG);
181             while (true)  {
182                 XmlUtils.nextElement(parser);
183                 String tag = parser.getName();
184                 if (DBG) {
185                     Rlog.d(TAG, "tag: " + tag);
186                 }
187                 if (XML_TRANSLATION_TYPE_TAG.equals(tag)) {
188                     String type = parser.getAttributeValue(null, "Type");
189                     if (DBG) {
190                         Rlog.d(TAG, "type: " + type);
191                     }
192                     if (type.equals("common")) {
193                         mTranslationTable = mTranslationTableCommon;
194                     } else if (type.equals("gsm")) {
195                         mTranslationTable = mTranslationTableGSM;
196                     } else if (type.equals("cdma")) {
197                         mTranslationTable = mTranslationTableCDMA;
198                     } else {
199                         Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type);
200                     }
201                 } else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) {
202                     int from = parser.getAttributeUnsignedIntValue(null,
203                             XML_FROM_TAG, -1);
204                     int to = parser.getAttributeUnsignedIntValue(null,
205                             XML_TO_TAG, -1);
206                     if ((from != -1) && (to != -1)) {
207                         if (DBG) {
208                             Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from)
209                                     .toUpperCase() + " -> " + Integer.toHexString(to)
210                                     .toUpperCase());
211                         }
212                         mTranslationTable.put (from, to);
213                     } else {
214                         Rlog.d(TAG, "Invalid translation table file format");
215                     }
216                 } else {
217                     break;
218                 }
219             }
220             if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded");
221         } catch (Exception e) {
222             Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e);
223         } finally {
224             if (parser instanceof XmlResourceParser) {
225                 ((XmlResourceParser)parser).close();
226             }
227         }
228     }
229 }
230