BluetoothPbapVcardManager.java revision 18bde766b20c899310ebdd5ca823e30ff27d407f
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.android.bluetooth.pbap;
34
35import com.android.bluetooth.R;
36
37import android.net.Uri;
38import android.os.Handler;
39import android.syncml.pim.vcard.ContactStruct;
40import android.syncml.pim.vcard.VCardException;
41import android.text.TextUtils;
42import android.util.Log;
43import android.database.Cursor;
44import android.content.ContentResolver;
45import android.content.Context;
46import android.provider.CallLog;
47import android.provider.CallLog.Calls;
48import android.provider.ContactsContract.RawContacts;
49import android.provider.ContactsContract.PhoneLookup;
50import android.pim.vcard.VCardComposer;
51import android.pim.vcard.VCardConfig;
52import android.pim.vcard.VCardComposer.OneEntryHandler;
53import android.provider.ContactsContract;
54import android.provider.ContactsContract.CommonDataKinds;
55import android.provider.ContactsContract.Contacts;
56import android.provider.ContactsContract.Data;
57import android.provider.ContactsContract.CommonDataKinds.Phone;
58
59import javax.obex.ResponseCodes;
60import javax.obex.Operation;
61import java.io.IOException;
62import java.io.OutputStream;
63import java.io.Writer;
64import java.util.ArrayList;
65
66public class BluetoothPbapVcardManager {
67    private static final String TAG = "BluetoothPbapVcardManager";
68
69    private static final boolean V = BluetoothPbapService.VERBOSE;
70
71    private ContentResolver mResolver;
72
73    private Context mContext;
74
75    private StringBuilder mVcardResults = null;
76
77    static final String[] PHONES_PROJECTION = new String[] {
78            Data._ID, // 0
79            CommonDataKinds.Phone.TYPE, // 1
80            CommonDataKinds.Phone.LABEL, // 2
81            CommonDataKinds.Phone.NUMBER, // 3
82            Contacts.DISPLAY_NAME, // 4
83    };
84
85    private static final int ID_COLUMN_INDEX = 0;
86
87    private static final int PHONE_TYPE_COLUMN_INDEX = 1;
88
89    private static final int PHONE_LABEL_COLUMN_INDEX = 2;
90
91    private static final int PHOEN_NUMBER_COLUMN_INDEX = 3;
92
93    private static final int CONTACTS_DISPLAY_NAME_COLUMN_INDEX = 4;
94
95    static final String SORT_ORDER_NAME = Contacts.DISPLAY_NAME + " ASC";
96
97    static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC";
98
99    public BluetoothPbapVcardManager(final Context context) {
100        mContext = context;
101        mResolver = mContext.getContentResolver();
102    }
103
104    public final String getOwnerPhoneNumberVcard(final boolean vcardType21) {
105        VCardComposer composer = new VCardComposer(mContext);
106        String name = BluetoothPbapService.getLocalPhoneName();
107        String number = BluetoothPbapService.getLocalPhoneNum();
108        String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number,
109                vcardType21);
110        return vcard;
111    }
112
113    public final int getPhonebookSize(final int type) {
114        int size;
115        switch (type) {
116            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
117                size = getContactsSize();
118                break;
119            default:
120                size = getCallHistorySize(type);
121                break;
122        }
123        if (V) Log.v(TAG, "getPhonebookSzie size = " + size + " type = " + type);
124        return size;
125    }
126
127    public final int getContactsSize() {
128        Uri myUri = RawContacts.CONTENT_URI;
129        int size = 0;
130        Cursor contactCursor = null;
131        try {
132            contactCursor = mResolver.query(myUri, null, null, null, null);
133            if (contactCursor != null) {
134                size = contactCursor.getCount() + 1; // always has the 0.vcf
135            }
136        } finally {
137            if (contactCursor != null) {
138                contactCursor.close();
139            }
140        }
141        return size;
142    }
143
144    public final int getCallHistorySize(final int type) {
145        Uri myUri = CallLog.Calls.CONTENT_URI;
146        String selection = BluetoothPbapObexServer.createSelectionPara(type);
147        int size = 0;
148        Cursor callCursor = null;
149        try {
150            callCursor = mResolver.query(myUri, null, selection, null,
151                    CallLog.Calls.DEFAULT_SORT_ORDER);
152            if (callCursor != null) {
153                size = callCursor.getCount();
154            }
155        } finally {
156            if (callCursor != null) {
157                callCursor.close();
158            }
159        }
160        return size;
161    }
162
163    public final ArrayList<String> loadCallHistoryList(final int type) {
164        Uri myUri = CallLog.Calls.CONTENT_URI;
165        String selection = BluetoothPbapObexServer.createSelectionPara(type);
166        String[] projection = new String[] {
167                Calls.NUMBER, Calls.CACHED_NAME
168        };
169        final int CALLS_NUMBER_COLUMN_INDEX = 0;
170        final int CALLS_NAME_COLUMN_INDEX = 1;
171
172        Cursor callCursor = null;
173        ArrayList<String> list = new ArrayList<String>();
174        try {
175            callCursor = mResolver.query(myUri, projection, selection, null,
176                    CallLog.Calls.DEFAULT_SORT_ORDER);
177            if (callCursor != null) {
178                for (callCursor.moveToFirst(); !callCursor.isAfterLast();
179                        callCursor.moveToNext()) {
180                    String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX);
181                    if (TextUtils.isEmpty(name)) {
182                        // name not found,use number instead
183                        name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX);
184                    }
185                    list.add(name);
186                }
187            }
188        } finally {
189            if (callCursor != null) {
190                callCursor.close();
191            }
192        }
193        return list;
194    }
195
196    public final ArrayList<String> getPhonebookNameList() {
197        ArrayList<String> nameList = new ArrayList<String>();
198        nameList.add(BluetoothPbapService.getLocalPhoneName());
199
200        Uri myUri = Phone.CONTENT_URI;
201        Cursor phoneCursor = null;
202        try {
203            phoneCursor = mResolver.query(myUri, PHONES_PROJECTION, null, null, SORT_ORDER_NAME);
204            if (phoneCursor != null) {
205                for (phoneCursor.moveToFirst(); !phoneCursor.isAfterLast(); phoneCursor
206                        .moveToNext()) {
207                    String name = phoneCursor.getString(CONTACTS_DISPLAY_NAME_COLUMN_INDEX);
208                    if (TextUtils.isEmpty(name)) {
209                        name = mContext.getString(android.R.string.unknownName);
210                    }
211                    nameList.add(name);
212                }
213            }
214        } finally {
215            if (phoneCursor != null) {
216                phoneCursor.close();
217            }
218        }
219        return nameList;
220    }
221
222    public final ArrayList<String> getPhonebookNumberList() {
223        ArrayList<String> numberList = new ArrayList<String>();
224        numberList.add(BluetoothPbapService.getLocalPhoneNum());
225
226        Uri myUri = Phone.CONTENT_URI;
227        Cursor phoneCursor = null;
228        try {
229            phoneCursor = mResolver.query(myUri, PHONES_PROJECTION, null, null,
230                    SORT_ORDER_PHONE_NUMBER);
231            if (phoneCursor != null) {
232                for (phoneCursor.moveToFirst(); !phoneCursor.isAfterLast(); phoneCursor
233                        .moveToNext()) {
234                    String number = phoneCursor.getString(PHOEN_NUMBER_COLUMN_INDEX);
235                    if (TextUtils.isEmpty(number)) {
236                        number = mContext.getString(R.string.defaultnumber);
237                    }
238                    numberList.add(number);
239                }
240            }
241        } finally {
242            if (phoneCursor != null) {
243                phoneCursor.close();
244            }
245        }
246        return numberList;
247    }
248
249    public final int composeAndSendCallLogVcards(final int type, final Operation op,
250            final int startPoint, final int endPoint, final boolean vcardType21) {
251        String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
252        String recordSelection;
253        if (startPoint == endPoint) {
254            recordSelection = Calls._ID + "=" + startPoint;
255        } else {
256            recordSelection = Calls._ID + ">=" + startPoint + " AND "
257                    + Calls._ID + "<=" + endPoint;
258        }
259
260        String selection;
261        if (typeSelection == null) {
262            selection = recordSelection;
263        } else {
264            selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
265        }
266
267        if (V) Log.v(TAG, "Query selection is: " + selection);
268
269        return composeAndSendVCards(op, selection, vcardType21, null, false);
270    }
271
272    public final int composeAndSendPhonebookVcards(final Operation op, final int startPoint,
273            final int endPoint, final boolean vcardType21, String ownerVCard) {
274        String selection;
275        if (startPoint == endPoint) {
276            selection = RawContacts._ID + "=" + startPoint;
277        } else {
278            selection = RawContacts._ID + ">=" + startPoint + " AND " + RawContacts._ID + "<="
279                    + endPoint;
280        }
281
282        if (V) Log.v(TAG, "Query selection is: " + selection);
283
284        return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
285    }
286
287    public final int composeAndSendVCards(final Operation op, final String selection,
288            final boolean vcardType21, String ownerVCard, boolean isContacts) {
289        long timestamp = 0;
290        if (V) timestamp = System.currentTimeMillis();
291
292        VCardComposer composer = null;
293        try {
294            // Currently only support Generic Vcard 2.1 and 3.0
295            int vcardType;
296            if (vcardType21) {
297                vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
298            } else {
299                vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
300            }
301
302            if (isContacts) {
303                // create VCardComposer for contacts
304                composer = new VCardComposer(mContext, vcardType, true);
305            } else {
306                // create VCardComposer for call logs
307                composer = new VCardComposer(mContext, vcardType, true, true);
308            }
309
310            composer.addHandler(new HandlerForStringBuffer(op, ownerVCard));
311
312            if (!composer.init(selection, null)) {
313                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
314            }
315
316            while (!composer.isAfterLast()) {
317                if (!composer.createOneEntry()) {
318                    Log.e(TAG, "Failed to read a contact. Error reason: "
319                            + composer.getErrorReason());
320                    return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
321                }
322            }
323        } finally {
324            if (composer != null) {
325                composer.terminate();
326            }
327        }
328
329        if (V) Log.v(TAG, "Total vcard composing and sending out takes "
330                    + (System.currentTimeMillis() - timestamp) + " ms");
331
332        return ResponseCodes.OBEX_HTTP_OK;
333    }
334
335    /**
336     * Handler to emit VCard String to PCE once size grow to maxPacketSize.
337     */
338    public class HandlerForStringBuffer implements OneEntryHandler {
339        @SuppressWarnings("hiding")
340        private Operation operation;
341
342        private OutputStream outputStream;
343
344        private int maxPacketSize;
345
346        private String phoneOwnVCard = null;
347
348        public HandlerForStringBuffer(Operation op, String ownerVCard) {
349            operation = op;
350            maxPacketSize = operation.getMaxPacketSize();
351            if (V) Log.v(TAG, "getMaxPacketSize() = " + maxPacketSize);
352            if (ownerVCard != null) {
353                phoneOwnVCard = ownerVCard;
354                if (V) Log.v(TAG, "phone own number vcard:");
355                if (V) Log.v(TAG, phoneOwnVCard);
356            }
357        }
358
359        public boolean onInit(Context context) {
360            try {
361                outputStream = operation.openOutputStream();
362                mVcardResults = new StringBuilder();
363                if (phoneOwnVCard != null) {
364                    mVcardResults.append(phoneOwnVCard);
365                }
366            } catch (IOException e) {
367                Log.e(TAG, "open outputstrem failed" + e.toString());
368                return false;
369            }
370            if (V) Log.v(TAG, "openOutputStream() ok.");
371            return true;
372        }
373
374        public boolean onEntryCreated(String vcard) {
375            int vcardLen = vcard.length();
376            if (V) Log.v(TAG, "The length of this vcard is: " + vcardLen);
377
378            mVcardResults.append(vcard);
379            int vcardStringLen = mVcardResults.toString().length();
380            if (V) Log.v(TAG, "The length of this vcardResults is: " + vcardStringLen);
381
382            if (vcardStringLen >= maxPacketSize) {
383                long timestamp = 0;
384                int position = 0;
385
386                // Need while loop to handle the big vcard case
387                while (position < (vcardStringLen - maxPacketSize)) {
388                    if (V) timestamp = System.currentTimeMillis();
389
390                    String subStr = mVcardResults.toString().substring(position,
391                            position + maxPacketSize);
392                    try {
393                        outputStream.write(subStr.getBytes(), 0, maxPacketSize);
394                    } catch (IOException e) {
395                        Log.e(TAG, "write outputstrem failed" + e.toString());
396                        return false;
397                    }
398                    if (V) Log.v(TAG, "Sending vcard String " + maxPacketSize + " bytes took "
399                            + (System.currentTimeMillis() - timestamp) + " ms");
400
401                    position += maxPacketSize;
402                }
403                mVcardResults.delete(0, position);
404            }
405            return true;
406        }
407
408        public void onTerminate() {
409            // Send out last packet
410            String lastStr = mVcardResults.toString();
411            try {
412                outputStream.write(lastStr.getBytes(), 0, lastStr.length());
413            } catch (IOException e) {
414                Log.e(TAG, "write outputstrem failed" + e.toString());
415            }
416            if (V) Log.v(TAG, "Last packet sent out, sending process complete!");
417
418            if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
419                if (V) Log.v(TAG, "CloseStream failed!");
420            } else {
421                if (V) Log.v(TAG, "CloseStream ok!");
422            }
423        }
424    }
425}
426