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