Home | History | Annotate | Download | only in pbap
      1 /*
      2  * Copyright (c) 2008-2009, Motorola, Inc.
      3  * Copyright (C) 2009-2012, Broadcom Corporation
      4  *
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions are met:
      9  *
     10  * - Redistributions of source code must retain the above copyright notice,
     11  * this list of conditions and the following disclaimer.
     12  *
     13  * - Redistributions in binary form must reproduce the above copyright notice,
     14  * this list of conditions and the following disclaimer in the documentation
     15  * and/or other materials provided with the distribution.
     16  *
     17  * - Neither the name of the Motorola, Inc. nor the names of its contributors
     18  * may be used to endorse or promote products derived from this software
     19  * without specific prior written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     31  * POSSIBILITY OF SUCH DAMAGE.
     32  */
     33 
     34 package com.android.bluetooth.pbap;
     35 
     36 import android.content.ContentResolver;
     37 import android.content.Context;
     38 import android.database.CursorWindowAllocationException;
     39 import android.database.Cursor;
     40 import android.net.Uri;
     41 import android.provider.CallLog;
     42 import android.provider.CallLog.Calls;
     43 import android.provider.ContactsContract.CommonDataKinds;
     44 import android.provider.ContactsContract.Contacts;
     45 import android.provider.ContactsContract.Data;
     46 import android.provider.ContactsContract.CommonDataKinds.Phone;
     47 import android.provider.ContactsContract.PhoneLookup;
     48 import android.telephony.PhoneNumberUtils;
     49 import android.text.TextUtils;
     50 import android.util.Log;
     51 
     52 import com.android.bluetooth.R;
     53 import com.android.vcard.VCardComposer;
     54 import com.android.vcard.VCardConfig;
     55 import com.android.vcard.VCardPhoneNumberTranslationCallback;
     56 
     57 import java.io.IOException;
     58 import java.io.OutputStream;
     59 import java.util.ArrayList;
     60 
     61 import javax.obex.ServerOperation;
     62 import javax.obex.Operation;
     63 import javax.obex.ResponseCodes;
     64 
     65 import com.android.bluetooth.Utils;
     66 
     67 public class BluetoothPbapVcardManager {
     68     private static final String TAG = "BluetoothPbapVcardManager";
     69 
     70     private static final boolean V = BluetoothPbapService.VERBOSE;
     71 
     72     private ContentResolver mResolver;
     73 
     74     private Context mContext;
     75 
     76     static final String[] PHONES_PROJECTION = new String[] {
     77             Data._ID, // 0
     78             CommonDataKinds.Phone.TYPE, // 1
     79             CommonDataKinds.Phone.LABEL, // 2
     80             CommonDataKinds.Phone.NUMBER, // 3
     81             Contacts.DISPLAY_NAME, // 4
     82     };
     83 
     84     private static final int PHONE_NUMBER_COLUMN_INDEX = 3;
     85 
     86     static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC";
     87 
     88     static final String[] CONTACTS_PROJECTION = new String[] {
     89             Contacts._ID, // 0
     90             Contacts.DISPLAY_NAME, // 1
     91     };
     92 
     93     static final int CONTACTS_ID_COLUMN_INDEX = 0;
     94 
     95     static final int CONTACTS_NAME_COLUMN_INDEX = 1;
     96 
     97     // call histories use dynamic handles, and handles should order by date; the
     98     // most recently one should be the first handle. In table "calls", _id and
     99     // date are consistent in ordering, to implement simply, we sort by _id
    100     // here.
    101     static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC";
    102 
    103     private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
    104 
    105     public BluetoothPbapVcardManager(final Context context) {
    106         mContext = context;
    107         mResolver = mContext.getContentResolver();
    108     }
    109 
    110     /**
    111      * Create an owner vcard from the configured profile
    112      * @param vcardType21
    113      * @return
    114      */
    115     private final String getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, final byte[] filter) {
    116         // Currently only support Generic Vcard 2.1 and 3.0
    117         int vcardType;
    118         if (vcardType21) {
    119             vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
    120         } else {
    121             vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
    122         }
    123 
    124         if (!BluetoothPbapConfig.includePhotosInVcard()) {
    125             vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
    126         }
    127 
    128         return BluetoothPbapUtils.createProfileVCard(mContext, vcardType,filter);
    129     }
    130 
    131     public final String getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter) {
    132         //Owner vCard enhancement: Use "ME" profile if configured
    133         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
    134             String vcard = getOwnerPhoneNumberVcardFromProfile(vcardType21, filter);
    135             if (vcard != null && vcard.length() != 0) {
    136                 return vcard;
    137             }
    138         }
    139         //End enhancement
    140 
    141         BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext);
    142         String name = BluetoothPbapService.getLocalPhoneName();
    143         String number = BluetoothPbapService.getLocalPhoneNum();
    144         String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number,
    145                 vcardType21);
    146         return vcard;
    147     }
    148 
    149     public final int getPhonebookSize(final int type) {
    150         int size;
    151         switch (type) {
    152             case BluetoothPbapObexServer.ContentType.PHONEBOOK:
    153                 size = getContactsSize();
    154                 break;
    155             default:
    156                 size = getCallHistorySize(type);
    157                 break;
    158         }
    159         if (V) Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type);
    160         return size;
    161     }
    162 
    163     public final int getContactsSize() {
    164         final Uri myUri = Contacts.CONTENT_URI;
    165         int size = 0;
    166         Cursor contactCursor = null;
    167         try {
    168             contactCursor = mResolver.query(myUri, null, CLAUSE_ONLY_VISIBLE, null, null);
    169             if (contactCursor != null) {
    170                 size = contactCursor.getCount() + 1; // always has the 0.vcf
    171             }
    172         } catch (CursorWindowAllocationException e) {
    173             Log.e(TAG, "CursorWindowAllocationException while getting Contacts size");
    174         } finally {
    175             if (contactCursor != null) {
    176                 contactCursor.close();
    177                 contactCursor = null;
    178             }
    179         }
    180         return size;
    181     }
    182 
    183     public final int getCallHistorySize(final int type) {
    184         final Uri myUri = CallLog.Calls.CONTENT_URI;
    185         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    186         int size = 0;
    187         Cursor callCursor = null;
    188         try {
    189             callCursor = mResolver.query(myUri, null, selection, null,
    190                     CallLog.Calls.DEFAULT_SORT_ORDER);
    191             if (callCursor != null) {
    192                 size = callCursor.getCount();
    193             }
    194         } catch (CursorWindowAllocationException e) {
    195             Log.e(TAG, "CursorWindowAllocationException while getting CallHistory size");
    196         } finally {
    197             if (callCursor != null) {
    198                 callCursor.close();
    199                 callCursor = null;
    200             }
    201         }
    202         return size;
    203     }
    204 
    205     public final ArrayList<String> loadCallHistoryList(final int type) {
    206         final Uri myUri = CallLog.Calls.CONTENT_URI;
    207         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    208         String[] projection = new String[] {
    209                 Calls.NUMBER, Calls.CACHED_NAME, Calls.NUMBER_PRESENTATION
    210         };
    211         final int CALLS_NUMBER_COLUMN_INDEX = 0;
    212         final int CALLS_NAME_COLUMN_INDEX = 1;
    213         final int CALLS_NUMBER_PRESENTATION_COLUMN_INDEX = 2;
    214 
    215         Cursor callCursor = null;
    216         ArrayList<String> list = new ArrayList<String>();
    217         try {
    218             callCursor = mResolver.query(myUri, projection, selection, null,
    219                     CALLLOG_SORT_ORDER);
    220             if (callCursor != null) {
    221                 for (callCursor.moveToFirst(); !callCursor.isAfterLast();
    222                         callCursor.moveToNext()) {
    223                     String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX);
    224                     if (TextUtils.isEmpty(name)) {
    225                         // name not found, use number instead
    226                         final int numberPresentation = callCursor.getInt(
    227                                 CALLS_NUMBER_PRESENTATION_COLUMN_INDEX);
    228                         if (numberPresentation != Calls.PRESENTATION_ALLOWED) {
    229                             name = mContext.getString(R.string.unknownNumber);
    230                         } else {
    231                             name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX);
    232                         }
    233                     }
    234                     list.add(name);
    235                 }
    236             }
    237         } catch (CursorWindowAllocationException e) {
    238             Log.e(TAG, "CursorWindowAllocationException while loading CallHistory");
    239         } finally {
    240             if (callCursor != null) {
    241                 callCursor.close();
    242                 callCursor = null;
    243             }
    244         }
    245         return list;
    246     }
    247 
    248     public final ArrayList<String> getPhonebookNameList(final int orderByWhat) {
    249         ArrayList<String> nameList = new ArrayList<String>();
    250         //Owner vCard enhancement. Use "ME" profile if configured
    251         String ownerName = null;
    252         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
    253             ownerName = BluetoothPbapUtils.getProfileName(mContext);
    254         }
    255         if (ownerName == null || ownerName.length()==0) {
    256             ownerName = BluetoothPbapService.getLocalPhoneName();
    257         }
    258         nameList.add(ownerName);
    259         //End enhancement
    260 
    261         final Uri myUri = Contacts.CONTENT_URI;
    262         Cursor contactCursor = null;
    263         try {
    264             if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    265                 if (V) Log.v(TAG, "getPhonebookNameList, order by index");
    266                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    267                         null, Contacts._ID);
    268             } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    269                 if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
    270                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    271                         null, Contacts.DISPLAY_NAME);
    272             }
    273             if (contactCursor != null) {
    274                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    275                         .moveToNext()) {
    276                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    277                     long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    278                     if (TextUtils.isEmpty(name)) {
    279                         name = mContext.getString(android.R.string.unknownName);
    280                     }
    281                     nameList.add(name + "," + id);
    282                 }
    283             }
    284         } catch (CursorWindowAllocationException e) {
    285             Log.e(TAG, "CursorWindowAllocationException while getting Phonebook name list");
    286         } finally {
    287             if (contactCursor != null) {
    288                 contactCursor.close();
    289                 contactCursor = null;
    290             }
    291         }
    292         return nameList;
    293     }
    294 
    295     public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) {
    296         ArrayList<String> nameList = new ArrayList<String>();
    297         ArrayList<String> tempNameList = new ArrayList<String>();
    298 
    299         Cursor contactCursor = null;
    300         Uri uri = null;
    301 
    302         if (phoneNumber != null && phoneNumber.length() == 0) {
    303             uri = Contacts.CONTENT_URI;
    304         } else {
    305             uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
    306                 Uri.encode(phoneNumber));
    307         }
    308 
    309         try {
    310             contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    311                         null, Contacts._ID);
    312 
    313             if (contactCursor != null) {
    314                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    315                         .moveToNext()) {
    316                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    317                     long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    318                     if (TextUtils.isEmpty(name)) {
    319                         name = mContext.getString(android.R.string.unknownName);
    320                     }
    321                     if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
    322                     tempNameList.add(name + "," + id);
    323                 }
    324             }
    325         } catch (CursorWindowAllocationException e) {
    326             Log.e(TAG, "CursorWindowAllocationException while getting contact names");
    327         } finally {
    328             if (contactCursor != null) {
    329                 contactCursor.close();
    330                 contactCursor = null;
    331             }
    332         }
    333         int tempListSize = tempNameList.size();
    334         for (int index = 0; index < tempListSize; index++) {
    335             String object = tempNameList.get(index);
    336             if (!nameList.contains(object))
    337                 nameList.add(object);
    338         }
    339 
    340         return nameList;
    341     }
    342 
    343     public final int composeAndSendCallLogVcards(final int type, Operation op,
    344             final int startPoint, final int endPoint, final boolean vcardType21,
    345             boolean ignorefilter, byte[] filter) {
    346         if (startPoint < 1 || startPoint > endPoint) {
    347             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    348             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    349         }
    350         String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
    351 
    352         final Uri myUri = CallLog.Calls.CONTENT_URI;
    353         final String[] CALLLOG_PROJECTION = new String[] {
    354             CallLog.Calls._ID, // 0
    355         };
    356         final int ID_COLUMN_INDEX = 0;
    357 
    358         Cursor callsCursor = null;
    359         long startPointId = 0;
    360         long endPointId = 0;
    361         try {
    362             // Need test to see if order by _ID is ok here, or by date?
    363             callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null,
    364                     CALLLOG_SORT_ORDER);
    365             if (callsCursor != null) {
    366                 callsCursor.moveToPosition(startPoint - 1);
    367                 startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    368                 if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
    369                 if (startPoint == endPoint) {
    370                     endPointId = startPointId;
    371                 } else {
    372                     callsCursor.moveToPosition(endPoint - 1);
    373                     endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    374                 }
    375                 if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
    376             }
    377         } catch (CursorWindowAllocationException e) {
    378             Log.e(TAG, "CursorWindowAllocationException while composing calllog vcards");
    379         } finally {
    380             if (callsCursor != null) {
    381                 callsCursor.close();
    382                 callsCursor = null;
    383             }
    384         }
    385 
    386         String recordSelection;
    387         if (startPoint == endPoint) {
    388             recordSelection = Calls._ID + "=" + startPointId;
    389         } else {
    390             // The query to call table is by "_id DESC" order, so change
    391             // correspondingly.
    392             recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
    393                     + startPointId;
    394         }
    395 
    396         String selection;
    397         if (typeSelection == null) {
    398             selection = recordSelection;
    399         } else {
    400             selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
    401         }
    402 
    403         if (V) Log.v(TAG, "Call log query selection is: " + selection);
    404 
    405         return composeAndSendVCards(op, selection, vcardType21, null, false, ignorefilter, filter);
    406     }
    407 
    408     public final int composeAndSendPhonebookVcards(Operation op, final int startPoint,
    409             final int endPoint, final boolean vcardType21, String ownerVCard,
    410             boolean ignorefilter, byte[] filter) {
    411         if (startPoint < 1 || startPoint > endPoint) {
    412             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    413             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    414         }
    415         final Uri myUri = Contacts.CONTENT_URI;
    416 
    417         Cursor contactCursor = null;
    418         long startPointId = 0;
    419         long endPointId = 0;
    420         try {
    421             contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null,
    422                     Contacts._ID);
    423             if (contactCursor != null) {
    424                 contactCursor.moveToPosition(startPoint - 1);
    425                 startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    426                 if (V) Log.v(TAG, "Query startPointId = " + startPointId);
    427                 if (startPoint == endPoint) {
    428                     endPointId = startPointId;
    429                 } else {
    430                     contactCursor.moveToPosition(endPoint - 1);
    431                     endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    432                 }
    433                 if (V) Log.v(TAG, "Query endPointId = " + endPointId);
    434             }
    435         } catch (CursorWindowAllocationException e) {
    436             Log.e(TAG, "CursorWindowAllocationException while composing phonebook vcards");
    437         } finally {
    438             if (contactCursor != null) {
    439                 contactCursor.close();
    440                 contactCursor = null;
    441             }
    442         }
    443 
    444         final String selection;
    445         if (startPoint == endPoint) {
    446             selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    447         } else {
    448             selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<="
    449                     + endPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    450         }
    451 
    452         if (V) Log.v(TAG, "Query selection is: " + selection);
    453 
    454         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true,
    455             ignorefilter, filter);
    456     }
    457 
    458     public final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
    459             final boolean vcardType21, String ownerVCard, int orderByWhat,
    460             boolean ignorefilter, byte[] filter) {
    461         if (offset < 1) {
    462             Log.e(TAG, "Internal error: offset is not correct.");
    463             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    464         }
    465         final Uri myUri = Contacts.CONTENT_URI;
    466         Cursor contactCursor = null;
    467         String selection = null;
    468         long contactId = 0;
    469         if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    470             try {
    471                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    472                         null, Contacts._ID);
    473                 if (contactCursor != null) {
    474                     contactCursor.moveToPosition(offset - 1);
    475                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    476                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    477                 }
    478             } catch (CursorWindowAllocationException e) {
    479                 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by index");
    480             } finally {
    481                 if (contactCursor != null) {
    482                     contactCursor.close();
    483                     contactCursor = null;
    484                 }
    485             }
    486         } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    487             try {
    488                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    489                         null, Contacts.DISPLAY_NAME);
    490                 if (contactCursor != null) {
    491                     contactCursor.moveToPosition(offset - 1);
    492                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    493                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    494                 }
    495             } catch (CursorWindowAllocationException e) {
    496                 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by alphabetical");
    497             } finally {
    498                 if (contactCursor != null) {
    499                     contactCursor.close();
    500                     contactCursor = null;
    501                 }
    502             }
    503         } else {
    504             Log.e(TAG, "Parameter orderByWhat is not supported!");
    505             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    506         }
    507         selection = Contacts._ID + "=" + contactId;
    508 
    509         if (V) Log.v(TAG, "Query selection is: " + selection);
    510 
    511         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true,
    512             ignorefilter, filter);
    513     }
    514 
    515     public final int composeAndSendVCards(Operation op, final String selection,
    516             final boolean vcardType21, String ownerVCard, boolean isContacts,
    517             boolean ignorefilter, byte[] filter) {
    518         long timestamp = 0;
    519         if (V) timestamp = System.currentTimeMillis();
    520 
    521         if (isContacts) {
    522             VCardComposer composer = null;
    523             VCardFilter vcardfilter= new VCardFilter(ignorefilter ? null : filter);
    524 
    525             HandlerForStringBuffer buffer = null;
    526             try {
    527                 // Currently only support Generic Vcard 2.1 and 3.0
    528                 int vcardType;
    529                 if (vcardType21) {
    530                     vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
    531                 } else {
    532                     vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
    533                 }
    534                 if (!vcardfilter.isPhotoEnabled()) {
    535                     vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
    536                 }
    537 
    538                 //Enhancement: customize Vcard based on preferences/settings and input from caller
    539                 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null);
    540                 //End enhancement
    541 
    542                 // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
    543                 // done by vCard library by default.
    544                 composer.setPhoneNumberTranslationCallback(
    545                         new VCardPhoneNumberTranslationCallback() {
    546                             public String onValueReceived(
    547                                     String rawValue, int type, String label, boolean isPrimary) {
    548                                 // 'p' and 'w' are the standard characters for pause and wait
    549                                 // (see RFC 3601)
    550                                 // so use those when exporting phone numbers via vCard.
    551                                 String numberWithControlSequence = rawValue
    552                                         .replace(PhoneNumberUtils.PAUSE, 'p')
    553                                         .replace(PhoneNumberUtils.WAIT, 'w');
    554                                 return numberWithControlSequence;
    555                             }
    556                         });
    557                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    558                 if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) ||
    559                         !buffer.onInit(mContext)) {
    560                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    561                 }
    562 
    563                 while (!composer.isAfterLast()) {
    564                     if (BluetoothPbapObexServer.sIsAborted) {
    565                         ((ServerOperation)op).isAborted = true;
    566                         BluetoothPbapObexServer.sIsAborted = false;
    567                         break;
    568                     }
    569                     String vcard = composer.createOneEntry();
    570                     if (vcard == null) {
    571                         Log.e(TAG, "Failed to read a contact. Error reason: "
    572                                 + composer.getErrorReason());
    573                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    574                     }
    575                     if (V) Log.v (TAG , "vCard from composer: " + vcard);
    576 
    577                     vcard = vcardfilter.apply(vcard, vcardType21);
    578                     vcard = StripTelephoneNumber(vcard);
    579 
    580                     if (V) Log.v (TAG, "vCard after cleanup: " + vcard);
    581 
    582                     if (!buffer.onEntryCreated(vcard)) {
    583                         // onEntryCreate() already emits error.
    584                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    585                     }
    586                 }
    587             } finally {
    588                 if (composer != null) {
    589                     composer.terminate();
    590                 }
    591                 if (buffer != null) {
    592                     buffer.onTerminate();
    593                 }
    594             }
    595         } else { // CallLog
    596             BluetoothPbapCallLogComposer composer = null;
    597             HandlerForStringBuffer buffer = null;
    598             try {
    599 
    600                 composer = new BluetoothPbapCallLogComposer(mContext);
    601                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    602                 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null,
    603                                    CALLLOG_SORT_ORDER) ||
    604                                    !buffer.onInit(mContext)) {
    605                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    606                 }
    607 
    608                 while (!composer.isAfterLast()) {
    609                     if (BluetoothPbapObexServer.sIsAborted) {
    610                         ((ServerOperation)op).isAborted = true;
    611                         BluetoothPbapObexServer.sIsAborted = false;
    612                         break;
    613                     }
    614                     String vcard = composer.createOneEntry(vcardType21);
    615                     if (vcard == null) {
    616                         Log.e(TAG, "Failed to read a contact. Error reason: "
    617                                 + composer.getErrorReason());
    618                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    619                     }
    620                     if (V) {
    621                         Log.v(TAG, "Vcard Entry:");
    622                         Log.v(TAG,vcard);
    623                     }
    624 
    625                     buffer.onEntryCreated(vcard);
    626                 }
    627             } finally {
    628                 if (composer != null) {
    629                     composer.terminate();
    630                 }
    631                 if (buffer != null) {
    632                     buffer.onTerminate();
    633                 }
    634             }
    635         }
    636 
    637         if (V) Log.v(TAG, "Total vcard composing and sending out takes "
    638                     + (System.currentTimeMillis() - timestamp) + " ms");
    639 
    640         return ResponseCodes.OBEX_HTTP_OK;
    641     }
    642 
    643     public String StripTelephoneNumber (String vCard){
    644         String attr [] = vCard.split(System.getProperty("line.separator"));
    645         String Vcard = "";
    646             for (int i=0; i < attr.length; i++) {
    647                 if(attr[i].startsWith("TEL")) {
    648                     attr[i] = attr[i].replace("(", "");
    649                     attr[i] = attr[i].replace(")", "");
    650                     attr[i] = attr[i].replace("-", "");
    651                     attr[i] = attr[i].replace(" ", "");
    652                 }
    653             }
    654 
    655             for (int i=0; i < attr.length; i++) {
    656                 if(!attr[i].equals("")){
    657                     Vcard = Vcard.concat(attr[i] + "\n");
    658                 }
    659             }
    660         if (V) Log.v(TAG, "Vcard with stripped telephone no.: " + Vcard);
    661         return Vcard;
    662     }
    663 
    664     /**
    665      * Handler to emit vCards to PCE.
    666      */
    667     public class HandlerForStringBuffer {
    668         private Operation operation;
    669 
    670         private OutputStream outputStream;
    671 
    672         private String phoneOwnVCard = null;
    673 
    674         public HandlerForStringBuffer(Operation op, String ownerVCard) {
    675             operation = op;
    676             if (ownerVCard != null) {
    677                 phoneOwnVCard = ownerVCard;
    678                 if (V) Log.v(TAG, "phone own number vcard:");
    679                 if (V) Log.v(TAG, phoneOwnVCard);
    680             }
    681         }
    682 
    683         private boolean write(String vCard) {
    684             try {
    685                 if (vCard != null) {
    686                     outputStream.write(vCard.getBytes());
    687                     return true;
    688                 }
    689             } catch (IOException e) {
    690                 Log.e(TAG, "write outputstrem failed" + e.toString());
    691             }
    692             return false;
    693         }
    694 
    695         public boolean onInit(Context context) {
    696             try {
    697                 outputStream = operation.openOutputStream();
    698                 if (phoneOwnVCard != null) {
    699                     return write(phoneOwnVCard);
    700                 }
    701                 return true;
    702             } catch (IOException e) {
    703                 Log.e(TAG, "open outputstrem failed" + e.toString());
    704             }
    705             return false;
    706         }
    707 
    708         public boolean onEntryCreated(String vcard) {
    709             return write(vcard);
    710         }
    711 
    712         public void onTerminate() {
    713             if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
    714                 if (V) Log.v(TAG, "CloseStream failed!");
    715             } else {
    716                 if (V) Log.v(TAG, "CloseStream ok!");
    717             }
    718         }
    719     }
    720 
    721     public static class VCardFilter {
    722         private static enum FilterBit {
    723             //       bit  property    onlyCheckV21  excludeForV21
    724             FN (       1, "FN",       true,         false),
    725             PHOTO(     3, "PHOTO",    false,        false),
    726             BDAY(      4, "BDAY",     false,        false),
    727             ADR(       5, "ADR",      false,        false),
    728             EMAIL(     8, "EMAIL",    false,        false),
    729             TITLE(    12, "TITLE",    false,        false),
    730             ORG(      16, "ORG",      false,        false),
    731             NOTES(    17, "NOTES",    false,        false),
    732             URL(      20, "URL",      false,        false),
    733             NICKNAME( 23, "NICKNAME", false,        true);
    734 
    735             public final int pos;
    736             public final String prop;
    737             public final boolean onlyCheckV21;
    738             public final boolean excludeForV21;
    739 
    740             FilterBit(int pos, String prop, boolean onlyCheckV21, boolean excludeForV21) {
    741                 this.pos = pos;
    742                 this.prop = prop;
    743                 this.onlyCheckV21 = onlyCheckV21;
    744                 this.excludeForV21 = excludeForV21;
    745             }
    746         }
    747 
    748         private static final String SEPARATOR = System.getProperty("line.separator");
    749         private final byte[] filter;
    750 
    751         private boolean isFilteredOut(FilterBit bit, boolean vCardType21) {
    752             final int offset = (bit.pos / 8) + 1;
    753             final int bit_pos = bit.pos % 8;
    754             if (!vCardType21 && bit.onlyCheckV21) return false;
    755             if (vCardType21 && bit.excludeForV21) return true;
    756             if (filter == null || offset >= filter.length) return false;
    757             return ((filter[filter.length - offset] >> bit_pos) & 0x01) != 0;
    758         }
    759 
    760         VCardFilter(byte[] filter) {
    761             this.filter = filter;
    762         }
    763 
    764         public boolean isPhotoEnabled() {
    765             return !isFilteredOut(FilterBit.PHOTO, false);
    766         }
    767 
    768         public String apply(String vCard, boolean vCardType21){
    769             if (filter == null) return vCard;
    770             String lines[] = vCard.split(SEPARATOR);
    771             StringBuilder filteredVCard = new StringBuilder();
    772             boolean filteredOut = false;
    773 
    774             for (String line : lines) {
    775                 // Check whether the current property is changing (ignoring multi-line properties)
    776                 // and determine if the current property is filtered in.
    777                 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) {
    778                     String currentProp = line.split("[;:]")[0];
    779                     filteredOut = false;
    780 
    781                     for (FilterBit bit : FilterBit.values()) {
    782                         if (bit.prop.equals(currentProp)) {
    783                             filteredOut = isFilteredOut(bit, vCardType21);
    784                             break;
    785                         }
    786                     }
    787 
    788                     // Since PBAP does not have filter bits for IM and SIP,
    789                     // exclude them by default. Easiest way is to exclude all
    790                     // X- fields....
    791                     if (currentProp.startsWith("X-")) filteredOut = true;
    792                 }
    793 
    794                 // Build filtered vCard
    795                 if (!filteredOut) filteredVCard.append(line + SEPARATOR);
    796             }
    797 
    798             return filteredVCard.toString();
    799         }
    800     }
    801 }
    802