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
17package com.android.internal.telephony;
18
19import android.telephony.Rlog;
20import android.os.Build;
21import android.util.SparseIntArray;
22import android.content.res.Resources;
23import android.content.res.XmlResourceParser;
24import android.telephony.SmsManager;
25import android.telephony.TelephonyManager;
26
27import com.android.internal.util.XmlUtils;
28import com.android.internal.telephony.cdma.sms.UserData;
29
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32
33public 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