Home | History | Annotate | Download | only in pbap
      1 /*
      2  * Copyright (c) 2008-2009, Motorola, Inc.
      3  *
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are met:
      8  *
      9  * - Redistributions of source code must retain the above copyright notice,
     10  * this list of conditions and the following disclaimer.
     11  *
     12  * - Redistributions in binary form must reproduce the above copyright notice,
     13  * this list of conditions and the following disclaimer in the documentation
     14  * and/or other materials provided with the distribution.
     15  *
     16  * - Neither the name of the Motorola, Inc. nor the names of its contributors
     17  * may be used to endorse or promote products derived from this software
     18  * without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     30  * POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.android.bluetooth.pbap;
     34 
     35 import android.content.ContentResolver;
     36 import android.content.Context;
     37 import android.database.Cursor;
     38 import android.net.Uri;
     39 import android.provider.CallLog;
     40 import android.provider.CallLog.Calls;
     41 import android.provider.ContactsContract.CommonDataKinds;
     42 import android.provider.ContactsContract.Contacts;
     43 import android.provider.ContactsContract.Data;
     44 import android.provider.ContactsContract.CommonDataKinds.Phone;
     45 import android.provider.ContactsContract.PhoneLookup;
     46 import android.telephony.PhoneNumberUtils;
     47 import android.text.TextUtils;
     48 import android.util.Log;
     49 
     50 import com.android.bluetooth.R;
     51 import com.android.vcard.VCardComposer;
     52 import com.android.vcard.VCardConfig;
     53 import com.android.internal.telephony.CallerInfo;
     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 public class BluetoothPbapVcardManager {
     65     private static final String TAG = "BluetoothPbapVcardManager";
     66 
     67     private static final boolean V = BluetoothPbapService.VERBOSE;
     68 
     69     private ContentResolver mResolver;
     70 
     71     private Context mContext;
     72 
     73     static final String[] PHONES_PROJECTION = new String[] {
     74             Data._ID, // 0
     75             CommonDataKinds.Phone.TYPE, // 1
     76             CommonDataKinds.Phone.LABEL, // 2
     77             CommonDataKinds.Phone.NUMBER, // 3
     78             Contacts.DISPLAY_NAME, // 4
     79     };
     80 
     81     private static final int PHONE_NUMBER_COLUMN_INDEX = 3;
     82 
     83     static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC";
     84 
     85     static final String[] CONTACTS_PROJECTION = new String[] {
     86             Contacts._ID, // 0
     87             Contacts.DISPLAY_NAME, // 1
     88     };
     89 
     90     static final int CONTACTS_ID_COLUMN_INDEX = 0;
     91 
     92     static final int CONTACTS_NAME_COLUMN_INDEX = 1;
     93 
     94     // call histories use dynamic handles, and handles should order by date; the
     95     // most recently one should be the first handle. In table "calls", _id and
     96     // date are consistent in ordering, to implement simply, we sort by _id
     97     // here.
     98     static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC";
     99 
    100     private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
    101 
    102     public BluetoothPbapVcardManager(final Context context) {
    103         mContext = context;
    104         mResolver = mContext.getContentResolver();
    105     }
    106 
    107     public final String getOwnerPhoneNumberVcard(final boolean vcardType21) {
    108         BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext);
    109         String name = BluetoothPbapService.getLocalPhoneName();
    110         String number = BluetoothPbapService.getLocalPhoneNum();
    111         String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number,
    112                 vcardType21);
    113         return vcard;
    114     }
    115 
    116     public final int getPhonebookSize(final int type) {
    117         int size;
    118         switch (type) {
    119             case BluetoothPbapObexServer.ContentType.PHONEBOOK:
    120                 size = getContactsSize();
    121                 break;
    122             default:
    123                 size = getCallHistorySize(type);
    124                 break;
    125         }
    126         if (V) Log.v(TAG, "getPhonebookSzie size = " + size + " type = " + type);
    127         return size;
    128     }
    129 
    130     public final int getContactsSize() {
    131         final Uri myUri = Contacts.CONTENT_URI;
    132         int size = 0;
    133         Cursor contactCursor = null;
    134         try {
    135             contactCursor = mResolver.query(myUri, null, CLAUSE_ONLY_VISIBLE, null, null);
    136             if (contactCursor != null) {
    137                 size = contactCursor.getCount() + 1; // always has the 0.vcf
    138             }
    139         } finally {
    140             if (contactCursor != null) {
    141                 contactCursor.close();
    142             }
    143         }
    144         return size;
    145     }
    146 
    147     public final int getCallHistorySize(final int type) {
    148         final Uri myUri = CallLog.Calls.CONTENT_URI;
    149         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    150         int size = 0;
    151         Cursor callCursor = null;
    152         try {
    153             callCursor = mResolver.query(myUri, null, selection, null,
    154                     CallLog.Calls.DEFAULT_SORT_ORDER);
    155             if (callCursor != null) {
    156                 size = callCursor.getCount();
    157             }
    158         } finally {
    159             if (callCursor != null) {
    160                 callCursor.close();
    161             }
    162         }
    163         return size;
    164     }
    165 
    166     public final ArrayList<String> loadCallHistoryList(final int type) {
    167         final Uri myUri = CallLog.Calls.CONTENT_URI;
    168         String selection = BluetoothPbapObexServer.createSelectionPara(type);
    169         String[] projection = new String[] {
    170                 Calls.NUMBER, Calls.CACHED_NAME
    171         };
    172         final int CALLS_NUMBER_COLUMN_INDEX = 0;
    173         final int CALLS_NAME_COLUMN_INDEX = 1;
    174 
    175         Cursor callCursor = null;
    176         ArrayList<String> list = new ArrayList<String>();
    177         try {
    178             callCursor = mResolver.query(myUri, projection, selection, null,
    179                     CALLLOG_SORT_ORDER);
    180             if (callCursor != null) {
    181                 for (callCursor.moveToFirst(); !callCursor.isAfterLast();
    182                         callCursor.moveToNext()) {
    183                     String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX);
    184                     if (TextUtils.isEmpty(name)) {
    185                         // name not found, use number instead
    186                         name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX);
    187                         if (CallerInfo.UNKNOWN_NUMBER.equals(name) ||
    188                                 CallerInfo.PRIVATE_NUMBER.equals(name) ||
    189                                 CallerInfo.PAYPHONE_NUMBER.equals(name)) {
    190                             name = mContext.getString(R.string.unknownNumber);
    191                         }
    192                     }
    193                     list.add(name);
    194                 }
    195             }
    196         } finally {
    197             if (callCursor != null) {
    198                 callCursor.close();
    199             }
    200         }
    201         return list;
    202     }
    203 
    204     public final ArrayList<String> getPhonebookNameList(final int orderByWhat) {
    205         ArrayList<String> nameList = new ArrayList<String>();
    206         nameList.add(BluetoothPbapService.getLocalPhoneName());
    207 
    208         final Uri myUri = Contacts.CONTENT_URI;
    209         Cursor contactCursor = null;
    210         try {
    211             if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    212                 if (V) Log.v(TAG, "getPhonebookNameList, order by index");
    213                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    214                         null, Contacts._ID);
    215             } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    216                 if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
    217                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    218                         null, Contacts.DISPLAY_NAME);
    219             }
    220             if (contactCursor != null) {
    221                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    222                         .moveToNext()) {
    223                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    224                     if (TextUtils.isEmpty(name)) {
    225                         name = mContext.getString(android.R.string.unknownName);
    226                     }
    227                     nameList.add(name);
    228                 }
    229             }
    230         } finally {
    231             if (contactCursor != null) {
    232                 contactCursor.close();
    233             }
    234         }
    235         return nameList;
    236     }
    237 
    238     public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) {
    239         ArrayList<String> nameList = new ArrayList<String>();
    240 
    241         Cursor contactCursor = null;
    242         Uri uri = null;
    243 
    244         if (phoneNumber != null && phoneNumber.length() == 0) {
    245             uri = Contacts.CONTENT_URI;
    246         } else {
    247             uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
    248                 Uri.encode(phoneNumber));
    249         }
    250 
    251         try {
    252             contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    253                         null, Contacts._ID);
    254 
    255             if (contactCursor != null) {
    256                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
    257                         .moveToNext()) {
    258                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
    259                     long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    260                     if (TextUtils.isEmpty(name)) {
    261                         name = mContext.getString(android.R.string.unknownName);
    262                     }
    263                     if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
    264                     nameList.add(name);
    265                 }
    266             }
    267         } finally {
    268             if (contactCursor != null) {
    269                 contactCursor.close();
    270             }
    271         }
    272         return nameList;
    273     }
    274 
    275     public final int composeAndSendCallLogVcards(final int type, Operation op,
    276             final int startPoint, final int endPoint, final boolean vcardType21) {
    277         if (startPoint < 1 || startPoint > endPoint) {
    278             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    279             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    280         }
    281         String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
    282 
    283         final Uri myUri = CallLog.Calls.CONTENT_URI;
    284         final String[] CALLLOG_PROJECTION = new String[] {
    285             CallLog.Calls._ID, // 0
    286         };
    287         final int ID_COLUMN_INDEX = 0;
    288 
    289         Cursor callsCursor = null;
    290         long startPointId = 0;
    291         long endPointId = 0;
    292         try {
    293             // Need test to see if order by _ID is ok here, or by date?
    294             callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null,
    295                     CALLLOG_SORT_ORDER);
    296             if (callsCursor != null) {
    297                 callsCursor.moveToPosition(startPoint - 1);
    298                 startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    299                 if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
    300                 if (startPoint == endPoint) {
    301                     endPointId = startPointId;
    302                 } else {
    303                     callsCursor.moveToPosition(endPoint - 1);
    304                     endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
    305                 }
    306                 if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
    307             }
    308         } finally {
    309             if (callsCursor != null) {
    310                 callsCursor.close();
    311             }
    312         }
    313 
    314         String recordSelection;
    315         if (startPoint == endPoint) {
    316             recordSelection = Calls._ID + "=" + startPointId;
    317         } else {
    318             // The query to call table is by "_id DESC" order, so change
    319             // correspondingly.
    320             recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
    321                     + startPointId;
    322         }
    323 
    324         String selection;
    325         if (typeSelection == null) {
    326             selection = recordSelection;
    327         } else {
    328             selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
    329         }
    330 
    331         if (V) Log.v(TAG, "Call log query selection is: " + selection);
    332 
    333         return composeAndSendVCards(op, selection, vcardType21, null, false);
    334     }
    335 
    336     public final int composeAndSendPhonebookVcards(Operation op, final int startPoint,
    337             final int endPoint, final boolean vcardType21, String ownerVCard) {
    338         if (startPoint < 1 || startPoint > endPoint) {
    339             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
    340             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    341         }
    342         final Uri myUri = Contacts.CONTENT_URI;
    343 
    344         Cursor contactCursor = null;
    345         long startPointId = 0;
    346         long endPointId = 0;
    347         try {
    348             contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null,
    349                     Contacts._ID);
    350             if (contactCursor != null) {
    351                 contactCursor.moveToPosition(startPoint - 1);
    352                 startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    353                 if (V) Log.v(TAG, "Query startPointId = " + startPointId);
    354                 if (startPoint == endPoint) {
    355                     endPointId = startPointId;
    356                 } else {
    357                     contactCursor.moveToPosition(endPoint - 1);
    358                     endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    359                 }
    360                 if (V) Log.v(TAG, "Query endPointId = " + endPointId);
    361             }
    362         } finally {
    363             if (contactCursor != null) {
    364                 contactCursor.close();
    365             }
    366         }
    367 
    368         final String selection;
    369         if (startPoint == endPoint) {
    370             selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    371         } else {
    372             selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<="
    373                     + endPointId + " AND " + CLAUSE_ONLY_VISIBLE;
    374         }
    375 
    376         if (V) Log.v(TAG, "Query selection is: " + selection);
    377 
    378         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
    379     }
    380 
    381     public final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
    382             final boolean vcardType21, String ownerVCard, int orderByWhat) {
    383         if (offset < 1) {
    384             Log.e(TAG, "Internal error: offset is not correct.");
    385             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    386         }
    387         final Uri myUri = Contacts.CONTENT_URI;
    388         Cursor contactCursor = null;
    389         String selection = null;
    390         long contactId = 0;
    391         if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
    392             try {
    393                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    394                         null, Contacts._ID);
    395                 if (contactCursor != null) {
    396                     contactCursor.moveToPosition(offset - 1);
    397                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    398                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    399                 }
    400             } finally {
    401                 if (contactCursor != null) {
    402                     contactCursor.close();
    403                 }
    404             }
    405         } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
    406             try {
    407                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
    408                         null, Contacts.DISPLAY_NAME);
    409                 if (contactCursor != null) {
    410                     contactCursor.moveToPosition(offset - 1);
    411                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
    412                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
    413                 }
    414             } finally {
    415                 if (contactCursor != null) {
    416                     contactCursor.close();
    417                 }
    418             }
    419         } else {
    420             Log.e(TAG, "Parameter orderByWhat is not supported!");
    421             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    422         }
    423         selection = Contacts._ID + "=" + contactId;
    424 
    425         if (V) Log.v(TAG, "Query selection is: " + selection);
    426 
    427         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
    428     }
    429 
    430     public final int composeAndSendVCards(Operation op, final String selection,
    431             final boolean vcardType21, String ownerVCard, boolean isContacts) {
    432         long timestamp = 0;
    433         if (V) timestamp = System.currentTimeMillis();
    434 
    435         if (isContacts) {
    436             VCardComposer composer = null;
    437             HandlerForStringBuffer buffer = null;
    438             try {
    439                 // Currently only support Generic Vcard 2.1 and 3.0
    440                 int vcardType;
    441                 if (vcardType21) {
    442                     vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
    443                 } else {
    444                     vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
    445                 }
    446                 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
    447 
    448                 composer = new VCardComposer(mContext, vcardType, true);
    449                 // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
    450                 // done by vCard library by default.
    451                 composer.setPhoneNumberTranslationCallback(
    452                         new VCardPhoneNumberTranslationCallback() {
    453                             public String onValueReceived(
    454                                     String rawValue, int type, String label, boolean isPrimary) {
    455                                 // 'p' and 'w' are the standard characters for pause and wait
    456                                 // (see RFC 3601)
    457                                 // so use those when exporting phone numbers via vCard.
    458                                 String numberWithControlSequence = rawValue
    459                                         .replace(PhoneNumberUtils.PAUSE, 'p')
    460                                         .replace(PhoneNumberUtils.WAIT, 'w');
    461                                 return numberWithControlSequence;
    462                             }
    463                         });
    464                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    465                 if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) ||
    466                         !buffer.onInit(mContext)) {
    467                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    468                 }
    469 
    470                 while (!composer.isAfterLast()) {
    471                     if (BluetoothPbapObexServer.sIsAborted) {
    472                         ((ServerOperation)op).isAborted = true;
    473                         BluetoothPbapObexServer.sIsAborted = false;
    474                         break;
    475                     }
    476                     String vcard = composer.createOneEntry();
    477                     if (vcard == null) {
    478                         Log.e(TAG, "Failed to read a contact. Error reason: "
    479                                 + composer.getErrorReason());
    480                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    481                     }
    482                     if (!buffer.onEntryCreated(vcard)) {
    483                         // onEntryCreate() already emits error.
    484                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    485                     }
    486                 }
    487             } finally {
    488                 if (composer != null) {
    489                     composer.terminate();
    490                 }
    491                 if (buffer != null) {
    492                     buffer.onTerminate();
    493                 }
    494             }
    495         } else { // CallLog
    496             BluetoothPbapCallLogComposer composer = null;
    497             HandlerForStringBuffer buffer = null;
    498             try {
    499 
    500                 composer = new BluetoothPbapCallLogComposer(mContext);
    501                 buffer = new HandlerForStringBuffer(op, ownerVCard);
    502                 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null,
    503                                    CALLLOG_SORT_ORDER) ||
    504                                    !buffer.onInit(mContext)) {
    505                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    506                 }
    507 
    508                 while (!composer.isAfterLast()) {
    509                     if (BluetoothPbapObexServer.sIsAborted) {
    510                         ((ServerOperation)op).isAborted = true;
    511                         BluetoothPbapObexServer.sIsAborted = false;
    512                         break;
    513                     }
    514                     String vcard = composer.createOneEntry(vcardType21);
    515                     if (vcard == null) {
    516                         Log.e(TAG, "Failed to read a contact. Error reason: "
    517                                 + composer.getErrorReason());
    518                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    519                     }
    520                     buffer.onEntryCreated(vcard);
    521                 }
    522             } finally {
    523                 if (composer != null) {
    524                     composer.terminate();
    525                 }
    526                 if (buffer != null) {
    527                     buffer.onTerminate();
    528                 }
    529             }
    530         }
    531 
    532         if (V) Log.v(TAG, "Total vcard composing and sending out takes "
    533                     + (System.currentTimeMillis() - timestamp) + " ms");
    534 
    535         return ResponseCodes.OBEX_HTTP_OK;
    536     }
    537 
    538     /**
    539      * Handler to emit vCards to PCE.
    540      */
    541     public class HandlerForStringBuffer {
    542         private Operation operation;
    543 
    544         private OutputStream outputStream;
    545 
    546         private String phoneOwnVCard = null;
    547 
    548         public HandlerForStringBuffer(Operation op, String ownerVCard) {
    549             operation = op;
    550             if (ownerVCard != null) {
    551                 phoneOwnVCard = ownerVCard;
    552                 if (V) Log.v(TAG, "phone own number vcard:");
    553                 if (V) Log.v(TAG, phoneOwnVCard);
    554             }
    555         }
    556 
    557         private boolean write(String vCard) {
    558             try {
    559                 if (vCard != null) {
    560                     outputStream.write(vCard.getBytes());
    561                     return true;
    562                 }
    563             } catch (IOException e) {
    564                 Log.e(TAG, "write outputstrem failed" + e.toString());
    565             }
    566             return false;
    567         }
    568 
    569         public boolean onInit(Context context) {
    570             try {
    571                 outputStream = operation.openOutputStream();
    572                 if (phoneOwnVCard != null) {
    573                     return write(phoneOwnVCard);
    574                 }
    575                 return true;
    576             } catch (IOException e) {
    577                 Log.e(TAG, "open outputstrem failed" + e.toString());
    578             }
    579             return false;
    580         }
    581 
    582         public boolean onEntryCreated(String vcard) {
    583             return write(vcard);
    584         }
    585 
    586         public void onTerminate() {
    587             if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
    588                 if (V) Log.v(TAG, "CloseStream failed!");
    589             } else {
    590                 if (V) Log.v(TAG, "CloseStream ok!");
    591             }
    592         }
    593     }
    594 }
    595