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