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.database.CursorWindowAllocationException; 40 import android.database.MatrixCursor; 41 import android.net.Uri; 42 import android.provider.CallLog; 43 import android.provider.CallLog.Calls; 44 import android.provider.ContactsContract.CommonDataKinds; 45 import android.provider.ContactsContract.CommonDataKinds.Phone; 46 import android.provider.ContactsContract.Contacts; 47 import android.provider.ContactsContract.Data; 48 import android.provider.ContactsContract.PhoneLookup; 49 import android.provider.ContactsContract.RawContactsEntity; 50 import android.telephony.PhoneNumberUtils; 51 import android.text.TextUtils; 52 import android.util.Log; 53 54 import com.android.bluetooth.R; 55 import com.android.bluetooth.util.DevicePolicyUtils; 56 import com.android.vcard.VCardComposer; 57 import com.android.vcard.VCardConfig; 58 import com.android.vcard.VCardPhoneNumberTranslationCallback; 59 60 import java.io.IOException; 61 import java.io.OutputStream; 62 import java.nio.ByteBuffer; 63 import java.util.ArrayList; 64 import java.util.Collections; 65 66 import javax.obex.Operation; 67 import javax.obex.ResponseCodes; 68 import javax.obex.ServerOperation; 69 70 public class BluetoothPbapVcardManager { 71 private static final String TAG = "BluetoothPbapVcardManager"; 72 73 private static final boolean V = BluetoothPbapService.VERBOSE; 74 75 private ContentResolver mResolver; 76 77 private Context mContext; 78 79 private static final int PHONE_NUMBER_COLUMN_INDEX = 3; 80 81 static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC"; 82 83 static final String[] PHONES_CONTACTS_PROJECTION = new String[]{ 84 Phone.CONTACT_ID, // 0 85 Phone.DISPLAY_NAME, // 1 86 }; 87 88 static final String[] PHONE_LOOKUP_PROJECTION = new String[]{ 89 PhoneLookup._ID, PhoneLookup.DISPLAY_NAME 90 }; 91 92 static final int CONTACTS_ID_COLUMN_INDEX = 0; 93 94 static final int CONTACTS_NAME_COLUMN_INDEX = 1; 95 96 static long sLastFetchedTimeStamp; 97 98 // call histories use dynamic handles, and handles should order by date; the 99 // most recently one should be the first handle. In table "calls", _id and 100 // date are consistent in ordering, to implement simply, we sort by _id 101 // here. 102 static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC"; 103 104 private static final int NEED_SEND_BODY = -1; 105 BluetoothPbapVcardManager(final Context context)106 public BluetoothPbapVcardManager(final Context context) { 107 mContext = context; 108 mResolver = mContext.getContentResolver(); 109 sLastFetchedTimeStamp = System.currentTimeMillis(); 110 } 111 112 /** 113 * Create an owner vcard from the configured profile 114 * @param vcardType21 115 * @return 116 */ getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, final byte[] filter)117 private String getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, 118 final byte[] filter) { 119 // Currently only support Generic Vcard 2.1 and 3.0 120 int vcardType; 121 if (vcardType21) { 122 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 123 } else { 124 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 125 } 126 127 if (!BluetoothPbapConfig.includePhotosInVcard()) { 128 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT; 129 } 130 131 return BluetoothPbapUtils.createProfileVCard(mContext, vcardType, filter); 132 } 133 getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter)134 public final String getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter) { 135 //Owner vCard enhancement: Use "ME" profile if configured 136 if (BluetoothPbapConfig.useProfileForOwnerVcard()) { 137 String vcard = getOwnerPhoneNumberVcardFromProfile(vcardType21, filter); 138 if (vcard != null && vcard.length() != 0) { 139 return vcard; 140 } 141 } 142 //End enhancement 143 144 BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext); 145 String name = BluetoothPbapService.getLocalPhoneName(); 146 String number = BluetoothPbapService.getLocalPhoneNum(); 147 String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number, 148 vcardType21); 149 return vcard; 150 } 151 getPhonebookSize(final int type)152 public final int getPhonebookSize(final int type) { 153 int size; 154 switch (type) { 155 case BluetoothPbapObexServer.ContentType.PHONEBOOK: 156 size = getContactsSize(); 157 break; 158 default: 159 size = getCallHistorySize(type); 160 break; 161 } 162 if (V) { 163 Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type); 164 } 165 return size; 166 } 167 getContactsSize()168 public final int getContactsSize() { 169 final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 170 Cursor contactCursor = null; 171 try { 172 contactCursor = mResolver.query(myUri, new String[]{Phone.CONTACT_ID}, null, null, 173 Phone.CONTACT_ID); 174 if (contactCursor == null) { 175 return 0; 176 } 177 return getDistinctContactIdSize(contactCursor) + 1; // always has the 0.vcf 178 } catch (CursorWindowAllocationException e) { 179 Log.e(TAG, "CursorWindowAllocationException while getting Contacts size"); 180 } finally { 181 if (contactCursor != null) { 182 contactCursor.close(); 183 } 184 } 185 return 0; 186 } 187 getCallHistorySize(final int type)188 public final int getCallHistorySize(final int type) { 189 final Uri myUri = CallLog.Calls.CONTENT_URI; 190 String selection = BluetoothPbapObexServer.createSelectionPara(type); 191 int size = 0; 192 Cursor callCursor = null; 193 try { 194 callCursor = 195 mResolver.query(myUri, null, selection, null, CallLog.Calls.DEFAULT_SORT_ORDER); 196 if (callCursor != null) { 197 size = callCursor.getCount(); 198 } 199 } catch (CursorWindowAllocationException e) { 200 Log.e(TAG, "CursorWindowAllocationException while getting CallHistory size"); 201 } finally { 202 if (callCursor != null) { 203 callCursor.close(); 204 callCursor = null; 205 } 206 } 207 return size; 208 } 209 210 private static final int CALLS_NUMBER_COLUMN_INDEX = 0; 211 private static final int CALLS_NAME_COLUMN_INDEX = 1; 212 private static final int CALLS_NUMBER_PRESENTATION_COLUMN_INDEX = 2; 213 loadCallHistoryList(final int type)214 public final ArrayList<String> loadCallHistoryList(final int type) { 215 final Uri myUri = CallLog.Calls.CONTENT_URI; 216 String selection = BluetoothPbapObexServer.createSelectionPara(type); 217 String[] projection = new String[]{ 218 Calls.NUMBER, Calls.CACHED_NAME, Calls.NUMBER_PRESENTATION 219 }; 220 221 222 Cursor callCursor = null; 223 ArrayList<String> list = new ArrayList<String>(); 224 try { 225 callCursor = mResolver.query(myUri, projection, selection, null, CALLLOG_SORT_ORDER); 226 if (callCursor != null) { 227 for (callCursor.moveToFirst(); !callCursor.isAfterLast(); callCursor.moveToNext()) { 228 String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX); 229 if (TextUtils.isEmpty(name)) { 230 // name not found, use number instead 231 final int numberPresentation = 232 callCursor.getInt(CALLS_NUMBER_PRESENTATION_COLUMN_INDEX); 233 if (numberPresentation != Calls.PRESENTATION_ALLOWED) { 234 name = mContext.getString(R.string.unknownNumber); 235 } else { 236 name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX); 237 } 238 } 239 list.add(name); 240 } 241 } 242 } catch (CursorWindowAllocationException e) { 243 Log.e(TAG, "CursorWindowAllocationException while loading CallHistory"); 244 } finally { 245 if (callCursor != null) { 246 callCursor.close(); 247 callCursor = null; 248 } 249 } 250 return list; 251 } 252 getPhonebookNameList(final int orderByWhat)253 public final ArrayList<String> getPhonebookNameList(final int orderByWhat) { 254 ArrayList<String> nameList = new ArrayList<String>(); 255 //Owner vCard enhancement. Use "ME" profile if configured 256 String ownerName = null; 257 if (BluetoothPbapConfig.useProfileForOwnerVcard()) { 258 ownerName = BluetoothPbapUtils.getProfileName(mContext); 259 } 260 if (ownerName == null || ownerName.length() == 0) { 261 ownerName = BluetoothPbapService.getLocalPhoneName(); 262 } 263 nameList.add(ownerName); 264 //End enhancement 265 266 final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 267 Cursor contactCursor = null; 268 // By default order is indexed 269 String orderBy = Phone.CONTACT_ID; 270 try { 271 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) { 272 orderBy = Phone.DISPLAY_NAME; 273 } 274 contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, orderBy); 275 if (contactCursor != null) { 276 appendDistinctNameIdList(nameList, mContext.getString(android.R.string.unknownName), 277 contactCursor); 278 } 279 } catch (CursorWindowAllocationException e) { 280 Log.e(TAG, "CursorWindowAllocationException while getting phonebook name list"); 281 } catch (Exception e) { 282 Log.e(TAG, "Exception while getting phonebook name list", e); 283 } finally { 284 if (contactCursor != null) { 285 contactCursor.close(); 286 contactCursor = null; 287 } 288 } 289 return nameList; 290 } 291 getSelectedPhonebookNameList(final int orderByWhat, final boolean vcardType21, int needSendBody, int pbSize, byte[] selector, String vcardselectorop)292 final ArrayList<String> getSelectedPhonebookNameList(final int orderByWhat, 293 final boolean vcardType21, int needSendBody, int pbSize, byte[] selector, 294 String vcardselectorop) { 295 ArrayList<String> nameList = new ArrayList<String>(); 296 PropertySelector vcardselector = new PropertySelector(selector); 297 VCardComposer composer = null; 298 int vcardType; 299 300 if (vcardType21) { 301 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 302 } else { 303 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 304 } 305 306 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType, null); 307 composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() { 308 309 @Override 310 public String onValueReceived(String rawValue, int type, String label, 311 boolean isPrimary) { 312 String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p') 313 .replace(PhoneNumberUtils.WAIT, 'w'); 314 return numberWithControlSequence; 315 } 316 }); 317 318 // Owner vCard enhancement. Use "ME" profile if configured 319 String ownerName = null; 320 if (BluetoothPbapConfig.useProfileForOwnerVcard()) { 321 ownerName = BluetoothPbapUtils.getProfileName(mContext); 322 } 323 if (ownerName == null || ownerName.length() == 0) { 324 ownerName = BluetoothPbapService.getLocalPhoneName(); 325 } 326 nameList.add(ownerName); 327 // End enhancement 328 329 final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 330 Cursor contactCursor = null; 331 try { 332 contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, 333 Phone.CONTACT_ID); 334 335 ArrayList<String> contactNameIdList = new ArrayList<String>(); 336 appendDistinctNameIdList(contactNameIdList, 337 mContext.getString(android.R.string.unknownName), contactCursor); 338 339 if (contactCursor != null) { 340 if (!composer.initWithCallback(contactCursor, 341 new EnterpriseRawContactEntitlesInfoCallback())) { 342 return nameList; 343 } 344 345 int i = 0; 346 contactCursor.moveToFirst(); 347 while (!composer.isAfterLast()) { 348 String vcard = composer.createOneEntry(); 349 if (vcard == null) { 350 Log.e(TAG, "Failed to read a contact. Error reason: " 351 + composer.getErrorReason()); 352 return nameList; 353 } else if (vcard.isEmpty()) { 354 Log.i(TAG, "Contact may have been deleted during operation"); 355 continue; 356 } 357 if (V) { 358 Log.v(TAG, "Checking selected bits in the vcard composer" + vcard); 359 } 360 361 if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) { 362 Log.e(TAG, "vcard selector check fail"); 363 vcard = null; 364 pbSize--; 365 continue; 366 } else { 367 String name = vcardselector.getName(vcard); 368 if (TextUtils.isEmpty(name)) { 369 name = mContext.getString(android.R.string.unknownName); 370 } 371 nameList.add(contactNameIdList.get(i)); 372 } 373 i++; 374 } 375 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) { 376 if (V) { 377 Log.v(TAG, "getPhonebookNameList, order by index"); 378 } 379 // Do not need to do anything, as we sort it by index already 380 } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) { 381 if (V) { 382 Log.v(TAG, "getPhonebookNameList, order by alpha"); 383 } 384 Collections.sort(nameList); 385 } 386 } 387 } catch (CursorWindowAllocationException e) { 388 Log.e(TAG, "CursorWindowAllocationException while getting Phonebook name list"); 389 } finally { 390 if (contactCursor != null) { 391 contactCursor.close(); 392 contactCursor = null; 393 } 394 } 395 return nameList; 396 } 397 getContactNamesByNumber(final String phoneNumber)398 public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) { 399 ArrayList<String> nameList = new ArrayList<String>(); 400 ArrayList<String> tempNameList = new ArrayList<String>(); 401 402 Cursor contactCursor = null; 403 Uri uri = null; 404 String[] projection = null; 405 406 if (TextUtils.isEmpty(phoneNumber)) { 407 uri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 408 projection = PHONES_CONTACTS_PROJECTION; 409 } else { 410 uri = Uri.withAppendedPath(getPhoneLookupFilterUri(), Uri.encode(phoneNumber)); 411 projection = PHONE_LOOKUP_PROJECTION; 412 } 413 414 try { 415 contactCursor = mResolver.query(uri, projection, null, null, Phone.CONTACT_ID); 416 417 if (contactCursor != null) { 418 appendDistinctNameIdList(nameList, mContext.getString(android.R.string.unknownName), 419 contactCursor); 420 if (V) { 421 for (String nameIdStr : nameList) { 422 Log.v(TAG, "got name " + nameIdStr + " by number " + phoneNumber); 423 } 424 } 425 } 426 } catch (CursorWindowAllocationException e) { 427 Log.e(TAG, "CursorWindowAllocationException while getting contact names"); 428 } finally { 429 if (contactCursor != null) { 430 contactCursor.close(); 431 contactCursor = null; 432 } 433 } 434 int tempListSize = tempNameList.size(); 435 for (int index = 0; index < tempListSize; index++) { 436 String object = tempNameList.get(index); 437 if (!nameList.contains(object)) { 438 nameList.add(object); 439 } 440 } 441 442 return nameList; 443 } 444 getCallHistoryPrimaryFolderVersion(final int type)445 byte[] getCallHistoryPrimaryFolderVersion(final int type) { 446 final Uri myUri = CallLog.Calls.CONTENT_URI; 447 String selection = BluetoothPbapObexServer.createSelectionPara(type); 448 selection = selection + " AND date >= " + sLastFetchedTimeStamp; 449 450 Log.d(TAG, "LAST_FETCHED_TIME_STAMP is " + sLastFetchedTimeStamp); 451 Cursor callCursor = null; 452 long count = 0; 453 long primaryVcMsb = 0; 454 ArrayList<String> list = new ArrayList<String>(); 455 try { 456 callCursor = mResolver.query(myUri, null, selection, null, null); 457 while (callCursor != null && callCursor.moveToNext()) { 458 count = count + 1; 459 } 460 } catch (Exception e) { 461 Log.e(TAG, "exception while fetching callHistory pvc"); 462 } finally { 463 if (callCursor != null) { 464 callCursor.close(); 465 callCursor = null; 466 } 467 } 468 469 sLastFetchedTimeStamp = System.currentTimeMillis(); 470 Log.d(TAG, "getCallHistoryPrimaryFolderVersion count is " + count + " type is " + type); 471 ByteBuffer pvc = ByteBuffer.allocate(16); 472 pvc.putLong(primaryVcMsb); 473 Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter); 474 pvc.putLong(count); 475 return pvc.array(); 476 } 477 478 private static final String[] CALLLOG_PROJECTION = new String[]{ 479 CallLog.Calls._ID, // 0 480 }; 481 private static final int ID_COLUMN_INDEX = 0; 482 composeAndSendSelectedCallLogVcards(final int type, Operation op, final int startPoint, final int endPoint, final boolean vcardType21, int needSendBody, int pbSize, boolean ignorefilter, byte[] filter, byte[] vcardselector, String vcardselectorop, boolean vcardselect)483 final int composeAndSendSelectedCallLogVcards(final int type, Operation op, 484 final int startPoint, final int endPoint, final boolean vcardType21, int needSendBody, 485 int pbSize, boolean ignorefilter, byte[] filter, byte[] vcardselector, 486 String vcardselectorop, boolean vcardselect) { 487 if (startPoint < 1 || startPoint > endPoint) { 488 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 489 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 490 } 491 String typeSelection = BluetoothPbapObexServer.createSelectionPara(type); 492 493 final Uri myUri = CallLog.Calls.CONTENT_URI; 494 Cursor callsCursor = null; 495 long startPointId = 0; 496 long endPointId = 0; 497 try { 498 // Need test to see if order by _ID is ok here, or by date? 499 callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null, 500 CALLLOG_SORT_ORDER); 501 if (callsCursor != null) { 502 callsCursor.moveToPosition(startPoint - 1); 503 startPointId = callsCursor.getLong(ID_COLUMN_INDEX); 504 if (V) { 505 Log.v(TAG, "Call Log query startPointId = " + startPointId); 506 } 507 if (startPoint == endPoint) { 508 endPointId = startPointId; 509 } else { 510 callsCursor.moveToPosition(endPoint - 1); 511 endPointId = callsCursor.getLong(ID_COLUMN_INDEX); 512 } 513 if (V) { 514 Log.v(TAG, "Call log query endPointId = " + endPointId); 515 } 516 } 517 } catch (CursorWindowAllocationException e) { 518 Log.e(TAG, "CursorWindowAllocationException while composing calllog vcards"); 519 } finally { 520 if (callsCursor != null) { 521 callsCursor.close(); 522 callsCursor = null; 523 } 524 } 525 526 String recordSelection; 527 if (startPoint == endPoint) { 528 recordSelection = Calls._ID + "=" + startPointId; 529 } else { 530 // The query to call table is by "_id DESC" order, so change 531 // correspondingly. 532 recordSelection = 533 Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<=" + startPointId; 534 } 535 536 String selection; 537 if (typeSelection == null) { 538 selection = recordSelection; 539 } else { 540 selection = "(" + typeSelection + ") AND (" + recordSelection + ")"; 541 } 542 543 if (V) { 544 Log.v(TAG, "Call log query selection is: " + selection); 545 } 546 547 return composeCallLogsAndSendSelectedVCards(op, selection, vcardType21, needSendBody, 548 pbSize, null, ignorefilter, filter, vcardselector, vcardselectorop, vcardselect); 549 } 550 composeAndSendPhonebookVcards(Operation op, final int startPoint, final int endPoint, final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, boolean ignorefilter, byte[] filter, byte[] vcardselector, String vcardselectorop, boolean vcardselect)551 final int composeAndSendPhonebookVcards(Operation op, final int startPoint, final int endPoint, 552 final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, 553 boolean ignorefilter, byte[] filter, byte[] vcardselector, String vcardselectorop, 554 boolean vcardselect) { 555 if (startPoint < 1 || startPoint > endPoint) { 556 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 557 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 558 } 559 560 final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 561 Cursor contactCursor = null; 562 Cursor contactIdCursor = new MatrixCursor(new String[]{ 563 Phone.CONTACT_ID 564 }); 565 try { 566 contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, 567 Phone.CONTACT_ID); 568 if (contactCursor != null) { 569 contactIdCursor = 570 ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint); 571 } 572 } catch (CursorWindowAllocationException e) { 573 Log.e(TAG, "CursorWindowAllocationException while composing phonebook vcards"); 574 } finally { 575 if (contactCursor != null) { 576 contactCursor.close(); 577 } 578 } 579 580 if (vcardselect) { 581 return composeContactsAndSendSelectedVCards(op, contactIdCursor, vcardType21, 582 ownerVCard, needSendBody, pbSize, ignorefilter, filter, vcardselector, 583 vcardselectorop); 584 } else { 585 return composeContactsAndSendVCards(op, contactIdCursor, vcardType21, ownerVCard, 586 ignorefilter, filter); 587 } 588 } 589 composeAndSendPhonebookOneVcard(Operation op, final int offset, final boolean vcardType21, String ownerVCard, int orderByWhat, boolean ignorefilter, byte[] filter)590 final int composeAndSendPhonebookOneVcard(Operation op, final int offset, 591 final boolean vcardType21, String ownerVCard, int orderByWhat, boolean ignorefilter, 592 byte[] filter) { 593 if (offset < 1) { 594 Log.e(TAG, "Internal error: offset is not correct."); 595 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 596 } 597 final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 598 599 Cursor contactCursor = null; 600 Cursor contactIdCursor = new MatrixCursor(new String[]{ 601 Phone.CONTACT_ID 602 }); 603 // By default order is indexed 604 String orderBy = Phone.CONTACT_ID; 605 try { 606 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) { 607 orderBy = Phone.DISPLAY_NAME; 608 } 609 contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, orderBy); 610 } catch (CursorWindowAllocationException e) { 611 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard"); 612 } finally { 613 if (contactCursor != null) { 614 contactIdCursor = ContactCursorFilter.filterByOffset(contactCursor, offset); 615 contactCursor.close(); 616 contactCursor = null; 617 } 618 } 619 return composeContactsAndSendVCards(op, contactIdCursor, vcardType21, ownerVCard, 620 ignorefilter, filter); 621 } 622 623 /** 624 * Filter contact cursor by certain condition. 625 */ 626 private static final class ContactCursorFilter { 627 /** 628 * 629 * @param contactCursor 630 * @param offset 631 * @return a cursor containing contact id of {@code offset} contact. 632 */ filterByOffset(Cursor contactCursor, int offset)633 public static Cursor filterByOffset(Cursor contactCursor, int offset) { 634 return filterByRange(contactCursor, offset, offset); 635 } 636 637 /** 638 * 639 * @param contactCursor 640 * @param startPoint 641 * @param endPoint 642 * @return a cursor containing contact ids of {@code startPoint}th to {@code endPoint}th 643 * contact. 644 */ filterByRange(Cursor contactCursor, int startPoint, int endPoint)645 public static Cursor filterByRange(Cursor contactCursor, int startPoint, int endPoint) { 646 final int contactIdColumn = contactCursor.getColumnIndex(Data.CONTACT_ID); 647 long previousContactId = -1; 648 // As startPoint, endOffset index starts from 1 to n, we set 649 // currentPoint base as 1 not 0 650 int currentOffset = 1; 651 final MatrixCursor contactIdsCursor = new MatrixCursor(new String[]{ 652 Phone.CONTACT_ID 653 }); 654 while (contactCursor.moveToNext() && currentOffset <= endPoint) { 655 long currentContactId = contactCursor.getLong(contactIdColumn); 656 if (previousContactId != currentContactId) { 657 previousContactId = currentContactId; 658 if (currentOffset >= startPoint) { 659 contactIdsCursor.addRow(new Long[]{currentContactId}); 660 if (V) { 661 Log.v(TAG, "contactIdsCursor.addRow: " + currentContactId); 662 } 663 } 664 currentOffset++; 665 } 666 } 667 return contactIdsCursor; 668 } 669 } 670 671 /** 672 * Handler enterprise contact id in VCardComposer 673 */ 674 private static class EnterpriseRawContactEntitlesInfoCallback 675 implements VCardComposer.RawContactEntitlesInfoCallback { 676 @Override getRawContactEntitlesInfo(long contactId)677 public VCardComposer.RawContactEntitlesInfo getRawContactEntitlesInfo(long contactId) { 678 if (Contacts.isEnterpriseContactId(contactId)) { 679 return new VCardComposer.RawContactEntitlesInfo(RawContactsEntity.CORP_CONTENT_URI, 680 contactId - Contacts.ENTERPRISE_CONTACT_ID_BASE); 681 } else { 682 return new VCardComposer.RawContactEntitlesInfo(RawContactsEntity.CONTENT_URI, 683 contactId); 684 } 685 } 686 } 687 composeContactsAndSendVCards(Operation op, final Cursor contactIdCursor, final boolean vcardType21, String ownerVCard, boolean ignorefilter, byte[] filter)688 private int composeContactsAndSendVCards(Operation op, final Cursor contactIdCursor, 689 final boolean vcardType21, String ownerVCard, boolean ignorefilter, byte[] filter) { 690 long timestamp = 0; 691 if (V) { 692 timestamp = System.currentTimeMillis(); 693 } 694 695 VCardComposer composer = null; 696 VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter); 697 698 HandlerForStringBuffer buffer = null; 699 try { 700 // Currently only support Generic Vcard 2.1 and 3.0 701 int vcardType; 702 if (vcardType21) { 703 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 704 vcardType |= VCardConfig.FLAG_CONVERT_PHONETIC_NAME_STRINGS; 705 vcardType |= VCardConfig.FLAG_REFRAIN_QP_TO_NAME_PROPERTIES; 706 } else { 707 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 708 } 709 if (!vcardfilter.isPhotoEnabled()) { 710 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT; 711 } 712 713 // Enhancement: customize Vcard based on preferences/settings and 714 // input from caller 715 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType, null); 716 // End enhancement 717 718 // BT does want PAUSE/WAIT conversion while it doesn't want the 719 // other formatting 720 // done by vCard library by default. 721 composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() { 722 @Override 723 public String onValueReceived(String rawValue, int type, String label, 724 boolean isPrimary) { 725 // 'p' and 'w' are the standard characters for pause and 726 // wait 727 // (see RFC 3601) 728 // so use those when exporting phone numbers via vCard. 729 String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p') 730 .replace(PhoneNumberUtils.WAIT, 'w'); 731 return numberWithControlSequence; 732 } 733 }); 734 buffer = new HandlerForStringBuffer(op, ownerVCard); 735 Log.v(TAG, "contactIdCursor size: " + contactIdCursor.getCount()); 736 if (!composer.initWithCallback(contactIdCursor, 737 new EnterpriseRawContactEntitlesInfoCallback()) || !buffer.onInit(mContext)) { 738 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 739 } 740 741 while (!composer.isAfterLast()) { 742 if (BluetoothPbapObexServer.sIsAborted) { 743 ((ServerOperation) op).isAborted = true; 744 BluetoothPbapObexServer.sIsAborted = false; 745 break; 746 } 747 String vcard = composer.createOneEntry(); 748 if (vcard == null) { 749 Log.e(TAG, 750 "Failed to read a contact. Error reason: " + composer.getErrorReason()); 751 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 752 } else if (vcard.isEmpty()) { 753 Log.i(TAG, "Contact may have been deleted during operation"); 754 continue; 755 } 756 if (V) { 757 Log.v(TAG, "vCard from composer: " + vcard); 758 } 759 760 vcard = vcardfilter.apply(vcard, vcardType21); 761 vcard = stripTelephoneNumber(vcard); 762 763 if (V) { 764 Log.v(TAG, "vCard after cleanup: " + vcard); 765 } 766 767 if (!buffer.onEntryCreated(vcard)) { 768 // onEntryCreate() already emits error. 769 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 770 } 771 } 772 } finally { 773 if (composer != null) { 774 composer.terminate(); 775 } 776 if (buffer != null) { 777 buffer.onTerminate(); 778 } 779 } 780 781 if (V) { 782 Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis() 783 - timestamp) + " ms"); 784 } 785 786 return ResponseCodes.OBEX_HTTP_OK; 787 } 788 composeContactsAndSendSelectedVCards(Operation op, final Cursor contactIdCursor, final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop)789 private int composeContactsAndSendSelectedVCards(Operation op, final Cursor contactIdCursor, 790 final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, 791 boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop) { 792 long timestamp = 0; 793 if (V) { 794 timestamp = System.currentTimeMillis(); 795 } 796 797 VCardComposer composer = null; 798 VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter); 799 PropertySelector vcardselector = new PropertySelector(selector); 800 801 HandlerForStringBuffer buffer = null; 802 803 try { 804 // Currently only support Generic Vcard 2.1 and 3.0 805 int vcardType; 806 if (vcardType21) { 807 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 808 } else { 809 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 810 } 811 if (!vcardfilter.isPhotoEnabled()) { 812 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT; 813 } 814 815 // Enhancement: customize Vcard based on preferences/settings and 816 // input from caller 817 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType, null); 818 // End enhancement 819 820 /* BT does want PAUSE/WAIT conversion while it doesn't want the 821 * other formatting done by vCard library by default. */ 822 composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() { 823 @Override 824 public String onValueReceived(String rawValue, int type, String label, 825 boolean isPrimary) { 826 /* 'p' and 'w' are the standard characters for pause and wait 827 * (see RFC 3601) so use those when exporting phone numbers via vCard.*/ 828 String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p') 829 .replace(PhoneNumberUtils.WAIT, 'w'); 830 return numberWithControlSequence; 831 } 832 }); 833 buffer = new HandlerForStringBuffer(op, ownerVCard); 834 Log.v(TAG, "contactIdCursor size: " + contactIdCursor.getCount()); 835 if (!composer.initWithCallback(contactIdCursor, 836 new EnterpriseRawContactEntitlesInfoCallback()) || !buffer.onInit(mContext)) { 837 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 838 } 839 840 while (!composer.isAfterLast()) { 841 if (BluetoothPbapObexServer.sIsAborted) { 842 ((ServerOperation) op).isAborted = true; 843 BluetoothPbapObexServer.sIsAborted = false; 844 break; 845 } 846 String vcard = composer.createOneEntry(); 847 if (vcard == null) { 848 Log.e(TAG, 849 "Failed to read a contact. Error reason: " + composer.getErrorReason()); 850 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 851 } else if (vcard.isEmpty()) { 852 Log.i(TAG, "Contact may have been deleted during operation"); 853 continue; 854 } 855 if (V) { 856 Log.v(TAG, "Checking selected bits in the vcard composer" + vcard); 857 } 858 859 if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) { 860 Log.e(TAG, "vcard selector check fail"); 861 vcard = null; 862 pbSize--; 863 continue; 864 } 865 866 Log.e(TAG, "vcard selector check pass"); 867 868 if (needSendBody == NEED_SEND_BODY) { 869 vcard = vcardfilter.apply(vcard, vcardType21); 870 vcard = stripTelephoneNumber(vcard); 871 872 if (V) { 873 Log.v(TAG, "vCard after cleanup: " + vcard); 874 } 875 876 if (!buffer.onEntryCreated(vcard)) { 877 // onEntryCreate() already emits error. 878 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 879 } 880 } 881 } 882 883 if (needSendBody != NEED_SEND_BODY) { 884 return pbSize; 885 } 886 } finally { 887 if (composer != null) { 888 composer.terminate(); 889 } 890 if (buffer != null) { 891 buffer.onTerminate(); 892 } 893 } 894 895 if (V) { 896 Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis() 897 - timestamp) + " ms"); 898 } 899 900 return ResponseCodes.OBEX_HTTP_OK; 901 } 902 composeCallLogsAndSendSelectedVCards(Operation op, final String selection, final boolean vcardType21, int needSendBody, int pbSize, String ownerVCard, boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop, boolean vCardSelct)903 private int composeCallLogsAndSendSelectedVCards(Operation op, final String selection, 904 final boolean vcardType21, int needSendBody, int pbSize, String ownerVCard, 905 boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop, 906 boolean vCardSelct) { 907 long timestamp = 0; 908 if (V) { 909 timestamp = System.currentTimeMillis(); 910 } 911 912 BluetoothPbapCallLogComposer composer = null; 913 HandlerForStringBuffer buffer = null; 914 915 try { 916 VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter); 917 PropertySelector vcardselector = new PropertySelector(selector); 918 composer = new BluetoothPbapCallLogComposer(mContext); 919 buffer = new HandlerForStringBuffer(op, ownerVCard); 920 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null, CALLLOG_SORT_ORDER) 921 || !buffer.onInit(mContext)) { 922 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 923 } 924 925 while (!composer.isAfterLast()) { 926 if (BluetoothPbapObexServer.sIsAborted) { 927 ((ServerOperation) op).isAborted = true; 928 BluetoothPbapObexServer.sIsAborted = false; 929 break; 930 } 931 String vcard = composer.createOneEntry(vcardType21); 932 if (vCardSelct) { 933 if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) { 934 Log.e(TAG, "Checking vcard selector for call log"); 935 vcard = null; 936 pbSize--; 937 continue; 938 } 939 if (needSendBody == NEED_SEND_BODY) { 940 if (vcard == null) { 941 Log.e(TAG, "Failed to read a contact. Error reason: " 942 + composer.getErrorReason()); 943 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 944 } else if (vcard.isEmpty()) { 945 Log.i(TAG, "Call Log may have been deleted during operation"); 946 continue; 947 } 948 vcard = vcardfilter.apply(vcard, vcardType21); 949 950 if (V) { 951 Log.v(TAG, "Vcard Entry:"); 952 Log.v(TAG, vcard); 953 } 954 buffer.onEntryCreated(vcard); 955 } 956 } else { 957 if (vcard == null) { 958 Log.e(TAG, "Failed to read a contact. Error reason: " 959 + composer.getErrorReason()); 960 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 961 } 962 if (V) { 963 Log.v(TAG, "Vcard Entry:"); 964 Log.v(TAG, vcard); 965 } 966 buffer.onEntryCreated(vcard); 967 } 968 } 969 if (needSendBody != NEED_SEND_BODY && vCardSelct) { 970 return pbSize; 971 } 972 } finally { 973 if (composer != null) { 974 composer.terminate(); 975 } 976 if (buffer != null) { 977 buffer.onTerminate(); 978 } 979 } 980 981 if (V) { 982 Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis() 983 - timestamp) + " ms"); 984 } 985 return ResponseCodes.OBEX_HTTP_OK; 986 } 987 stripTelephoneNumber(String vCard)988 public String stripTelephoneNumber(String vCard) { 989 String[] attr = vCard.split(System.getProperty("line.separator")); 990 String stripedVCard = ""; 991 for (int i = 0; i < attr.length; i++) { 992 if (attr[i].startsWith("TEL")) { 993 String[] vTagAndTel = attr[i].split(":", 2); 994 int telLenBefore = vTagAndTel[1].length(); 995 // Remove '-', '(', ')' or ' ' from TEL number 996 vTagAndTel[1] = vTagAndTel[1].replace("-", "") 997 .replace("(", "") 998 .replace(")", "") 999 .replace(" ", ""); 1000 if (vTagAndTel[1].length() < telLenBefore) { 1001 if (V) { 1002 Log.v(TAG, "Fixing vCard TEL to " + vTagAndTel[1]); 1003 } 1004 attr[i] = new StringBuilder().append(vTagAndTel[0]).append(":") 1005 .append(vTagAndTel[1]).toString(); 1006 } 1007 } 1008 } 1009 1010 for (int i = 0; i < attr.length; i++) { 1011 if (!attr[i].isEmpty()) { 1012 stripedVCard = stripedVCard.concat(attr[i] + "\n"); 1013 } 1014 } 1015 if (V) { 1016 Log.v(TAG, "vCard with stripped telephone no.: " + stripedVCard); 1017 } 1018 return stripedVCard; 1019 } 1020 1021 /** 1022 * Handler to emit vCards to PCE. 1023 */ 1024 public class HandlerForStringBuffer { 1025 private Operation mOperation; 1026 1027 private OutputStream mOutputStream; 1028 1029 private String mPhoneOwnVCard = null; 1030 HandlerForStringBuffer(Operation op, String ownerVCard)1031 public HandlerForStringBuffer(Operation op, String ownerVCard) { 1032 mOperation = op; 1033 if (ownerVCard != null) { 1034 mPhoneOwnVCard = ownerVCard; 1035 if (V) { 1036 Log.v(TAG, "phone own number vcard:"); 1037 } 1038 if (V) { 1039 Log.v(TAG, mPhoneOwnVCard); 1040 } 1041 } 1042 } 1043 write(String vCard)1044 private boolean write(String vCard) { 1045 try { 1046 if (vCard != null) { 1047 mOutputStream.write(vCard.getBytes()); 1048 return true; 1049 } 1050 } catch (IOException e) { 1051 Log.e(TAG, "write outputstrem failed" + e.toString()); 1052 } 1053 return false; 1054 } 1055 onInit(Context context)1056 public boolean onInit(Context context) { 1057 try { 1058 mOutputStream = mOperation.openOutputStream(); 1059 if (mPhoneOwnVCard != null) { 1060 return write(mPhoneOwnVCard); 1061 } 1062 return true; 1063 } catch (IOException e) { 1064 Log.e(TAG, "open outputstrem failed" + e.toString()); 1065 } 1066 return false; 1067 } 1068 onEntryCreated(String vcard)1069 public boolean onEntryCreated(String vcard) { 1070 return write(vcard); 1071 } 1072 onTerminate()1073 public void onTerminate() { 1074 if (!BluetoothPbapObexServer.closeStream(mOutputStream, mOperation)) { 1075 if (V) { 1076 Log.v(TAG, "CloseStream failed!"); 1077 } 1078 } else { 1079 if (V) { 1080 Log.v(TAG, "CloseStream ok!"); 1081 } 1082 } 1083 } 1084 } 1085 1086 public static class VCardFilter { 1087 private enum FilterBit { 1088 // bit property onlyCheckV21 excludeForV21 1089 FN(1, "FN", true, false), 1090 PHOTO(3, "PHOTO", false, false), 1091 BDAY(4, "BDAY", false, false), 1092 ADR(5, "ADR", false, false), 1093 EMAIL(8, "EMAIL", false, false), 1094 TITLE(12, "TITLE", false, false), 1095 ORG(16, "ORG", false, false), 1096 NOTE(17, "NOTE", false, false), 1097 SOUND(19, "SOUND", false, false), 1098 URL(20, "URL", false, false), 1099 NICKNAME(23, "NICKNAME", false, true), 1100 DATETIME(28, "X-IRMC-CALL-DATETIME", false, false); 1101 1102 public final int pos; 1103 public final String prop; 1104 public final boolean onlyCheckV21; 1105 public final boolean excludeForV21; 1106 FilterBit(int pos, String prop, boolean onlyCheckV21, boolean excludeForV21)1107 FilterBit(int pos, String prop, boolean onlyCheckV21, boolean excludeForV21) { 1108 this.pos = pos; 1109 this.prop = prop; 1110 this.onlyCheckV21 = onlyCheckV21; 1111 this.excludeForV21 = excludeForV21; 1112 } 1113 } 1114 1115 private static final String SEPARATOR = System.getProperty("line.separator"); 1116 private final byte[] mFilter; 1117 1118 //This function returns true if the attributes needs to be included in the filtered vcard. isFilteredIn(FilterBit bit, boolean vCardType21)1119 private boolean isFilteredIn(FilterBit bit, boolean vCardType21) { 1120 final int offset = (bit.pos / 8) + 1; 1121 final int bitPos = bit.pos % 8; 1122 if (!vCardType21 && bit.onlyCheckV21) { 1123 return true; 1124 } 1125 if (vCardType21 && bit.excludeForV21) { 1126 return false; 1127 } 1128 if (mFilter == null || offset >= mFilter.length) { 1129 return true; 1130 } 1131 return ((mFilter[mFilter.length - offset] >> bitPos) & 0x01) != 0; 1132 } 1133 VCardFilter(byte[] filter)1134 VCardFilter(byte[] filter) { 1135 this.mFilter = filter; 1136 } 1137 isPhotoEnabled()1138 public boolean isPhotoEnabled() { 1139 return isFilteredIn(FilterBit.PHOTO, false); 1140 } 1141 apply(String vCard, boolean vCardType21)1142 public String apply(String vCard, boolean vCardType21) { 1143 if (mFilter == null) { 1144 return vCard; 1145 } 1146 String[] lines = vCard.split(SEPARATOR); 1147 StringBuilder filteredVCard = new StringBuilder(); 1148 boolean filteredIn = false; 1149 1150 for (String line : lines) { 1151 // Check whether the current property is changing (ignoring multi-line properties) 1152 // and determine if the current property is filtered in. 1153 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) { 1154 String currentProp = line.split("[;:]")[0]; 1155 filteredIn = true; 1156 1157 for (FilterBit bit : FilterBit.values()) { 1158 if (bit.prop.equals(currentProp)) { 1159 filteredIn = isFilteredIn(bit, vCardType21); 1160 break; 1161 } 1162 } 1163 1164 // Since PBAP does not have filter bits for IM and SIP, 1165 // exclude them by default. Easiest way is to exclude all 1166 // X- fields, except date time.... 1167 if (currentProp.startsWith("X-")) { 1168 filteredIn = false; 1169 if (currentProp.equals("X-IRMC-CALL-DATETIME")) { 1170 filteredIn = true; 1171 } 1172 } 1173 } 1174 1175 // Build filtered vCard 1176 if (filteredIn) { 1177 filteredVCard.append(line + SEPARATOR); 1178 } 1179 } 1180 1181 return filteredVCard.toString(); 1182 } 1183 } 1184 1185 private static class PropertySelector { 1186 private enum PropertyMask { 1187 // bit property 1188 VERSION(0, "VERSION"), 1189 FN(1, "FN"), 1190 NAME(2, "N"), 1191 PHOTO(3, "PHOTO"), 1192 BDAY(4, "BDAY"), 1193 ADR(5, "ADR"), 1194 LABEL(6, "LABEL"), 1195 TEL(7, "TEL"), 1196 EMAIL(8, "EMAIL"), 1197 TITLE(12, "TITLE"), 1198 ORG(16, "ORG"), 1199 NOTE(17, "NOTE"), 1200 URL(20, "URL"), 1201 NICKNAME(23, "NICKNAME"), 1202 DATETIME(28, "DATETIME"); 1203 1204 public final int pos; 1205 public final String prop; 1206 PropertyMask(int pos, String prop)1207 PropertyMask(int pos, String prop) { 1208 this.pos = pos; 1209 this.prop = prop; 1210 } 1211 } 1212 1213 private static final String SEPARATOR = System.getProperty("line.separator"); 1214 private final byte[] mSelector; 1215 PropertySelector(byte[] selector)1216 PropertySelector(byte[] selector) { 1217 this.mSelector = selector; 1218 } 1219 checkbit(int attrBit, byte[] selector)1220 private boolean checkbit(int attrBit, byte[] selector) { 1221 int selectorlen = selector.length; 1222 if (((selector[selectorlen - 1 - ((int) attrBit / 8)] >> (attrBit % 8)) & 0x01) == 0) { 1223 return false; 1224 } 1225 return true; 1226 } 1227 checkprop(String vcard, String prop)1228 private boolean checkprop(String vcard, String prop) { 1229 String[] lines = vcard.split(SEPARATOR); 1230 boolean isPresent = false; 1231 for (String line : lines) { 1232 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) { 1233 String currentProp = line.split("[;:]")[0]; 1234 if (prop.equals(currentProp)) { 1235 Log.d(TAG, "bit.prop.equals current prop :" + prop); 1236 isPresent = true; 1237 return isPresent; 1238 } 1239 } 1240 } 1241 1242 return isPresent; 1243 } 1244 checkVCardSelector(String vcard, String vcardselectorop)1245 private boolean checkVCardSelector(String vcard, String vcardselectorop) { 1246 boolean selectedIn = true; 1247 1248 for (PropertyMask bit : PropertyMask.values()) { 1249 if (checkbit(bit.pos, mSelector)) { 1250 Log.d(TAG, "checking for prop :" + bit.prop); 1251 if (vcardselectorop.equals("0")) { 1252 if (checkprop(vcard, bit.prop)) { 1253 Log.d(TAG, "bit.prop.equals current prop :" + bit.prop); 1254 selectedIn = true; 1255 break; 1256 } else { 1257 selectedIn = false; 1258 } 1259 } else if (vcardselectorop.equals("1")) { 1260 if (!checkprop(vcard, bit.prop)) { 1261 Log.d(TAG, "bit.prop.notequals current prop" + bit.prop); 1262 selectedIn = false; 1263 return selectedIn; 1264 } else { 1265 selectedIn = true; 1266 } 1267 } 1268 } 1269 } 1270 return selectedIn; 1271 } 1272 getName(String vcard)1273 private String getName(String vcard) { 1274 String[] lines = vcard.split(SEPARATOR); 1275 String name = ""; 1276 for (String line : lines) { 1277 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) { 1278 if (line.startsWith("N:")) { 1279 name = line.substring(line.lastIndexOf(':'), line.length()); 1280 } 1281 } 1282 } 1283 Log.d(TAG, "returning name: " + name); 1284 return name; 1285 } 1286 } 1287 getPhoneLookupFilterUri()1288 private static Uri getPhoneLookupFilterUri() { 1289 return PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI; 1290 } 1291 1292 /** 1293 * Get size of the cursor without duplicated contact id. This assumes the 1294 * given cursor is sorted by CONTACT_ID. 1295 */ getDistinctContactIdSize(Cursor cursor)1296 private static int getDistinctContactIdSize(Cursor cursor) { 1297 final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID); 1298 final int idColumn = cursor.getColumnIndex(Data._ID); 1299 long previousContactId = -1; 1300 int count = 0; 1301 cursor.moveToPosition(-1); 1302 while (cursor.moveToNext()) { 1303 final long contactId = 1304 cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn); 1305 if (previousContactId != contactId) { 1306 count++; 1307 previousContactId = contactId; 1308 } 1309 } 1310 if (V) { 1311 Log.i(TAG, "getDistinctContactIdSize result: " + count); 1312 } 1313 return count; 1314 } 1315 1316 /** 1317 * Append "display_name,contact_id" string array from cursor to ArrayList. 1318 * This assumes the given cursor is sorted by CONTACT_ID. 1319 */ appendDistinctNameIdList(ArrayList<String> resultList, String defaultName, Cursor cursor)1320 private static void appendDistinctNameIdList(ArrayList<String> resultList, String defaultName, 1321 Cursor cursor) { 1322 final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID); 1323 final int idColumn = cursor.getColumnIndex(Data._ID); 1324 final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME); 1325 cursor.moveToPosition(-1); 1326 while (cursor.moveToNext()) { 1327 final long contactId = 1328 cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn); 1329 String displayName = nameColumn != -1 ? cursor.getString(nameColumn) : defaultName; 1330 if (TextUtils.isEmpty(displayName)) { 1331 displayName = defaultName; 1332 } 1333 1334 String newString = displayName + "," + contactId; 1335 if (!resultList.contains(newString)) { 1336 resultList.add(newString); 1337 } 1338 } 1339 if (V) { 1340 for (String nameId : resultList) { 1341 Log.i(TAG, "appendDistinctNameIdList result: " + nameId); 1342 } 1343 } 1344 } 1345 } 1346