• 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 android.content.ContentResolver;
37 import android.content.Context;
38 import android.database.CursorWindowAllocationException;
39 import android.database.Cursor;
40 import android.net.Uri;
41 import android.provider.CallLog;
42 import android.provider.CallLog.Calls;
43 import android.provider.ContactsContract.CommonDataKinds;
44 import android.provider.ContactsContract.Contacts;
45 import android.provider.ContactsContract.Data;
46 import android.provider.ContactsContract.CommonDataKinds.Phone;
47 import android.provider.ContactsContract.PhoneLookup;
48 import android.telephony.PhoneNumberUtils;
49 import android.text.TextUtils;
50 import android.util.Log;
51 
52 import com.android.bluetooth.R;
53 import com.android.vcard.VCardComposer;
54 import com.android.vcard.VCardConfig;
55 import com.android.vcard.VCardPhoneNumberTranslationCallback;
56 
57 import java.io.IOException;
58 import java.io.OutputStream;
59 import java.util.ArrayList;
60 
61 import javax.obex.ServerOperation;
62 import javax.obex.Operation;
63 import javax.obex.ResponseCodes;
64 
65 import com.android.bluetooth.Utils;
66 
67 public class BluetoothPbapVcardManager {
68     private static final String TAG = "BluetoothPbapVcardManager";
69 
70     private static final boolean V = BluetoothPbapService.VERBOSE;
71 
72     private ContentResolver mResolver;
73 
74     private Context mContext;
75 
76     static final String[] PHONES_PROJECTION = new String[] {
77             Data._ID, // 0
78             CommonDataKinds.Phone.TYPE, // 1
79             CommonDataKinds.Phone.LABEL, // 2
80             CommonDataKinds.Phone.NUMBER, // 3
81             Contacts.DISPLAY_NAME, // 4
82     };
83 
84     private static final int PHONE_NUMBER_COLUMN_INDEX = 3;
85 
86     static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC";
87 
88     static final String[] CONTACTS_PROJECTION = new String[] {
89             Contacts._ID, // 0
90             Contacts.DISPLAY_NAME, // 1
91     };
92 
93     static final int CONTACTS_ID_COLUMN_INDEX = 0;
94 
95     static final int CONTACTS_NAME_COLUMN_INDEX = 1;
96 
97     // call histories use dynamic handles, and handles should order by date; the
98     // most recently one should be the first handle. In table "calls", _id and
99     // date are consistent in ordering, to implement simply, we sort by _id
100     // here.
101     static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC";
102 
103     private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
104 
BluetoothPbapVcardManager(final Context context)105     public BluetoothPbapVcardManager(final Context context) {
106         mContext = context;
107         mResolver = mContext.getContentResolver();
108     }
109 
110     /**
111      * Create an owner vcard from the configured profile
112      * @param vcardType21
113      * @return
114      */
getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, final byte[] filter)115     private final String getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, final byte[] filter) {
116         // Currently only support Generic Vcard 2.1 and 3.0
117         int vcardType;
118         if (vcardType21) {
119             vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
120         } else {
121             vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
122         }
123 
124         if (!BluetoothPbapConfig.includePhotosInVcard()) {
125             vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
126         }
127 
128         return BluetoothPbapUtils.createProfileVCard(mContext, vcardType,filter);
129     }
130 
getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter)131     public final String getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter) {
132         //Owner vCard enhancement: Use "ME" profile if configured
133         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
134             String vcard = getOwnerPhoneNumberVcardFromProfile(vcardType21, filter);
135             if (vcard != null && vcard.length() != 0) {
136                 return vcard;
137             }
138         }
139         //End enhancement
140 
141         BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext);
142         String name = BluetoothPbapService.getLocalPhoneName();
143         String number = BluetoothPbapService.getLocalPhoneNum();
144         String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number,
145                 vcardType21);
146         return vcard;
147     }
148 
getPhonebookSize(final int type)149     public final int getPhonebookSize(final int type) {
150         int size;
151         switch (type) {
152             case BluetoothPbapObexServer.ContentType.PHONEBOOK:
153                 size = getContactsSize();
154                 break;
155             default:
156                 size = getCallHistorySize(type);
157                 break;
158         }
159         if (V) Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type);
160         return size;
161     }
162 
getContactsSize()163     public final int getContactsSize() {
164         final Uri myUri = Contacts.CONTENT_URI;
165         int size = 0;
166         Cursor contactCursor = null;
167         try {
168             contactCursor = mResolver.query(myUri, null, CLAUSE_ONLY_VISIBLE, null, null);
169             if (contactCursor != null) {
170                 size = contactCursor.getCount() + 1; // always has the 0.vcf
171             }
172         } catch (CursorWindowAllocationException e) {
173             Log.e(TAG, "CursorWindowAllocationException while getting Contacts size");
174         } finally {
175             if (contactCursor != null) {
176                 contactCursor.close();
177                 contactCursor = null;
178             }
179         }
180         return size;
181     }
182 
getCallHistorySize(final int type)183     public final int getCallHistorySize(final int type) {
184         final Uri myUri = CallLog.Calls.CONTENT_URI;
185         String selection = BluetoothPbapObexServer.createSelectionPara(type);
186         int size = 0;
187         Cursor callCursor = null;
188         try {
189             callCursor = mResolver.query(myUri, null, selection, null,
190                     CallLog.Calls.DEFAULT_SORT_ORDER);
191             if (callCursor != null) {
192                 size = callCursor.getCount();
193             }
194         } catch (CursorWindowAllocationException e) {
195             Log.e(TAG, "CursorWindowAllocationException while getting CallHistory size");
196         } finally {
197             if (callCursor != null) {
198                 callCursor.close();
199                 callCursor = null;
200             }
201         }
202         return size;
203     }
204 
loadCallHistoryList(final int type)205     public final ArrayList<String> loadCallHistoryList(final int type) {
206         final Uri myUri = CallLog.Calls.CONTENT_URI;
207         String selection = BluetoothPbapObexServer.createSelectionPara(type);
208         String[] projection = new String[] {
209                 Calls.NUMBER, Calls.CACHED_NAME, Calls.NUMBER_PRESENTATION
210         };
211         final int CALLS_NUMBER_COLUMN_INDEX = 0;
212         final int CALLS_NAME_COLUMN_INDEX = 1;
213         final int CALLS_NUMBER_PRESENTATION_COLUMN_INDEX = 2;
214 
215         Cursor callCursor = null;
216         ArrayList<String> list = new ArrayList<String>();
217         try {
218             callCursor = mResolver.query(myUri, projection, selection, null,
219                     CALLLOG_SORT_ORDER);
220             if (callCursor != null) {
221                 for (callCursor.moveToFirst(); !callCursor.isAfterLast();
222                         callCursor.moveToNext()) {
223                     String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX);
224                     if (TextUtils.isEmpty(name)) {
225                         // name not found, use number instead
226                         final int numberPresentation = callCursor.getInt(
227                                 CALLS_NUMBER_PRESENTATION_COLUMN_INDEX);
228                         if (numberPresentation != Calls.PRESENTATION_ALLOWED) {
229                             name = mContext.getString(R.string.unknownNumber);
230                         } else {
231                             name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX);
232                         }
233                     }
234                     list.add(name);
235                 }
236             }
237         } catch (CursorWindowAllocationException e) {
238             Log.e(TAG, "CursorWindowAllocationException while loading CallHistory");
239         } finally {
240             if (callCursor != null) {
241                 callCursor.close();
242                 callCursor = null;
243             }
244         }
245         return list;
246     }
247 
getPhonebookNameList(final int orderByWhat)248     public final ArrayList<String> getPhonebookNameList(final int orderByWhat) {
249         ArrayList<String> nameList = new ArrayList<String>();
250         //Owner vCard enhancement. Use "ME" profile if configured
251         String ownerName = null;
252         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
253             ownerName = BluetoothPbapUtils.getProfileName(mContext);
254         }
255         if (ownerName == null || ownerName.length()==0) {
256             ownerName = BluetoothPbapService.getLocalPhoneName();
257         }
258         nameList.add(ownerName);
259         //End enhancement
260 
261         final Uri myUri = Contacts.CONTENT_URI;
262         Cursor contactCursor = null;
263         try {
264             if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
265                 if (V) Log.v(TAG, "getPhonebookNameList, order by index");
266                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
267                         null, Contacts._ID);
268             } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
269                 if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
270                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
271                         null, Contacts.DISPLAY_NAME);
272             }
273             if (contactCursor != null) {
274                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
275                         .moveToNext()) {
276                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
277                     long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
278                     if (TextUtils.isEmpty(name)) {
279                         name = mContext.getString(android.R.string.unknownName);
280                     }
281                     nameList.add(name + "," + id);
282                 }
283             }
284         } catch (CursorWindowAllocationException e) {
285             Log.e(TAG, "CursorWindowAllocationException while getting Phonebook name list");
286         } finally {
287             if (contactCursor != null) {
288                 contactCursor.close();
289                 contactCursor = null;
290             }
291         }
292         return nameList;
293     }
294 
getContactNamesByNumber(final String phoneNumber)295     public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) {
296         ArrayList<String> nameList = new ArrayList<String>();
297         ArrayList<String> tempNameList = new ArrayList<String>();
298 
299         Cursor contactCursor = null;
300         Uri uri = null;
301 
302         if (phoneNumber != null && phoneNumber.length() == 0) {
303             uri = Contacts.CONTENT_URI;
304         } else {
305             uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
306                 Uri.encode(phoneNumber));
307         }
308 
309         try {
310             contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
311                         null, Contacts._ID);
312 
313             if (contactCursor != null) {
314                 for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
315                         .moveToNext()) {
316                     String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
317                     long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
318                     if (TextUtils.isEmpty(name)) {
319                         name = mContext.getString(android.R.string.unknownName);
320                     }
321                     if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
322                     tempNameList.add(name + "," + id);
323                 }
324             }
325         } catch (CursorWindowAllocationException e) {
326             Log.e(TAG, "CursorWindowAllocationException while getting contact names");
327         } finally {
328             if (contactCursor != null) {
329                 contactCursor.close();
330                 contactCursor = null;
331             }
332         }
333         int tempListSize = tempNameList.size();
334         for (int index = 0; index < tempListSize; index++) {
335             String object = tempNameList.get(index);
336             if (!nameList.contains(object))
337                 nameList.add(object);
338         }
339 
340         return nameList;
341     }
342 
composeAndSendCallLogVcards(final int type, Operation op, final int startPoint, final int endPoint, final boolean vcardType21, boolean ignorefilter, byte[] filter)343     public final int composeAndSendCallLogVcards(final int type, Operation op,
344             final int startPoint, final int endPoint, final boolean vcardType21,
345             boolean ignorefilter, byte[] filter) {
346         if (startPoint < 1 || startPoint > endPoint) {
347             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
348             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
349         }
350         String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
351 
352         final Uri myUri = CallLog.Calls.CONTENT_URI;
353         final String[] CALLLOG_PROJECTION = new String[] {
354             CallLog.Calls._ID, // 0
355         };
356         final int ID_COLUMN_INDEX = 0;
357 
358         Cursor callsCursor = null;
359         long startPointId = 0;
360         long endPointId = 0;
361         try {
362             // Need test to see if order by _ID is ok here, or by date?
363             callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null,
364                     CALLLOG_SORT_ORDER);
365             if (callsCursor != null) {
366                 callsCursor.moveToPosition(startPoint - 1);
367                 startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
368                 if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
369                 if (startPoint == endPoint) {
370                     endPointId = startPointId;
371                 } else {
372                     callsCursor.moveToPosition(endPoint - 1);
373                     endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
374                 }
375                 if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
376             }
377         } catch (CursorWindowAllocationException e) {
378             Log.e(TAG, "CursorWindowAllocationException while composing calllog vcards");
379         } finally {
380             if (callsCursor != null) {
381                 callsCursor.close();
382                 callsCursor = null;
383             }
384         }
385 
386         String recordSelection;
387         if (startPoint == endPoint) {
388             recordSelection = Calls._ID + "=" + startPointId;
389         } else {
390             // The query to call table is by "_id DESC" order, so change
391             // correspondingly.
392             recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
393                     + startPointId;
394         }
395 
396         String selection;
397         if (typeSelection == null) {
398             selection = recordSelection;
399         } else {
400             selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
401         }
402 
403         if (V) Log.v(TAG, "Call log query selection is: " + selection);
404 
405         return composeAndSendVCards(op, selection, vcardType21, null, false, ignorefilter, filter);
406     }
407 
composeAndSendPhonebookVcards(Operation op, final int startPoint, final int endPoint, final boolean vcardType21, String ownerVCard, boolean ignorefilter, byte[] filter)408     public final int composeAndSendPhonebookVcards(Operation op, final int startPoint,
409             final int endPoint, final boolean vcardType21, String ownerVCard,
410             boolean ignorefilter, byte[] filter) {
411         if (startPoint < 1 || startPoint > endPoint) {
412             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
413             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
414         }
415         final Uri myUri = Contacts.CONTENT_URI;
416 
417         Cursor contactCursor = null;
418         long startPointId = 0;
419         long endPointId = 0;
420         try {
421             contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null,
422                     Contacts._ID);
423             if (contactCursor != null) {
424                 contactCursor.moveToPosition(startPoint - 1);
425                 startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
426                 if (V) Log.v(TAG, "Query startPointId = " + startPointId);
427                 if (startPoint == endPoint) {
428                     endPointId = startPointId;
429                 } else {
430                     contactCursor.moveToPosition(endPoint - 1);
431                     endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
432                 }
433                 if (V) Log.v(TAG, "Query endPointId = " + endPointId);
434             }
435         } catch (CursorWindowAllocationException e) {
436             Log.e(TAG, "CursorWindowAllocationException while composing phonebook vcards");
437         } finally {
438             if (contactCursor != null) {
439                 contactCursor.close();
440                 contactCursor = null;
441             }
442         }
443 
444         final String selection;
445         if (startPoint == endPoint) {
446             selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE;
447         } else {
448             selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<="
449                     + endPointId + " AND " + CLAUSE_ONLY_VISIBLE;
450         }
451 
452         if (V) Log.v(TAG, "Query selection is: " + selection);
453 
454         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true,
455             ignorefilter, filter);
456     }
457 
composeAndSendPhonebookOneVcard(Operation op, final int offset, final boolean vcardType21, String ownerVCard, int orderByWhat, boolean ignorefilter, byte[] filter)458     public final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
459             final boolean vcardType21, String ownerVCard, int orderByWhat,
460             boolean ignorefilter, byte[] filter) {
461         if (offset < 1) {
462             Log.e(TAG, "Internal error: offset is not correct.");
463             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
464         }
465         final Uri myUri = Contacts.CONTENT_URI;
466         Cursor contactCursor = null;
467         String selection = null;
468         long contactId = 0;
469         if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
470             try {
471                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
472                         null, Contacts._ID);
473                 if (contactCursor != null) {
474                     contactCursor.moveToPosition(offset - 1);
475                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
476                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
477                 }
478             } catch (CursorWindowAllocationException e) {
479                 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by index");
480             } finally {
481                 if (contactCursor != null) {
482                     contactCursor.close();
483                     contactCursor = null;
484                 }
485             }
486         } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
487             try {
488                 contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
489                         null, Contacts.DISPLAY_NAME);
490                 if (contactCursor != null) {
491                     contactCursor.moveToPosition(offset - 1);
492                     contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
493                     if (V) Log.v(TAG, "Query startPointId = " + contactId);
494                 }
495             } catch (CursorWindowAllocationException e) {
496                 Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by alphabetical");
497             } finally {
498                 if (contactCursor != null) {
499                     contactCursor.close();
500                     contactCursor = null;
501                 }
502             }
503         } else {
504             Log.e(TAG, "Parameter orderByWhat is not supported!");
505             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
506         }
507         selection = Contacts._ID + "=" + contactId;
508 
509         if (V) Log.v(TAG, "Query selection is: " + selection);
510 
511         return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true,
512             ignorefilter, filter);
513     }
514 
composeAndSendVCards(Operation op, final String selection, final boolean vcardType21, String ownerVCard, boolean isContacts, boolean ignorefilter, byte[] filter)515     public final int composeAndSendVCards(Operation op, final String selection,
516             final boolean vcardType21, String ownerVCard, boolean isContacts,
517             boolean ignorefilter, byte[] filter) {
518         long timestamp = 0;
519         if (V) timestamp = System.currentTimeMillis();
520 
521         if (isContacts) {
522             VCardComposer composer = null;
523             FilterVcard vcardfilter= new FilterVcard();
524             if (!ignorefilter) {
525                 vcardfilter.setFilter(filter);
526             }
527             HandlerForStringBuffer buffer = null;
528             try {
529                 // Currently only support Generic Vcard 2.1 and 3.0
530                 int vcardType;
531                 if (vcardType21) {
532                     vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
533                 } else {
534                     vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
535                 }
536                 if (!vcardfilter.isPhotoEnabled()) {
537                     vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
538                 }
539 
540                 //Enhancement: customize Vcard based on preferences/settings and input from caller
541                 composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null);
542                 //End enhancement
543 
544                 // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
545                 // done by vCard library by default.
546                 composer.setPhoneNumberTranslationCallback(
547                         new VCardPhoneNumberTranslationCallback() {
548                             public String onValueReceived(
549                                     String rawValue, int type, String label, boolean isPrimary) {
550                                 // 'p' and 'w' are the standard characters for pause and wait
551                                 // (see RFC 3601)
552                                 // so use those when exporting phone numbers via vCard.
553                                 String numberWithControlSequence = rawValue
554                                         .replace(PhoneNumberUtils.PAUSE, 'p')
555                                         .replace(PhoneNumberUtils.WAIT, 'w');
556                                 return numberWithControlSequence;
557                             }
558                         });
559                 buffer = new HandlerForStringBuffer(op, ownerVCard);
560                 if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) ||
561                         !buffer.onInit(mContext)) {
562                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
563                 }
564 
565                 while (!composer.isAfterLast()) {
566                     if (BluetoothPbapObexServer.sIsAborted) {
567                         ((ServerOperation)op).isAborted = true;
568                         BluetoothPbapObexServer.sIsAborted = false;
569                         break;
570                     }
571                     String vcard = composer.createOneEntry();
572                     if (vcard == null) {
573                         Log.e(TAG, "Failed to read a contact. Error reason: "
574                                 + composer.getErrorReason());
575                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
576                     }
577                     if (V) Log.v (TAG , "vCard from composer: " + vcard);
578                     if (!ignorefilter) {
579                         vcard = vcardfilter.applyFilter(vcard, vcardType21);
580                         if (V) Log.v (TAG , "vCard on applying filter: " + vcard);
581                     }
582                     vcard = StripTelephoneNumber(vcard);
583                     if (V) {
584                         Log.v(TAG, "Vcard Entry:");
585                         Log.v(TAG,vcard);
586                     }
587 
588                     if (!buffer.onEntryCreated(vcard)) {
589                         // onEntryCreate() already emits error.
590                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
591                     }
592                 }
593             } finally {
594                 if (composer != null) {
595                     composer.terminate();
596                 }
597                 if (buffer != null) {
598                     buffer.onTerminate();
599                 }
600             }
601         } else { // CallLog
602             BluetoothPbapCallLogComposer composer = null;
603             HandlerForStringBuffer buffer = null;
604             try {
605 
606                 composer = new BluetoothPbapCallLogComposer(mContext);
607                 buffer = new HandlerForStringBuffer(op, ownerVCard);
608                 if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null,
609                                    CALLLOG_SORT_ORDER) ||
610                                    !buffer.onInit(mContext)) {
611                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
612                 }
613 
614                 while (!composer.isAfterLast()) {
615                     if (BluetoothPbapObexServer.sIsAborted) {
616                         ((ServerOperation)op).isAborted = true;
617                         BluetoothPbapObexServer.sIsAborted = false;
618                         break;
619                     }
620                     String vcard = composer.createOneEntry(vcardType21);
621                     if (vcard == null) {
622                         Log.e(TAG, "Failed to read a contact. Error reason: "
623                                 + composer.getErrorReason());
624                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
625                     }
626                     if (V) {
627                         Log.v(TAG, "Vcard Entry:");
628                         Log.v(TAG,vcard);
629                     }
630 
631                     buffer.onEntryCreated(vcard);
632                 }
633             } finally {
634                 if (composer != null) {
635                     composer.terminate();
636                 }
637                 if (buffer != null) {
638                     buffer.onTerminate();
639                 }
640             }
641         }
642 
643         if (V) Log.v(TAG, "Total vcard composing and sending out takes "
644                     + (System.currentTimeMillis() - timestamp) + " ms");
645 
646         return ResponseCodes.OBEX_HTTP_OK;
647     }
648 
StripTelephoneNumber(String vCard)649     public String StripTelephoneNumber (String vCard){
650         String attr [] = vCard.split(System.getProperty("line.separator"));
651         String Vcard = "";
652             for (int i=0; i < attr.length; i++) {
653                 if(attr[i].startsWith("TEL")) {
654                     attr[i] = attr[i].replace("(", "");
655                     attr[i] = attr[i].replace(")", "");
656                     attr[i] = attr[i].replace("-", "");
657                     attr[i] = attr[i].replace(" ", "");
658                 }
659             }
660 
661             for (int i=0; i < attr.length; i++) {
662                 if(!attr[i].equals("")){
663                     Vcard = Vcard.concat(attr[i] + "\n");
664                 }
665             }
666         if (V) Log.v(TAG, "Vcard with stripped telephone no.: " + Vcard);
667         return Vcard;
668     }
669 
670     /**
671      * Handler to emit vCards to PCE.
672      */
673     public class HandlerForStringBuffer {
674         private Operation operation;
675 
676         private OutputStream outputStream;
677 
678         private String phoneOwnVCard = null;
679 
HandlerForStringBuffer(Operation op, String ownerVCard)680         public HandlerForStringBuffer(Operation op, String ownerVCard) {
681             operation = op;
682             if (ownerVCard != null) {
683                 phoneOwnVCard = ownerVCard;
684                 if (V) Log.v(TAG, "phone own number vcard:");
685                 if (V) Log.v(TAG, phoneOwnVCard);
686             }
687         }
688 
write(String vCard)689         private boolean write(String vCard) {
690             try {
691                 if (vCard != null) {
692                     outputStream.write(vCard.getBytes());
693                     return true;
694                 }
695             } catch (IOException e) {
696                 Log.e(TAG, "write outputstrem failed" + e.toString());
697             }
698             return false;
699         }
700 
onInit(Context context)701         public boolean onInit(Context context) {
702             try {
703                 outputStream = operation.openOutputStream();
704                 if (phoneOwnVCard != null) {
705                     return write(phoneOwnVCard);
706                 }
707                 return true;
708             } catch (IOException e) {
709                 Log.e(TAG, "open outputstrem failed" + e.toString());
710             }
711             return false;
712         }
713 
onEntryCreated(String vcard)714         public boolean onEntryCreated(String vcard) {
715             return write(vcard);
716         }
717 
onTerminate()718         public void onTerminate() {
719             if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
720                 if (V) Log.v(TAG, "CloseStream failed!");
721             } else {
722                 if (V) Log.v(TAG, "CloseStream ok!");
723             }
724         }
725     }
726 
727     public class FilterVcard{
728 
FilterVcard()729         public FilterVcard(){
730         };
731 
732         private final int FN_BIT = 1;
733 
734         private boolean fn = true;
735 
736         private final int PHOTO_BIT = 3;
737 
738         private boolean photo = true;
739 
740         //BDAY falls under events
741         private final int BDAY_BIT = 4;
742 
743         private boolean bday = true;
744 
745         private final int ADR_BIT = 5;
746 
747         private boolean adr = true;
748 
749         private final int EMAIL_BIT = 8;
750 
751         private boolean email = true;
752 
753         private final int TITLE_BIT = 12;
754 
755         private boolean title = true;
756 
757         private final int ORG_BIT = 16;
758 
759         private boolean org = true;
760 
761         private final int NOTES_BIT = 17;
762 
763         private boolean notes = true;
764 
765         private final int URL_BIT = 20;
766 
767         private boolean url = true;
768 
769         private final int NICKNAME_BIT = 23;
770 
771         private boolean nickname = true;
772 
setFilter(byte[] filter)773         public void setFilter(byte[] filter){
774 
775            fn = checkbit(FN_BIT, filter);
776            photo = checkbit(PHOTO_BIT, filter);
777            bday = checkbit(BDAY_BIT, filter);
778            adr = checkbit(ADR_BIT, filter);
779            email = checkbit(EMAIL_BIT, filter);
780            title = checkbit(TITLE_BIT, filter);
781            org = checkbit(ORG_BIT, filter);
782            notes = checkbit(NOTES_BIT, filter);
783            url = checkbit(URL_BIT, filter);
784            nickname = checkbit(NICKNAME_BIT, filter);
785         }
786 
checkbit(int attr_bit, byte[] filter)787         private boolean checkbit (int attr_bit, byte[] filter){
788             int filterlen = filter.length;
789             if( ((filter[filterlen -1 -((int)attr_bit/8)] >> (attr_bit%8)) & 0x01) == 0) {
790                 return false;
791             }
792             return true;
793         }
794 
isPhotoEnabled()795         public boolean isPhotoEnabled(){
796             return photo;
797         }
798 
checkValidFilter(String attr)799         private boolean checkValidFilter (String attr) {
800             if((attr.startsWith("N:")) || (attr.startsWith("TEL"))
801                 || (attr.startsWith("VERSION")) || (attr.startsWith("URL"))
802                 || (attr.startsWith("FN")) || (attr.startsWith("BDAY"))
803                 || (attr.startsWith("ADR")) || (attr.startsWith("EMAIL"))
804                 || (attr.startsWith("TITLE")) || (attr.startsWith("ORG"))
805                 || (attr.startsWith("NOTE")) || (attr.startsWith("NICKNAME"))) {
806                 return true;
807             }
808             return false;
809         }
810 
applyFilter( String vCard, boolean vCardType21)811         public String applyFilter ( String vCard, boolean vCardType21){
812             String attr [] = vCard.split(System.getProperty("line.separator"));
813             String filteredVcard = "";
814 
815             //FN is not the mandatory field in 2.1 vCard
816             if(((!fn) && (vCardType21)) && (vCard.contains("FN"))) {
817                 for (int i=0; i < attr.length; i++) {
818                     if(attr[i].startsWith("FN")){
819                         attr[i] = "";
820                         /** Remove multiline Content, if any */
821                         /** End traversal before END:VCARD */
822                         for (int j = i+1; j < attr.length - 1; j++) {
823                             if (checkValidFilter(attr[j])) {
824                                 break;
825                             } else {
826                                 /** Continuation of above attribute, remove */
827                                 attr[j] = "";
828                             }
829                         }
830                     }
831                 }
832             }
833 
834           //NOTE: No need to check photo, we already refrained it if it is not set in the filter
835             if((!bday) && (vCard.contains("BDAY"))) {
836                 for (int i=0; i < attr.length; i++) {
837                     if(attr[i].startsWith("BDAY")){
838                         attr[i] = "";
839                         /** Remove multiline Content, if any */
840                         /** End traversal before END:VCARD */
841                         for (int j = i+1; j < attr.length - 1; j++) {
842                             if (checkValidFilter(attr[j])) {
843                                 break;
844                             } else {
845                                 /** Continuation of above attribute, remove */
846                                 attr[j] = "";
847                             }
848                         }
849                     }
850                 }
851             }
852 
853             if((!adr) && (vCard.contains("ADR"))) {
854                 for (int i=0; i < attr.length; i++) {
855                     if(attr[i].startsWith("ADR")){
856                         attr[i] = "";
857                         /** Remove multiline Content, if any */
858                         /** End traversal before END:VCARD */
859                         for (int j = i+1; j < attr.length - 1; j++) {
860                             if (checkValidFilter(attr[j])) {
861                                 break;
862                             } else {
863                                 /** Continuation of above attribute, remove */
864                                 attr[j] = "";
865                             }
866                         }
867                     }
868                 }
869             }
870 
871             if((!email) && (vCard.contains("EMAIL"))) {
872                 for (int i=0; i < attr.length; i++) {
873                     if(attr[i].startsWith("EMAIL")){
874                         attr[i] = "";
875                         /** Remove multiline Content, if any */
876                         /** End traversal before END:VCARD */
877                         for (int j = i+1; j < attr.length - 1; j++) {
878                             if (checkValidFilter(attr[j])) {
879                                 break;
880                             } else {
881                                 /** Continuation of above attribute, remove */
882                                 attr[j] = "";
883                             }
884                         }
885                     }
886                 }
887             }
888 
889             if((!title) && (vCard.contains("TITLE"))) {
890                 for (int i=0; i < attr.length; i++) {
891                     if(attr[i].startsWith("TITLE")){
892                         attr[i] = "";
893                         /** Remove multiline Content, if any */
894                         /** End traversal before END:VCARD */
895                         for (int j = i+1; j < attr.length - 1; j++) {
896                             if (checkValidFilter(attr[j])) {
897                                 break;
898                             } else {
899                                 /** Continuation of above attribute, remove */
900                                 attr[j] = "";
901                             }
902                         }
903                     }
904                 }
905             }
906 
907             if((!org) && (vCard.contains("ORG"))) {
908                 for (int i=0; i < attr.length; i++) {
909                     if(attr[i].startsWith("ORG")){
910                         attr[i] = "";
911                         /** Remove multiline Content, if any */
912                         /** End traversal before END:VCARD */
913                         for (int j = i+1; j < attr.length - 1; j++) {
914                             if (checkValidFilter(attr[j])) {
915                                 break;
916                             } else {
917                                 /** Continuation of above attribute, remove */
918                                 attr[j] = "";
919                             }
920                         }
921                     }
922                 }
923             }
924 
925             if((!notes) && (vCard.contains("NOTE"))) {
926                 for (int i=0; i < attr.length; i++) {
927                     if(attr[i].startsWith("NOTE")){
928                         attr[i] = "";
929                         /** Remove multiline Content, if any */
930                         /** End traversal before END:VCARD */
931                         for (int j = i+1; j < attr.length - 1; j++) {
932                             if (checkValidFilter(attr[j])) {
933                                 break;
934                             } else {
935                                 /** Continuation of above attribute, remove */
936                                 attr[j] = "";
937                             }
938                         }
939                     }
940                 }
941             }
942             /*Nickname is not supported in 2.1 version.
943              *Android still ads it for 2.1 with nickname mentioned in lower case, and therefore
944              *we need to check for both cases.
945              */
946             if(((!nickname) || (vCardType21)) && (vCard.contains("NICKNAME"))) {
947                 for (int i=0; i < attr.length; i++) {
948                     if(attr[i].startsWith("NICKNAME")){
949                         attr[i] = "";
950                         /** Remove multiline Content, if any */
951                         /** End traversal before END:VCARD */
952                         for (int j = i+1; j < attr.length - 1; j++) {
953                             if (checkValidFilter(attr[j])) {
954                                 break;
955                             } else {
956                                 /** Continuation of above attribute, remove */
957                                 attr[j] = "";
958                             }
959                         }
960                     }
961                 }
962             }
963 
964             if((!url) && (vCard.contains("URL"))) {
965                 for (int i=0; i < attr.length; i++) {
966                     if(attr[i].startsWith("URL")){
967                         attr[i] = "";
968                         /** Remove multiline Content, if any */
969                         /** End traversal before END:VCARD */
970                         for (int j = i+1; j < attr.length - 1; j++) {
971                             if (checkValidFilter(attr[j])) {
972                                 break;
973                             } else {
974                                 /** Continuation of above attribute, remove */
975                                 attr[j] = "";
976                             }
977                         }
978                     }
979                 }
980             }
981             /*Since PBAP does not have filter bit for IM and SIP,
982              *removing them by default.
983             */
984             if(vCard.toUpperCase().contains("IM")) {
985                 for (int i=0; i < attr.length; i++) {
986                     if(attr[i].toUpperCase().contains("IM")){
987                         vCard = vCard.replace(attr[i] + "\n", "");
988                     }
989                 }
990             }
991 
992             if(vCard.toUpperCase().contains("SIP")) {
993                 for (int i=0; i < attr.length; i++) {
994                     if(attr[i].toUpperCase().contains("SIP")){
995                         vCard = vCard.replace(attr[i] + "\n", "");
996                     }
997                 }
998             }
999 
1000             Log.v(TAG, "Tokens after applying filter: ");
1001 
1002             for (int i=0; i < attr.length; i++) {
1003                 if(!attr[i].equals("")){
1004                     filteredVcard = filteredVcard.concat(attr[i] + "\n");
1005                 }
1006             }
1007 
1008             return filteredVcard;
1009         }
1010     }
1011 }
1012