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.Cursor;
     39 import android.net.Uri;
     40 import android.provider.CallLog;
     41 import android.provider.CallLog.Calls;
     42 import android.provider.ContactsContract.CommonDataKinds;
     43 import android.provider.ContactsContract.Contacts;
     44 import android.provider.ContactsContract.Data;
     45 import android.provider.ContactsContract.CommonDataKinds.Phone;
     46 import android.provider.ContactsContract.PhoneLookup;
     47 import android.telephony.PhoneNumberUtils;
     48 import android.text.TextUtils;
     49 import android.util.Log;
     50 
     51 import com.android.bluetooth.R;
     52 import com.android.vcard.VCardComposer;
     53 import com.android.vcard.VCardConfig;
     54 import com.android.internal.telephony.CallerInfo;
     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         } finally {
    173             if (contactCursor != null) {
    174                 contactCursor.close();
    175             }
    176         }
    177         return size;
    178     }
    179 
    180     public final int getCallHistorySize(final int type) {
    181         final Uri myUri = CallLog.Calls.CONTENT_URI;
    182         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    183         int size = 0;
    184         Cursor callCursor = null;
    185         try {
    186             callCursor = mResolver.query(myUri, null, selection, null,
    187                     CallLog.Calls.DEFAULT_SORT_ORDER);
    188             if (callCursor != null) {
    189                 size = callCursor.getCount();
    190             }
    191         } finally {
    192             if (callCursor != null) {
    193                 callCursor.close();
    194             }
    195         }
    196         return size;
    197     }
    198 
    199     public final ArrayList<String> loadCallHistoryList(final int type) {
    200         final Uri myUri = CallLog.Calls.CONTENT_URI;
    201         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    202         String[] projection = new String[] {
    203                 Calls.NUMBER, Calls.CACHED_NAME
    204         };
    205         final int CALLS_NUMBER_COLUMN_INDEX = 0;
    206         final int CALLS_NAME_COLUMN_INDEX = 1;
    207 
    208         Cursor callCursor = null;
    209         ArrayList<String> list = new ArrayList<String>();
    210         try {
    211             callCursor = mResolver.query(myUri, projection, selection, null,
    212                     CALLLOG_SORT_ORDER);
    213             if (callCursor != null) {
    214                 for (callCursor.moveToFirst(); !callCursor.isAfterLast();
    215                         callCursor.moveToNext()) {
    216                     String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX);
    217                     if (TextUtils.isEmpty(name)) {
    218                         // name not found, use number instead
    219                         name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX);
    220                         if (CallerInfo.UNKNOWN_NUMBER.equals(name) ||
    221                                 CallerInfo.PRIVATE_NUMBER.equals(name) ||
    222                                 CallerInfo.PAYPHONE_NUMBER.equals(name)) {
    223                             name = mContext.getString(R.string.unknownNumber);
    224                         }
    225                     }
    226                     list.add(name);
    227                 }
    228             }
    229         } finally {
    230             if (callCursor != null) {
    231                 callCursor.close();
    232             }
    233         }
    234         return list;
    235     }
    236 
    237     public final ArrayList<String> getPhonebookNameList(final int orderByWhat) {
    238         ArrayList<String> nameList = new ArrayList<String>();
    239         //Owner vCard enhancement. Use "ME" profile if configured
    240         String ownerName = null;
    241         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
    242             ownerName = BluetoothPbapUtils.getProfileName(mContext);
    243         }
    244         if (ownerName == null || ownerName.length()==0) {
    245             ownerName = BluetoothPbapService.getLocalPhoneName();
    246         }
    247         nameList.add(ownerName);
    248         //End enhancement
    249 
    250         final Uri myUri = Contacts.CONTENT_URI;
    251         Cursor contactCursor = null;
    252         try {
    253             if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    254                 if (V) Log.v(TAG, "getPhonebookNameList, order by index");
    255                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    256                         null, Contacts._ID);
    257             } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    258                 if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
    259                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    260                         null, Contacts.DISPLAY_NAME);
    261             }
    262             if (contactCursor != null) {
    263                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    264                         .moveToNext()) {
    265                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    266                     if (TextUtils.isEmpty(name)) {
    267                         name = mContext.getString(android.R.string.unknownName);
    268                     }
    269                     nameList.add(name);
    270                 }
    271             }
    272         } finally {
    273             if (contactCursor != null) {
    274                 contactCursor.close();
    275             }
    276         }
    277         return nameList;
    278     }
    279 
    280     public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) {
    281         ArrayList<String> nameList = new ArrayList<String>();
    282 
    283         Cursor contactCursor = null;
    284         Uri uri = null;
    285 
    286         if (phoneNumber != null && phoneNumber.length() == 0) {
    287             uri = Contacts.CONTENT_URI;
    288         } else {
    289             uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
    290                 Uri.encode(phoneNumber));
    291         }
    292 
    293         try {
    294             contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    295                         null, Contacts._ID);
    296 
    297             if (contactCursor != null) {
    298                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    299                         .moveToNext()) {
    300                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    301                     long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    302                     if (TextUtils.isEmpty(name)) {
    303                         name = mContext.getString(android.R.string.unknownName);
    304                     }
    305                     if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
    306                     nameList.add(name);
    307                 }
    308             }
    309         } finally {
    310             if (contactCursor != null) {
    311                 contactCursor.close();
    312             }
    313         }
    314         return nameList;
    315     }
    316 
    317     public final int composeAndSendCallLogVcards(final int type, Operation op,
    318             final int startPoint, final int endPoint, final boolean vcardType21) {
    319         if (startPoint < 1 || startPoint > endPoint) {
    320             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    321             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    322         }
    323         String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
    324 
    325         final Uri myUri = CallLog.Calls.CONTENT_URI;
    326         final String[] CALLLOG_PROJECTION = new String[] {
    327             CallLog.Calls._ID, // 0
    328         };
    329         final int ID_COLUMN_INDEX = 0;
    330 
    331         Cursor callsCursor = null;
    332         long startPointId = 0;
    333         long endPointId = 0;
    334         try {
    335             // Need test to see if order by _ID is ok here, or by date?
    336             callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null,
    337                     CALLLOG_SORT_ORDER);
    338             if (callsCursor != null) {
    339                 callsCursor.moveToPosition(startPoint - 1);
    340                 startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    341                 if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
    342                 if (startPoint == endPoint) {
    343                     endPointId = startPointId;
    344                 } else {
    345                     callsCursor.moveToPosition(endPoint - 1);
    346                     endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    347                 }
    348                 if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
    349             }
    350         } finally {
    351             if (callsCursor != null) {
    352                 callsCursor.close();
    353             }
    354         }
    355 
    356         String recordSelection;
    357         if (startPoint == endPoint) {
    358             recordSelection = Calls._ID + "=" + startPointId;
    359         } else {
    360             // The query to call table is by "_id DESC" order, so change
    361             // correspondingly.
    362             recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
    363                     + startPointId;
    364         }
    365 
    366         String selection;
    367         if (typeSelection == null) {
    368             selection = recordSelection;
    369         } else {
    370             selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
    371         }
    372 
    373         if (V) Log.v(TAG, "Call log query selection is: " + selection);
    374 
    375         return composeAndSendVCards(op, selection, vcardType21, null, false);
    376     }
    377 
    378     public final int composeAndSendPhonebookVcards(Operation op, final int startPoint,
    379             final int endPoint, final boolean vcardType21, String ownerVCard) {
    380         if (startPoint < 1 || startPoint > endPoint) {
    381             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    382             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    383         }
    384         final Uri myUri = Contacts.CONTENT_URI;
    385 
    386         Cursor contactCursor = null;
    387         long startPointId = 0;
    388         long endPointId = 0;
    389         try {
    390             contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null,
    391                     Contacts._ID);
    392             if (contactCursor != null) {
    393                 contactCursor.moveToPosition(startPoint - 1);
    394                 startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    395                 if (V) Log.v(TAG, "Query startPointId = " + startPointId);
    396                 if (startPoint == endPoint) {
    397                     endPointId = startPointId;
    398                 } else {
    399                     contactCursor.moveToPosition(endPoint - 1);
    400                     endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    401                 }
    402                 if (V) Log.v(TAG, "Query endPointId = " + endPointId);
    403             }
    404         } finally {
    405             if (contactCursor != null) {
    406                 contactCursor.close();
    407             }
    408         }
    409 
    410         final String selection;
    411         if (startPoint == endPoint) {
    412             selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    413         } else {
    414             selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<="
    415                     + endPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    416         }
    417 
    418         if (V) Log.v(TAG, "Query selection is: " + selection);
    419 
    420         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
    421     }
    422 
    423     public final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
    424             final boolean vcardType21, String ownerVCard, int orderByWhat) {
    425         if (offset < 1) {
    426             Log.e(TAG, "Internal error: offset is not correct.");
    427             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    428         }
    429         final Uri myUri = Contacts.CONTENT_URI;
    430         Cursor contactCursor = null;
    431         String selection = null;
    432         long contactId = 0;
    433         if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    434             try {
    435                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    436                         null, Contacts._ID);
    437                 if (contactCursor != null) {
    438                     contactCursor.moveToPosition(offset - 1);
    439                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    440                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    441                 }
    442             } finally {
    443                 if (contactCursor != null) {
    444                     contactCursor.close();
    445                 }
    446             }
    447         } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    448             try {
    449                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    450                         null, Contacts.DISPLAY_NAME);
    451                 if (contactCursor != null) {
    452                     contactCursor.moveToPosition(offset - 1);
    453                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    454                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    455                 }
    456             } finally {
    457                 if (contactCursor != null) {
    458                     contactCursor.close();
    459                 }
    460             }
    461         } else {
    462             Log.e(TAG, "Parameter orderByWhat is not supported!");
    463             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    464         }
    465         selection = Contacts._ID + "=" + contactId;
    466 
    467         if (V) Log.v(TAG, "Query selection is: " + selection);
    468 
    469         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
    470     }
    471 
    472     public final int composeAndSendVCards(Operation op, final String selection,
    473             final boolean vcardType21, String ownerVCard, boolean isContacts) {
    474         long timestamp = 0;
    475         if (V) timestamp = System.currentTimeMillis();
    476 
    477         if (isContacts) {
    478             VCardComposer composer = null;
    479             HandlerForStringBuffer buffer = null;
    480             try {
    481                 // Currently only support Generic Vcard 2.1 and 3.0
    482                 int vcardType;
    483                 if (vcardType21) {
    484                     vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
    485                 } else {
    486                     vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
    487                 }
    488 
    489                 if (!BluetoothPbapConfig.includePhotosInVcard()) {
    490                     vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
    491                 }
    492 
    493                 //Enhancement: customize Vcard based on preferences/settings and input from caller
    494                 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null);
    495                 //End enhancement
    496 
    497                 // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
    498                 // done by vCard library by default.
    499                 composer.setPhoneNumberTranslationCallback(
    500                         new VCardPhoneNumberTranslationCallback() {
    501                             public String onValueReceived(
    502                                     String rawValue, int type, String label, boolean isPrimary) {
    503                                 // 'p' and 'w' are the standard characters for pause and wait
    504                                 // (see RFC 3601)
    505                                 // so use those when exporting phone numbers via vCard.
    506                                 String numberWithControlSequence = rawValue
    507                                         .replace(PhoneNumberUtils.PAUSE, 'p')
    508                                         .replace(PhoneNumberUtils.WAIT, 'w');
    509                                 return numberWithControlSequence;
    510                             }
    511                         });
    512                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    513                 if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) ||
    514                         !buffer.onInit(mContext)) {
    515                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    516                 }
    517 
    518                 while (!composer.isAfterLast()) {
    519                     if (BluetoothPbapObexServer.sIsAborted) {
    520                         ((ServerOperation)op).isAborted = true;
    521                         BluetoothPbapObexServer.sIsAborted = false;
    522                         break;
    523                     }
    524                     String vcard = composer.createOneEntry();
    525                     if (vcard == null) {
    526                         Log.e(TAG, "Failed to read a contact. Error reason: "
    527                                 + composer.getErrorReason());
    528                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    529                     }
    530                     if (V) {
    531                         Log.v(TAG, "Vcard Entry:");
    532                         Log.v(TAG,vcard);
    533                     }
    534 
    535                     if (!buffer.onEntryCreated(vcard)) {
    536                         // onEntryCreate() already emits error.
    537                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    538                     }
    539                 }
    540             } finally {
    541                 if (composer != null) {
    542                     composer.terminate();
    543                 }
    544                 if (buffer != null) {
    545                     buffer.onTerminate();
    546                 }
    547             }
    548         } else { // CallLog
    549             BluetoothPbapCallLogComposer composer = null;
    550             HandlerForStringBuffer buffer = null;
    551             try {
    552 
    553                 composer = new BluetoothPbapCallLogComposer(mContext);
    554                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    555                 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null,
    556                                    CALLLOG_SORT_ORDER) ||
    557                                    !buffer.onInit(mContext)) {
    558                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    559                 }
    560 
    561                 while (!composer.isAfterLast()) {
    562                     if (BluetoothPbapObexServer.sIsAborted) {
    563                         ((ServerOperation)op).isAborted = true;
    564                         BluetoothPbapObexServer.sIsAborted = false;
    565                         break;
    566                     }
    567                     String vcard = composer.createOneEntry(vcardType21);
    568                     if (vcard == null) {
    569                         Log.e(TAG, "Failed to read a contact. Error reason: "
    570                                 + composer.getErrorReason());
    571                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    572                     }
    573                     if (V) {
    574                         Log.v(TAG, "Vcard Entry:");
    575                         Log.v(TAG,vcard);
    576                     }
    577 
    578                     buffer.onEntryCreated(vcard);
    579                 }
    580             } finally {
    581                 if (composer != null) {
    582                     composer.terminate();
    583                 }
    584                 if (buffer != null) {
    585                     buffer.onTerminate();
    586                 }
    587             }
    588         }
    589 
    590         if (V) Log.v(TAG, "Total vcard composing and sending out takes "
    591                     + (System.currentTimeMillis() - timestamp) + " ms");
    592 
    593         return ResponseCodes.OBEX_HTTP_OK;
    594     }
    595 
    596     /**
    597      * Handler to emit vCards to PCE.
    598      */
    599     public class HandlerForStringBuffer {
    600         private Operation operation;
    601 
    602         private OutputStream outputStream;
    603 
    604         private String phoneOwnVCard = null;
    605 
    606         public HandlerForStringBuffer(Operation op, String ownerVCard) {
    607             operation = op;
    608             if (ownerVCard != null) {
    609                 phoneOwnVCard = ownerVCard;
    610                 if (V) Log.v(TAG, "phone own number vcard:");
    611                 if (V) Log.v(TAG, phoneOwnVCard);
    612             }
    613         }
    614 
    615         private boolean write(String vCard) {
    616             try {
    617                 if (vCard != null) {
    618                     outputStream.write(vCard.getBytes());
    619                     return true;
    620                 }
    621             } catch (IOException e) {
    622                 Log.e(TAG, "write outputstrem failed" + e.toString());
    623             }
    624             return false;
    625         }
    626 
    627         public boolean onInit(Context context) {
    628             try {
    629                 outputStream = operation.openOutputStream();
    630                 if (phoneOwnVCard != null) {
    631                     return write(phoneOwnVCard);
    632                 }
    633                 return true;
    634             } catch (IOException e) {
    635                 Log.e(TAG, "open outputstrem failed" + e.toString());
    636             }
    637             return false;
    638         }
    639 
    640         public boolean onEntryCreated(String vcard) {
    641             return write(vcard);
    642         }
    643 
    644         public void onTerminate() {
    645             if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
    646                 if (V) Log.v(TAG, "CloseStream failed!");
    647             } else {
    648                 if (V) Log.v(TAG, "CloseStream ok!");
    649             }
    650         }
    651     }
    652 }
    653