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