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 if (contactCursor != null) { 336 if (!composer.initWithCallback(contactCursor, 337 new EnterpriseRawContactEntitlesInfoCallback())) { 338 return nameList; 339 } 340 341 while (!composer.isAfterLast()) { 342 String vcard = composer.createOneEntry(); 343 if (vcard == null) { 344 Log.e(TAG, "Failed to read a contact. Error reason: " 345 + composer.getErrorReason()); 346 return nameList; 347 } else if (vcard.isEmpty()) { 348 Log.i(TAG, "Contact may have been deleted during operation"); 349 continue; 350 } 351 if (V) { 352 Log.v(TAG, "Checking selected bits in the vcard composer" + vcard); 353 } 354 355 if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) { 356 Log.e(TAG, "vcard selector check fail"); 357 vcard = null; 358 pbSize--; 359 continue; 360 } else { 361 String name = vcardselector.getName(vcard); 362 if (TextUtils.isEmpty(name)) { 363 name = mContext.getString(android.R.string.unknownName); 364 } 365 nameList.add(name); 366 } 367 } 368 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) { 369 if (V) { 370 Log.v(TAG, "getPhonebookNameList, order by index"); 371 } 372 // Do not need to do anything, as we sort it by index already 373 } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) { 374 if (V) { 375 Log.v(TAG, "getPhonebookNameList, order by alpha"); 376 } 377 Collections.sort(nameList); 378 } 379 } 380 } catch (CursorWindowAllocationException e) { 381 Log.e(TAG, "CursorWindowAllocationException while getting Phonebook name list"); 382 } finally { 383 if (contactCursor != null) { 384 contactCursor.close(); 385 contactCursor = null; 386 } 387 } 388 return nameList; 389 } 390 getContactNamesByNumber(final String phoneNumber)391 public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) { 392 ArrayList<String> nameList = new ArrayList<String>(); 393 ArrayList<String> tempNameList = new ArrayList<String>(); 394 395 Cursor contactCursor = null; 396 Uri uri = null; 397 String[] projection = null; 398 399 if (TextUtils.isEmpty(phoneNumber)) { 400 uri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 401 projection = PHONES_CONTACTS_PROJECTION; 402 } else { 403 uri = Uri.withAppendedPath(getPhoneLookupFilterUri(), Uri.encode(phoneNumber)); 404 projection = PHONE_LOOKUP_PROJECTION; 405 } 406 407 try { 408 contactCursor = mResolver.query(uri, projection, null, null, Phone.CONTACT_ID); 409 410 if (contactCursor != null) { 411 appendDistinctNameIdList(nameList, mContext.getString(android.R.string.unknownName), 412 contactCursor); 413 if (V) { 414 for (String nameIdStr : nameList) { 415 Log.v(TAG, "got name " + nameIdStr + " by number " + phoneNumber); 416 } 417 } 418 } 419 } catch (CursorWindowAllocationException e) { 420 Log.e(TAG, "CursorWindowAllocationException while getting contact names"); 421 } finally { 422 if (contactCursor != null) { 423 contactCursor.close(); 424 contactCursor = null; 425 } 426 } 427 int tempListSize = tempNameList.size(); 428 for (int index = 0; index < tempListSize; index++) { 429 String object = tempNameList.get(index); 430 if (!nameList.contains(object)) { 431 nameList.add(object); 432 } 433 } 434 435 return nameList; 436 } 437 getCallHistoryPrimaryFolderVersion(final int type)438 byte[] getCallHistoryPrimaryFolderVersion(final int type) { 439 final Uri myUri = CallLog.Calls.CONTENT_URI; 440 String selection = BluetoothPbapObexServer.createSelectionPara(type); 441 selection = selection + " AND date >= " + sLastFetchedTimeStamp; 442 443 Log.d(TAG, "LAST_FETCHED_TIME_STAMP is " + sLastFetchedTimeStamp); 444 Cursor callCursor = null; 445 long count = 0; 446 long primaryVcMsb = 0; 447 ArrayList<String> list = new ArrayList<String>(); 448 try { 449 callCursor = mResolver.query(myUri, null, selection, null, null); 450 while (callCursor != null && callCursor.moveToNext()) { 451 count = count + 1; 452 } 453 } catch (Exception e) { 454 Log.e(TAG, "exception while fetching callHistory pvc"); 455 } finally { 456 if (callCursor != null) { 457 callCursor.close(); 458 callCursor = null; 459 } 460 } 461 462 sLastFetchedTimeStamp = System.currentTimeMillis(); 463 Log.d(TAG, "getCallHistoryPrimaryFolderVersion count is " + count + " type is " + type); 464 ByteBuffer pvc = ByteBuffer.allocate(16); 465 pvc.putLong(primaryVcMsb); 466 Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter); 467 pvc.putLong(count); 468 return pvc.array(); 469 } 470 471 private static final String[] CALLLOG_PROJECTION = new String[]{ 472 CallLog.Calls._ID, // 0 473 }; 474 private static final int ID_COLUMN_INDEX = 0; 475 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)476 final int composeAndSendSelectedCallLogVcards(final int type, Operation op, 477 final int startPoint, final int endPoint, final boolean vcardType21, int needSendBody, 478 int pbSize, boolean ignorefilter, byte[] filter, byte[] vcardselector, 479 String vcardselectorop, boolean vcardselect) { 480 if (startPoint < 1 || startPoint > endPoint) { 481 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 482 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 483 } 484 String typeSelection = BluetoothPbapObexServer.createSelectionPara(type); 485 486 final Uri myUri = CallLog.Calls.CONTENT_URI; 487 Cursor callsCursor = null; 488 long startPointId = 0; 489 long endPointId = 0; 490 try { 491 // Need test to see if order by _ID is ok here, or by date? 492 callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null, 493 CALLLOG_SORT_ORDER); 494 if (callsCursor != null) { 495 callsCursor.moveToPosition(startPoint - 1); 496 startPointId = callsCursor.getLong(ID_COLUMN_INDEX); 497 if (V) { 498 Log.v(TAG, "Call Log query startPointId = " + startPointId); 499 } 500 if (startPoint == endPoint) { 501 endPointId = startPointId; 502 } else { 503 callsCursor.moveToPosition(endPoint - 1); 504 endPointId = callsCursor.getLong(ID_COLUMN_INDEX); 505 } 506 if (V) { 507 Log.v(TAG, "Call log query endPointId = " + endPointId); 508 } 509 } 510 } catch (CursorWindowAllocationException e) { 511 Log.e(TAG, "CursorWindowAllocationException while composing calllog vcards"); 512 } finally { 513 if (callsCursor != null) { 514 callsCursor.close(); 515 callsCursor = null; 516 } 517 } 518 519 String recordSelection; 520 if (startPoint == endPoint) { 521 recordSelection = Calls._ID + "=" + startPointId; 522 } else { 523 // The query to call table is by "_id DESC" order, so change 524 // correspondingly. 525 recordSelection = 526 Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<=" + startPointId; 527 } 528 529 String selection; 530 if (typeSelection == null) { 531 selection = recordSelection; 532 } else { 533 selection = "(" + typeSelection + ") AND (" + recordSelection + ")"; 534 } 535 536 if (V) { 537 Log.v(TAG, "Call log query selection is: " + selection); 538 } 539 540 return composeCallLogsAndSendSelectedVCards(op, selection, vcardType21, needSendBody, 541 pbSize, null, ignorefilter, filter, vcardselector, vcardselectorop, vcardselect); 542 } 543 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)544 final int composeAndSendPhonebookVcards(Operation op, final int startPoint, final int endPoint, 545 final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, 546 boolean ignorefilter, byte[] filter, byte[] vcardselector, String vcardselectorop, 547 boolean vcardselect) { 548 if (startPoint < 1 || startPoint > endPoint) { 549 Log.e(TAG, "internal error: startPoint or endPoint is not correct."); 550 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 551 } 552 553 final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 554 Cursor contactCursor = null; 555 Cursor contactIdCursor = new MatrixCursor(new String[]{ 556 Phone.CONTACT_ID 557 }); 558 try { 559 contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, 560 Phone.CONTACT_ID); 561 if (contactCursor != null) { 562 contactIdCursor = 563 ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint); 564 } 565 } catch (CursorWindowAllocationException e) { 566 Log.e(TAG, "CursorWindowAllocationException while composing phonebook vcards"); 567 } finally { 568 if (contactCursor != null) { 569 contactCursor.close(); 570 } 571 } 572 573 if (vcardselect) { 574 return composeContactsAndSendSelectedVCards(op, contactIdCursor, vcardType21, 575 ownerVCard, needSendBody, pbSize, ignorefilter, filter, vcardselector, 576 vcardselectorop); 577 } else { 578 return composeContactsAndSendVCards(op, contactIdCursor, vcardType21, ownerVCard, 579 ignorefilter, filter); 580 } 581 } 582 composeAndSendPhonebookOneVcard(Operation op, final int offset, final boolean vcardType21, String ownerVCard, int orderByWhat, boolean ignorefilter, byte[] filter)583 final int composeAndSendPhonebookOneVcard(Operation op, final int offset, 584 final boolean vcardType21, String ownerVCard, int orderByWhat, boolean ignorefilter, 585 byte[] filter) { 586 if (offset < 1) { 587 Log.e(TAG, "Internal error: offset is not correct."); 588 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 589 } 590 final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); 591 592 Cursor contactCursor = null; 593 Cursor contactIdCursor = new MatrixCursor(new String[]{ 594 Phone.CONTACT_ID 595 }); 596 // By default order is indexed 597 String orderBy = Phone.CONTACT_ID; 598 try { 599 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) { 600 orderBy = Phone.DISPLAY_NAME; 601 } 602 contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, orderBy); 603 } catch (CursorWindowAllocationException e) { 604 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard"); 605 } finally { 606 if (contactCursor != null) { 607 contactIdCursor = ContactCursorFilter.filterByOffset(contactCursor, offset); 608 contactCursor.close(); 609 contactCursor = null; 610 } 611 } 612 return composeContactsAndSendVCards(op, contactIdCursor, vcardType21, ownerVCard, 613 ignorefilter, filter); 614 } 615 616 /** 617 * Filter contact cursor by certain condition. 618 */ 619 private static final class ContactCursorFilter { 620 /** 621 * 622 * @param contactCursor 623 * @param offset 624 * @return a cursor containing contact id of {@code offset} contact. 625 */ filterByOffset(Cursor contactCursor, int offset)626 public static Cursor filterByOffset(Cursor contactCursor, int offset) { 627 return filterByRange(contactCursor, offset, offset); 628 } 629 630 /** 631 * 632 * @param contactCursor 633 * @param startPoint 634 * @param endPoint 635 * @return a cursor containing contact ids of {@code startPoint}th to {@code endPoint}th 636 * contact. 637 */ filterByRange(Cursor contactCursor, int startPoint, int endPoint)638 public static Cursor filterByRange(Cursor contactCursor, int startPoint, int endPoint) { 639 final int contactIdColumn = contactCursor.getColumnIndex(Data.CONTACT_ID); 640 long previousContactId = -1; 641 // As startPoint, endOffset index starts from 1 to n, we set 642 // currentPoint base as 1 not 0 643 int currentOffset = 1; 644 final MatrixCursor contactIdsCursor = new MatrixCursor(new String[]{ 645 Phone.CONTACT_ID 646 }); 647 while (contactCursor.moveToNext() && currentOffset <= endPoint) { 648 long currentContactId = contactCursor.getLong(contactIdColumn); 649 if (previousContactId != currentContactId) { 650 previousContactId = currentContactId; 651 if (currentOffset >= startPoint) { 652 contactIdsCursor.addRow(new Long[]{currentContactId}); 653 if (V) { 654 Log.v(TAG, "contactIdsCursor.addRow: " + currentContactId); 655 } 656 } 657 currentOffset++; 658 } 659 } 660 return contactIdsCursor; 661 } 662 } 663 664 /** 665 * Handler enterprise contact id in VCardComposer 666 */ 667 private static class EnterpriseRawContactEntitlesInfoCallback 668 implements VCardComposer.RawContactEntitlesInfoCallback { 669 @Override getRawContactEntitlesInfo(long contactId)670 public VCardComposer.RawContactEntitlesInfo getRawContactEntitlesInfo(long contactId) { 671 if (Contacts.isEnterpriseContactId(contactId)) { 672 return new VCardComposer.RawContactEntitlesInfo(RawContactsEntity.CORP_CONTENT_URI, 673 contactId - Contacts.ENTERPRISE_CONTACT_ID_BASE); 674 } else { 675 return new VCardComposer.RawContactEntitlesInfo(RawContactsEntity.CONTENT_URI, 676 contactId); 677 } 678 } 679 } 680 composeContactsAndSendVCards(Operation op, final Cursor contactIdCursor, final boolean vcardType21, String ownerVCard, boolean ignorefilter, byte[] filter)681 private int composeContactsAndSendVCards(Operation op, final Cursor contactIdCursor, 682 final boolean vcardType21, String ownerVCard, boolean ignorefilter, byte[] filter) { 683 long timestamp = 0; 684 if (V) { 685 timestamp = System.currentTimeMillis(); 686 } 687 688 VCardComposer composer = null; 689 VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter); 690 691 HandlerForStringBuffer buffer = null; 692 try { 693 // Currently only support Generic Vcard 2.1 and 3.0 694 int vcardType; 695 if (vcardType21) { 696 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 697 vcardType |= VCardConfig.FLAG_CONVERT_PHONETIC_NAME_STRINGS; 698 vcardType |= VCardConfig.FLAG_REFRAIN_QP_TO_NAME_PROPERTIES; 699 } else { 700 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 701 } 702 if (!vcardfilter.isPhotoEnabled()) { 703 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT; 704 } 705 706 // Enhancement: customize Vcard based on preferences/settings and 707 // input from caller 708 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType, null); 709 // End enhancement 710 711 // BT does want PAUSE/WAIT conversion while it doesn't want the 712 // other formatting 713 // done by vCard library by default. 714 composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() { 715 @Override 716 public String onValueReceived(String rawValue, int type, String label, 717 boolean isPrimary) { 718 // 'p' and 'w' are the standard characters for pause and 719 // wait 720 // (see RFC 3601) 721 // so use those when exporting phone numbers via vCard. 722 String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p') 723 .replace(PhoneNumberUtils.WAIT, 'w'); 724 return numberWithControlSequence; 725 } 726 }); 727 buffer = new HandlerForStringBuffer(op, ownerVCard); 728 Log.v(TAG, "contactIdCursor size: " + contactIdCursor.getCount()); 729 if (!composer.initWithCallback(contactIdCursor, 730 new EnterpriseRawContactEntitlesInfoCallback()) || !buffer.onInit(mContext)) { 731 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 732 } 733 734 while (!composer.isAfterLast()) { 735 if (BluetoothPbapObexServer.sIsAborted) { 736 ((ServerOperation) op).isAborted = true; 737 BluetoothPbapObexServer.sIsAborted = false; 738 break; 739 } 740 String vcard = composer.createOneEntry(); 741 if (vcard == null) { 742 Log.e(TAG, 743 "Failed to read a contact. Error reason: " + composer.getErrorReason()); 744 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 745 } else if (vcard.isEmpty()) { 746 Log.i(TAG, "Contact may have been deleted during operation"); 747 continue; 748 } 749 if (V) { 750 Log.v(TAG, "vCard from composer: " + vcard); 751 } 752 753 vcard = vcardfilter.apply(vcard, vcardType21); 754 vcard = stripTelephoneNumber(vcard); 755 756 if (V) { 757 Log.v(TAG, "vCard after cleanup: " + vcard); 758 } 759 760 if (!buffer.onEntryCreated(vcard)) { 761 // onEntryCreate() already emits error. 762 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 763 } 764 } 765 } finally { 766 if (composer != null) { 767 composer.terminate(); 768 } 769 if (buffer != null) { 770 buffer.onTerminate(); 771 } 772 } 773 774 if (V) { 775 Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis() 776 - timestamp) + " ms"); 777 } 778 779 return ResponseCodes.OBEX_HTTP_OK; 780 } 781 composeContactsAndSendSelectedVCards(Operation op, final Cursor contactIdCursor, final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop)782 private int composeContactsAndSendSelectedVCards(Operation op, final Cursor contactIdCursor, 783 final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, 784 boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop) { 785 long timestamp = 0; 786 if (V) { 787 timestamp = System.currentTimeMillis(); 788 } 789 790 VCardComposer composer = null; 791 VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter); 792 PropertySelector vcardselector = new PropertySelector(selector); 793 794 HandlerForStringBuffer buffer = null; 795 796 try { 797 // Currently only support Generic Vcard 2.1 and 3.0 798 int vcardType; 799 if (vcardType21) { 800 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 801 } else { 802 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 803 } 804 if (!vcardfilter.isPhotoEnabled()) { 805 vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT; 806 } 807 808 // Enhancement: customize Vcard based on preferences/settings and 809 // input from caller 810 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType, null); 811 // End enhancement 812 813 /* BT does want PAUSE/WAIT conversion while it doesn't want the 814 * other formatting done by vCard library by default. */ 815 composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() { 816 @Override 817 public String onValueReceived(String rawValue, int type, String label, 818 boolean isPrimary) { 819 /* 'p' and 'w' are the standard characters for pause and wait 820 * (see RFC 3601) so use those when exporting phone numbers via vCard.*/ 821 String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p') 822 .replace(PhoneNumberUtils.WAIT, 'w'); 823 return numberWithControlSequence; 824 } 825 }); 826 buffer = new HandlerForStringBuffer(op, ownerVCard); 827 Log.v(TAG, "contactIdCursor size: " + contactIdCursor.getCount()); 828 if (!composer.initWithCallback(contactIdCursor, 829 new EnterpriseRawContactEntitlesInfoCallback()) || !buffer.onInit(mContext)) { 830 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 831 } 832 833 while (!composer.isAfterLast()) { 834 if (BluetoothPbapObexServer.sIsAborted) { 835 ((ServerOperation) op).isAborted = true; 836 BluetoothPbapObexServer.sIsAborted = false; 837 break; 838 } 839 String vcard = composer.createOneEntry(); 840 if (vcard == null) { 841 Log.e(TAG, 842 "Failed to read a contact. Error reason: " + composer.getErrorReason()); 843 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 844 } else if (vcard.isEmpty()) { 845 Log.i(TAG, "Contact may have been deleted during operation"); 846 continue; 847 } 848 if (V) { 849 Log.v(TAG, "Checking selected bits in the vcard composer" + vcard); 850 } 851 852 if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) { 853 Log.e(TAG, "vcard selector check fail"); 854 vcard = null; 855 pbSize--; 856 continue; 857 } 858 859 Log.e(TAG, "vcard selector check pass"); 860 861 if (needSendBody == NEED_SEND_BODY) { 862 vcard = vcardfilter.apply(vcard, vcardType21); 863 vcard = stripTelephoneNumber(vcard); 864 865 if (V) { 866 Log.v(TAG, "vCard after cleanup: " + vcard); 867 } 868 869 if (!buffer.onEntryCreated(vcard)) { 870 // onEntryCreate() already emits error. 871 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 872 } 873 } 874 } 875 876 if (needSendBody != NEED_SEND_BODY) { 877 return pbSize; 878 } 879 } finally { 880 if (composer != null) { 881 composer.terminate(); 882 } 883 if (buffer != null) { 884 buffer.onTerminate(); 885 } 886 } 887 888 if (V) { 889 Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis() 890 - timestamp) + " ms"); 891 } 892 893 return ResponseCodes.OBEX_HTTP_OK; 894 } 895 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)896 private int composeCallLogsAndSendSelectedVCards(Operation op, final String selection, 897 final boolean vcardType21, int needSendBody, int pbSize, String ownerVCard, 898 boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop, 899 boolean vCardSelct) { 900 long timestamp = 0; 901 if (V) { 902 timestamp = System.currentTimeMillis(); 903 } 904 905 BluetoothPbapCallLogComposer composer = null; 906 HandlerForStringBuffer buffer = null; 907 908 try { 909 VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter); 910 PropertySelector vcardselector = new PropertySelector(selector); 911 composer = new BluetoothPbapCallLogComposer(mContext); 912 buffer = new HandlerForStringBuffer(op, ownerVCard); 913 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null, CALLLOG_SORT_ORDER) 914 || !buffer.onInit(mContext)) { 915 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 916 } 917 918 while (!composer.isAfterLast()) { 919 if (BluetoothPbapObexServer.sIsAborted) { 920 ((ServerOperation) op).isAborted = true; 921 BluetoothPbapObexServer.sIsAborted = false; 922 break; 923 } 924 String vcard = composer.createOneEntry(vcardType21); 925 if (vCardSelct) { 926 if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) { 927 Log.e(TAG, "Checking vcard selector for call log"); 928 vcard = null; 929 pbSize--; 930 continue; 931 } 932 if (needSendBody == NEED_SEND_BODY) { 933 if (vcard == null) { 934 Log.e(TAG, "Failed to read a contact. Error reason: " 935 + composer.getErrorReason()); 936 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 937 } else if (vcard.isEmpty()) { 938 Log.i(TAG, "Call Log may have been deleted during operation"); 939 continue; 940 } 941 vcard = vcardfilter.apply(vcard, vcardType21); 942 943 if (V) { 944 Log.v(TAG, "Vcard Entry:"); 945 Log.v(TAG, vcard); 946 } 947 buffer.onEntryCreated(vcard); 948 } 949 } else { 950 if (vcard == null) { 951 Log.e(TAG, "Failed to read a contact. Error reason: " 952 + composer.getErrorReason()); 953 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 954 } 955 if (V) { 956 Log.v(TAG, "Vcard Entry:"); 957 Log.v(TAG, vcard); 958 } 959 buffer.onEntryCreated(vcard); 960 } 961 } 962 if (needSendBody != NEED_SEND_BODY && vCardSelct) { 963 return pbSize; 964 } 965 } finally { 966 if (composer != null) { 967 composer.terminate(); 968 } 969 if (buffer != null) { 970 buffer.onTerminate(); 971 } 972 } 973 974 if (V) { 975 Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis() 976 - timestamp) + " ms"); 977 } 978 return ResponseCodes.OBEX_HTTP_OK; 979 } 980 stripTelephoneNumber(String vCard)981 public String stripTelephoneNumber(String vCard) { 982 String[] attr = vCard.split(System.getProperty("line.separator")); 983 String stripedVCard = ""; 984 for (int i = 0; i < attr.length; i++) { 985 if (attr[i].startsWith("TEL")) { 986 String[] vTagAndTel = attr[i].split(":", 2); 987 int telLenBefore = vTagAndTel[1].length(); 988 // Remove '-', '(', ')' or ' ' from TEL number 989 vTagAndTel[1] = vTagAndTel[1].replace("-", "") 990 .replace("(", "") 991 .replace(")", "") 992 .replace(" ", ""); 993 if (vTagAndTel[1].length() < telLenBefore) { 994 if (V) { 995 Log.v(TAG, "Fixing vCard TEL to " + vTagAndTel[1]); 996 } 997 attr[i] = new StringBuilder().append(vTagAndTel[0]).append(":") 998 .append(vTagAndTel[1]).toString(); 999 } 1000 } 1001 } 1002 1003 for (int i = 0; i < attr.length; i++) { 1004 if (!attr[i].isEmpty()) { 1005 stripedVCard = stripedVCard.concat(attr[i] + "\n"); 1006 } 1007 } 1008 if (V) { 1009 Log.v(TAG, "vCard with stripped telephone no.: " + stripedVCard); 1010 } 1011 return stripedVCard; 1012 } 1013 1014 /** 1015 * Handler to emit vCards to PCE. 1016 */ 1017 public class HandlerForStringBuffer { 1018 private Operation mOperation; 1019 1020 private OutputStream mOutputStream; 1021 1022 private String mPhoneOwnVCard = null; 1023 HandlerForStringBuffer(Operation op, String ownerVCard)1024 public HandlerForStringBuffer(Operation op, String ownerVCard) { 1025 mOperation = op; 1026 if (ownerVCard != null) { 1027 mPhoneOwnVCard = ownerVCard; 1028 if (V) { 1029 Log.v(TAG, "phone own number vcard:"); 1030 } 1031 if (V) { 1032 Log.v(TAG, mPhoneOwnVCard); 1033 } 1034 } 1035 } 1036 write(String vCard)1037 private boolean write(String vCard) { 1038 try { 1039 if (vCard != null) { 1040 mOutputStream.write(vCard.getBytes()); 1041 return true; 1042 } 1043 } catch (IOException e) { 1044 Log.e(TAG, "write outputstrem failed" + e.toString()); 1045 } 1046 return false; 1047 } 1048 onInit(Context context)1049 public boolean onInit(Context context) { 1050 try { 1051 mOutputStream = mOperation.openOutputStream(); 1052 if (mPhoneOwnVCard != null) { 1053 return write(mPhoneOwnVCard); 1054 } 1055 return true; 1056 } catch (IOException e) { 1057 Log.e(TAG, "open outputstrem failed" + e.toString()); 1058 } 1059 return false; 1060 } 1061 onEntryCreated(String vcard)1062 public boolean onEntryCreated(String vcard) { 1063 return write(vcard); 1064 } 1065 onTerminate()1066 public void onTerminate() { 1067 if (!BluetoothPbapObexServer.closeStream(mOutputStream, mOperation)) { 1068 if (V) { 1069 Log.v(TAG, "CloseStream failed!"); 1070 } 1071 } else { 1072 if (V) { 1073 Log.v(TAG, "CloseStream ok!"); 1074 } 1075 } 1076 } 1077 } 1078 1079 public static class VCardFilter { 1080 private enum FilterBit { 1081 // bit property onlyCheckV21 excludeForV21 1082 FN(1, "FN", true, false), 1083 PHOTO(3, "PHOTO", false, false), 1084 BDAY(4, "BDAY", false, false), 1085 ADR(5, "ADR", false, false), 1086 EMAIL(8, "EMAIL", false, false), 1087 TITLE(12, "TITLE", false, false), 1088 ORG(16, "ORG", false, false), 1089 NOTE(17, "NOTE", false, false), 1090 SOUND(19, "SOUND", false, false), 1091 URL(20, "URL", false, false), 1092 NICKNAME(23, "NICKNAME", false, true), 1093 DATETIME(28, "X-IRMC-CALL-DATETIME", false, false); 1094 1095 public final int pos; 1096 public final String prop; 1097 public final boolean onlyCheckV21; 1098 public final boolean excludeForV21; 1099 FilterBit(int pos, String prop, boolean onlyCheckV21, boolean excludeForV21)1100 FilterBit(int pos, String prop, boolean onlyCheckV21, boolean excludeForV21) { 1101 this.pos = pos; 1102 this.prop = prop; 1103 this.onlyCheckV21 = onlyCheckV21; 1104 this.excludeForV21 = excludeForV21; 1105 } 1106 } 1107 1108 private static final String SEPARATOR = System.getProperty("line.separator"); 1109 private final byte[] mFilter; 1110 1111 //This function returns true if the attributes needs to be included in the filtered vcard. isFilteredIn(FilterBit bit, boolean vCardType21)1112 private boolean isFilteredIn(FilterBit bit, boolean vCardType21) { 1113 final int offset = (bit.pos / 8) + 1; 1114 final int bitPos = bit.pos % 8; 1115 if (!vCardType21 && bit.onlyCheckV21) { 1116 return true; 1117 } 1118 if (vCardType21 && bit.excludeForV21) { 1119 return false; 1120 } 1121 if (mFilter == null || offset >= mFilter.length) { 1122 return true; 1123 } 1124 return ((mFilter[mFilter.length - offset] >> bitPos) & 0x01) != 0; 1125 } 1126 VCardFilter(byte[] filter)1127 VCardFilter(byte[] filter) { 1128 this.mFilter = filter; 1129 } 1130 isPhotoEnabled()1131 public boolean isPhotoEnabled() { 1132 return isFilteredIn(FilterBit.PHOTO, false); 1133 } 1134 apply(String vCard, boolean vCardType21)1135 public String apply(String vCard, boolean vCardType21) { 1136 if (mFilter == null) { 1137 return vCard; 1138 } 1139 String[] lines = vCard.split(SEPARATOR); 1140 StringBuilder filteredVCard = new StringBuilder(); 1141 boolean filteredIn = false; 1142 1143 for (String line : lines) { 1144 // Check whether the current property is changing (ignoring multi-line properties) 1145 // and determine if the current property is filtered in. 1146 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) { 1147 String currentProp = line.split("[;:]")[0]; 1148 filteredIn = true; 1149 1150 for (FilterBit bit : FilterBit.values()) { 1151 if (bit.prop.equals(currentProp)) { 1152 filteredIn = isFilteredIn(bit, vCardType21); 1153 break; 1154 } 1155 } 1156 1157 // Since PBAP does not have filter bits for IM and SIP, 1158 // exclude them by default. Easiest way is to exclude all 1159 // X- fields, except date time.... 1160 if (currentProp.startsWith("X-")) { 1161 filteredIn = false; 1162 if (currentProp.equals("X-IRMC-CALL-DATETIME")) { 1163 filteredIn = true; 1164 } 1165 } 1166 } 1167 1168 // Build filtered vCard 1169 if (filteredIn) { 1170 filteredVCard.append(line + SEPARATOR); 1171 } 1172 } 1173 1174 return filteredVCard.toString(); 1175 } 1176 } 1177 1178 private static class PropertySelector { 1179 private enum PropertyMask { 1180 // bit property 1181 VERSION(0, "VERSION"), 1182 FN(1, "FN"), 1183 NAME(2, "N"), 1184 PHOTO(3, "PHOTO"), 1185 BDAY(4, "BDAY"), 1186 ADR(5, "ADR"), 1187 LABEL(6, "LABEL"), 1188 TEL(7, "TEL"), 1189 EMAIL(8, "EMAIL"), 1190 TITLE(12, "TITLE"), 1191 ORG(16, "ORG"), 1192 NOTE(17, "NOTE"), 1193 URL(20, "URL"), 1194 NICKNAME(23, "NICKNAME"), 1195 DATETIME(28, "DATETIME"); 1196 1197 public final int pos; 1198 public final String prop; 1199 PropertyMask(int pos, String prop)1200 PropertyMask(int pos, String prop) { 1201 this.pos = pos; 1202 this.prop = prop; 1203 } 1204 } 1205 1206 private static final String SEPARATOR = System.getProperty("line.separator"); 1207 private final byte[] mSelector; 1208 PropertySelector(byte[] selector)1209 PropertySelector(byte[] selector) { 1210 this.mSelector = selector; 1211 } 1212 checkbit(int attrBit, byte[] selector)1213 private boolean checkbit(int attrBit, byte[] selector) { 1214 int selectorlen = selector.length; 1215 if (((selector[selectorlen - 1 - ((int) attrBit / 8)] >> (attrBit % 8)) & 0x01) == 0) { 1216 return false; 1217 } 1218 return true; 1219 } 1220 checkprop(String vcard, String prop)1221 private boolean checkprop(String vcard, String prop) { 1222 String[] lines = vcard.split(SEPARATOR); 1223 boolean isPresent = false; 1224 for (String line : lines) { 1225 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) { 1226 String currentProp = line.split("[;:]")[0]; 1227 if (prop.equals(currentProp)) { 1228 Log.d(TAG, "bit.prop.equals current prop :" + prop); 1229 isPresent = true; 1230 return isPresent; 1231 } 1232 } 1233 } 1234 1235 return isPresent; 1236 } 1237 checkVCardSelector(String vcard, String vcardselectorop)1238 private boolean checkVCardSelector(String vcard, String vcardselectorop) { 1239 boolean selectedIn = true; 1240 1241 for (PropertyMask bit : PropertyMask.values()) { 1242 if (checkbit(bit.pos, mSelector)) { 1243 Log.d(TAG, "checking for prop :" + bit.prop); 1244 if (vcardselectorop.equals("0")) { 1245 if (checkprop(vcard, bit.prop)) { 1246 Log.d(TAG, "bit.prop.equals current prop :" + bit.prop); 1247 selectedIn = true; 1248 break; 1249 } else { 1250 selectedIn = false; 1251 } 1252 } else if (vcardselectorop.equals("1")) { 1253 if (!checkprop(vcard, bit.prop)) { 1254 Log.d(TAG, "bit.prop.notequals current prop" + bit.prop); 1255 selectedIn = false; 1256 return selectedIn; 1257 } else { 1258 selectedIn = true; 1259 } 1260 } 1261 } 1262 } 1263 return selectedIn; 1264 } 1265 getName(String vcard)1266 private String getName(String vcard) { 1267 String[] lines = vcard.split(SEPARATOR); 1268 String name = ""; 1269 for (String line : lines) { 1270 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) { 1271 if (line.startsWith("N:")) { 1272 name = line.substring(line.lastIndexOf(':'), line.length()); 1273 } 1274 } 1275 } 1276 Log.d(TAG, "returning name: " + name); 1277 return name; 1278 } 1279 } 1280 getPhoneLookupFilterUri()1281 private static Uri getPhoneLookupFilterUri() { 1282 return PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI; 1283 } 1284 1285 /** 1286 * Get size of the cursor without duplicated contact id. This assumes the 1287 * given cursor is sorted by CONTACT_ID. 1288 */ getDistinctContactIdSize(Cursor cursor)1289 private static int getDistinctContactIdSize(Cursor cursor) { 1290 final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID); 1291 final int idColumn = cursor.getColumnIndex(Data._ID); 1292 long previousContactId = -1; 1293 int count = 0; 1294 cursor.moveToPosition(-1); 1295 while (cursor.moveToNext()) { 1296 final long contactId = 1297 cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn); 1298 if (previousContactId != contactId) { 1299 count++; 1300 previousContactId = contactId; 1301 } 1302 } 1303 if (V) { 1304 Log.i(TAG, "getDistinctContactIdSize result: " + count); 1305 } 1306 return count; 1307 } 1308 1309 /** 1310 * Append "display_name,contact_id" string array from cursor to ArrayList. 1311 * This assumes the given cursor is sorted by CONTACT_ID. 1312 */ appendDistinctNameIdList(ArrayList<String> resultList, String defaultName, Cursor cursor)1313 private static void appendDistinctNameIdList(ArrayList<String> resultList, String defaultName, 1314 Cursor cursor) { 1315 final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID); 1316 final int idColumn = cursor.getColumnIndex(Data._ID); 1317 final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME); 1318 cursor.moveToPosition(-1); 1319 while (cursor.moveToNext()) { 1320 final long contactId = 1321 cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn); 1322 String displayName = nameColumn != -1 ? cursor.getString(nameColumn) : defaultName; 1323 if (TextUtils.isEmpty(displayName)) { 1324 displayName = defaultName; 1325 } 1326 1327 String newString = displayName + "," + contactId; 1328 if (!resultList.contains(newString)) { 1329 resultList.add(newString); 1330 } 1331 } 1332 if (V) { 1333 for (String nameId : resultList) { 1334 Log.i(TAG, "appendDistinctNameIdList result: " + nameId); 1335 } 1336 } 1337 } 1338 } 1339