BluetoothPbapVcardManager.java revision 145880aa83b9e88d31daad5d47f250840573824e
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.vcard.VCardPhoneNumberTranslationCallback; 55 56import java.io.IOException; 57import java.io.OutputStream; 58import java.util.ArrayList; 59 60import javax.obex.ServerOperation; 61import javax.obex.Operation; 62import javax.obex.ResponseCodes; 63 64import com.android.bluetooth.Utils; 65 66public 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 long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 268 if (TextUtils.isEmpty(name)) { 269 name = mContext.getString(android.R.string.unknownName); 270 } 271 nameList.add(name + "," + id); 272 } 273 } 274 } finally { 275 if (contactCursor != null) { 276 contactCursor.close(); 277 } 278 } 279 return nameList; 280 } 281 282 public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) { 283 ArrayList<String> nameList = new ArrayList<String>(); 284 285 Cursor contactCursor = null; 286 Uri uri = null; 287 288 if (phoneNumber != null && phoneNumber.length() == 0) { 289 uri = Contacts.CONTENT_URI; 290 } else { 291 uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, 292 Uri.encode(phoneNumber)); 293 } 294 295 try { 296 contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, 297 null, Contacts._ID); 298 299 if (contactCursor != null) { 300 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor 301 .moveToNext()) { 302 String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX); 303 long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 304 if (TextUtils.isEmpty(name)) { 305 name = mContext.getString(android.R.string.unknownName); 306 } 307 if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id); 308 nameList.add(name + "," + id); 309 } 310 } 311 } finally { 312 if (contactCursor != null) { 313 contactCursor.close(); 314 } 315 } 316 return nameList; 317 } 318 319 public final int composeAndSendCallLogVcards(final int type, Operation op, 320 final int startPoint, final int endPoint, final boolean vcardType21) { 321 if (startPoint < 1 || startPoint > endPoint) { 322 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 323 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 324 } 325 String typeSelection = BluetoothPbapObexServer.createSelectionPara(type); 326 327 final Uri myUri = CallLog.Calls.CONTENT_URI; 328 final String[] CALLLOG_PROJECTION = new String[] { 329 CallLog.Calls._ID, // 0 330 }; 331 final int ID_COLUMN_INDEX = 0; 332 333 Cursor callsCursor = null; 334 long startPointId = 0; 335 long endPointId = 0; 336 try { 337 // Need test to see if order by _ID is ok here, or by date? 338 callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null, 339 CALLLOG_SORT_ORDER); 340 if (callsCursor != null) { 341 callsCursor.moveToPosition(startPoint - 1); 342 startPointId = callsCursor.getLong(ID_COLUMN_INDEX); 343 if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId); 344 if (startPoint == endPoint) { 345 endPointId = startPointId; 346 } else { 347 callsCursor.moveToPosition(endPoint - 1); 348 endPointId = callsCursor.getLong(ID_COLUMN_INDEX); 349 } 350 if (V) Log.v(TAG, "Call log query endPointId = " + endPointId); 351 } 352 } finally { 353 if (callsCursor != null) { 354 callsCursor.close(); 355 } 356 } 357 358 String recordSelection; 359 if (startPoint == endPoint) { 360 recordSelection = Calls._ID + "=" + startPointId; 361 } else { 362 // The query to call table is by "_id DESC" order, so change 363 // correspondingly. 364 recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<=" 365 + startPointId; 366 } 367 368 String selection; 369 if (typeSelection == null) { 370 selection = recordSelection; 371 } else { 372 selection = "(" + typeSelection + ") AND (" + recordSelection + ")"; 373 } 374 375 if (V) Log.v(TAG, "Call log query selection is: " + selection); 376 377 return composeAndSendVCards(op, selection, vcardType21, null, false); 378 } 379 380 public final int composeAndSendPhonebookVcards(Operation op, final int startPoint, 381 final int endPoint, final boolean vcardType21, String ownerVCard) { 382 if (startPoint < 1 || startPoint > endPoint) { 383 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 384 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 385 } 386 final Uri myUri = Contacts.CONTENT_URI; 387 388 Cursor contactCursor = null; 389 long startPointId = 0; 390 long endPointId = 0; 391 try { 392 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null, 393 Contacts._ID); 394 if (contactCursor != null) { 395 contactCursor.moveToPosition(startPoint - 1); 396 startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 397 if (V) Log.v(TAG, "Query startPointId = " + startPointId); 398 if (startPoint == endPoint) { 399 endPointId = startPointId; 400 } else { 401 contactCursor.moveToPosition(endPoint - 1); 402 endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 403 } 404 if (V) Log.v(TAG, "Query endPointId = " + endPointId); 405 } 406 } finally { 407 if (contactCursor != null) { 408 contactCursor.close(); 409 } 410 } 411 412 final String selection; 413 if (startPoint == endPoint) { 414 selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE; 415 } else { 416 selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<=" 417 + endPointId + " AND " + CLAUSE_ONLY_VISIBLE; 418 } 419 420 if (V) Log.v(TAG, "Query selection is: " + selection); 421 422 return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true); 423 } 424 425 public final int composeAndSendPhonebookOneVcard(Operation op, final int offset, 426 final boolean vcardType21, String ownerVCard, int orderByWhat) { 427 if (offset < 1) { 428 Log.e(TAG, "Internal error: offset is not correct."); 429 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 430 } 431 final Uri myUri = Contacts.CONTENT_URI; 432 Cursor contactCursor = null; 433 String selection = null; 434 long contactId = 0; 435 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) { 436 try { 437 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, 438 null, Contacts._ID); 439 if (contactCursor != null) { 440 contactCursor.moveToPosition(offset - 1); 441 contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 442 if (V) Log.v(TAG, "Query startPointId = " + contactId); 443 } 444 } finally { 445 if (contactCursor != null) { 446 contactCursor.close(); 447 } 448 } 449 } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) { 450 try { 451 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, 452 null, Contacts.DISPLAY_NAME); 453 if (contactCursor != null) { 454 contactCursor.moveToPosition(offset - 1); 455 contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 456 if (V) Log.v(TAG, "Query startPointId = " + contactId); 457 } 458 } finally { 459 if (contactCursor != null) { 460 contactCursor.close(); 461 } 462 } 463 } else { 464 Log.e(TAG, "Parameter orderByWhat is not supported!"); 465 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 466 } 467 selection = Contacts._ID + "=" + contactId; 468 469 if (V) Log.v(TAG, "Query selection is: " + selection); 470 471 return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true); 472 } 473 474 public final int composeAndSendVCards(Operation op, final String selection, 475 final boolean vcardType21, String ownerVCard, boolean isContacts) { 476 long timestamp = 0; 477 if (V) timestamp = System.currentTimeMillis(); 478 479 if (isContacts) { 480 VCardComposer composer = null; 481 HandlerForStringBuffer buffer = null; 482 try { 483 // Currently only support Generic Vcard 2.1 and 3.0 484 int vcardType; 485 if (vcardType21) { 486 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 487 } else { 488 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 489 } 490 491 if (!BluetoothPbapConfig.includePhotosInVcard()) { 492 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT; 493 } 494 495 //Enhancement: customize Vcard based on preferences/settings and input from caller 496 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null); 497 //End enhancement 498 499 // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting 500 // done by vCard library by default. 501 composer.setPhoneNumberTranslationCallback( 502 new VCardPhoneNumberTranslationCallback() { 503 public String onValueReceived( 504 String rawValue, int type, String label, boolean isPrimary) { 505 // 'p' and 'w' are the standard characters for pause and wait 506 // (see RFC 3601) 507 // so use those when exporting phone numbers via vCard. 508 String numberWithControlSequence = rawValue 509 .replace(PhoneNumberUtils.PAUSE, 'p') 510 .replace(PhoneNumberUtils.WAIT, 'w'); 511 return numberWithControlSequence; 512 } 513 }); 514 buffer = new HandlerForStringBuffer(op, ownerVCard); 515 if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) || 516 !buffer.onInit(mContext)) { 517 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 518 } 519 520 while (!composer.isAfterLast()) { 521 if (BluetoothPbapObexServer.sIsAborted) { 522 ((ServerOperation)op).isAborted = true; 523 BluetoothPbapObexServer.sIsAborted = false; 524 break; 525 } 526 String vcard = composer.createOneEntry(); 527 if (vcard == null) { 528 Log.e(TAG, "Failed to read a contact. Error reason: " 529 + composer.getErrorReason()); 530 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 531 } 532 if (V) { 533 Log.v(TAG, "Vcard Entry:"); 534 Log.v(TAG,vcard); 535 } 536 537 if (!buffer.onEntryCreated(vcard)) { 538 // onEntryCreate() already emits error. 539 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 540 } 541 } 542 } finally { 543 if (composer != null) { 544 composer.terminate(); 545 } 546 if (buffer != null) { 547 buffer.onTerminate(); 548 } 549 } 550 } else { // CallLog 551 BluetoothPbapCallLogComposer composer = null; 552 HandlerForStringBuffer buffer = null; 553 try { 554 555 composer = new BluetoothPbapCallLogComposer(mContext); 556 buffer = new HandlerForStringBuffer(op, ownerVCard); 557 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null, 558 CALLLOG_SORT_ORDER) || 559 !buffer.onInit(mContext)) { 560 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 561 } 562 563 while (!composer.isAfterLast()) { 564 if (BluetoothPbapObexServer.sIsAborted) { 565 ((ServerOperation)op).isAborted = true; 566 BluetoothPbapObexServer.sIsAborted = false; 567 break; 568 } 569 String vcard = composer.createOneEntry(vcardType21); 570 if (vcard == null) { 571 Log.e(TAG, "Failed to read a contact. Error reason: " 572 + composer.getErrorReason()); 573 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 574 } 575 if (V) { 576 Log.v(TAG, "Vcard Entry:"); 577 Log.v(TAG,vcard); 578 } 579 580 buffer.onEntryCreated(vcard); 581 } 582 } finally { 583 if (composer != null) { 584 composer.terminate(); 585 } 586 if (buffer != null) { 587 buffer.onTerminate(); 588 } 589 } 590 } 591 592 if (V) Log.v(TAG, "Total vcard composing and sending out takes " 593 + (System.currentTimeMillis() - timestamp) + " ms"); 594 595 return ResponseCodes.OBEX_HTTP_OK; 596 } 597 598 /** 599 * Handler to emit vCards to PCE. 600 */ 601 public class HandlerForStringBuffer { 602 private Operation operation; 603 604 private OutputStream outputStream; 605 606 private String phoneOwnVCard = null; 607 608 public HandlerForStringBuffer(Operation op, String ownerVCard) { 609 operation = op; 610 if (ownerVCard != null) { 611 phoneOwnVCard = ownerVCard; 612 if (V) Log.v(TAG, "phone own number vcard:"); 613 if (V) Log.v(TAG, phoneOwnVCard); 614 } 615 } 616 617 private boolean write(String vCard) { 618 try { 619 if (vCard != null) { 620 outputStream.write(vCard.getBytes()); 621 return true; 622 } 623 } catch (IOException e) { 624 Log.e(TAG, "write outputstrem failed" + e.toString()); 625 } 626 return false; 627 } 628 629 public boolean onInit(Context context) { 630 try { 631 outputStream = operation.openOutputStream(); 632 if (phoneOwnVCard != null) { 633 return write(phoneOwnVCard); 634 } 635 return true; 636 } catch (IOException e) { 637 Log.e(TAG, "open outputstrem failed" + e.toString()); 638 } 639 return false; 640 } 641 642 public boolean onEntryCreated(String vcard) { 643 return write(vcard); 644 } 645 646 public void onTerminate() { 647 if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) { 648 if (V) Log.v(TAG, "CloseStream failed!"); 649 } else { 650 if (V) Log.v(TAG, "CloseStream ok!"); 651 } 652 } 653 } 654} 655