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
17fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport java.io.ByteArrayOutputStream;
1870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulzimport java.io.File;
1970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulzimport java.io.FileInputStream;
2070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulzimport java.io.FileNotFoundException;
2170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulzimport java.io.FileOutputStream;
22fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport java.io.IOException;
23fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport java.io.InputStream;
24fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport java.io.UnsupportedEncodingException;
25fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport java.util.ArrayList;
26fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
2770be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulzimport android.os.Environment;
28fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport android.telephony.PhoneNumberUtils;
29fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport android.util.Log;
3070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
31fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xieimport com.android.bluetooth.map.BluetoothMapUtils.TYPE;
32fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
33fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xiepublic abstract class BluetoothMapbMessage {
34fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
35fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    protected static String TAG = "BluetoothMapbMessage";
36326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected static final boolean D = BluetoothMapService.DEBUG;
37326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected static final boolean V = BluetoothMapService.VERBOSE;
38326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
395a60e47497f21f64e6d79420dc4c56c1907df22akschulz    private String mVersionString = "VERSION:1.0";
40fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
41fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public static int INVALID_VALUE = -1;
42fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
43326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected int mAppParamCharset = BluetoothMapAppParams.INVALID_VALUE_PARAMETER;
44fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
45fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /* BMSG attributes */
46326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private String mStatus = null; // READ/UNREAD
47326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected TYPE mType = null;   // SMS/MMS/EMAIL
48fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
49326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private String mFolder = null;
50fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
51fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /* BBODY attributes */
52326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private long mPartId = INVALID_VALUE;
53326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected String mEncoding = null;
54326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected String mCharset = null;
55326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private String mLanguage = null;
56fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
57326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private int mBMsgLength = INVALID_VALUE;
58fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
59326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private ArrayList<vCard> mOriginator = null;
60326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private ArrayList<vCard> mRecipient = null;
61fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
62fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
63fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public static class vCard {
64fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /* VCARD attributes */
65326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        private String mVersion;
66326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        private String mName = null;
67326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        private String mFormattedName = null;
68326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        private String[] mPhoneNumbers = {};
69326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        private String[] mEmailAddresses = {};
70326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        private int mEnvLevel = 0;
715a60e47497f21f64e6d79420dc4c56c1907df22akschulz        private String[] mBtUcis = {};
725a60e47497f21f64e6d79420dc4c56c1907df22akschulz        private String[] mBtUids = {};
73fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
74fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
75fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Construct a version 3.0 vCard
76fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param name Structured
77fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param formattedName Formatted name
78fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param phoneNumbers a String[] of phone numbers
79fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param emailAddresses a String[] of email addresses
80fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param the bmessage envelope level (0 is the top/most outer level)
81fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
82fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public vCard(String name, String formattedName, String[] phoneNumbers,
83fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String[] emailAddresses, int envLevel) {
84326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mEnvLevel = envLevel;
85326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mVersion = "3.0";
86326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mName = name != null ? name : "";
87326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mFormattedName = formattedName != null ? formattedName : "";
88fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            setPhoneNumbers(phoneNumbers);
89fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if (emailAddresses != null)
90326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                this.mEmailAddresses = emailAddresses;
91fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
92fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
93fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
94fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Construct a version 2.1 vCard
95fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param name Structured name
96fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param phoneNumbers a String[] of phone numbers
97fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param emailAddresses a String[] of email addresses
98fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param the bmessage envelope level (0 is the top/most outer level)
99fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
100fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public vCard(String name, String[] phoneNumbers,
101fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String[] emailAddresses, int envLevel) {
102326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mEnvLevel = envLevel;
103326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mVersion = "2.1";
104326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mName = name != null ? name : "";
105fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            setPhoneNumbers(phoneNumbers);
106fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if (emailAddresses != null)
107326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                this.mEmailAddresses = emailAddresses;
108fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
109fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
110fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
111fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Construct a version 3.0 vCard
112fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param name Structured name
113fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param formattedName Formatted name
114fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param phoneNumbers a String[] of phone numbers
1155a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * @param emailAddresses a String[] of email addresses if available, else null
1165a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * @param btUids a String[] of X-BT-UIDs if available, else null
1175a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * @param btUcis a String[] of X-BT-UCIs if available, else null
118fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
1195a60e47497f21f64e6d79420dc4c56c1907df22akschulz        public vCard(String name, String formattedName,
1205a60e47497f21f64e6d79420dc4c56c1907df22akschulz                     String[] phoneNumbers,
1215a60e47497f21f64e6d79420dc4c56c1907df22akschulz                     String[] emailAddresses,
1225a60e47497f21f64e6d79420dc4c56c1907df22akschulz                     String[] btUids,
1235a60e47497f21f64e6d79420dc4c56c1907df22akschulz                     String[] btUcis) {
124326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mVersion = "3.0";
125326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mName = (name != null) ? name : "";
126326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mFormattedName = (formattedName != null) ? formattedName : "";
127fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            setPhoneNumbers(phoneNumbers);
1285a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if (emailAddresses != null) {
129326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                this.mEmailAddresses = emailAddresses;
1305a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
1315a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if (btUcis != null) {
1325a60e47497f21f64e6d79420dc4c56c1907df22akschulz                this.mBtUcis = btUcis;
1335a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
134fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
135fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
136fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
137fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Construct a version 2.1 vCard
138fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param name Structured Name
139fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param phoneNumbers a String[] of phone numbers
140fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param emailAddresses a String[] of email addresses
141fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
142fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public vCard(String name, String[] phoneNumbers, String[] emailAddresses) {
143326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mVersion = "2.1";
144326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mName = name != null ? name : "";
145fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            setPhoneNumbers(phoneNumbers);
146fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if (emailAddresses != null)
147326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                this.mEmailAddresses = emailAddresses;
148fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
149fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
150fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        private void setPhoneNumbers(String[] numbers) {
151326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(numbers != null && numbers.length > 0) {
152326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                mPhoneNumbers = new String[numbers.length];
153fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                for(int i = 0, n = numbers.length; i < n; i++){
154326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    String networkNumber = PhoneNumberUtils.extractNetworkPortion(numbers[i]);
1555a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    /* extractNetworkPortion can return N if the number is a service
1565a60e47497f21f64e6d79420dc4c56c1907df22akschulz                     * "number" = a string with the a name in (i.e. "Some-Tele-company" would
1575a60e47497f21f64e6d79420dc4c56c1907df22akschulz                     * return N because of the N in compaNy)
158326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                     * Hence we need to check if the number is actually a string with alpha chars.
159326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                     * */
1605a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    String strippedNumber = PhoneNumberUtils.stripSeparators(numbers[i]);
1615a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    Boolean alpha = false;
1625a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(strippedNumber != null){
1635a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        alpha = strippedNumber.matches("[0-9]*[a-zA-Z]+[0-9]*");
1645a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    }
165326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    if(networkNumber != null && networkNumber.length() > 1 && !alpha) {
166326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        mPhoneNumbers[i] = networkNumber;
167326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    } else {
168326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        mPhoneNumbers[i] = numbers[i];
169326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    }
170fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
171fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
172fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
173fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
174fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public String getFirstPhoneNumber() {
175326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(mPhoneNumbers.length > 0) {
176326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return mPhoneNumbers[0];
177fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            } else
178326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return null;
179fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
180fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
181fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public int getEnvLevel() {
182326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            return mEnvLevel;
183326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
184326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
185326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        public String getName() {
186326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            return mName;
187326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
188326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
189326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        public String getFirstEmail() {
190326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(mEmailAddresses.length > 0) {
191326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return mEmailAddresses[0];
192326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else
193326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return null;
194fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
1955a60e47497f21f64e6d79420dc4c56c1907df22akschulz        public String getFirstBtUci() {
1965a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if(mBtUcis.length > 0) {
1975a60e47497f21f64e6d79420dc4c56c1907df22akschulz                return mBtUcis[0];
1985a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else
1995a60e47497f21f64e6d79420dc4c56c1907df22akschulz                return null;
2005a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
2015a60e47497f21f64e6d79420dc4c56c1907df22akschulz
2025a60e47497f21f64e6d79420dc4c56c1907df22akschulz        public String getFirstBtUid() {
2035a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if(mBtUids.length > 0) {
2045a60e47497f21f64e6d79420dc4c56c1907df22akschulz                return mBtUids[0];
2055a60e47497f21f64e6d79420dc4c56c1907df22akschulz            } else
2065a60e47497f21f64e6d79420dc4c56c1907df22akschulz                return null;
2075a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
208fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
209fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public void encode(StringBuilder sb)
210fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        {
211fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            sb.append("BEGIN:VCARD").append("\r\n");
212326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            sb.append("VERSION:").append(mVersion).append("\r\n");
213326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(mVersion.equals("3.0") && mFormattedName != null)
214fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            {
215326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                sb.append("FN:").append(mFormattedName).append("\r\n");
216fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
217326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if (mName != null)
218326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                sb.append("N:").append(mName).append("\r\n");
219326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            for(String phoneNumber : mPhoneNumbers)
220fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            {
221fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                sb.append("TEL:").append(phoneNumber).append("\r\n");
222fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
223326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            for(String emailAddress : mEmailAddresses)
224fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            {
225fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                sb.append("EMAIL:").append(emailAddress).append("\r\n");
226fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
2275a60e47497f21f64e6d79420dc4c56c1907df22akschulz            for(String btUid : mBtUids)
2285a60e47497f21f64e6d79420dc4c56c1907df22akschulz            {
2295a60e47497f21f64e6d79420dc4c56c1907df22akschulz                sb.append("X-BT-UID:").append(btUid).append("\r\n");
2305a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
2315a60e47497f21f64e6d79420dc4c56c1907df22akschulz            for(String btUci : mBtUcis)
2325a60e47497f21f64e6d79420dc4c56c1907df22akschulz            {
2335a60e47497f21f64e6d79420dc4c56c1907df22akschulz                sb.append("X-BT-UCI:").append(btUci).append("\r\n");
2345a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
235fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            sb.append("END:VCARD").append("\r\n");
236fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
237fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
238fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
2395a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD"
2405a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * have just been read.
241fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param reader
2425a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * @param envLevel
243fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @return
244fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
245fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public static vCard parseVcard(BMsgReader reader, int envLevel) {
246fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            String formattedName = null;
247fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            String name = null;
248fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            ArrayList<String> phoneNumbers = null;
249fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            ArrayList<String> emailAddresses = null;
2505a60e47497f21f64e6d79420dc4c56c1907df22akschulz            ArrayList<String> btUids = null;
2515a60e47497f21f64e6d79420dc4c56c1907df22akschulz            ArrayList<String> btUcis = null;
252fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            String[] parts;
253fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            String line = reader.getLineEnforce();
254fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
2555a60e47497f21f64e6d79420dc4c56c1907df22akschulz            while(!line.contains("END:VCARD")){
256fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                line = line.trim();
257fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if(line.startsWith("N:")){
258fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
259fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if(parts.length == 2) {
260fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        name = parts[1];
261fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } else
262fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        name = "";
263fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
264fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                else if(line.startsWith("FN:")){
265fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
266fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if(parts.length == 2) {
267fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        formattedName = parts[1];
268fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } else
269fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        formattedName = "";
270fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
271fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                else if(line.startsWith("TEL:")){
272fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
273fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if(parts.length == 2) {
274fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        String[] subParts = parts[1].split("[^\\\\];");
275fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        if(phoneNumbers == null)
276fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                            phoneNumbers = new ArrayList<String>(1);
2775a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        // only keep actual phone number
2785a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        phoneNumbers.add(subParts[subParts.length-1]);
279fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } else {}
280fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        // Empty phone number - ignore
281fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
282fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                else if(line.startsWith("EMAIL:")){
283fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
284fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if(parts.length == 2) {
285fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        String[] subParts = parts[1].split("[^\\\\];");
286fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        if(emailAddresses == null)
287fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                            emailAddresses = new ArrayList<String>(1);
2885a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        // only keep actual email address
2895a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        emailAddresses.add(subParts[subParts.length-1]);
290fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } else {}
291fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        // Empty email address entry - ignore
292fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
2935a60e47497f21f64e6d79420dc4c56c1907df22akschulz                else if(line.startsWith("X-BT-UCI:")){
2945a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
2955a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(parts.length == 2) {
2965a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        String[] subParts = parts[1].split("[^\\\\];");
2975a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        if(btUcis == null)
2985a60e47497f21f64e6d79420dc4c56c1907df22akschulz                            btUcis = new ArrayList<String>(1);
2995a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        btUcis.add(subParts[subParts.length-1]); // only keep actual UCI
3005a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    } else {}
3015a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        // Empty UCIentry - ignore
3025a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
3035a60e47497f21f64e6d79420dc4c56c1907df22akschulz                else if(line.startsWith("X-BT-UID:")){
3045a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
3055a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    if(parts.length == 2) {
3065a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        String[] subParts = parts[1].split("[^\\\\];");
3075a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        if(btUids == null)
3085a60e47497f21f64e6d79420dc4c56c1907df22akschulz                            btUids = new ArrayList<String>(1);
3095a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        btUids.add(subParts[subParts.length-1]); // only keep actual UID
3105a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    } else {}
3115a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        // Empty UID entry - ignore
3125a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
3135a60e47497f21f64e6d79420dc4c56c1907df22akschulz
3145a60e47497f21f64e6d79420dc4c56c1907df22akschulz
315fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                line = reader.getLineEnforce();
316fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
317fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return new vCard(name, formattedName,
3185a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    phoneNumbers == null?
3195a60e47497f21f64e6d79420dc4c56c1907df22akschulz                            null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
3205a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    emailAddresses == null ?
3215a60e47497f21f64e6d79420dc4c56c1907df22akschulz                            null : emailAddresses.toArray(new String[emailAddresses.size()]),
322fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    envLevel);
323fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
324fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    };
325fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
326fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    private static class BMsgReader {
327fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        InputStream mInStream;
328fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public BMsgReader(InputStream is)
329fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        {
330fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            this.mInStream = is;
331fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
332fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
333fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        private byte[] getLineAsBytes() {
334fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            int readByte;
335fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
336fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            /* TODO: Actually the vCard spec. allows to break lines by using a newLine
3375a60e47497f21f64e6d79420dc4c56c1907df22akschulz             * followed by a white space character(space or tab). Not sure this is a good idea to
3385a60e47497f21f64e6d79420dc4c56c1907df22akschulz             * implement as the Bluetooth MAP spec. illustrates vCards using tab alignment,
3395a60e47497f21f64e6d79420dc4c56c1907df22akschulz             * hence actually showing an invalid vCard format...
340fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie             * If we read such a folded line, the folded part will be skipped in the parser
341326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde             * UPDATE: Check if we actually do unfold before parsing the input stream
342fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie             */
343fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
344fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            ByteArrayOutputStream output = new ByteArrayOutputStream();
345fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            try {
346fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                while ((readByte = mInStream.read()) != -1) {
347fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if (readByte == '\r') {
348fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        if ((readByte = mInStream.read()) != -1 && readByte == '\n') {
349fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                            if(output.size() == 0)
350fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                                continue; /* Skip empty lines */
351fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                            else
352fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                                break;
353fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        } else {
354fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                            output.write('\r');
355fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        }
356fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } else if (readByte == '\n' && output.size() == 0) {
357fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        /* Empty line - skip */
358fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        continue;
359fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    }
360fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
361fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    output.write(readByte);
362fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
363fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            } catch (IOException e) {
364fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                Log.w(TAG, e);
365fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                return null;
366fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
367fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return output.toByteArray();
368fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
369fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
370fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
371fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Read a line of text from the BMessage.
372fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @return the next line of text, or null at end of file, or if UTF-8 is not supported.
373fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
374fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public String getLine() {
375fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            try {
376fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                byte[] line = getLineAsBytes();
377fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (line.length == 0)
378fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    return null;
379fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                else
380fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    return new String(line, "UTF-8");
381fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            } catch (UnsupportedEncodingException e) {
382fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                Log.w(TAG, e);
383fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                return null;
384fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
385fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
386fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
387fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
388fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * same as getLine(), but throws an exception, if we run out of lines.
389fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Use this function when ever more lines are needed for the bMessage to be complete.
390fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @return the next line
391fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
392fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public String getLineEnforce() {
39370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        String line = getLine();
39470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        if (line == null)
39570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            throw new IllegalArgumentException("Bmessage too short");
39670be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
39770be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        return line;
398fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
399fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
400fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
401fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
402fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Reads a line from the InputStream, and examines if the subString
403fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * matches the line read.
404fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param subString
405fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * The string to match against the line.
406fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @throws IllegalArgumentException
407fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * If the expected substring is not found.
408fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         *
409fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
410fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public void expect(String subString) throws IllegalArgumentException{
411fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            String line = getLine();
41270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            if(line == null || subString == null){
41370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                throw new IllegalArgumentException("Line or substring is null");
41470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            }else if(!line.toUpperCase().contains(subString.toUpperCase()))
4155a60e47497f21f64e6d79420dc4c56c1907df22akschulz                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
4165a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                    + line + "\"");
417fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
418fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
419fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
420fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Same as expect(String), but with two strings.
421fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param subString
422fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param subString2
423fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @throws IllegalArgumentException
424fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * If one or all of the strings are not found.
425fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
426fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public void expect(String subString, String subString2) throws IllegalArgumentException{
427fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            String line = getLine();
42870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            if(!line.toUpperCase().contains(subString.toUpperCase()))
4295a60e47497f21f64e6d79420dc4c56c1907df22akschulz                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
4305a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                   + line + "\"");
43170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            if(!line.toUpperCase().contains(subString2.toUpperCase()))
4325a60e47497f21f64e6d79420dc4c56c1907df22akschulz                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
4335a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                   + line + "\"");
434fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
435fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
436fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /**
437fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * Read a part of the bMessage as raw data.
438fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         * @param length the number of bytes to read
4395a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * @return the byte[] containing the number of bytes or null if an error occurs or EOF is
4405a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * reached before length bytes have been read.
441fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
442fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        public byte[] getDataBytes(int length) {
443fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            byte[] data = new byte[length];
444fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            try {
445fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                int bytesRead;
446fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                int offset=0;
4475a60e47497f21f64e6d79420dc4c56c1907df22akschulz                while ((bytesRead = mInStream.read(data, offset, length-offset))
4485a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                 != (length - offset)) {
449fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if(bytesRead == -1)
450fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        return null;
451fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    offset += bytesRead;
452fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
453fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            } catch (IOException e) {
454fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                Log.w(TAG, e);
455fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                return null;
456fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
457fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return data;
458fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
459fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    };
460fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
46170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz    public BluetoothMapbMessage(){
462fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
463fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
464fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
4655a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public String getVersionString() {
4665a60e47497f21f64e6d79420dc4c56c1907df22akschulz        return mVersionString;
4675a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
4685a60e47497f21f64e6d79420dc4c56c1907df22akschulz    /**
4695a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Set the version string for VCARD
4705a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * @param version the actual number part of the version string i.e. 1.0
4715a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * */
4725a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public void setVersionString(String version) {
4735a60e47497f21f64e6d79420dc4c56c1907df22akschulz        this.mVersionString = "VERSION:"+version;
4745a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
4755a60e47497f21f64e6d79420dc4c56c1907df22akschulz
4765a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public static BluetoothMapbMessage parse(InputStream bMsgStream,
4775a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                             int appParamCharset) throws IllegalArgumentException{
47870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        BMsgReader reader;
479fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        String line = "";
480fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        BluetoothMapbMessage newBMsg = null;
481fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        boolean status = false;
482fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        boolean statusFound = false;
483fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        TYPE type = null;
484fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        String folder = null;
485fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
4865a60e47497f21f64e6d79420dc4c56c1907df22akschulz        /* This section is used for debug. It will write the incoming message to a file on the
4875a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * SD-card, hence should only be used for test/debug.
48870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz         * If an error occurs, it will result in a OBEX_HTTP_PRECON_FAILED to be send to the client,
4895a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * even though the message might be formatted correctly, hence only enable this code for
4905a60e47497f21f64e6d79420dc4c56c1907df22akschulz         * test. */
49170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        if(V) {
49270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            /* Read the entire stream into a file on the SD card*/
49370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            File sdCard = Environment.getExternalStorageDirectory();
49470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            File dir = new File (sdCard.getAbsolutePath() + "/bluetooth/log/");
49570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            dir.mkdirs();
49670be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            File file = new File(dir, "receivedBMessage.txt");
49770be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            FileOutputStream outStream = null;
49870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            boolean failed = false;
49970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            int writtenLen = 0;
50070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
50170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            try {
5025a60e47497f21f64e6d79420dc4c56c1907df22akschulz                /* overwrite if it does already exist */
5035a60e47497f21f64e6d79420dc4c56c1907df22akschulz                outStream = new FileOutputStream(file, false);
50470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
50570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                byte[] buffer = new byte[4*1024];
50670be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                int len = 0;
50770be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                while ((len = bMsgStream.read(buffer)) > 0) {
50870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    outStream.write(buffer, 0, len);
50970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    writtenLen += len;
51070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                }
51170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            } catch (FileNotFoundException e) {
51270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                Log.e(TAG,"Unable to create output stream",e);
51370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            } catch (IOException e) {
51470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                Log.e(TAG,"Failed to copy the received message",e);
51570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                if(writtenLen != 0)
5165a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    failed = true; /* We failed to write the complete file,
5175a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                      hence the received message is lost... */
51870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            } finally {
51970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                if(outStream != null)
52070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    try {
52170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                        outStream.close();
52270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    } catch (IOException e) {
52370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    }
52470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            }
52570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
52670be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            /* Return if we corrupted the incoming bMessage. */
52770be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            if(failed) {
52870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                throw new IllegalArgumentException(); /* terminate this function with an error. */
52970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            }
53070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
53170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            if (outStream == null) {
5325a60e47497f21f64e6d79420dc4c56c1907df22akschulz                /* We failed to create the log-file, just continue using the original bMsgStream. */
53370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            } else {
53470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                /* overwrite the bMsgStream using the file written to the SD-Card */
53570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                try {
53670be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    bMsgStream.close();
53770be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                } catch (IOException e) {
53870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    /* Ignore if we cannot close the stream. */
53970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                }
54070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                /* Open the file and overwrite bMsgStream to read from the file */
54170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                try {
54270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    bMsgStream = new FileInputStream(file);
54370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                } catch (FileNotFoundException e) {
54470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                    Log.e(TAG,"Failed to open the bMessage file", e);
5455a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    /* terminate this function with an error */
5465a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    throw new IllegalArgumentException();
54770be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                }
54870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            }
54970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            Log.i(TAG, "The incoming bMessage have been dumped to " + file.getAbsolutePath());
55070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        } /* End of if(V) log-section */
55170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
55270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        reader = new BMsgReader(bMsgStream);
55370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        reader.expect("BEGIN:BMSG");
5545a60e47497f21f64e6d79420dc4c56c1907df22akschulz        reader.expect("VERSION");
55570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
556fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        line = reader.getLineEnforce();
557fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        // Parse the properties - which end with either a VCARD or a BENV
558fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        while(!line.contains("BEGIN:VCARD") && !line.contains("BEGIN:BENV")) {
559fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(line.contains("STATUS")){
560fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String arg[] = line.split(":");
561fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
562fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if (arg[1].trim().equals("READ")) {
563fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        status = true;
564fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } else if (arg[1].trim().equals("UNREAD")) {
565fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        status =false;
566fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } else {
567fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        throw new IllegalArgumentException("Wrong value in 'STATUS': " + arg[1]);
568fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    }
569fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                } else {
570fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    throw new IllegalArgumentException("Missing value for 'STATUS': " + line);
571fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
572fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
5735a60e47497f21f64e6d79420dc4c56c1907df22akschulz            if(line.contains("EXTENDEDDATA")){
5745a60e47497f21f64e6d79420dc4c56c1907df22akschulz                String arg[] = line.split(":");
5755a60e47497f21f64e6d79420dc4c56c1907df22akschulz                if (arg != null && arg.length == 2) {
5765a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    String value = arg[1].trim();
5775a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    //FIXME what should we do with this
5785a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    Log.i(TAG,"We got extended data with: "+value);
5795a60e47497f21f64e6d79420dc4c56c1907df22akschulz                }
5805a60e47497f21f64e6d79420dc4c56c1907df22akschulz            }
581fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(line.contains("TYPE")) {
582fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String arg[] = line.split(":");
583fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
584fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    String value = arg[1].trim();
5855a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    /* Will throw IllegalArgumentException if value is wrong */
5865a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    type = TYPE.valueOf(value);
587fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE
588fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                            && type != TYPE.SMS_CDMA && type != TYPE.SMS_GSM) {
5895a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        throw new IllegalArgumentException("Native appParamsCharset "
5905a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                             +"only supported for SMS");
591fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    }
592fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    switch(type) {
593fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    case SMS_CDMA:
594fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    case SMS_GSM:
595fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        newBMsg = new BluetoothMapbMessageSms();
596fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        break;
597fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    case MMS:
5985a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        newBMsg = new BluetoothMapbMessageMime();
599326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        break;
600fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    case EMAIL:
601326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        newBMsg = new BluetoothMapbMessageEmail();
602fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        break;
6035a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    case IM:
6045a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        newBMsg = new BluetoothMapbMessageMime();
6055a60e47497f21f64e6d79420dc4c56c1907df22akschulz                        break;
606fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    default:
607fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        break;
608fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    }
609fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                } else {
610fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    throw new IllegalArgumentException("Missing value for 'TYPE':" + line);
611fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
612fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
613fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(line.contains("FOLDER")) {
614fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String[] arg = line.split(":");
615fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
616fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    folder = arg[1].trim();
617fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
61870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                // This can be empty for push message - hence ignore if there is no value
619fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
620fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            line = reader.getLineEnforce();
621fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
622fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(newBMsg == null)
6235a60e47497f21f64e6d79420dc4c56c1907df22akschulz            throw new IllegalArgumentException("Missing bMessage TYPE: "+
6245a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                    "- unable to parse body-content");
625fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        newBMsg.setType(type);
626326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        newBMsg.mAppParamCharset = appParamCharset;
627fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(folder != null)
62870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            newBMsg.setCompleteFolder(folder);
629fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(statusFound)
630fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            newBMsg.setStatus(status);
631fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
632fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        // Now check for originator VCARDs
633fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        while(line.contains("BEGIN:VCARD")){
634fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(D) Log.d(TAG,"Decoding vCard");
635fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            newBMsg.addOriginator(vCard.parseVcard(reader,0));
636fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            line = reader.getLineEnforce();
637fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
638fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(line.contains("BEGIN:BENV")) {
639fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            newBMsg.parseEnvelope(reader, 0);
640fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        } else
641fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
642fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
6435a60e47497f21f64e6d79420dc4c56c1907df22akschulz        /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts
6445a60e47497f21f64e6d79420dc4c56c1907df22akschulz         *        additional info below the END:MSG - in which case we don't handle it.
6455a60e47497f21f64e6d79420dc4c56c1907df22akschulz         *        We need to parse the message based on the length field, to ensure MAP 1.0
6465a60e47497f21f64e6d79420dc4c56c1907df22akschulz         *        compatibility, since this spec. do not suggest to escape the end-tag if it
6475a60e47497f21f64e6d79420dc4c56c1907df22akschulz         *        occurs inside the message text.
648fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
64970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
65070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        try {
65170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            bMsgStream.close();
65270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        } catch (IOException e) {
65370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            /* Ignore if we cannot close the stream. */
65470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        }
65570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
656fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        return newBMsg;
657fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
658fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
659fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    private void parseEnvelope(BMsgReader reader, int level) {
660fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        String line;
661fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        line = reader.getLineEnforce();
662fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(D) Log.d(TAG,"Decoding envelope level " + level);
663fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
664fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie       while(line.contains("BEGIN:VCARD")){
665fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie           if(D) Log.d(TAG,"Decoding recipient vCard level " + level);
666326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(mRecipient == null)
667326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                mRecipient = new ArrayList<vCard>(1);
668326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            mRecipient.add(vCard.parseVcard(reader, level));
669fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            line = reader.getLineEnforce();
670fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
671fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(line.contains("BEGIN:BENV")) {
672fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(D) Log.d(TAG,"Decoding nested envelope");
673fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            parseEnvelope(reader, ++level); // Nested BENV
674fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
675fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(line.contains("BEGIN:BBODY")){
676fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(D) Log.d(TAG,"Decoding bbody");
677fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            parseBody(reader);
678fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
679fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
680fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
681fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    private void parseBody(BMsgReader reader) {
682fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        String line;
683fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        line = reader.getLineEnforce();
684da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker        parseMsgInit();
685fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        while(!line.contains("END:")) {
686fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(line.contains("PARTID:")) {
687fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String arg[] = line.split(":");
688fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
689fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    try {
690326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    mPartId = Long.parseLong(arg[1].trim());
691fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } catch (NumberFormatException e) {
692fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        throw new IllegalArgumentException("Wrong value in 'PARTID': " + arg[1]);
693fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    }
694fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                } else {
695fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    throw new IllegalArgumentException("Missing value for 'PARTID': " + line);
696fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
697fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
698fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            else if(line.contains("ENCODING:")) {
699fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String arg[] = line.split(":");
700fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
701326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    mEncoding = arg[1].trim();
702326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    // If needed validation will be done when the value is used
703fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                } else {
704fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    throw new IllegalArgumentException("Missing value for 'ENCODING': " + line);
705fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
706fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
707fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            else if(line.contains("CHARSET:")) {
708fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String arg[] = line.split(":");
709fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
710326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    mCharset = arg[1].trim();
711326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    // If needed validation will be done when the value is used
712fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                } else {
713fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    throw new IllegalArgumentException("Missing value for 'CHARSET': " + line);
714fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
715fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
716fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            else if(line.contains("LANGUAGE:")) {
717fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String arg[] = line.split(":");
718fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
719326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    mLanguage = arg[1].trim();
720326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    // If needed validation will be done when the value is used
721fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                } else {
722fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    throw new IllegalArgumentException("Missing value for 'LANGUAGE': " + line);
723fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
724fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
725fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            else if(line.contains("LENGTH:")) {
726fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                String arg[] = line.split(":");
727fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                if (arg != null && arg.length == 2) {
728fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    try {
729326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        mBMsgLength = Integer.parseInt(arg[1].trim());
730fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    } catch (NumberFormatException e) {
731fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                        throw new IllegalArgumentException("Wrong value in 'LENGTH': " + arg[1]);
732fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    }
733fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                } else {
734fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                    throw new IllegalArgumentException("Missing value for 'LENGTH': " + line);
735fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
736fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
737fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            else if(line.contains("BEGIN:MSG")) {
738da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                if (V) Log.v(TAG, "bMsgLength: " + mBMsgLength);
739326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(mBMsgLength == INVALID_VALUE)
740326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    throw new IllegalArgumentException("Missing value for 'LENGTH'. " +
741326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                            "Unable to read remaining part of the message");
742da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker
743326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                /* For SMS: Encoding of MSG is always UTF-8 compliant, regardless of any properties,
744326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                   since PDUs are encodes as hex-strings */
745fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                /* PTS has a bug regarding the message length, and sets it 2 bytes too short, hence
746fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                 * using the length field to determine the amount of data to read, might not be the
747fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                 * best solution.
748da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                 * Errata ESR06 section 5.8.12 introduced escaping of END:MSG in the actual message
749da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                 * content, it is now safe to use the END:MSG tag as terminator, and simply ignore
750da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                 * the length field.*/
751da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker
752da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                // Read until we receive END:MSG as some carkits send bad message lengths
753da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                String data = "";
754da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                String message_line = "";
755da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                while (!message_line.equals("END:MSG")) {
756da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                    data += message_line;
757da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                    message_line = reader.getLineEnforce();
758fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                }
759da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker
760da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                // The MAP spec says that all END:MSG strings in the body
761da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                // of the message must be escaped upon encoding and the
762da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                // escape removed upon decoding
763da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                data.replaceAll("([/]*)/END\\:MSG", "$1END:MSG");
764da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                data.trim();
765da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker
766da6bccfffb75e8452faafa8906082dc192cd7ce9Ajay Panicker                parseMsgPart(data);
767fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
768fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            line = reader.getLineEnforce();
769fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
770fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
771fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
772fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
773fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Parse the 'message' part of <bmessage-body-content>"
774fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param msgPart
775fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
776fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public abstract void parseMsgPart(String msgPart);
777fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
778fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Set initial values before parsing - will be called is a message body is found
779fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * during parsing.
780fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
781fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public abstract void parseMsgInit();
782fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
783fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public abstract byte[] encode() throws UnsupportedEncodingException;
784fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
785fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void setStatus(boolean read) {
786fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(read)
787326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mStatus = "READ";
788fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        else
789326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mStatus = "UNREAD";
790fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
791fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
792fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void setType(TYPE type) {
793326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        this.mType = type;
794fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
795fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
796fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
797fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @return the type
798fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
799fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public TYPE getType() {
800326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return mType;
801fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
802fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
80370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz    public void setCompleteFolder(String folder) {
804326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        this.mFolder = folder;
80570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz    }
80670be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
807fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void setFolder(String folder) {
808326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        this.mFolder = "telecom/msg/" + folder;
809fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
810fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
81170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz    public String getFolder() {
812326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return mFolder;
81370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz    }
81470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
81570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz
816fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void setEncoding(String encoding) {
817326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        this.mEncoding = encoding;
818fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
819fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
820fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public ArrayList<vCard> getOriginators() {
821326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return mOriginator;
822fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
823fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
824fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void addOriginator(vCard originator) {
825326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(this.mOriginator == null)
826326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mOriginator = new ArrayList<vCard>();
827326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        this.mOriginator.add(originator);
828fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
829fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
830fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
831fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Add a version 3.0 vCard with a formatted name
832fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param name e.g. Bonde;Casper
833fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param formattedName e.g. "Casper Bonde"
834fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param phoneNumbers
835fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param emailAddresses
836fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
8375a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public void addOriginator(String name, String formattedName,
8385a60e47497f21f64e6d79420dc4c56c1907df22akschulz                              String[] phoneNumbers,
8395a60e47497f21f64e6d79420dc4c56c1907df22akschulz                              String[] emailAddresses,
8405a60e47497f21f64e6d79420dc4c56c1907df22akschulz                              String[] btUids,
8415a60e47497f21f64e6d79420dc4c56c1907df22akschulz                              String[] btUcis) {
842326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mOriginator == null)
843326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            mOriginator = new ArrayList<vCard>();
8445a60e47497f21f64e6d79420dc4c56c1907df22akschulz        mOriginator.add(new vCard(name, formattedName, phoneNumbers,
8455a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    emailAddresses, btUids, btUcis));
846fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
847fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
8485a60e47497f21f64e6d79420dc4c56c1907df22akschulz
8495a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public void addOriginator(String[] btUcis, String[] btUids) {
8505a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(mOriginator == null)
8515a60e47497f21f64e6d79420dc4c56c1907df22akschulz            mOriginator = new ArrayList<vCard>();
8525a60e47497f21f64e6d79420dc4c56c1907df22akschulz        mOriginator.add(new vCard(null,null,null,null,btUids, btUcis));
8535a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
8545a60e47497f21f64e6d79420dc4c56c1907df22akschulz
8555a60e47497f21f64e6d79420dc4c56c1907df22akschulz
856fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /** Add a version 2.1 vCard with only a name.
857fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     *
858fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param name e.g. Bonde;Casper
859fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param phoneNumbers
860fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param emailAddresses
861fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
862fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void addOriginator(String name, String[] phoneNumbers, String[] emailAddresses) {
863326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mOriginator == null)
864326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            mOriginator = new ArrayList<vCard>();
865326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        mOriginator.add(new vCard(name, phoneNumbers, emailAddresses));
866fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
867fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
868fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public ArrayList<vCard> getRecipients() {
869326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return mRecipient;
870fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
871fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
872fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void setRecipient(vCard recipient) {
873326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(this.mRecipient == null)
874326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            this.mRecipient = new ArrayList<vCard>();
875326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        this.mRecipient.add(recipient);
876fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
8775a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public void addRecipient(String[] btUcis, String[] btUids) {
8785a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(mRecipient == null)
8795a60e47497f21f64e6d79420dc4c56c1907df22akschulz            mRecipient = new ArrayList<vCard>();
8805a60e47497f21f64e6d79420dc4c56c1907df22akschulz        mRecipient.add(new vCard(null,null,null,null,btUids, btUcis));
8815a60e47497f21f64e6d79420dc4c56c1907df22akschulz    }
8825a60e47497f21f64e6d79420dc4c56c1907df22akschulz    public void addRecipient(String name, String formattedName,
8835a60e47497f21f64e6d79420dc4c56c1907df22akschulz                             String[] phoneNumbers,
8845a60e47497f21f64e6d79420dc4c56c1907df22akschulz                             String[] emailAddresses,
8855a60e47497f21f64e6d79420dc4c56c1907df22akschulz                             String[] btUids,
8865a60e47497f21f64e6d79420dc4c56c1907df22akschulz                             String[] btUcis) {
887326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mRecipient == null)
888326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            mRecipient = new ArrayList<vCard>();
8895a60e47497f21f64e6d79420dc4c56c1907df22akschulz        mRecipient.add(new vCard(name, formattedName, phoneNumbers,
8905a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    emailAddresses,btUids, btUcis));
891fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
892fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
893fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public void addRecipient(String name, String[] phoneNumbers, String[] emailAddresses) {
894326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mRecipient == null)
895326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            mRecipient = new ArrayList<vCard>();
896326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        mRecipient.add(new vCard(name, phoneNumbers, emailAddresses));
897fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
898fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
899fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
9005a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * Convert a byte[] of data to a hex string representation, converting each nibble to the
9015a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * corresponding hex char.
9025a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented
9035a60e47497f21f64e6d79420dc4c56c1907df22akschulz     * as a string as only the characters [0-9] and [a-f] is used.
904fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param pduData the byte-array of data.
905fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param scAddressData the byte-array of the encoded sc-Address.
906fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @return the resulting string.
907fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
908fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    protected String encodeBinary(byte[] pduData, byte[] scAddressData) {
909fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        StringBuilder out = new StringBuilder((pduData.length + scAddressData.length)*2);
910fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        for(int i = 0; i < scAddressData.length; i++) {
911fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            out.append(Integer.toString((scAddressData[i] >> 4) & 0x0f,16)); // MS-nibble first
912fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            out.append(Integer.toString( scAddressData[i]       & 0x0f,16));
913fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
914fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        for(int i = 0; i < pduData.length; i++) {
915fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            out.append(Integer.toString((pduData[i] >> 4) & 0x0f,16)); // MS-nibble first
916fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            out.append(Integer.toString( pduData[i]       & 0x0f,16));
9175a60e47497f21f64e6d79420dc4c56c1907df22akschulz            /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not
9185a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                           * include the needed 0's
9195a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                           * e.g. it converts the value 3 to "3"
9205a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                           * and not "03" */
921fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
922fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        return out.toString();
923fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
924fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
925fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    /**
926fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * Decodes a binary hex-string encoded UTF-8 string to the represented binary data set.
927fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @param data The string representation of the data - must have an even number of characters.
928fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     * @return the byte[] represented in the data.
929fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie     */
930fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    protected byte[] decodeBinary(String data) {
931fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        byte[] out = new byte[data.length()/2];
932fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        String value;
933fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END");
934fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        for(int i = 0, j = 0, n = out.length; i < n; i++)
935fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        {
9365a60e47497f21f64e6d79420dc4c56c1907df22akschulz            value = data.substring(j++, ++j);
9375a60e47497f21f64e6d79420dc4c56c1907df22akschulz            // same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index
93870be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            out[i] = (byte)(Integer.valueOf(value, 16) & 0xff);
93970be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        }
94070be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        if(D) {
94170be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            StringBuilder sb = new StringBuilder(out.length);
94270be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            for(int i = 0, n = out.length; i < n; i++)
94370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            {
94470be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz                sb.append(String.format("%02X",out[i] & 0xff));
94570be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            }
94670be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz            Log.d(TAG,"Decoded binary data: START:" + sb.toString() + ":END");
947fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
948fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        return out;
949fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
950fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
951fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    public byte[] encodeGeneric(ArrayList<byte[]> bodyFragments) throws UnsupportedEncodingException
952fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    {
953fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        StringBuilder sb = new StringBuilder(256);
954fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        byte[] msgStart, msgEnd;
955fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb.append("BEGIN:BMSG").append("\r\n");
9565a60e47497f21f64e6d79420dc4c56c1907df22akschulz
9575a60e47497f21f64e6d79420dc4c56c1907df22akschulz        sb.append(mVersionString).append("\r\n");
958326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        sb.append("STATUS:").append(mStatus).append("\r\n");
959326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        sb.append("TYPE:").append(mType.name()).append("\r\n");
960326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mFolder.length() > 512)
9615a60e47497f21f64e6d79420dc4c56c1907df22akschulz            sb.append("FOLDER:").append(
9625a60e47497f21f64e6d79420dc4c56c1907df22akschulz                    mFolder.substring(mFolder.length()-512, mFolder.length())).append("\r\n");
96370be005a18a35ec5fcb46152f0dfbe82156efa3aKim Schulz        else
964326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            sb.append("FOLDER:").append(mFolder).append("\r\n");
9655a60e47497f21f64e6d79420dc4c56c1907df22akschulz        if(!mVersionString.contains("1.0")){
9665a60e47497f21f64e6d79420dc4c56c1907df22akschulz            sb.append("EXTENDEDDATA:").append("\r\n");
9675a60e47497f21f64e6d79420dc4c56c1907df22akschulz        }
968326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mOriginator != null){
969326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            for(vCard element : mOriginator)
970fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                element.encode(sb);
971fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
972326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        /* If we need the three levels of env. at some point - we do have a level in the
973326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         *  vCards that could be used to determine the levels of the envelope.
974fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie         */
975fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
976fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb.append("BEGIN:BENV").append("\r\n");
977326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mRecipient != null){
978326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            for(vCard element : mRecipient) {
979326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(V) Log.v(TAG, "encodeGeneric: recipient email" + element.getFirstEmail());
980fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                element.encode(sb);
981326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
982fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
983fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb.append("BEGIN:BBODY").append("\r\n");
984326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mEncoding != null && mEncoding != "")
985326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            sb.append("ENCODING:").append(mEncoding).append("\r\n");
986326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mCharset != null && mCharset != "")
987326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            sb.append("CHARSET:").append(mCharset).append("\r\n");
988fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
989fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
990fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        int length = 0;
991fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        /* 22 is the length of the 'BEGIN:MSG' and 'END:MSG' + 3*CRLF */
992fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        for (byte[] fragment : bodyFragments) {
993fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            length += fragment.length + 22;
994fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
995fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb.append("LENGTH:").append(length).append("\r\n");
996fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
997fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        // Extract the initial part of the bMessage string
998fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        msgStart = sb.toString().getBytes("UTF-8");
999fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
1000fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb = new StringBuilder(31);
1001fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb.append("END:BBODY").append("\r\n");
1002fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb.append("END:BENV").append("\r\n");
1003fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        sb.append("END:BMSG").append("\r\n");
1004fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
1005fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        msgEnd = sb.toString().getBytes("UTF-8");
1006fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
1007fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        try {
1008fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
10095a60e47497f21f64e6d79420dc4c56c1907df22akschulz            ByteArrayOutputStream stream = new ByteArrayOutputStream(
10105a60e47497f21f64e6d79420dc4c56c1907df22akschulz                                                       msgStart.length + msgEnd.length + length);
1011fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            stream.write(msgStart);
1012fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
1013fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            for (byte[] fragment : bodyFragments) {
1014fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                stream.write("BEGIN:MSG\r\n".getBytes("UTF-8"));
1015fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                stream.write(fragment);
1016fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie                stream.write("\r\nEND:MSG\r\n".getBytes("UTF-8"));
1017fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            }
1018fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            stream.write(msgEnd);
1019fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie
1020fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            if(V) Log.v(TAG,stream.toString("UTF-8"));
1021fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return stream.toByteArray();
1022fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        } catch (IOException e) {
1023fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            Log.w(TAG,e);
1024fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie            return null;
1025fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie        }
1026fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie    }
1027fd6603b8bf9ed72dcc8bd59aaef3209251b6e17cMatthew Xie}
1028