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