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