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