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