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