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.vcard.VCardPhoneNumberTranslationCallback;
     55 
     56 import java.io.IOException;
     57 import java.io.OutputStream;
     58 import java.util.ArrayList;
     59 
     60 import javax.obex.ServerOperation;
     61 import javax.obex.Operation;
     62 import javax.obex.ResponseCodes;
     63 
     64 import com.android.bluetooth.Utils;
     65 
     66 public class BluetoothPbapVcardManager {
     67     private static final String TAG = "BluetoothPbapVcardManager";
     68 
     69     private static final boolean V = BluetoothPbapService.VERBOSE;
     70 
     71     private ContentResolver mResolver;
     72 
     73     private Context mContext;
     74 
     75     static final String[] PHONES_PROJECTION = new String[] {
     76             Data._ID, // 0
     77             CommonDataKinds.Phone.TYPE, // 1
     78             CommonDataKinds.Phone.LABEL, // 2
     79             CommonDataKinds.Phone.NUMBER, // 3
     80             Contacts.DISPLAY_NAME, // 4
     81     };
     82 
     83     private static final int PHONE_NUMBER_COLUMN_INDEX = 3;
     84 
     85     static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC";
     86 
     87     static final String[] CONTACTS_PROJECTION = new String[] {
     88             Contacts._ID, // 0
     89             Contacts.DISPLAY_NAME, // 1
     90     };
     91 
     92     static final int CONTACTS_ID_COLUMN_INDEX = 0;
     93 
     94     static final int CONTACTS_NAME_COLUMN_INDEX = 1;
     95 
     96     // call histories use dynamic handles, and handles should order by date; the
     97     // most recently one should be the first handle. In table "calls", _id and
     98     // date are consistent in ordering, to implement simply, we sort by _id
     99     // here.
    100     static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC";
    101 
    102     private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
    103 
    104     public BluetoothPbapVcardManager(final Context context) {
    105         mContext = context;
    106         mResolver = mContext.getContentResolver();
    107     }
    108 
    109     /**
    110      * Create an owner vcard from the configured profile
    111      * @param vcardType21
    112      * @return
    113      */
    114     private final String getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, final byte[] filter) {
    115         // Currently only support Generic Vcard 2.1 and 3.0
    116         int vcardType;
    117         if (vcardType21) {
    118             vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
    119         } else {
    120             vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
    121         }
    122 
    123         if (!BluetoothPbapConfig.includePhotosInVcard()) {
    124             vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
    125         }
    126 
    127         return BluetoothPbapUtils.createProfileVCard(mContext, vcardType,filter);
    128     }
    129 
    130     public final String getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter) {
    131         //Owner vCard enhancement: Use "ME" profile if configured
    132         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
    133             String vcard = getOwnerPhoneNumberVcardFromProfile(vcardType21, filter);
    134             if (vcard != null && vcard.length() != 0) {
    135                 return vcard;
    136             }
    137         }
    138         //End enhancement
    139 
    140         BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext);
    141         String name = BluetoothPbapService.getLocalPhoneName();
    142         String number = BluetoothPbapService.getLocalPhoneNum();
    143         String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number,
    144                 vcardType21);
    145         return vcard;
    146     }
    147 
    148     public final int getPhonebookSize(final int type) {
    149         int size;
    150         switch (type) {
    151             case BluetoothPbapObexServer.ContentType.PHONEBOOK:
    152                 size = getContactsSize();
    153                 break;
    154             default:
    155                 size = getCallHistorySize(type);
    156                 break;
    157         }
    158         if (V) Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type);
    159         return size;
    160     }
    161 
    162     public final int getContactsSize() {
    163         final Uri myUri = Contacts.CONTENT_URI;
    164         int size = 0;
    165         Cursor contactCursor = null;
    166         try {
    167             contactCursor = mResolver.query(myUri, null, CLAUSE_ONLY_VISIBLE, null, null);
    168             if (contactCursor != null) {
    169                 size = contactCursor.getCount() + 1; // always has the 0.vcf
    170             }
    171         } finally {
    172             if (contactCursor != null) {
    173                 contactCursor.close();
    174             }
    175         }
    176         return size;
    177     }
    178 
    179     public final int getCallHistorySize(final int type) {
    180         final Uri myUri = CallLog.Calls.CONTENT_URI;
    181         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    182         int size = 0;
    183         Cursor callCursor = null;
    184         try {
    185             callCursor = mResolver.query(myUri, null, selection, null,
    186                     CallLog.Calls.DEFAULT_SORT_ORDER);
    187             if (callCursor != null) {
    188                 size = callCursor.getCount();
    189             }
    190         } finally {
    191             if (callCursor != null) {
    192                 callCursor.close();
    193             }
    194         }
    195         return size;
    196     }
    197 
    198     public final ArrayList<String> loadCallHistoryList(final int type) {
    199         final Uri myUri = CallLog.Calls.CONTENT_URI;
    200         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    201         String[] projection = new String[] {
    202                 Calls.NUMBER, Calls.CACHED_NAME, Calls.NUMBER_PRESENTATION
    203         };
    204         final int CALLS_NUMBER_COLUMN_INDEX = 0;
    205         final int CALLS_NAME_COLUMN_INDEX = 1;
    206         final int CALLS_NUMBER_PRESENTATION_COLUMN_INDEX = 2;
    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                         final int numberPresentation = callCursor.getInt(
    220                                 CALLS_NUMBER_PRESENTATION_COLUMN_INDEX);
    221                         if (numberPresentation != Calls.PRESENTATION_ALLOWED) {
    222                             name = mContext.getString(R.string.unknownNumber);
    223                         } else {
    224                             name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX);
    225                         }
    226                     }
    227                     list.add(name);
    228                 }
    229             }
    230         } finally {
    231             if (callCursor != null) {
    232                 callCursor.close();
    233             }
    234         }
    235         return list;
    236     }
    237 
    238     public final ArrayList<String> getPhonebookNameList(final int orderByWhat) {
    239         ArrayList<String> nameList = new ArrayList<String>();
    240         //Owner vCard enhancement. Use "ME" profile if configured
    241         String ownerName = null;
    242         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
    243             ownerName = BluetoothPbapUtils.getProfileName(mContext);
    244         }
    245         if (ownerName == null || ownerName.length()==0) {
    246             ownerName = BluetoothPbapService.getLocalPhoneName();
    247         }
    248         nameList.add(ownerName);
    249         //End enhancement
    250 
    251         final Uri myUri = Contacts.CONTENT_URI;
    252         Cursor contactCursor = null;
    253         try {
    254             if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    255                 if (V) Log.v(TAG, "getPhonebookNameList, order by index");
    256                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    257                         null, Contacts._ID);
    258             } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    259                 if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
    260                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    261                         null, Contacts.DISPLAY_NAME);
    262             }
    263             if (contactCursor != null) {
    264                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    265                         .moveToNext()) {
    266                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    267                     if (TextUtils.isEmpty(name)) {
    268                         name = mContext.getString(android.R.string.unknownName);
    269                     }
    270                     nameList.add(name);
    271                 }
    272             }
    273         } finally {
    274             if (contactCursor != null) {
    275                 contactCursor.close();
    276             }
    277         }
    278         return nameList;
    279     }
    280 
    281     public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) {
    282         ArrayList<String> nameList = new ArrayList<String>();
    283 
    284         Cursor contactCursor = null;
    285         Uri uri = null;
    286 
    287         if (phoneNumber != null && phoneNumber.length() == 0) {
    288             uri = Contacts.CONTENT_URI;
    289         } else {
    290             uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
    291                 Uri.encode(phoneNumber));
    292         }
    293 
    294         try {
    295             contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    296                         null, Contacts._ID);
    297 
    298             if (contactCursor != null) {
    299                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    300                         .moveToNext()) {
    301                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    302                     long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    303                     if (TextUtils.isEmpty(name)) {
    304                         name = mContext.getString(android.R.string.unknownName);
    305                     }
    306                     if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
    307                     nameList.add(name);
    308                 }
    309             }
    310         } finally {
    311             if (contactCursor != null) {
    312                 contactCursor.close();
    313             }
    314         }
    315         return nameList;
    316     }
    317 
    318     public final int composeAndSendCallLogVcards(final int type, Operation op,
    319             final int startPoint, final int endPoint, final boolean vcardType21) {
    320         if (startPoint < 1 || startPoint > endPoint) {
    321             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    322             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    323         }
    324         String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
    325 
    326         final Uri myUri = CallLog.Calls.CONTENT_URI;
    327         final String[] CALLLOG_PROJECTION = new String[] {
    328             CallLog.Calls._ID, // 0
    329         };
    330         final int ID_COLUMN_INDEX = 0;
    331 
    332         Cursor callsCursor = null;
    333         long startPointId = 0;
    334         long endPointId = 0;
    335         try {
    336             // Need test to see if order by _ID is ok here, or by date?
    337             callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null,
    338                     CALLLOG_SORT_ORDER);
    339             if (callsCursor != null) {
    340                 callsCursor.moveToPosition(startPoint - 1);
    341                 startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    342                 if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
    343                 if (startPoint == endPoint) {
    344                     endPointId = startPointId;
    345                 } else {
    346                     callsCursor.moveToPosition(endPoint - 1);
    347                     endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    348                 }
    349                 if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
    350             }
    351         } finally {
    352             if (callsCursor != null) {
    353                 callsCursor.close();
    354             }
    355         }
    356 
    357         String recordSelection;
    358         if (startPoint == endPoint) {
    359             recordSelection = Calls._ID + "=" + startPointId;
    360         } else {
    361             // The query to call table is by "_id DESC" order, so change
    362             // correspondingly.
    363             recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
    364                     + startPointId;
    365         }
    366 
    367         String selection;
    368         if (typeSelection == null) {
    369             selection = recordSelection;
    370         } else {
    371             selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
    372         }
    373 
    374         if (V) Log.v(TAG, "Call log query selection is: " + selection);
    375 
    376         return composeAndSendVCards(op, selection, vcardType21, null, false);
    377     }
    378 
    379     public final int composeAndSendPhonebookVcards(Operation op, final int startPoint,
    380             final int endPoint, final boolean vcardType21, String ownerVCard) {
    381         if (startPoint < 1 || startPoint > endPoint) {
    382             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    383             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    384         }
    385         final Uri myUri = Contacts.CONTENT_URI;
    386 
    387         Cursor contactCursor = null;
    388         long startPointId = 0;
    389         long endPointId = 0;
    390         try {
    391             contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null,
    392                     Contacts._ID);
    393             if (contactCursor != null) {
    394                 contactCursor.moveToPosition(startPoint - 1);
    395                 startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    396                 if (V) Log.v(TAG, "Query startPointId = " + startPointId);
    397                 if (startPoint == endPoint) {
    398                     endPointId = startPointId;
    399                 } else {
    400                     contactCursor.moveToPosition(endPoint - 1);
    401                     endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    402                 }
    403                 if (V) Log.v(TAG, "Query endPointId = " + endPointId);
    404             }
    405         } finally {
    406             if (contactCursor != null) {
    407                 contactCursor.close();
    408             }
    409         }
    410 
    411         final String selection;
    412         if (startPoint == endPoint) {
    413             selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    414         } else {
    415             selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<="
    416                     + endPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    417         }
    418 
    419         if (V) Log.v(TAG, "Query selection is: " + selection);
    420 
    421         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
    422     }
    423 
    424     public final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
    425             final boolean vcardType21, String ownerVCard, int orderByWhat) {
    426         if (offset < 1) {
    427             Log.e(TAG, "Internal error: offset is not correct.");
    428             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    429         }
    430         final Uri myUri = Contacts.CONTENT_URI;
    431         Cursor contactCursor = null;
    432         String selection = null;
    433         long contactId = 0;
    434         if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    435             try {
    436                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    437                         null, Contacts._ID);
    438                 if (contactCursor != null) {
    439                     contactCursor.moveToPosition(offset - 1);
    440                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    441                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    442                 }
    443             } finally {
    444                 if (contactCursor != null) {
    445                     contactCursor.close();
    446                 }
    447             }
    448         } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    449             try {
    450                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    451                         null, Contacts.DISPLAY_NAME);
    452                 if (contactCursor != null) {
    453                     contactCursor.moveToPosition(offset - 1);
    454                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    455                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    456                 }
    457             } finally {
    458                 if (contactCursor != null) {
    459                     contactCursor.close();
    460                 }
    461             }
    462         } else {
    463             Log.e(TAG, "Parameter orderByWhat is not supported!");
    464             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    465         }
    466         selection = Contacts._ID + "=" + contactId;
    467 
    468         if (V) Log.v(TAG, "Query selection is: " + selection);
    469 
    470         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
    471     }
    472 
    473     public final int composeAndSendVCards(Operation op, final String selection,
    474             final boolean vcardType21, String ownerVCard, boolean isContacts) {
    475         long timestamp = 0;
    476         if (V) timestamp = System.currentTimeMillis();
    477 
    478         if (isContacts) {
    479             VCardComposer composer = null;
    480             HandlerForStringBuffer buffer = null;
    481             try {
    482                 // Currently only support Generic Vcard 2.1 and 3.0
    483                 int vcardType;
    484                 if (vcardType21) {
    485                     vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
    486                 } else {
    487                     vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
    488                 }
    489 
    490                 if (!BluetoothPbapConfig.includePhotosInVcard()) {
    491                     vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
    492                 }
    493 
    494                 //Enhancement: customize Vcard based on preferences/settings and input from caller
    495                 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null);
    496                 //End enhancement
    497 
    498                 // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
    499                 // done by vCard library by default.
    500                 composer.setPhoneNumberTranslationCallback(
    501                         new VCardPhoneNumberTranslationCallback() {
    502                             public String onValueReceived(
    503                                     String rawValue, int type, String label, boolean isPrimary) {
    504                                 // 'p' and 'w' are the standard characters for pause and wait
    505                                 // (see RFC 3601)
    506                                 // so use those when exporting phone numbers via vCard.
    507                                 String numberWithControlSequence = rawValue
    508                                         .replace(PhoneNumberUtils.PAUSE, 'p')
    509                                         .replace(PhoneNumberUtils.WAIT, 'w');
    510                                 return numberWithControlSequence;
    511                             }
    512                         });
    513                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    514                 if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) ||
    515                         !buffer.onInit(mContext)) {
    516                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    517                 }
    518 
    519                 while (!composer.isAfterLast()) {
    520                     if (BluetoothPbapObexServer.sIsAborted) {
    521                         ((ServerOperation)op).isAborted = true;
    522                         BluetoothPbapObexServer.sIsAborted = false;
    523                         break;
    524                     }
    525                     String vcard = composer.createOneEntry();
    526                     if (vcard == null) {
    527                         Log.e(TAG, "Failed to read a contact. Error reason: "
    528                                 + composer.getErrorReason());
    529                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    530                     }
    531                     if (V) {
    532                         Log.v(TAG, "Vcard Entry:");
    533                         Log.v(TAG,vcard);
    534                     }
    535 
    536                     if (!buffer.onEntryCreated(vcard)) {
    537                         // onEntryCreate() already emits error.
    538                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    539                     }
    540                 }
    541             } finally {
    542                 if (composer != null) {
    543                     composer.terminate();
    544                 }
    545                 if (buffer != null) {
    546                     buffer.onTerminate();
    547                 }
    548             }
    549         } else { // CallLog
    550             BluetoothPbapCallLogComposer composer = null;
    551             HandlerForStringBuffer buffer = null;
    552             try {
    553 
    554                 composer = new BluetoothPbapCallLogComposer(mContext);
    555                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    556                 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null,
    557                                    CALLLOG_SORT_ORDER) ||
    558                                    !buffer.onInit(mContext)) {
    559                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    560                 }
    561 
    562                 while (!composer.isAfterLast()) {
    563                     if (BluetoothPbapObexServer.sIsAborted) {
    564                         ((ServerOperation)op).isAborted = true;
    565                         BluetoothPbapObexServer.sIsAborted = false;
    566                         break;
    567                     }
    568                     String vcard = composer.createOneEntry(vcardType21);
    569                     if (vcard == null) {
    570                         Log.e(TAG, "Failed to read a contact. Error reason: "
    571                                 + composer.getErrorReason());
    572                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    573                     }
    574                     if (V) {
    575                         Log.v(TAG, "Vcard Entry:");
    576                         Log.v(TAG,vcard);
    577                     }
    578 
    579                     buffer.onEntryCreated(vcard);
    580                 }
    581             } finally {
    582                 if (composer != null) {
    583                     composer.terminate();
    584                 }
    585                 if (buffer != null) {
    586                     buffer.onTerminate();
    587                 }
    588             }
    589         }
    590 
    591         if (V) Log.v(TAG, "Total vcard composing and sending out takes "
    592                     + (System.currentTimeMillis() - timestamp) + " ms");
    593 
    594         return ResponseCodes.OBEX_HTTP_OK;
    595     }
    596 
    597     /**
    598      * Handler to emit vCards to PCE.
    599      */
    600     public class HandlerForStringBuffer {
    601         private Operation operation;
    602 
    603         private OutputStream outputStream;
    604 
    605         private String phoneOwnVCard = null;
    606 
    607         public HandlerForStringBuffer(Operation op, String ownerVCard) {
    608             operation = op;
    609             if (ownerVCard != null) {
    610                 phoneOwnVCard = ownerVCard;
    611                 if (V) Log.v(TAG, "phone own number vcard:");
    612                 if (V) Log.v(TAG, phoneOwnVCard);
    613             }
    614         }
    615 
    616         private boolean write(String vCard) {
    617             try {
    618                 if (vCard != null) {
    619                     outputStream.write(vCard.getBytes());
    620                     return true;
    621                 }
    622             } catch (IOException e) {
    623                 Log.e(TAG, "write outputstrem failed" + e.toString());
    624             }
    625             return false;
    626         }
    627 
    628         public boolean onInit(Context context) {
    629             try {
    630                 outputStream = operation.openOutputStream();
    631                 if (phoneOwnVCard != null) {
    632                     return write(phoneOwnVCard);
    633                 }
    634                 return true;
    635             } catch (IOException e) {
    636                 Log.e(TAG, "open outputstrem failed" + e.toString());
    637             }
    638             return false;
    639         }
    640 
    641         public boolean onEntryCreated(String vcard) {
    642             return write(vcard);
    643         }
    644 
    645         public void onTerminate() {
    646             if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
    647                 if (V) Log.v(TAG, "CloseStream failed!");
    648             } else {
    649                 if (V) Log.v(TAG, "CloseStream ok!");
    650             }
    651         }
    652     }
    653 }
    654