1fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie/*
2fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* Copyright (C) 2013 Samsung System LSI
3fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* Licensed under the Apache License, Version 2.0 (the "License");
4fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* you may not use this file except in compliance with the License.
5fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* You may obtain a copy of the License at
6fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie*
7fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie*      http://www.apache.org/licenses/LICENSE-2.0
8fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie*
9fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* Unless required by applicable law or agreed to in writing, software
10fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* distributed under the License is distributed on an "AS IS" BASIS,
11fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* See the License for the specific language governing permissions and
13fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie* limitations under the License.
14fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie*/
15fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xiepackage com.android.bluetooth.map;
16fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
175a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport android.database.Cursor;
185a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport android.util.Base64;
19326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.util.Log;
20326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
215a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport com.android.bluetooth.mapapi.BluetoothMapContract;
225a60e47497f21f64e6d79420dc4c56c1907df22akschulz
235a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.io.ByteArrayOutputStream;
245a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.io.UnsupportedEncodingException;
255a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.nio.charset.Charset;
265a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.nio.charset.IllegalCharsetNameException;
275a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.text.SimpleDateFormat;
285a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.util.Arrays;
295a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.util.BitSet;
305a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.util.Date;
315a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.util.regex.Matcher;
325a60e47497f21f64e6d79420dc4c56c1907df22akschulzimport java.util.regex.Pattern;
335a60e47497f21f64e6d79420dc4c56c1907df22akschulz
34fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
35fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie/**
36fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie * Various utility methods and generic defines that can be used throughout MAPS
37fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie */
38fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xiepublic class BluetoothMapUtils {
39fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
405a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final String TAG = "BluetoothMapUtils";
41326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private static final boolean D = BluetoothMapService.DEBUG;
42fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    private static final boolean V = BluetoothMapService.VERBOSE;
43326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /* We use the upper 4 bits for the type mask.
44326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * TODO: When more types are needed, consider just using a number
45326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *       in stead of a bit to indicate the message type. Then 4
46326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *       bit can be use for 16 different message types.
47fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
485a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final long HANDLE_TYPE_MASK            = (((long)0xff)<<56);
495a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final long HANDLE_TYPE_MMS_MASK        = (((long)0x01)<<56);
505a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final long HANDLE_TYPE_EMAIL_MASK      = (((long)0x02)<<56);
515a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final long HANDLE_TYPE_SMS_GSM_MASK    = (((long)0x04)<<56);
525a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final long HANDLE_TYPE_SMS_CDMA_MASK   = (((long)0x08)<<56);
535a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final long HANDLE_TYPE_IM_MASK         = (((long)0x10)<<56);
545a60e47497f21f64e6d79420dc4c56c1907df22akschulz
555a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static final long CONVO_ID_TYPE_SMS_MMS = 1;
565a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static final long CONVO_ID_TYPE_EMAIL_IM= 2;
575a60e47497f21f64e6d79420dc4c56c1907df22akschulz
585a60e47497f21f64e6d79420dc4c56c1907df22akschulz    // MAP supported feature bit - included from MAP Spec 1.2
595a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_DEFAULT_BITMASK                    = 0x0000001F;
605a60e47497f21f64e6d79420dc4c56c1907df22akschulz
615a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_NOTIFICATION_REGISTRATION_BIT      = 1 << 0;
625a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_NOTIFICATION_BIT                   = 1 << 1;
635a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_BROWSING_BIT                       = 1 << 2;
645a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_UPLOADING_BIT                      = 1 << 3;
655a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_DELETE_BIT                         = 1 << 4;
665a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_INSTANCE_INFORMATION_BIT           = 1 << 5;
675a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT       = 1 << 6;
685a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_EVENT_REPORT_V12_BIT               = 1 << 7;
695a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_MESSAGE_FORMAT_V11_BIT             = 1 << 8;
705a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT     = 1 << 9;
715a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_PERSISTENT_MESSAGE_HANDLE_BIT      = 1 << 10;
725a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_DATABASE_INDENTIFIER_BIT           = 1 << 11;
735a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT         = 1 << 12;
745a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT   = 1 << 13;
755a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT    = 1 << 14;
765a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT  = 1 << 15;
775a60e47497f21f64e6d79420dc4c56c1907df22akschulz
785a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_PBAP_CONTACT_CROSS_REFERENCE_BIT   = 1 << 16;
795a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_NOTIFICATION_FILTERING_BIT         = 1 << 17;
805a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT       = 1 << 18;
815a60e47497f21f64e6d79420dc4c56c1907df22akschulz
825a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final String MAP_V10_STR = "1.0";
835a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final String MAP_V11_STR = "1.1";
845a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final String MAP_V12_STR = "1.2";
85fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
865a60e47497f21f64e6d79420dc4c56c1907df22akschulz    // Event Report versions
875a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_EVENT_REPORT_V10           = 10; // MAP spec 1.1
885a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_EVENT_REPORT_V11           = 11; // MAP spec 1.2
895a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_EVENT_REPORT_V12           = 12; // MAP spec 1.3 'to be' incl. IM
90bbb4110b455b3aa29106d5b4f0a37e1be8e09475Casper Bonde
915a60e47497f21f64e6d79420dc4c56c1907df22akschulz    // Message Format versions
925a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_MESSAGE_FORMAT_V10         = 10; // MAP spec below 1.3
935a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_MESSAGE_FORMAT_V11         = 11; // MAP spec 1.3
945a60e47497f21f64e6d79420dc4c56c1907df22akschulz
955a60e47497f21f64e6d79420dc4c56c1907df22akschulz    // Message Listing Format versions
965a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_MESSAGE_LISTING_FORMAT_V10 = 10; // MAP spec below 1.3
975a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static final int MAP_MESSAGE_LISTING_FORMAT_V11 = 11; // MAP spec 1.3
98bbb4110b455b3aa29106d5b4f0a37e1be8e09475Casper Bonde
99fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
100fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * This enum is used to convert from the bMessage type property to a type safe
101fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * type. Hence do not change the names of the enum values.
102fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
103fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public enum TYPE{
1045a60e47497f21f64e6d79420dc4c56c1907df22akschulz        NONE,
105fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        EMAIL,
106fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        SMS_GSM,
107fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        SMS_CDMA,
1085a60e47497f21f64e6d79420dc4c56c1907df22akschulz        MMS,
109ed70219e41ba68196798dcbf75b782d13fb88603kschulz        IM;
110ed70219e41ba68196798dcbf75b782d13fb88603kschulz        private static TYPE[] allValues = values();
111ed70219e41ba68196798dcbf75b782d13fb88603kschulz        public static TYPE fromOrdinal(int n) {
112ed70219e41ba68196798dcbf75b782d13fb88603kschulz            if(n < allValues.length)
113ed70219e41ba68196798dcbf75b782d13fb88603kschulz               return allValues[n];
114ed70219e41ba68196798dcbf75b782d13fb88603kschulz            return NONE;
115ed70219e41ba68196798dcbf75b782d13fb88603kschulz        }
1165a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
1175a60e47497f21f64e6d79420dc4c56c1907df22akschulz
1185a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static public String getDateTimeString(long timestamp) {
1195a60e47497f21f64e6d79420dc4c56c1907df22akschulz        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
1205a60e47497f21f64e6d79420dc4c56c1907df22akschulz        Date date = new Date(timestamp);
1215a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
1225a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
1235a60e47497f21f64e6d79420dc4c56c1907df22akschulz
1245a60e47497f21f64e6d79420dc4c56c1907df22akschulz
1255a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static void printCursor(Cursor c) {
1265a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if (D) {
1275a60e47497f21f64e6d79420dc4c56c1907df22akschulz            StringBuilder sb = new StringBuilder();
1285a60e47497f21f64e6d79420dc4c56c1907df22akschulz            sb.append("\nprintCursor:\n");
1295a60e47497f21f64e6d79420dc4c56c1907df22akschulz            for(int i = 0; i < c.getColumnCount(); i++) {
1305a60e47497f21f64e6d79420dc4c56c1907df22akschulz                if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) ||
1315a60e47497f21f64e6d79420dc4c56c1907df22akschulz                   c.getColumnName(i).equals(
1325a60e47497f21f64e6d79420dc4c56c1907df22akschulz                           BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) ||
1335a60e47497f21f64e6d79420dc4c56c1907df22akschulz                   c.getColumnName(i).equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) ||
1345a60e47497f21f64e6d79420dc4c56c1907df22akschulz                   c.getColumnName(i).equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE) ){
1355a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    sb.append("  ").append(c.getColumnName(i)).append(" : ").append(
1365a60e47497f21f64e6d79420dc4c56c1907df22akschulz                            getDateTimeString(c.getLong(i))).append("\n");
1375a60e47497f21f64e6d79420dc4c56c1907df22akschulz                } else {
1385a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    sb.append("  ").append(c.getColumnName(i)).append(" : ").append(
1395a60e47497f21f64e6d79420dc4c56c1907df22akschulz                            c.getString(i)).append("\n");
1405a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
1415a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
1425a60e47497f21f64e6d79420dc4c56c1907df22akschulz            Log.d(TAG, sb.toString());
1435a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
144fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
145fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
146326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public static String getLongAsString(long v) {
147326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        char[] result = new char[16];
148326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        int v1 = (int) (v & 0xffffffff);
149326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        int v2 = (int) ((v>>32) & 0xffffffff);
150326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        int c;
151326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        for (int i = 0; i < 8; i++) {
152326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            c = v2 & 0x0f;
153326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            c += (c < 10) ? '0' : ('A'-10);
154326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            result[7 - i] = (char) c;
155326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            v2 >>= 4;
156326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            c = v1 & 0x0f;
157326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            c += (c < 10) ? '0' : ('A'-10);
158326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            result[15 - i] = (char)c;
159326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            v1 >>= 4;
160326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
161326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return new String(result);
162326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
163326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
164fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
1655a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Converts a hex-string to a long - please mind that Java has no unsigned data types, hence
1665a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * any value passed to this function, which has the upper bit set, will return a negative value.
1675a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * The bitwise content of the variable will however be the same.
1685a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Will ignore any white-space characters as well as '-' seperators
1695a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param valueStr a hexstring - NOTE: shall not contain any "0x" prefix.
1705a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @return
1715a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @throws UnsupportedEncodingException if "US-ASCII" charset is not supported,
1725a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * NullPointerException if a null pointer is passed to the function,
1735a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * NumberFormatException if the string contains invalid characters.
1745a60e47497f21f64e6d79420dc4c56c1907df22akschulz     *
1755a60e47497f21f64e6d79420dc4c56c1907df22akschulz     */
1765a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static long getLongFromString(String valueStr) throws UnsupportedEncodingException {
1775a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(valueStr == null) throw new NullPointerException();
1785a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(V) Log.i(TAG, "getLongFromString(): converting: " + valueStr);
1795a60e47497f21f64e6d79420dc4c56c1907df22akschulz        byte[] nibbles;
1805a60e47497f21f64e6d79420dc4c56c1907df22akschulz        nibbles = valueStr.getBytes("US-ASCII");
1815a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(V) Log.i(TAG, "  byte values: " + Arrays.toString(nibbles));
1825a60e47497f21f64e6d79420dc4c56c1907df22akschulz        byte c;
1835a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int count = 0;
1845a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int length = nibbles.length;
1855a60e47497f21f64e6d79420dc4c56c1907df22akschulz        long value = 0;
1865a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for(int i = 0; i != length; i++) {
1875a60e47497f21f64e6d79420dc4c56c1907df22akschulz            c = nibbles[i];
1885a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if(c >= '0' && c <= '9') {
1895a60e47497f21f64e6d79420dc4c56c1907df22akschulz                c -= '0';
1905a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else if(c >= 'A' && c <= 'F') {
1915a60e47497f21f64e6d79420dc4c56c1907df22akschulz                c -= ('A'-10);
1925a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else if(c >= 'a' && c <= 'f') {
1935a60e47497f21f64e6d79420dc4c56c1907df22akschulz                c -= ('a'-10);
1945a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else if(c <= ' ' || c == '-') {
1955a60e47497f21f64e6d79420dc4c56c1907df22akschulz                if(V)Log.v(TAG, "Skipping c = '" + new String(new byte[]{ (byte)c }, "US-ASCII")
1965a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        + "'");
1975a60e47497f21f64e6d79420dc4c56c1907df22akschulz                continue; // Skip any whitespace and '-' (which is used for UUIDs)
1985a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else {
1995a60e47497f21f64e6d79420dc4c56c1907df22akschulz                throw new NumberFormatException("Invalid character:" + c);
2005a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
2015a60e47497f21f64e6d79420dc4c56c1907df22akschulz            value = value << 4; // The last nibble shall not be shifted
2025a60e47497f21f64e6d79420dc4c56c1907df22akschulz            value += c;
2035a60e47497f21f64e6d79420dc4c56c1907df22akschulz            count++;
2045a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if(count > 16) throw new NullPointerException("String to large - count: " + count);
2055a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
2065a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(V) Log.i(TAG, "  length: " + count);
2075a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return value;
2085a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
2095a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static final int LONG_LONG_LENGTH = 32;
2105a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static String getLongLongAsString(long vLow, long vHigh) {
2115a60e47497f21f64e6d79420dc4c56c1907df22akschulz        char[] result = new char[LONG_LONG_LENGTH];
2125a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int v1 = (int) (vLow & 0xffffffff);
2135a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int v2 = (int) ((vLow>>32) & 0xffffffff);
2145a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int v3 = (int) (vHigh & 0xffffffff);
2155a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int v4 = (int) ((vHigh>>32) & 0xffffffff);
2165a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int c,d,i;
2175a60e47497f21f64e6d79420dc4c56c1907df22akschulz        // Handle the lower bytes
2185a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for (i = 0; i < 8; i++) {
2195a60e47497f21f64e6d79420dc4c56c1907df22akschulz            c = v2 & 0x0f;
2205a60e47497f21f64e6d79420dc4c56c1907df22akschulz            c += (c < 10) ? '0' : ('A'-10);
2215a60e47497f21f64e6d79420dc4c56c1907df22akschulz            d = v4 & 0x0f;
2225a60e47497f21f64e6d79420dc4c56c1907df22akschulz            d += (d < 10) ? '0' : ('A'-10);
2235a60e47497f21f64e6d79420dc4c56c1907df22akschulz            result[23 - i] = (char) c;
2245a60e47497f21f64e6d79420dc4c56c1907df22akschulz            result[7 - i] = (char) d;
2255a60e47497f21f64e6d79420dc4c56c1907df22akschulz            v2 >>= 4;
2265a60e47497f21f64e6d79420dc4c56c1907df22akschulz            v4 >>= 4;
2275a60e47497f21f64e6d79420dc4c56c1907df22akschulz            c = v1 & 0x0f;
2285a60e47497f21f64e6d79420dc4c56c1907df22akschulz            c += (c < 10) ? '0' : ('A'-10);
2295a60e47497f21f64e6d79420dc4c56c1907df22akschulz            d = v3 & 0x0f;
2305a60e47497f21f64e6d79420dc4c56c1907df22akschulz            d += (d < 10) ? '0' : ('A'-10);
2315a60e47497f21f64e6d79420dc4c56c1907df22akschulz            result[31 - i] = (char)c;
2325a60e47497f21f64e6d79420dc4c56c1907df22akschulz            result[15 - i] = (char)d;
2335a60e47497f21f64e6d79420dc4c56c1907df22akschulz            v1 >>= 4;
2345a60e47497f21f64e6d79420dc4c56c1907df22akschulz            v3 >>= 4;
2355a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
2365a60e47497f21f64e6d79420dc4c56c1907df22akschulz        // Remove any leading 0's
2375a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for(i = 0; i < LONG_LONG_LENGTH; i++) {
2385a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if(result[i] != '0') {
2395a60e47497f21f64e6d79420dc4c56c1907df22akschulz                break;
2405a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
2415a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
2425a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return new String(result, i, LONG_LONG_LENGTH-i);
2435a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
2445a60e47497f21f64e6d79420dc4c56c1907df22akschulz
2455a60e47497f21f64e6d79420dc4c56c1907df22akschulz
2465a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
247fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Convert a Content Provider handle and a Messagetype into a unique handle
248fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param cpHandle content provider handle
249fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
250fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @return String Formatted Map Handle
251fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
252326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public static String getMapHandle(long cpHandle, TYPE messageType){
253fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        String mapHandle = "-1";
2542e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta        /* Avoid NPE for possible "null" value of messageType */
2552e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta        if(messageType != null) {
2562e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta            switch(messageType)
2572e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta            {
2582e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                case MMS:
2592e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_MMS_MASK);
2602e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    break;
2612e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                case SMS_GSM:
2622e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_SMS_GSM_MASK);
2632e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    break;
2642e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                case SMS_CDMA:
2652e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_SMS_CDMA_MASK);
2662e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    break;
2672e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                case EMAIL:
2682e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_EMAIL_MASK);
2692e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    break;
2702e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                case IM:
2712e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_IM_MASK);
2722e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    break;
2732e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                case NONE:
2742e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    break;
2752e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                default:
2762e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta                    throw new IllegalArgumentException("Message type not supported");
2772e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta            }
2782e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta        } else {
2792e7dd83a6b3b4bf15e0dec6aad9ab826e6e2531bHemant Gupta            if(D)Log.e(TAG," Invalid messageType input");
2805a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
2815a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return mapHandle;
2825a60e47497f21f64e6d79420dc4c56c1907df22akschulz
2835a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
2845a60e47497f21f64e6d79420dc4c56c1907df22akschulz
2855a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
2865a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Convert a Content Provider handle and a Messagetype into a unique handle
2875a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param cpHandle content provider handle
2885a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
2895a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @return String Formatted Map Handle
2905a60e47497f21f64e6d79420dc4c56c1907df22akschulz     */
2915a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static String getMapConvoHandle(long cpHandle, TYPE messageType){
2925a60e47497f21f64e6d79420dc4c56c1907df22akschulz        String mapHandle = "-1";
2935a60e47497f21f64e6d79420dc4c56c1907df22akschulz        switch(messageType)
2945a60e47497f21f64e6d79420dc4c56c1907df22akschulz        {
2955a60e47497f21f64e6d79420dc4c56c1907df22akschulz            case MMS:
2965a60e47497f21f64e6d79420dc4c56c1907df22akschulz            case SMS_GSM:
2975a60e47497f21f64e6d79420dc4c56c1907df22akschulz            case SMS_CDMA:
2985a60e47497f21f64e6d79420dc4c56c1907df22akschulz                mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_SMS_MMS);
2995a60e47497f21f64e6d79420dc4c56c1907df22akschulz                break;
3005a60e47497f21f64e6d79420dc4c56c1907df22akschulz            case EMAIL:
3015a60e47497f21f64e6d79420dc4c56c1907df22akschulz            case IM:
3025a60e47497f21f64e6d79420dc4c56c1907df22akschulz                mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_EMAIL_IM);
3035a60e47497f21f64e6d79420dc4c56c1907df22akschulz                break;
3045a60e47497f21f64e6d79420dc4c56c1907df22akschulz            default:
3055a60e47497f21f64e6d79420dc4c56c1907df22akschulz                throw new IllegalArgumentException("Message type not supported");
306fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
307fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        return mapHandle;
308fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
309fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
310fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
311fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
312fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Convert a handle string the the raw long representation, including the type bit.
313fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param mapHandle the handle string
314fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @return the handle value
315fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
316fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    static public long getMsgHandleAsLong(String mapHandle){
317fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        return Long.parseLong(mapHandle, 16);
318fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
319fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
320fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Convert a Map Handle into a content provider Handle
321fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param mapHandle handle to convert from
322fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @return content provider handle without message type mask
323fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
324fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    static public long getCpHandle(String mapHandle)
325fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    {
326fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        long cpHandle = getMsgHandleAsLong(mapHandle);
327326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(D)Log.d(TAG,"-> MAP handle:"+mapHandle);
328fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /* remove masks as the call should already know what type of message this handle is for */
329fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        cpHandle &= ~HANDLE_TYPE_MASK;
330326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(D)Log.d(TAG,"->CP handle:"+cpHandle);
331326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
332fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        return cpHandle;
333fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
334fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
335fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
336fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Extract the message type from the handle.
337fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param mapHandle
338fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @return
339fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
340fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    static public TYPE getMsgTypeFromHandle(String mapHandle) {
341fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        long cpHandle = getMsgHandleAsLong(mapHandle);
342fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
343fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if((cpHandle & HANDLE_TYPE_MMS_MASK) != 0)
344fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return TYPE.MMS;
345fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if((cpHandle & HANDLE_TYPE_EMAIL_MASK) != 0)
346fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return TYPE.EMAIL;
347fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if((cpHandle & HANDLE_TYPE_SMS_GSM_MASK) != 0)
348fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return TYPE.SMS_GSM;
349fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if((cpHandle & HANDLE_TYPE_SMS_CDMA_MASK) != 0)
350fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return TYPE.SMS_CDMA;
3515a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if((cpHandle & HANDLE_TYPE_IM_MASK) != 0)
3525a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return TYPE.IM;
353fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
354fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        throw new IllegalArgumentException("Message type not found in handle string.");
355fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
3565a60e47497f21f64e6d79420dc4c56c1907df22akschulz
3575a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
3585a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * TODO: Is this still needed after changing to another XML encoder? It should escape illegal
3595a60e47497f21f64e6d79420dc4c56c1907df22akschulz     *       characters.
3605a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Strip away any illegal XML characters, that would otherwise cause the
3615a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * xml serializer to throw an exception.
3625a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Examples of such characters are the emojis used on Android.
3635a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param text The string to validate
3645a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @return the same string if valid, otherwise a new String stripped for
3655a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * any illegal characters. If a null pointer is passed an empty string will be returned.
3665a60e47497f21f64e6d79420dc4c56c1907df22akschulz     */
3675a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static public String stripInvalidChars(String text) {
3685a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(text == null) {
3695a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return "";
3705a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
3715a60e47497f21f64e6d79420dc4c56c1907df22akschulz        char out[] = new char[text.length()];
3725a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int i, o, l;
3735a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for(i=0, o=0, l=text.length(); i<l; i++){
3745a60e47497f21f64e6d79420dc4c56c1907df22akschulz            char c = text.charAt(i);
3755a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) {
3765a60e47497f21f64e6d79420dc4c56c1907df22akschulz                out[o++] = c;
3775a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } // Else we skip the character
3785a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
3795a60e47497f21f64e6d79420dc4c56c1907df22akschulz
3805a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(i==o) {
3815a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return text;
3825a60e47497f21f64e6d79420dc4c56c1907df22akschulz        } else { // We removed some characters, create the new string
3835a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return new String(out,0,o);
3845a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
3855a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
3865a60e47497f21f64e6d79420dc4c56c1907df22akschulz
3875a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
3885a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Truncate UTF-8 string encoded byte array to desired length
3895a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param utf8String String to convert to bytes array h
3905a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param length Max length of byte array returned including null termination
3915a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @return byte array containing valid utf8 characters with max length
3925a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @throws UnsupportedEncodingException
3935a60e47497f21f64e6d79420dc4c56c1907df22akschulz     */
3945a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static public byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
3955a60e47497f21f64e6d79420dc4c56c1907df22akschulz            throws UnsupportedEncodingException {
3965a60e47497f21f64e6d79420dc4c56c1907df22akschulz
397bc5ec351447ea8841b9a27e18772aefd62b047bcAjay Panicker        byte[] utf8Bytes = new byte[utf8String.length() + 1];
3985a60e47497f21f64e6d79420dc4c56c1907df22akschulz        try {
399bc5ec351447ea8841b9a27e18772aefd62b047bcAjay Panicker            System.arraycopy(utf8String.getBytes("UTF-8"), 0,
400bc5ec351447ea8841b9a27e18772aefd62b047bcAjay Panicker                             utf8Bytes, 0, utf8String.length());
4015a60e47497f21f64e6d79420dc4c56c1907df22akschulz        } catch (UnsupportedEncodingException e) {
4025a60e47497f21f64e6d79420dc4c56c1907df22akschulz            Log.e(TAG,"truncateUtf8StringToBytearray: getBytes exception ", e);
4035a60e47497f21f64e6d79420dc4c56c1907df22akschulz            throw e;
4045a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
4055a60e47497f21f64e6d79420dc4c56c1907df22akschulz
406bc5ec351447ea8841b9a27e18772aefd62b047bcAjay Panicker        if (utf8Bytes.length > maxLength) {
4075a60e47497f21f64e6d79420dc4c56c1907df22akschulz            /* if 'continuation' byte is in place 200,
4085a60e47497f21f64e6d79420dc4c56c1907df22akschulz             * then strip previous bytes until utf-8 start byte is found */
4095a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if ( (utf8Bytes[maxLength - 1] & 0xC0) == 0x80 ) {
4105a60e47497f21f64e6d79420dc4c56c1907df22akschulz                for (int i = maxLength - 2; i >= 0; i--) {
4115a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if ((utf8Bytes[i] & 0xC0) == 0xC0) {
4125a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        /* first byte in utf-8 character found,
4135a60e47497f21f64e6d79420dc4c56c1907df22akschulz                         * now copy i - 1 bytes to outBytes and add null termination */
4145a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        utf8Bytes = Arrays.copyOf(utf8Bytes, i+1);
4155a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        utf8Bytes[i] = 0;
4165a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        break;
4175a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    }
4185a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
4195a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else {
4205a60e47497f21f64e6d79420dc4c56c1907df22akschulz                /* copy bytes to outBytes and null terminate */
4215a60e47497f21f64e6d79420dc4c56c1907df22akschulz                utf8Bytes = Arrays.copyOf(utf8Bytes, maxLength);
4225a60e47497f21f64e6d79420dc4c56c1907df22akschulz                utf8Bytes[maxLength - 1] = 0;
4235a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
4245a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
4255a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return utf8Bytes;
4265a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
4275a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static Pattern p = Pattern.compile("=\\?(.+?)\\?(.)\\?(.+?(?=\\?=))\\?=");
4285a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4295a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
4305a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Method for converting quoted printable og base64 encoded string from headers.
4315a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param in the string with encoding
4325a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @return decoded string if success - else the same string as was as input.
4335a60e47497f21f64e6d79420dc4c56c1907df22akschulz     */
4345a60e47497f21f64e6d79420dc4c56c1907df22akschulz    static public String stripEncoding(String in){
4355a60e47497f21f64e6d79420dc4c56c1907df22akschulz        String str = null;
4365a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(in.contains("=?") && in.contains("?=")){
4375a60e47497f21f64e6d79420dc4c56c1907df22akschulz            String encoding;
4385a60e47497f21f64e6d79420dc4c56c1907df22akschulz            String charset;
4395a60e47497f21f64e6d79420dc4c56c1907df22akschulz            String encodedText;
4405a60e47497f21f64e6d79420dc4c56c1907df22akschulz            String match;
4415a60e47497f21f64e6d79420dc4c56c1907df22akschulz            Matcher m = p.matcher(in);
4425a60e47497f21f64e6d79420dc4c56c1907df22akschulz            while(m.find()){
4435a60e47497f21f64e6d79420dc4c56c1907df22akschulz                match = m.group(0);
4445a60e47497f21f64e6d79420dc4c56c1907df22akschulz                charset = m.group(1);
4455a60e47497f21f64e6d79420dc4c56c1907df22akschulz                encoding = m.group(2);
4465a60e47497f21f64e6d79420dc4c56c1907df22akschulz                encodedText = m.group(3);
4475a60e47497f21f64e6d79420dc4c56c1907df22akschulz                Log.v(TAG, "Matching:" + match +"\nCharset: "+charset +"\nEncoding : " +encoding
4485a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        + "\nText: " + encodedText);
4495a60e47497f21f64e6d79420dc4c56c1907df22akschulz                if(encoding.equalsIgnoreCase("Q")){
4505a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    //quoted printable
4515a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    Log.d(TAG,"StripEncoding: Quoted Printable string : " + encodedText);
4525a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    str = new String(quotedPrintableToUtf8(encodedText,charset));
4535a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    in = in.replace(match, str);
4545a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }else if(encoding.equalsIgnoreCase("B")){
4555a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    // base64
4565a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    try{
4575a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4585a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        Log.d(TAG,"StripEncoding: base64 string : " + encodedText);
4595a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        str = new String(Base64.decode(encodedText.getBytes(charset),
4605a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                Base64.DEFAULT), charset);
4615a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        Log.d(TAG,"StripEncoding: decoded string : " + str);
4625a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        in = in.replace(match, str);
4635a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    }catch(UnsupportedEncodingException e){
4645a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        Log.e(TAG, "stripEncoding: Unsupported charset: " + charset);
4655a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    }catch (IllegalArgumentException e){
4665a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        Log.e(TAG,"stripEncoding: string not encoded as base64: " +encodedText);
4675a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    }
4685a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }else{
4695a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    Log.e(TAG, "stripEncoding: Hit unknown encoding: "+encoding);
4705a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
4715a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
4725a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
4735a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return in;
4745a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
4755a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4765a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4775a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
4785a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Convert a quoted-printable encoded string to a UTF-8 string:
4795a60e47497f21f64e6d79420dc4c56c1907df22akschulz     *  - Remove any soft line breaks: "=<CRLF>"
4805a60e47497f21f64e6d79420dc4c56c1907df22akschulz     *  - Convert all "=xx" to the corresponding byte
4815a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param text quoted-printable encoded UTF-8 text
4825a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @return decoded UTF-8 string
4835a60e47497f21f64e6d79420dc4c56c1907df22akschulz     */
4845a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static byte[] quotedPrintableToUtf8(String text, String charset) {
4855a60e47497f21f64e6d79420dc4c56c1907df22akschulz        byte[] output = new byte[text.length()]; // We allocate for the worst case memory need
4865a60e47497f21f64e6d79420dc4c56c1907df22akschulz        byte[] input = null;
4875a60e47497f21f64e6d79420dc4c56c1907df22akschulz        try {
4885a60e47497f21f64e6d79420dc4c56c1907df22akschulz            input = text.getBytes("US-ASCII");
4895a60e47497f21f64e6d79420dc4c56c1907df22akschulz        } catch (UnsupportedEncodingException e) {
4905a60e47497f21f64e6d79420dc4c56c1907df22akschulz            /* This cannot happen as "US-ASCII" is supported for all Java implementations */ }
4915a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4925a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(input == null){
4935a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return "".getBytes();
4945a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
4955a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4965a60e47497f21f64e6d79420dc4c56c1907df22akschulz        int in, out, stopCnt = input.length-2; // Leave room for peaking the next two bytes
4975a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4985a60e47497f21f64e6d79420dc4c56c1907df22akschulz        /* Algorithm:
4995a60e47497f21f64e6d79420dc4c56c1907df22akschulz         *  - Search for token, copying all non token chars
5005a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * */
5015a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for(in=0, out=0; in < stopCnt; in++){
5025a60e47497f21f64e6d79420dc4c56c1907df22akschulz            byte b0 = input[in];
5035a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if(b0 == '=') {
5045a60e47497f21f64e6d79420dc4c56c1907df22akschulz                byte b1 = input[++in];
5055a60e47497f21f64e6d79420dc4c56c1907df22akschulz                byte b2 = input[++in];
5065a60e47497f21f64e6d79420dc4c56c1907df22akschulz                if(b1 == '\r' && b2 == '\n') {
5075a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    continue; // soft line break, remove all tree;
5085a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
5095a60e47497f21f64e6d79420dc4c56c1907df22akschulz                if(((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F')
5105a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        || (b1 >= 'a' && b1 <= 'f'))
5115a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        && ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F')
5125a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        || (b2 >= 'a' && b2 <= 'f'))) {
5135a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(V)Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2));
5145a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(b1 <= '9')       b1 = (byte) (b1 - '0');
5155a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    else if (b1 <= 'F') b1 = (byte) (b1 - 'A' + 10);
5165a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    else if (b1 <= 'f') b1 = (byte) (b1 - 'a' + 10);
5175a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5185a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(b2 <= '9')       b2 = (byte) (b2 - '0');
5195a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    else if (b2 <= 'F') b2 = (byte) (b2 - 'A' + 10);
5205a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    else if (b2 <= 'f') b2 = (byte) (b2 - 'a' + 10);
5215a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5225a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(V)Log.v(TAG, "Resulting nibble values: " +
5235a60e47497f21f64e6d79420dc4c56c1907df22akschulz                            String.format("b1=%x b2=%x", b1, b2));
5245a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5255a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    output[out++] = (byte)(b1<<4 | b2); // valid hex char, append
5265a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(V)Log.v(TAG, "Resulting value: "  + String.format("0x%2x", output[out-1]));
5275a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    continue;
5285a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
5295a60e47497f21f64e6d79420dc4c56c1907df22akschulz                Log.w(TAG, "Received wrongly quoted printable encoded text. " +
5305a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        "Continuing at best effort...");
5315a60e47497f21f64e6d79420dc4c56c1907df22akschulz                /* If we get a '=' without either a hex value or CRLF following, just add it and
5325a60e47497f21f64e6d79420dc4c56c1907df22akschulz                 * rewind the in counter. */
5335a60e47497f21f64e6d79420dc4c56c1907df22akschulz                output[out++] = b0;
5345a60e47497f21f64e6d79420dc4c56c1907df22akschulz                in -= 2;
5355a60e47497f21f64e6d79420dc4c56c1907df22akschulz                continue;
5365a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else {
5375a60e47497f21f64e6d79420dc4c56c1907df22akschulz                output[out++] = b0;
5385a60e47497f21f64e6d79420dc4c56c1907df22akschulz                continue;
5395a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
5405a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
5415a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5425a60e47497f21f64e6d79420dc4c56c1907df22akschulz        // Just add any remaining characters. If they contain any encoding, it is invalid,
5435a60e47497f21f64e6d79420dc4c56c1907df22akschulz        // and best effort would be just to display the characters.
5445a60e47497f21f64e6d79420dc4c56c1907df22akschulz        while (in < input.length) {
5455a60e47497f21f64e6d79420dc4c56c1907df22akschulz            output[out++] = input[in++];
5465a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
5475a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5485a60e47497f21f64e6d79420dc4c56c1907df22akschulz        String result = null;
5495a60e47497f21f64e6d79420dc4c56c1907df22akschulz        // Figure out if we support the charset, else fall back to UTF-8, as this is what
5505a60e47497f21f64e6d79420dc4c56c1907df22akschulz        // the MAP specification suggest to use, and is compatible with US-ASCII.
5515a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(charset == null){
5525a60e47497f21f64e6d79420dc4c56c1907df22akschulz            charset = "UTF-8";
5535a60e47497f21f64e6d79420dc4c56c1907df22akschulz        } else {
5545a60e47497f21f64e6d79420dc4c56c1907df22akschulz            charset = charset.toUpperCase();
5555a60e47497f21f64e6d79420dc4c56c1907df22akschulz            try {
5565a60e47497f21f64e6d79420dc4c56c1907df22akschulz                if(Charset.isSupported(charset) == false) {
5575a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    charset = "UTF-8";
5585a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
5595a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } catch (IllegalCharsetNameException e) {
5605a60e47497f21f64e6d79420dc4c56c1907df22akschulz                Log.w(TAG, "Received unknown charset: " + charset + " - using UTF-8.");
5615a60e47497f21f64e6d79420dc4c56c1907df22akschulz                charset = "UTF-8";
5625a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
5635a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
5645a60e47497f21f64e6d79420dc4c56c1907df22akschulz        try{
5655a60e47497f21f64e6d79420dc4c56c1907df22akschulz            result = new String(output, 0, out, charset);
5665a60e47497f21f64e6d79420dc4c56c1907df22akschulz        } catch (UnsupportedEncodingException e) {
5675a60e47497f21f64e6d79420dc4c56c1907df22akschulz            /* This cannot happen unless Charset.isSupported() is out of sync with String */
5685a60e47497f21f64e6d79420dc4c56c1907df22akschulz            try{
5695a60e47497f21f64e6d79420dc4c56c1907df22akschulz                result = new String(output, 0, out, "UTF-8");
5705a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } catch (UnsupportedEncodingException e2) {/* This cannot happen */}
5715a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
5725a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return result.getBytes(); /* return the result as "UTF-8" bytes */
5735a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
5745a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5755a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
5765a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Encodes an array of bytes into an array of quoted-printable 7-bit characters.
5775a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Unsafe characters are escaped.
5785a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Simplified version of encoder from QuetedPrintableCodec.java (Apache external)
5795a60e47497f21f64e6d79420dc4c56c1907df22akschulz     *
5805a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param bytes
5815a60e47497f21f64e6d79420dc4c56c1907df22akschulz     *                  array of bytes to be encoded
5825a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @return UTF-8 string containing quoted-printable characters
5835a60e47497f21f64e6d79420dc4c56c1907df22akschulz     */
5845a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5855a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static byte ESCAPE_CHAR = '=';
5865a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static byte TAB = 9;
5875a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private static byte SPACE = 32;
5885a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5895a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static final String encodeQuotedPrintable(byte[] bytes) {
5905a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if (bytes == null) {
5915a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return null;
5925a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
5935a60e47497f21f64e6d79420dc4c56c1907df22akschulz
5945a60e47497f21f64e6d79420dc4c56c1907df22akschulz        BitSet printable = new BitSet(256);
5955a60e47497f21f64e6d79420dc4c56c1907df22akschulz        // alpha characters
5965a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for (int i = 33; i <= 60; i++) {
5975a60e47497f21f64e6d79420dc4c56c1907df22akschulz            printable.set(i);
5985a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
5995a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for (int i = 62; i <= 126; i++) {
6005a60e47497f21f64e6d79420dc4c56c1907df22akschulz            printable.set(i);
6015a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
6025a60e47497f21f64e6d79420dc4c56c1907df22akschulz        printable.set(TAB);
6035a60e47497f21f64e6d79420dc4c56c1907df22akschulz        printable.set(SPACE);
6045a60e47497f21f64e6d79420dc4c56c1907df22akschulz        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
6055a60e47497f21f64e6d79420dc4c56c1907df22akschulz        for (int i = 0; i < bytes.length; i++) {
6065a60e47497f21f64e6d79420dc4c56c1907df22akschulz            int b = bytes[i];
6075a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if (b < 0) {
6085a60e47497f21f64e6d79420dc4c56c1907df22akschulz                b = 256 + b;
6095a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
6105a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if (printable.get(b)) {
6115a60e47497f21f64e6d79420dc4c56c1907df22akschulz                buffer.write(b);
6125a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else {
6135a60e47497f21f64e6d79420dc4c56c1907df22akschulz                buffer.write(ESCAPE_CHAR);
6145a60e47497f21f64e6d79420dc4c56c1907df22akschulz                char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
6155a60e47497f21f64e6d79420dc4c56c1907df22akschulz                char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
6165a60e47497f21f64e6d79420dc4c56c1907df22akschulz                buffer.write(hex1);
6175a60e47497f21f64e6d79420dc4c56c1907df22akschulz                buffer.write(hex2);
6185a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
6195a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
6205a60e47497f21f64e6d79420dc4c56c1907df22akschulz        try {
6215a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return buffer.toString("UTF-8");
6225a60e47497f21f64e6d79420dc4c56c1907df22akschulz        } catch (UnsupportedEncodingException e) {
6235a60e47497f21f64e6d79420dc4c56c1907df22akschulz            //cannot happen
6245a60e47497f21f64e6d79420dc4c56c1907df22akschulz            return "";
6255a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
6265a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
6275a60e47497f21f64e6d79420dc4c56c1907df22akschulz
628fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie}
629fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
630