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