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