BluetoothPbapVcardManager.java revision d896162ef022a5607b13bae5177637ff3f18b737
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.content.ContentResolver;
38import android.content.Context;
39import android.database.Cursor;
40import android.net.Uri;
41import android.provider.Contacts;
42import android.provider.CallLog.Calls;
43import android.provider.Contacts.Organizations;
44import android.provider.CallLog;
45import android.provider.Contacts.People;
46import android.provider.Contacts.Phones;
47import android.syncml.pim.vcard.ContactStruct;
48import android.syncml.pim.vcard.VCardComposer;
49import android.syncml.pim.vcard.VCardException;
50import android.syncml.pim.vcard.VCardParser;
51import android.util.Log;
52
53import java.util.ArrayList;
54
55public class BluetoothPbapVcardManager {
56    static private final String TAG = "BluetoothPbapVcardManager";
57
58    private ContentResolver mResolver;
59
60    private Context mContext;
61
62    private String mDefaultName = null;
63
64    private String mDefaultNumber = null;
65
66    /** The projection to use when querying the call log table */
67    public static final String[] CALL_LOG_PROJECTION = new String[] {
68            Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE, Calls.CACHED_NAME,
69            Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_LABEL
70    };
71
72    public static final int ID_COLUMN_INDEX = 0;
73
74    public static final int NUMBER_COLUMN_INDEX = 1;
75
76    public static final int DATE_COLUMN_INDEX = 2;
77
78    public static final int DURATION_COLUMN_INDEX = 3;
79
80    public static final int CALL_TYPE_COLUMN_INDEX = 4;
81
82    public static final int CALLER_NAME_COLUMN_INDEX = 5;
83
84    public static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6;
85
86    public static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7;
87
88    /** The projection to use when querying the phone book table */
89    public static final String[] CONTACT_PROJECTION = new String[] {
90            People._ID, // 0
91            People.NAME, // 1
92            People.NOTES, // 2
93            People.PRIMARY_PHONE_ID, // 3
94            People.PRESENCE_STATUS, // 4
95            People.STARRED, // 5
96            People.CUSTOM_RINGTONE, // 6
97            People.SEND_TO_VOICEMAIL, // 7
98            People.PHONETIC_NAME, // 8
99    };
100
101    public static final int CONTACT_ID_COLUMN = 0;
102
103    public static final int CONTACT_NAME_COLUMN = 1;
104
105    public static final int CONTACT_NOTES_COLUMN = 2;
106
107    public static final int CONTACT_PREFERRED_PHONE_COLUMN = 3;
108
109    public static final int CONTACT_SERVER_STATUS_COLUMN = 4;
110
111    public static final int CONTACT_STARRED_COLUMN = 5;
112
113    public static final int CONTACT_CUSTOM_RINGTONE_COLUMN = 6;
114
115    public static final int CONTACT_SEND_TO_VOICEMAIL_COLUMN = 7;
116
117    public static final int CONTACT_PHONETIC_NAME_COLUMN = 8;
118
119    public static final String[] PHONES_PROJECTION = new String[] {
120            People.Phones._ID, // 0
121            People.Phones.NUMBER, // 1
122            People.Phones.TYPE, // 2
123            People.Phones.LABEL, // 3
124            People.Phones.ISPRIMARY, // 4
125    };
126
127    public static final int PHONES_ID_COLUMN = 0;
128
129    public static final int PHONES_NUMBER_COLUMN = 1;
130
131    public static final int PHONES_TYPE_COLUMN = 2;
132
133    public static final int PHONES_LABEL_COLUMN = 3;
134
135    public static final int PHONES_ISPRIMARY_COLUMN = 4;
136
137    public static final String[] METHODS_PROJECTION = new String[] {
138            People.ContactMethods._ID, // 0
139            People.ContactMethods.KIND, // 1
140            People.ContactMethods.DATA, // 2
141            People.ContactMethods.TYPE, // 3
142            People.ContactMethods.LABEL, // 4
143            People.ContactMethods.ISPRIMARY, // 5
144            People.ContactMethods.AUX_DATA, // 6
145    };
146
147    public static final int METHODS_ID_COLUMN = 0;
148
149    public static final int METHODS_KIND_COLUMN = 1;
150
151    public static final int METHODS_DATA_COLUMN = 2;
152
153    public static final int METHODS_TYPE_COLUMN = 3;
154
155    public static final int METHODS_LABEL_COLUMN = 4;
156
157    public static final int METHODS_ISPRIMARY_COLUMN = 5;
158
159    public static final int METHODS_AUX_DATA_COLUMN = 6;
160
161    public static final int METHODS_STATUS_COLUMN = 7;
162
163    public static final String[] ORGANIZATIONS_PROJECTION = new String[] {
164            Organizations._ID, // 0
165            Organizations.TYPE, // 1
166            Organizations.LABEL, // 2
167            Organizations.COMPANY, // 3
168            Organizations.TITLE, // 4
169            Organizations.ISPRIMARY, // 5
170    };
171
172    public static final int ORGANIZATIONS_ID_COLUMN = 0;
173
174    public static final int ORGANIZATIONS_TYPE_COLUMN = 1;
175
176    public static final int ORGANIZATIONS_LABEL_COLUMN = 2;
177
178    public static final int ORGANIZATIONS_COMPANY_COLUMN = 3;
179
180    public static final int ORGANIZATIONS_TITLE_COLUMN = 4;
181
182    public static final int ORGANIZATIONS_ISPRIMARY_COLUMN = 5;
183
184    public BluetoothPbapVcardManager(final Context context) {
185        mContext = context;
186        mResolver = mContext.getContentResolver();
187        mDefaultName = context.getString(android.R.string.unknownName);
188        mDefaultNumber = context.getString(R.string.defaultnumber);
189    }
190
191    private String getThisPhoneName() {
192        String name;
193        name = BluetoothPbapService.getLocalPhoneName();
194        if (name == null || name.trim().length() == 0) {
195            name = mDefaultName;
196        }
197        return name;
198    }
199
200    private String getThisPhoneNumber() {
201        String number;
202        number = BluetoothPbapService.getLocalPhoneNum();
203        if (number == null || number.trim().length() == 0) {
204            number = mDefaultNumber;
205        }
206        return number;
207    }
208
209    public final int getPhonebookSize() {
210        Uri myUri = Contacts.People.CONTENT_URI;
211        Cursor contactC = mResolver.query(myUri, null, null, null, null);
212
213        int mPhonebookSize = 0;
214        if (contactC != null) {
215            mPhonebookSize = contactC.getCount() + 1; // always has the 0.vcf
216        }
217        return mPhonebookSize;
218    }
219
220    public final String getPhonebook(final int pos, final boolean vCard21) {
221        try {
222            if (pos >= 0 && pos < getPhonebookSize()) {
223                long id = 0;
224                Uri myUri = Contacts.People.CONTENT_URI;
225                // Individual Contact may be deleted, which lead to incontinuous
226                // ID for Uri; So we need to calculate the actual Uri.
227                if (pos > 0) {
228                    Cursor personCursor = mResolver.query(myUri, CONTACT_PROJECTION, null, null,
229                            null);
230                    if (personCursor != null) {
231                        personCursor.moveToPosition(pos - 1);
232                        id = personCursor.getLong(CONTACT_ID_COLUMN);
233                        personCursor.close();
234                    }
235                    String sPos = String.valueOf(id);
236                    myUri = Uri.withAppendedPath(myUri, sPos);
237                }
238                return loadPhonebook(myUri, pos, vCard21);
239            } else {
240                Log.w(TAG, "pos invalid");
241            }
242        } catch (Exception e) {
243            Log.e(TAG, "catch exception" + e.toString());
244        }
245        return null;
246    }
247
248    public final int getCallHistorySize(final int type) {
249        int size = 0;
250        Uri myUri = CallLog.Calls.CONTENT_URI;
251        String selection = null;
252        switch (type) {
253            case BluetoothPbapObexServer.ContentType.INCOMING_CALL_HISTORY:
254                selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
255                break;
256            case BluetoothPbapObexServer.ContentType.OUTGOING_CALL_HISTORY:
257                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
258                break;
259            case BluetoothPbapObexServer.ContentType.MISSED_CALL_HISTORY:
260                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
261                break;
262            default:
263                break;
264        }
265        Cursor callCursor = mResolver.query(myUri, null, selection, null,
266                CallLog.Calls.DEFAULT_SORT_ORDER);
267        if (callCursor != null) {
268            size = callCursor.getCount();
269            callCursor.close();
270        }
271        return size;
272    }
273
274    public final String getCallHistory(final int pos, final int type, final boolean vCard21) {
275        int size = 0;
276        String selection = null;
277        switch (type) {
278            case BluetoothPbapObexServer.ContentType.INCOMING_CALL_HISTORY:
279                selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
280                break;
281            case BluetoothPbapObexServer.ContentType.OUTGOING_CALL_HISTORY:
282                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
283                break;
284            case BluetoothPbapObexServer.ContentType.MISSED_CALL_HISTORY:
285                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
286                break;
287            default:
288                break;
289        }
290        size = getCallHistorySize(type);
291        try {
292            if (pos >= 0 && pos < size) {
293                Uri myUri = CallLog.Calls.CONTENT_URI;
294                return loadCallHistory(myUri, pos, selection, vCard21);
295            } else {
296                Log.w(TAG, "pos invalid");
297            }
298        } catch (Exception e) {
299            Log.e(TAG, "catch exception e" + e.toString());
300        }
301        return null;
302    }
303
304    public final ArrayList<String> loadNameList() {
305        ArrayList<String> nameList = new ArrayList<String>();
306        int size = getPhonebookSize();
307        Uri myUri = Contacts.People.CONTENT_URI;
308        Cursor contactC = mResolver.query(myUri, null, null, null, null);
309        for (int pos = 0; pos < size; pos++) {
310            if (pos == 0) {
311                nameList.add(getThisPhoneName());
312            } else {
313                if (contactC != null) {
314                    contactC.moveToPosition(pos - 1);
315                    String name = contactC.getString(contactC
316                            .getColumnIndexOrThrow(Contacts.People.NAME));
317                    if (name == null || name.trim().length() == 0) {
318                        name = mDefaultName;
319                    }
320                    nameList.add(name);
321                }
322            }
323        }
324        if (contactC != null) {
325            contactC.close();
326        }
327        return nameList;
328    }
329
330    public final ArrayList<String> loadNumberList() {
331        ArrayList<String> numberList = new ArrayList<String>();
332        if (numberList.size() > 0) {
333            numberList.clear();
334        }
335        int size = getPhonebookSize();
336        Uri myUri = Contacts.People.CONTENT_URI;
337        Cursor contactC = mResolver.query(myUri, null, null, null, null);
338        for (int pos = 0; pos < size; pos++) {
339            if (pos == 0) {
340                numberList.add(getThisPhoneNumber());
341            } else {
342                contactC.moveToPosition(pos - 1);
343                String number = contactC.getString(contactC
344                        .getColumnIndexOrThrow(Contacts.People.PRIMARY_PHONE_ID));
345                if (number == null || number.trim().length() == 0) {
346                    number = mDefaultNumber;
347                }
348                numberList.add(number);
349            }
350        }
351        return numberList;
352    }
353
354    public final ArrayList<String> loadCallHistoryList(final int type) {
355        int size = 0;
356        String selection = null;
357        Uri myUri = CallLog.Calls.CONTENT_URI;
358        ArrayList<String> list = new ArrayList<String>();
359        switch (type) {
360            case BluetoothPbapObexServer.ContentType.INCOMING_CALL_HISTORY:
361                selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
362                break;
363            case BluetoothPbapObexServer.ContentType.OUTGOING_CALL_HISTORY:
364                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
365                break;
366            case BluetoothPbapObexServer.ContentType.MISSED_CALL_HISTORY:
367                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
368                break;
369            default:
370                break;
371        }
372        size = getCallHistorySize(type);
373        Cursor callCursor = mResolver.query(myUri, CALL_LOG_PROJECTION, selection, null,
374                CallLog.Calls.DEFAULT_SORT_ORDER);
375        if (callCursor != null) {
376            for (int pos = 0; pos < size; pos++) {
377                callCursor.moveToPosition(pos);
378                String name = callCursor.getString(CALLER_NAME_COLUMN_INDEX);
379                if (name == null || name.trim().length() == 0) {
380                    // name not found,use number instead
381                    name = callCursor.getString(NUMBER_COLUMN_INDEX);
382                }
383                list.add(name);
384            }
385            callCursor.close();
386        }
387        return list;
388    }
389
390    private final String loadCallHistory(final Uri mUri, final int pos, final String selection,
391            final boolean vCard21) {
392        ContactStruct contactStruct = new ContactStruct();
393        Cursor callCursor = mResolver.query(mUri, CALL_LOG_PROJECTION, selection, null,
394                CallLog.Calls.DEFAULT_SORT_ORDER);
395        if (callCursor != null) {
396            if (callCursor.moveToPosition(pos)) {
397                contactStruct.name = callCursor.getString(CALLER_NAME_COLUMN_INDEX);
398                if (contactStruct.name == null || contactStruct.name.trim().length() == 0) {
399                    contactStruct.name = callCursor.getString(NUMBER_COLUMN_INDEX);
400                }
401                String number = callCursor.getString(NUMBER_COLUMN_INDEX);
402                int type = callCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
403                String label = callCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
404                if (label == null || label.trim().length() == 0) {
405                    label = Integer.toString(type);
406                }
407                contactStruct.addPhone(type, number, label, true);
408            }
409            callCursor.close();
410        }
411        try {
412            VCardComposer composer = new VCardComposer();
413            if (vCard21) {
414                return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD21_INT);
415            } else {
416                return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD30_INT);
417            }
418        } catch (VCardException e) {
419            Log.e(TAG, "catch exception" + e.toString());
420            return null;
421        }
422    }
423
424    private final String loadPhonebook(final Uri mUri, final int pos, final boolean vCard21) {
425        // Build up the phone entries
426        ContactStruct contactStruct = new ContactStruct();
427        Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
428        if (personCursor == null) {
429            return null;
430        }
431        if (pos == 0) {
432            contactStruct.name = getThisPhoneName();
433            contactStruct.addPhone(Contacts.PhonesColumns.TYPE_MOBILE, getThisPhoneNumber(), "",
434                    true);
435        } else if (personCursor.moveToNext()) {
436            contactStruct.name = personCursor.getString(CONTACT_NAME_COLUMN);
437            if (contactStruct.name == null || contactStruct.name.trim().length() == 0) {
438                contactStruct.name = mDefaultName;
439            }
440            contactStruct.notes.add(checkStrEnd(personCursor.getString(CONTACT_NOTES_COLUMN),
441                    vCard21));
442            final Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
443            final Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION, null, null,
444                    Phones.ISPRIMARY + " DESC");
445            if (phonesCursor != null) {
446                for (phonesCursor.moveToFirst(); !phonesCursor.isAfterLast();
447                            phonesCursor.moveToNext()) {
448                    int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
449                    String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
450                    String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
451                    contactStruct.addPhone(type, number, label, true);
452                }
453                phonesCursor.close();
454            }
455            // Build the contact method entries
456            final Uri methodsUri = Uri.withAppendedPath(mUri,
457                    People.ContactMethods.CONTENT_DIRECTORY);
458            Cursor methodsCursor = mResolver
459                    .query(methodsUri, METHODS_PROJECTION, null, null, null);
460            if (methodsCursor != null) {
461                for (methodsCursor.moveToFirst(); !methodsCursor.isAfterLast();
462                           methodsCursor.moveToNext()) {
463                    int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
464                    String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
465                    String data = methodsCursor.getString(METHODS_DATA_COLUMN);
466                    int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
467                    boolean isPrimary = methodsCursor.getInt(METHODS_ISPRIMARY_COLUMN) == 1 ? true
468                            : false;
469                    // TODO
470                    // Below code is totally depend on the implementation of
471                    // package android.syncml.pim.vcard
472                    // VcardComposer shall throw null pointer exception when
473                    // label is not set
474                    // function appendContactMethodStr is weak and shall be
475                    // improved in the future
476                    if (kind == Contacts.KIND_EMAIL) {
477                        if (type == Contacts.ContactMethodsColumns.TYPE_OTHER) {
478                            if (label == null || label.trim().length() == 0) {
479                                label = Integer.toString(type);
480                            }
481                        }
482                    }
483                    if (kind == Contacts.KIND_POSTAL) {
484                        data = checkStrEnd(data, vCard21);
485                    }
486                    contactStruct.addContactmethod(kind, type, data, label, isPrimary);
487                }
488                methodsCursor.close();
489            }
490
491            // Build the organization entries
492            final Uri organizationsUri = Uri
493                    .withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
494            Cursor organizationsCursor = mResolver.query(organizationsUri,
495                    ORGANIZATIONS_PROJECTION, "isprimary", null, null);
496
497            if (organizationsCursor != null) {
498                for (organizationsCursor.moveToFirst(); !organizationsCursor.isAfterLast();
499                           organizationsCursor.moveToNext()) {
500                    int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN);
501                    String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
502                    String title = checkStrEnd(organizationsCursor
503                            .getString(ORGANIZATIONS_TITLE_COLUMN), vCard21);
504                    boolean isPrimary = organizationsCursor.
505                                   getInt(ORGANIZATIONS_ISPRIMARY_COLUMN) == 1 ? true
506                            : false;
507                    contactStruct.addOrganization(type, company, title, isPrimary);
508                }
509                organizationsCursor.close();
510            }
511        }
512        personCursor.close();
513        // Generate vCard data.
514        try {
515            VCardComposer composer = new VCardComposer();
516            if (vCard21) {
517                return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD21_INT);
518            } else {
519                return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD30_INT);
520            }
521        } catch (VCardException e) {
522            Log.e(TAG, "catch exception in loadPhonebook" + e.toString());
523            return null;
524        }
525    }
526
527    /**
528     * This function is to check the string end to avoid function
529     * foldingString's returning null in package android.syncml.pim.vcard
530     */
531    private final String checkStrEnd(String str, final boolean vCard21) {
532        if (str == null) {
533            return str;
534        }
535        if (str.charAt(str.length() - 1) != '\n') {
536            if (vCard21) {
537                str += "\r\n";
538            } else {
539                str += "\n";
540            }
541        }
542        return str;
543    }
544
545}
546