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.CursorWindowAllocationException; 39 import android.database.Cursor; 40 import android.net.Uri; 41 import android.provider.CallLog; 42 import android.provider.CallLog.Calls; 43 import android.provider.ContactsContract.CommonDataKinds; 44 import android.provider.ContactsContract.Contacts; 45 import android.provider.ContactsContract.Data; 46 import android.provider.ContactsContract.CommonDataKinds.Phone; 47 import android.provider.ContactsContract.PhoneLookup; 48 import android.telephony.PhoneNumberUtils; 49 import android.text.TextUtils; 50 import android.util.Log; 51 52 import com.android.bluetooth.R; 53 import com.android.vcard.VCardComposer; 54 import com.android.vcard.VCardConfig; 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 } 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 boolean ignorefilter, byte[] filter) { 346 if (startPoint < 1 || startPoint > endPoint) { 347 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 348 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 349 } 350 String typeSelection = BluetoothPbapObexServer.createSelectionPara(type); 351 352 final Uri myUri = CallLog.Calls.CONTENT_URI; 353 final String[] CALLLOG_PROJECTION = new String[] { 354 CallLog.Calls._ID, // 0 355 }; 356 final int ID_COLUMN_INDEX = 0; 357 358 Cursor callsCursor = null; 359 long startPointId = 0; 360 long endPointId = 0; 361 try { 362 // Need test to see if order by _ID is ok here, or by date? 363 callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null, 364 CALLLOG_SORT_ORDER); 365 if (callsCursor != null) { 366 callsCursor.moveToPosition(startPoint - 1); 367 startPointId = callsCursor.getLong(ID_COLUMN_INDEX); 368 if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId); 369 if (startPoint == endPoint) { 370 endPointId = startPointId; 371 } else { 372 callsCursor.moveToPosition(endPoint - 1); 373 endPointId = callsCursor.getLong(ID_COLUMN_INDEX); 374 } 375 if (V) Log.v(TAG, "Call log query endPointId = " + endPointId); 376 } 377 } catch (CursorWindowAllocationException e) { 378 Log.e(TAG, "CursorWindowAllocationException while composing calllog vcards"); 379 } finally { 380 if (callsCursor != null) { 381 callsCursor.close(); 382 callsCursor = null; 383 } 384 } 385 386 String recordSelection; 387 if (startPoint == endPoint) { 388 recordSelection = Calls._ID + "=" + startPointId; 389 } else { 390 // The query to call table is by "_id DESC" order, so change 391 // correspondingly. 392 recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<=" 393 + startPointId; 394 } 395 396 String selection; 397 if (typeSelection == null) { 398 selection = recordSelection; 399 } else { 400 selection = "(" + typeSelection + ") AND (" + recordSelection + ")"; 401 } 402 403 if (V) Log.v(TAG, "Call log query selection is: " + selection); 404 405 return composeAndSendVCards(op, selection, vcardType21, null, false, ignorefilter, filter); 406 } 407 408 public final int composeAndSendPhonebookVcards(Operation op, final int startPoint, 409 final int endPoint, final boolean vcardType21, String ownerVCard, 410 boolean ignorefilter, byte[] filter) { 411 if (startPoint < 1 || startPoint > endPoint) { 412 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 413 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 414 } 415 final Uri myUri = Contacts.CONTENT_URI; 416 417 Cursor contactCursor = null; 418 long startPointId = 0; 419 long endPointId = 0; 420 try { 421 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null, 422 Contacts._ID); 423 if (contactCursor != null) { 424 contactCursor.moveToPosition(startPoint - 1); 425 startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 426 if (V) Log.v(TAG, "Query startPointId = " + startPointId); 427 if (startPoint == endPoint) { 428 endPointId = startPointId; 429 } else { 430 contactCursor.moveToPosition(endPoint - 1); 431 endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 432 } 433 if (V) Log.v(TAG, "Query endPointId = " + endPointId); 434 } 435 } catch (CursorWindowAllocationException e) { 436 Log.e(TAG, "CursorWindowAllocationException while composing phonebook vcards"); 437 } finally { 438 if (contactCursor != null) { 439 contactCursor.close(); 440 contactCursor = null; 441 } 442 } 443 444 final String selection; 445 if (startPoint == endPoint) { 446 selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE; 447 } else { 448 selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<=" 449 + endPointId + " AND " + CLAUSE_ONLY_VISIBLE; 450 } 451 452 if (V) Log.v(TAG, "Query selection is: " + selection); 453 454 return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true, 455 ignorefilter, filter); 456 } 457 458 public final int composeAndSendPhonebookOneVcard(Operation op, final int offset, 459 final boolean vcardType21, String ownerVCard, int orderByWhat, 460 boolean ignorefilter, byte[] filter) { 461 if (offset < 1) { 462 Log.e(TAG, "Internal error: offset is not correct."); 463 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 464 } 465 final Uri myUri = Contacts.CONTENT_URI; 466 Cursor contactCursor = null; 467 String selection = null; 468 long contactId = 0; 469 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) { 470 try { 471 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, 472 null, Contacts._ID); 473 if (contactCursor != null) { 474 contactCursor.moveToPosition(offset - 1); 475 contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 476 if (V) Log.v(TAG, "Query startPointId = " + contactId); 477 } 478 } catch (CursorWindowAllocationException e) { 479 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by index"); 480 } finally { 481 if (contactCursor != null) { 482 contactCursor.close(); 483 contactCursor = null; 484 } 485 } 486 } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) { 487 try { 488 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, 489 null, Contacts.DISPLAY_NAME); 490 if (contactCursor != null) { 491 contactCursor.moveToPosition(offset - 1); 492 contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX); 493 if (V) Log.v(TAG, "Query startPointId = " + contactId); 494 } 495 } catch (CursorWindowAllocationException e) { 496 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by alphabetical"); 497 } finally { 498 if (contactCursor != null) { 499 contactCursor.close(); 500 contactCursor = null; 501 } 502 } 503 } else { 504 Log.e(TAG, "Parameter orderByWhat is not supported!"); 505 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 506 } 507 selection = Contacts._ID + "=" + contactId; 508 509 if (V) Log.v(TAG, "Query selection is: " + selection); 510 511 return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true, 512 ignorefilter, filter); 513 } 514 515 public final int composeAndSendVCards(Operation op, final String selection, 516 final boolean vcardType21, String ownerVCard, boolean isContacts, 517 boolean ignorefilter, byte[] filter) { 518 long timestamp = 0; 519 if (V) timestamp = System.currentTimeMillis(); 520 521 if (isContacts) { 522 VCardComposer composer = null; 523 VCardFilter vcardfilter= new VCardFilter(ignorefilter ? null : filter); 524 525 HandlerForStringBuffer buffer = null; 526 try { 527 // Currently only support Generic Vcard 2.1 and 3.0 528 int vcardType; 529 if (vcardType21) { 530 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 531 } else { 532 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 533 } 534 if (!vcardfilter.isPhotoEnabled()) { 535 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT; 536 } 537 538 //Enhancement: customize Vcard based on preferences/settings and input from caller 539 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null); 540 //End enhancement 541 542 // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting 543 // done by vCard library by default. 544 composer.setPhoneNumberTranslationCallback( 545 new VCardPhoneNumberTranslationCallback() { 546 public String onValueReceived( 547 String rawValue, int type, String label, boolean isPrimary) { 548 // 'p' and 'w' are the standard characters for pause and wait 549 // (see RFC 3601) 550 // so use those when exporting phone numbers via vCard. 551 String numberWithControlSequence = rawValue 552 .replace(PhoneNumberUtils.PAUSE, 'p') 553 .replace(PhoneNumberUtils.WAIT, 'w'); 554 return numberWithControlSequence; 555 } 556 }); 557 buffer = new HandlerForStringBuffer(op, ownerVCard); 558 if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) || 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(); 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) Log.v (TAG , "vCard from composer: " + vcard); 576 577 vcard = vcardfilter.apply(vcard, vcardType21); 578 vcard = StripTelephoneNumber(vcard); 579 580 if (V) Log.v (TAG, "vCard after cleanup: " + vcard); 581 582 if (!buffer.onEntryCreated(vcard)) { 583 // onEntryCreate() already emits error. 584 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 585 } 586 } 587 } finally { 588 if (composer != null) { 589 composer.terminate(); 590 } 591 if (buffer != null) { 592 buffer.onTerminate(); 593 } 594 } 595 } else { // CallLog 596 BluetoothPbapCallLogComposer composer = null; 597 HandlerForStringBuffer buffer = null; 598 try { 599 600 composer = new BluetoothPbapCallLogComposer(mContext); 601 buffer = new HandlerForStringBuffer(op, ownerVCard); 602 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null, 603 CALLLOG_SORT_ORDER) || 604 !buffer.onInit(mContext)) { 605 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 606 } 607 608 while (!composer.isAfterLast()) { 609 if (BluetoothPbapObexServer.sIsAborted) { 610 ((ServerOperation)op).isAborted = true; 611 BluetoothPbapObexServer.sIsAborted = false; 612 break; 613 } 614 String vcard = composer.createOneEntry(vcardType21); 615 if (vcard == null) { 616 Log.e(TAG, "Failed to read a contact. Error reason: " 617 + composer.getErrorReason()); 618 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 619 } 620 if (V) { 621 Log.v(TAG, "Vcard Entry:"); 622 Log.v(TAG,vcard); 623 } 624 625 buffer.onEntryCreated(vcard); 626 } 627 } finally { 628 if (composer != null) { 629 composer.terminate(); 630 } 631 if (buffer != null) { 632 buffer.onTerminate(); 633 } 634 } 635 } 636 637 if (V) Log.v(TAG, "Total vcard composing and sending out takes " 638 + (System.currentTimeMillis() - timestamp) + " ms"); 639 640 return ResponseCodes.OBEX_HTTP_OK; 641 } 642 643 public String StripTelephoneNumber (String vCard){ 644 String attr [] = vCard.split(System.getProperty("line.separator")); 645 String Vcard = ""; 646 for (int i=0; i < attr.length; i++) { 647 if(attr[i].startsWith("TEL")) { 648 attr[i] = attr[i].replace("(", ""); 649 attr[i] = attr[i].replace(")", ""); 650 attr[i] = attr[i].replace("-", ""); 651 attr[i] = attr[i].replace(" ", ""); 652 } 653 } 654 655 for (int i=0; i < attr.length; i++) { 656 if(!attr[i].equals("")){ 657 Vcard = Vcard.concat(attr[i] + "\n"); 658 } 659 } 660 if (V) Log.v(TAG, "Vcard with stripped telephone no.: " + Vcard); 661 return Vcard; 662 } 663 664 /** 665 * Handler to emit vCards to PCE. 666 */ 667 public class HandlerForStringBuffer { 668 private Operation operation; 669 670 private OutputStream outputStream; 671 672 private String phoneOwnVCard = null; 673 674 public HandlerForStringBuffer(Operation op, String ownerVCard) { 675 operation = op; 676 if (ownerVCard != null) { 677 phoneOwnVCard = ownerVCard; 678 if (V) Log.v(TAG, "phone own number vcard:"); 679 if (V) Log.v(TAG, phoneOwnVCard); 680 } 681 } 682 683 private boolean write(String vCard) { 684 try { 685 if (vCard != null) { 686 outputStream.write(vCard.getBytes()); 687 return true; 688 } 689 } catch (IOException e) { 690 Log.e(TAG, "write outputstrem failed" + e.toString()); 691 } 692 return false; 693 } 694 695 public boolean onInit(Context context) { 696 try { 697 outputStream = operation.openOutputStream(); 698 if (phoneOwnVCard != null) { 699 return write(phoneOwnVCard); 700 } 701 return true; 702 } catch (IOException e) { 703 Log.e(TAG, "open outputstrem failed" + e.toString()); 704 } 705 return false; 706 } 707 708 public boolean onEntryCreated(String vcard) { 709 return write(vcard); 710 } 711 712 public void onTerminate() { 713 if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) { 714 if (V) Log.v(TAG, "CloseStream failed!"); 715 } else { 716 if (V) Log.v(TAG, "CloseStream ok!"); 717 } 718 } 719 } 720 721 public static class VCardFilter { 722 private static enum FilterBit { 723 // bit property onlyCheckV21 excludeForV21 724 FN ( 1, "FN", true, false), 725 PHOTO( 3, "PHOTO", false, false), 726 BDAY( 4, "BDAY", false, false), 727 ADR( 5, "ADR", false, false), 728 EMAIL( 8, "EMAIL", false, false), 729 TITLE( 12, "TITLE", false, false), 730 ORG( 16, "ORG", false, false), 731 NOTES( 17, "NOTES", false, false), 732 URL( 20, "URL", false, false), 733 NICKNAME( 23, "NICKNAME", false, true); 734 735 public final int pos; 736 public final String prop; 737 public final boolean onlyCheckV21; 738 public final boolean excludeForV21; 739 740 FilterBit(int pos, String prop, boolean onlyCheckV21, boolean excludeForV21) { 741 this.pos = pos; 742 this.prop = prop; 743 this.onlyCheckV21 = onlyCheckV21; 744 this.excludeForV21 = excludeForV21; 745 } 746 } 747 748 private static final String SEPARATOR = System.getProperty("line.separator"); 749 private final byte[] filter; 750 751 private boolean isFilteredOut(FilterBit bit, boolean vCardType21) { 752 final int offset = (bit.pos / 8) + 1; 753 final int bit_pos = bit.pos % 8; 754 if (!vCardType21 && bit.onlyCheckV21) return false; 755 if (vCardType21 && bit.excludeForV21) return true; 756 if (filter == null || offset >= filter.length) return false; 757 return ((filter[filter.length - offset] >> bit_pos) & 0x01) != 0; 758 } 759 760 VCardFilter(byte[] filter) { 761 this.filter = filter; 762 } 763 764 public boolean isPhotoEnabled() { 765 return !isFilteredOut(FilterBit.PHOTO, false); 766 } 767 768 public String apply(String vCard, boolean vCardType21){ 769 if (filter == null) return vCard; 770 String lines[] = vCard.split(SEPARATOR); 771 StringBuilder filteredVCard = new StringBuilder(); 772 boolean filteredOut = false; 773 774 for (String line : lines) { 775 // Check whether the current property is changing (ignoring multi-line properties) 776 // and determine if the current property is filtered in. 777 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) { 778 String currentProp = line.split("[;:]")[0]; 779 filteredOut = false; 780 781 for (FilterBit bit : FilterBit.values()) { 782 if (bit.prop.equals(currentProp)) { 783 filteredOut = isFilteredOut(bit, vCardType21); 784 break; 785 } 786 } 787 788 // Since PBAP does not have filter bits for IM and SIP, 789 // exclude them by default. Easiest way is to exclude all 790 // X- fields.... 791 if (currentProp.startsWith("X-")) filteredOut = true; 792 } 793 794 // Build filtered vCard 795 if (!filteredOut) filteredVCard.append(line + SEPARATOR); 796 } 797 798 return filteredVCard.toString(); 799 } 800 } 801 } 802