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 */ 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 for (int i = 0; i < size; i++) { 84 output[i] = translateIfNeeded(message.charAt(i)); 85 } 86 87 return String.valueOf(output); 88 } 89 90 return null; 91 } 92 93 /** 94 * Translates a single character into its corresponding acceptable one, if 95 * needed, based on GSM 7-bit alphabet 96 * 97 * @param c 98 * character to be translated 99 * @return original character, if it's present on GSM 7-bit alphabet; a 100 * corresponding character, based on the translation table or white 101 * space, if no mapping is found in the translation table for such 102 * character 103 */ 104 private static char translateIfNeeded(char c) { 105 if (noTranslationNeeded(c)) { 106 if (DBG) { 107 Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c)); 108 } 109 return c; 110 } 111 112 /* 113 * Trying to translate unicode to Gsm 7-bit alphabet; If c is not 114 * present on translation table, c does not belong to Unicode Latin-1 115 * (Basic + Supplement), so we don't know how to translate it to a Gsm 116 * 7-bit character! We replace c for an empty space and advises the user 117 * about it. 118 */ 119 int translation = -1; 120 121 if (mTranslationTableCommon != null) { 122 translation = mTranslationTableCommon.get(c, -1); 123 } 124 125 if (translation == -1) { 126 if (useCdmaFormatForMoSms()) { 127 if (mTranslationTableCDMA != null) { 128 translation = mTranslationTableCDMA.get(c, -1); 129 } 130 } else { 131 if (mTranslationTableGSM != null) { 132 translation = mTranslationTableGSM.get(c, -1); 133 } 134 } 135 } 136 137 if (translation != -1) { 138 if (DBG) { 139 Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to " 140 + Integer.toHexString(translation) + " (" + (char) translation + ")"); 141 } 142 return (char) translation; 143 } else { 144 if (DBG) { 145 Rlog.w(TAG, "No translation found for " + Integer.toHexString(c) 146 + "! Replacing for empty space"); 147 } 148 return ' '; 149 } 150 } 151 152 private static boolean noTranslationNeeded(char c) { 153 if (useCdmaFormatForMoSms()) { 154 return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1; 155 } 156 else { 157 return GsmAlphabet.isGsmSeptets(c); 158 } 159 } 160 161 private static boolean useCdmaFormatForMoSms() { 162 if (!SmsManager.getDefault().isImsSmsSupported()) { 163 // use Voice technology to determine SMS format. 164 return TelephonyManager.getDefault().getCurrentPhoneType() 165 == PhoneConstants.PHONE_TYPE_CDMA; 166 } 167 // IMS is registered with SMS support, check the SMS format supported 168 return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat())); 169 } 170 171 /** 172 * Load the whole translation table file from the framework resource 173 * encoded in XML. 174 */ 175 private static void load7BitTranslationTableFromXml() { 176 XmlResourceParser parser = null; 177 Resources r = Resources.getSystem(); 178 179 if (parser == null) { 180 if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file"); 181 parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table); 182 } 183 184 try { 185 XmlUtils.beginDocument(parser, XML_START_TAG); 186 while (true) { 187 XmlUtils.nextElement(parser); 188 String tag = parser.getName(); 189 if (DBG) { 190 Rlog.d(TAG, "tag: " + tag); 191 } 192 if (XML_TRANSLATION_TYPE_TAG.equals(tag)) { 193 String type = parser.getAttributeValue(null, "Type"); 194 if (DBG) { 195 Rlog.d(TAG, "type: " + type); 196 } 197 if (type.equals("common")) { 198 mTranslationTable = mTranslationTableCommon; 199 } else if (type.equals("gsm")) { 200 mTranslationTable = mTranslationTableGSM; 201 } else if (type.equals("cdma")) { 202 mTranslationTable = mTranslationTableCDMA; 203 } else { 204 Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type); 205 } 206 } else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) { 207 int from = parser.getAttributeUnsignedIntValue(null, 208 XML_FROM_TAG, -1); 209 int to = parser.getAttributeUnsignedIntValue(null, 210 XML_TO_TAG, -1); 211 if ((from != -1) && (to != -1)) { 212 if (DBG) { 213 Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from) 214 .toUpperCase() + " -> " + Integer.toHexString(to) 215 .toUpperCase()); 216 } 217 mTranslationTable.put (from, to); 218 } else { 219 Rlog.d(TAG, "Invalid translation table file format"); 220 } 221 } else { 222 break; 223 } 224 } 225 if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded"); 226 } catch (Exception e) { 227 Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e); 228 } finally { 229 if (parser instanceof XmlResourceParser) { 230 ((XmlResourceParser)parser).close(); 231 } 232 } 233 } 234 } 235