• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /************************************************************************************
2  *
3  *  Copyright (C) 2009-2012 Broadcom Corporation
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ************************************************************************************/
18 package com.android.bluetooth.pbap;
19 
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.content.SharedPreferences.Editor;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.preference.PreferenceManager;
27 import android.provider.ContactsContract.CommonDataKinds.Email;
28 import android.provider.ContactsContract.CommonDataKinds.Phone;
29 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
30 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
31 import android.provider.ContactsContract.Contacts;
32 import android.provider.ContactsContract.Data;
33 import android.provider.ContactsContract.Profile;
34 import android.provider.ContactsContract.RawContactsEntity;
35 import android.util.Log;
36 
37 import com.android.bluetooth.BluetoothMethodProxy;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.vcard.VCardComposer;
40 import com.android.vcard.VCardConfig;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Calendar;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.Objects;
48 import java.util.concurrent.atomic.AtomicLong;
49 
50 class BluetoothPbapUtils {
51     private static final String TAG = "BluetoothPbapUtils";
52     private static final boolean V = BluetoothPbapService.VERBOSE;
53 
54     // Filter constants from Bluetooth PBAP specification
55     private static final int FILTER_PHOTO = 3;
56     private static final int FILTER_BDAY = 4;
57     private static final int FILTER_ADDRESS = 5;
58     private static final int FILTER_LABEL = 6;
59     private static final int FILTER_EMAIL = 8;
60     private static final int FILTER_MAILER = 9;
61     private static final int FILTER_ORG = 16;
62     private static final int FILTER_NOTE = 17;
63     private static final int FILTER_SOUND = 19;
64     private static final int FILTER_URL = 20;
65     private static final int FILTER_NICKNAME = 23;
66 
67     private static final long QUERY_CONTACT_RETRY_INTERVAL = 4000;
68 
69     static AtomicLong sDbIdentifier = new AtomicLong();
70 
71     static long sPrimaryVersionCounter = 0;
72     static long sSecondaryVersionCounter = 0;
73     @VisibleForTesting
74     static long sTotalContacts = 0;
75 
76     /* totalFields and totalSvcFields used to update primary/secondary version
77      * counter between pbap sessions*/
78     @VisibleForTesting
79     static long sTotalFields = 0;
80     @VisibleForTesting
81     static long sTotalSvcFields = 0;
82     @VisibleForTesting
83     static long sContactsLastUpdated = 0;
84 
85     private static class ContactData {
86         private String mName;
87         private ArrayList<String> mEmail;
88         private ArrayList<String> mPhone;
89         private ArrayList<String> mAddress;
90 
ContactData()91         ContactData() {
92             mPhone = new ArrayList<>();
93             mEmail = new ArrayList<>();
94             mAddress = new ArrayList<>();
95         }
96 
ContactData(String name, ArrayList<String> phone, ArrayList<String> email, ArrayList<String> address)97         ContactData(String name, ArrayList<String> phone, ArrayList<String> email,
98                 ArrayList<String> address) {
99             this.mName = name;
100             this.mPhone = phone;
101             this.mEmail = email;
102             this.mAddress = address;
103         }
104     }
105 
106     @VisibleForTesting
107     static HashMap<String, ContactData> sContactDataset = new HashMap<>();
108 
109     @VisibleForTesting
110     static HashSet<String> sContactSet = new HashSet<>();
111 
112     @VisibleForTesting
113     static final String TYPE_NAME = "name";
114     @VisibleForTesting
115     static final String TYPE_PHONE = "phone";
116     @VisibleForTesting
117     static final String TYPE_EMAIL = "email";
118     @VisibleForTesting
119     static final String TYPE_ADDRESS = "address";
120 
hasFilter(byte[] filter)121     private static boolean hasFilter(byte[] filter) {
122         return filter != null && filter.length > 0;
123     }
124 
isFilterBitSet(byte[] filter, int filterBit)125     private static boolean isFilterBitSet(byte[] filter, int filterBit) {
126         if (hasFilter(filter)) {
127             int byteNumber = 7 - filterBit / 8;
128             int bitNumber = filterBit % 8;
129             if (byteNumber < filter.length) {
130                 return (filter[byteNumber] & (1 << bitNumber)) > 0;
131             }
132         }
133         return false;
134     }
135 
createFilteredVCardComposer(final Context ctx, final int vcardType, final byte[] filter)136     static VCardComposer createFilteredVCardComposer(final Context ctx, final int vcardType,
137             final byte[] filter) {
138         int vType = vcardType;
139         boolean includePhoto =
140                 BluetoothPbapConfig.includePhotosInVcard() && (!hasFilter(filter) || isFilterBitSet(
141                         filter, FILTER_PHOTO));
142         if (!includePhoto) {
143             if (V) {
144                 Log.v(TAG, "Excluding images from VCardComposer...");
145             }
146             vType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
147         }
148         if (hasFilter(filter)) {
149             if (!isFilterBitSet(filter, FILTER_ADDRESS) && !isFilterBitSet(filter, FILTER_LABEL)) {
150                 Log.i(TAG, "Excluding addresses from VCardComposer...");
151                 vType |= VCardConfig.FLAG_REFRAIN_ADDRESS_EXPORT;
152             }
153             if (!isFilterBitSet(filter, FILTER_EMAIL) && !isFilterBitSet(filter, FILTER_MAILER)) {
154                 Log.i(TAG, "Excluding email addresses from VCardComposer...");
155                 vType |= VCardConfig.FLAG_REFRAIN_EMAIL_EXPORT;
156             }
157             if (!isFilterBitSet(filter, FILTER_ORG)) {
158                 Log.i(TAG, "Excluding organization from VCardComposer...");
159                 vType |= VCardConfig.FLAG_REFRAIN_ORGANIZATION_EXPORT;
160             }
161             if (!isFilterBitSet(filter, FILTER_URL)) {
162                 Log.i(TAG, "Excluding URLS from VCardComposer...");
163                 vType |= VCardConfig.FLAG_REFRAIN_WEBSITES_EXPORT;
164             }
165             if (!isFilterBitSet(filter, FILTER_NOTE)) {
166                 Log.i(TAG, "Excluding notes from VCardComposer...");
167                 vType |= VCardConfig.FLAG_REFRAIN_NOTES_EXPORT;
168             }
169             if (!isFilterBitSet(filter, FILTER_NICKNAME)) {
170                 Log.i(TAG, "Excluding nickname from VCardComposer...");
171                 vType |= VCardConfig.FLAG_REFRAIN_NICKNAME_EXPORT;
172             }
173             if (!isFilterBitSet(filter, FILTER_SOUND)) {
174                 Log.i(TAG, "Excluding phonetic name from VCardComposer...");
175                 vType |= VCardConfig.FLAG_REFRAIN_PHONETIC_NAME_EXPORT;
176             }
177             if (!isFilterBitSet(filter, FILTER_BDAY)) {
178                 Log.i(TAG, "Excluding birthday from VCardComposer...");
179                 vType |= VCardConfig.FLAG_REFRAIN_EVENTS_EXPORT;
180             }
181         }
182         return new VCardComposer(ctx, vType, true);
183     }
184 
getProfileName(Context context)185     public static String getProfileName(Context context) {
186         Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(
187                 context.getContentResolver(), Profile.CONTENT_URI,
188                 new String[]{Profile.DISPLAY_NAME}, null, null, null);
189         String ownerName = null;
190         if (c != null && c.moveToFirst()) {
191             ownerName = c.getString(0);
192         }
193         if (c != null) {
194             c.close();
195         }
196         return ownerName;
197     }
198 
createProfileVCard(Context ctx, final int vcardType, final byte[] filter)199     static String createProfileVCard(Context ctx, final int vcardType, final byte[] filter) {
200         VCardComposer composer = null;
201         String vcard = null;
202         try {
203             composer = createFilteredVCardComposer(ctx, vcardType, filter);
204             if (composer.init(Profile.CONTENT_URI, null, null, null, null,
205                     Uri.withAppendedPath(Profile.CONTENT_URI,
206                             RawContactsEntity.CONTENT_URI.getLastPathSegment()))) {
207                 vcard = composer.createOneEntry();
208             } else {
209                 Log.e(TAG, "Unable to create profile vcard. Error initializing composer: "
210                         + composer.getErrorReason());
211             }
212         } catch (Throwable t) {
213             Log.e(TAG, "Unable to create profile vcard.", t);
214         }
215         if (composer != null) {
216             composer.terminate();
217         }
218         return vcard;
219     }
220 
savePbapParams(Context ctx)221     static void savePbapParams(Context ctx) {
222         SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);
223         long dbIdentifier = sDbIdentifier.get();
224         Editor edit = pref.edit();
225         edit.putLong("primary", sPrimaryVersionCounter);
226         edit.putLong("secondary", sSecondaryVersionCounter);
227         edit.putLong("dbIdentifier", dbIdentifier);
228         edit.putLong("totalContacts", sTotalContacts);
229         edit.putLong("lastUpdatedTimestamp", sContactsLastUpdated);
230         edit.putLong("totalFields", sTotalFields);
231         edit.putLong("totalSvcFields", sTotalSvcFields);
232         edit.apply();
233 
234         if (V) {
235             Log.v(TAG, "Saved Primary:" + sPrimaryVersionCounter + ", Secondary:"
236                     + sSecondaryVersionCounter + ", Database Identifier: " + dbIdentifier);
237         }
238     }
239 
240     /* fetchPbapParams() loads preserved value of Database Identifiers and folder
241      * version counters. Servers using a database identifier 0 or regenerating
242      * one at each connection will not benefit from the resulting performance and
243      * user experience improvements. So database identifier is set with current
244      * timestamp and updated on rollover of folder version counter.*/
fetchPbapParams(Context ctx)245     static void fetchPbapParams(Context ctx) {
246         SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);
247         long timeStamp = Calendar.getInstance().getTimeInMillis();
248         BluetoothPbapUtils.sDbIdentifier.set(pref.getLong("DbIdentifier", timeStamp));
249         BluetoothPbapUtils.sPrimaryVersionCounter = pref.getLong("primary", 0);
250         BluetoothPbapUtils.sSecondaryVersionCounter = pref.getLong("secondary", 0);
251         BluetoothPbapUtils.sTotalFields = pref.getLong("totalContacts", 0);
252         BluetoothPbapUtils.sContactsLastUpdated = pref.getLong("lastUpdatedTimestamp", timeStamp);
253         BluetoothPbapUtils.sTotalFields = pref.getLong("totalFields", 0);
254         BluetoothPbapUtils.sTotalSvcFields = pref.getLong("totalSvcFields", 0);
255         if (V) {
256             Log.v(TAG, " fetchPbapParams " + pref.getAll());
257         }
258     }
259 
loadAllContacts(Context context, Handler handler)260     static void loadAllContacts(Context context, Handler handler) {
261         if (V) {
262             Log.v(TAG, "Loading Contacts ...");
263         }
264 
265         String[] projection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
266         sTotalContacts = fetchAndSetContacts(context, handler, projection, null, null, true);
267         if (sTotalContacts < 0) {
268             sTotalContacts = 0;
269             return;
270         }
271         handler.sendMessage(handler.obtainMessage(BluetoothPbapService.CONTACTS_LOADED));
272     }
273 
updateSecondaryVersionCounter(Context context, Handler handler)274     static void updateSecondaryVersionCounter(Context context, Handler handler) {
275             /* updatedList stores list of contacts which are added/updated after
276              * the time when contacts were last updated. (contactsLastUpdated
277              * indicates the time when contact/contacts were last updated and
278              * corresponding changes were reflected in Folder Version Counters).*/
279         ArrayList<String> updatedList = new ArrayList<>();
280         HashSet<String> currentContactSet = new HashSet<>();
281 
282         String[] projection = {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP};
283         Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(
284                 context.getContentResolver(), Contacts.CONTENT_URI, projection, null, null, null);
285 
286         if (c == null) {
287             Log.d(TAG, "Failed to fetch data from contact database");
288             return;
289         }
290         while (c.moveToNext()) {
291             String contactId = c.getString(0);
292             long lastUpdatedTime = c.getLong(1);
293             if (lastUpdatedTime > sContactsLastUpdated) {
294                 updatedList.add(contactId);
295             }
296             currentContactSet.add(contactId);
297         }
298         int currentContactCount = c.getCount();
299         c.close();
300 
301         if (V) {
302             Log.v(TAG, "updated list =" + updatedList);
303         }
304         String[] dataProjection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
305 
306         String whereClause = Data.CONTACT_ID + "=?";
307 
308             /* code to check if new contact/contacts are added */
309         if (currentContactCount > sTotalContacts) {
310             for (String contact : updatedList) {
311                 String[] selectionArgs = {contact};
312                 fetchAndSetContacts(context, handler, dataProjection, whereClause, selectionArgs,
313                         false);
314                 sSecondaryVersionCounter++;
315                 sPrimaryVersionCounter++;
316                 sTotalContacts = currentContactCount;
317             }
318                 /* When contact/contacts are deleted */
319         } else if (currentContactCount < sTotalContacts) {
320             sTotalContacts = currentContactCount;
321             ArrayList<String> svcFields = new ArrayList<>(
322                     Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
323                             Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE));
324             HashSet<String> deletedContacts = new HashSet<>(sContactSet);
325             deletedContacts.removeAll(currentContactSet);
326             sPrimaryVersionCounter += deletedContacts.size();
327             sSecondaryVersionCounter += deletedContacts.size();
328             if (V) {
329                 Log.v(TAG, "Deleted Contacts : " + deletedContacts);
330             }
331 
332             // to decrement totalFields and totalSvcFields count
333             for (String deletedContact : deletedContacts) {
334                 sContactSet.remove(deletedContact);
335                 String[] selectionArgs = {deletedContact};
336                 Cursor dataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
337                         context.getContentResolver(), Data.CONTENT_URI, dataProjection, whereClause,
338                         selectionArgs, null);
339 
340                 if (dataCursor == null) {
341                     Log.d(TAG, "Failed to fetch data from contact database");
342                     return;
343                 }
344 
345                 while (dataCursor.moveToNext()) {
346                     if (svcFields.contains(
347                             dataCursor.getString(dataCursor.getColumnIndex(Data.MIMETYPE)))) {
348                         sTotalSvcFields--;
349                     }
350                     sTotalFields--;
351                 }
352                 dataCursor.close();
353             }
354 
355                 /* When contacts are updated. i.e. Fields of existing contacts are
356                  * added/updated/deleted */
357         } else {
358             for (String contact : updatedList) {
359                 sPrimaryVersionCounter++;
360                 ArrayList<String> phoneTmp = new ArrayList<>();
361                 ArrayList<String> emailTmp = new ArrayList<>();
362                 ArrayList<String> addressTmp = new ArrayList<>();
363                 String nameTmp = null;
364                 boolean updated = false;
365 
366                 String[] selectionArgs = {contact};
367                 Cursor dataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
368                         context.getContentResolver(), Data.CONTENT_URI, dataProjection, whereClause,
369                         selectionArgs, null);
370 
371                 if (dataCursor == null) {
372                     Log.d(TAG, "Failed to fetch data from contact database");
373                     return;
374                 }
375                 // fetch all updated contacts and compare with cached copy of contacts
376                 int indexData = dataCursor.getColumnIndex(Data.DATA1);
377                 int indexMimeType = dataCursor.getColumnIndex(Data.MIMETYPE);
378                 String data;
379                 String mimeType;
380                 while (dataCursor.moveToNext()) {
381                     data = dataCursor.getString(indexData);
382                     mimeType = dataCursor.getString(indexMimeType);
383                     switch (mimeType) {
384                         case Email.CONTENT_ITEM_TYPE:
385                             emailTmp.add(data);
386                             break;
387                         case Phone.CONTENT_ITEM_TYPE:
388                             phoneTmp.add(data);
389                             break;
390                         case StructuredPostal.CONTENT_ITEM_TYPE:
391                             addressTmp.add(data);
392                             break;
393                         case StructuredName.CONTENT_ITEM_TYPE:
394                             nameTmp = data;
395                             break;
396                     }
397                 }
398                 ContactData cData = new ContactData(nameTmp, phoneTmp, emailTmp, addressTmp);
399                 dataCursor.close();
400 
401                 ContactData currentContactData = sContactDataset.get(contact);
402                 if (currentContactData == null) {
403                     Log.e(TAG, "Null contact in the updateList: " + contact);
404                     continue;
405                 }
406 
407                 if (!Objects.equals(nameTmp, currentContactData.mName)) {
408                     updated = true;
409                 } else if (checkFieldUpdates(currentContactData.mPhone, phoneTmp)) {
410                     updated = true;
411                 } else if (checkFieldUpdates(currentContactData.mEmail, emailTmp)) {
412                     updated = true;
413                 } else if (checkFieldUpdates(currentContactData.mAddress, addressTmp)) {
414                     updated = true;
415                 }
416 
417                 if (updated) {
418                     sSecondaryVersionCounter++;
419                     sContactDataset.put(contact, cData);
420                 }
421             }
422         }
423 
424         Log.d(TAG,
425                 "primaryVersionCounter = " + sPrimaryVersionCounter + ", secondaryVersionCounter="
426                         + sSecondaryVersionCounter);
427 
428         // check if Primary/Secondary version Counter has rolled over
429         if (sSecondaryVersionCounter < 0 || sPrimaryVersionCounter < 0) {
430             handler.sendMessage(handler.obtainMessage(BluetoothPbapService.ROLLOVER_COUNTERS));
431         }
432     }
433 
434     /* checkFieldUpdates checks update contact fields of a particular contact.
435      * Field update can be a field updated/added/deleted in an existing contact.
436      * Returns true if any contact field is updated else return false. */
437     @VisibleForTesting
checkFieldUpdates(ArrayList<String> oldFields, ArrayList<String> newFields)438     static boolean checkFieldUpdates(ArrayList<String> oldFields,
439             ArrayList<String> newFields) {
440         if (newFields != null && oldFields != null) {
441             if (newFields.size() != oldFields.size()) {
442                 sTotalSvcFields += Math.abs(newFields.size() - oldFields.size());
443                 sTotalFields += Math.abs(newFields.size() - oldFields.size());
444                 return true;
445             }
446             for (String newField : newFields) {
447                 if (!oldFields.contains(newField)) {
448                     return true;
449                 }
450             }
451             /* when all fields of type(phone/email/address) are deleted in a given contact*/
452         } else if (newFields == null && oldFields != null && oldFields.size() > 0) {
453             sTotalSvcFields += oldFields.size();
454             sTotalFields += oldFields.size();
455             return true;
456 
457             /* when new fields are added for a type(phone/email/address) in a contact
458              * for which there were no fields of this type earliar.*/
459         } else if (oldFields == null && newFields != null && newFields.size() > 0) {
460             sTotalSvcFields += newFields.size();
461             sTotalFields += newFields.size();
462             return true;
463         }
464         return false;
465     }
466 
467     /* fetchAndSetContacts reads contacts and caches them
468      * isLoad = true indicates its loading all contacts
469      * isLoad = false indiacates its caching recently added contact in database*/
470     @VisibleForTesting
fetchAndSetContacts(Context context, Handler handler, String[] projection, String whereClause, String[] selectionArgs, boolean isLoad)471     static int fetchAndSetContacts(Context context, Handler handler, String[] projection,
472             String whereClause, String[] selectionArgs, boolean isLoad) {
473         long currentTotalFields = 0, currentSvcFieldCount = 0;
474         Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(
475                 context.getContentResolver(), Data.CONTENT_URI, projection, whereClause,
476                 selectionArgs, null);
477 
478         /* send delayed message to loadContact when ContentResolver is unable
479          * to fetch data from contact database using the specified URI at that
480          * moment (Case: immediate Pbap connect on system boot with BT ON)*/
481         if (c == null) {
482             Log.d(TAG, "Failed to fetch contacts data from database..");
483             if (isLoad) {
484                 handler.sendMessageDelayed(
485                         handler.obtainMessage(BluetoothPbapService.LOAD_CONTACTS),
486                         QUERY_CONTACT_RETRY_INTERVAL);
487             }
488             return -1;
489         }
490 
491         int indexCId = c.getColumnIndex(Data.CONTACT_ID);
492         int indexData = c.getColumnIndex(Data.DATA1);
493         int indexMimeType = c.getColumnIndex(Data.MIMETYPE);
494         String contactId, data, mimeType;
495 
496         while (c.moveToNext()) {
497             if (c.isNull(indexCId)) {
498                 Log.w(TAG, "_id column is null. Row was deleted during iteration, skipping");
499                 continue;
500             }
501             contactId = c.getString(indexCId);
502             data = c.getString(indexData);
503             mimeType = c.getString(indexMimeType);
504             /* fetch phone/email/address/name information of the contact */
505             switch (mimeType) {
506                 case Phone.CONTENT_ITEM_TYPE:
507                     setContactFields(TYPE_PHONE, contactId, data);
508                     currentSvcFieldCount++;
509                     break;
510                 case Email.CONTENT_ITEM_TYPE:
511                     setContactFields(TYPE_EMAIL, contactId, data);
512                     currentSvcFieldCount++;
513                     break;
514                 case StructuredPostal.CONTENT_ITEM_TYPE:
515                     setContactFields(TYPE_ADDRESS, contactId, data);
516                     currentSvcFieldCount++;
517                     break;
518                 case StructuredName.CONTENT_ITEM_TYPE:
519                     setContactFields(TYPE_NAME, contactId, data);
520                     currentSvcFieldCount++;
521                     break;
522             }
523             sContactSet.add(contactId);
524             currentTotalFields++;
525         }
526         c.close();
527 
528         /* This code checks if there is any update in contacts after last pbap
529          * disconnect has happenned (even if BT is turned OFF during this time)*/
530         if (isLoad && currentTotalFields != sTotalFields) {
531             sPrimaryVersionCounter += Math.abs(sTotalContacts - sContactSet.size());
532 
533             if (currentSvcFieldCount != sTotalSvcFields) {
534                 if (sTotalContacts != sContactSet.size()) {
535                     sSecondaryVersionCounter += Math.abs(sTotalContacts - sContactSet.size());
536                 } else {
537                     sSecondaryVersionCounter++;
538                 }
539             }
540             if (sPrimaryVersionCounter < 0 || sSecondaryVersionCounter < 0) {
541                 rolloverCounters();
542             }
543 
544             sTotalFields = currentTotalFields;
545             sTotalSvcFields = currentSvcFieldCount;
546             sContactsLastUpdated = System.currentTimeMillis();
547             Log.d(TAG, "Contacts updated between last BT OFF and current"
548                     + "Pbap Connect, primaryVersionCounter=" + sPrimaryVersionCounter
549                     + ", secondaryVersionCounter=" + sSecondaryVersionCounter);
550         } else if (!isLoad) {
551             sTotalFields++;
552             sTotalSvcFields++;
553         }
554         return sContactSet.size();
555     }
556 
557     /* setContactFields() is used to store contacts data in local cache (phone,
558      * email or address which is required for updating Secondary Version counter).
559      * contactsFieldData - List of field data for phone/email/address.
560      * contactId - Contact ID, data1 - field value from data table for phone/email/address*/
561     @VisibleForTesting
setContactFields(String fieldType, String contactId, String data)562     static void setContactFields(String fieldType, String contactId, String data) {
563         ContactData cData;
564         if (sContactDataset.containsKey(contactId)) {
565             cData = sContactDataset.get(contactId);
566         } else {
567             cData = new ContactData();
568         }
569 
570         switch (fieldType) {
571             case TYPE_NAME:
572                 cData.mName = data;
573                 break;
574             case TYPE_PHONE:
575                 cData.mPhone.add(data);
576                 break;
577             case TYPE_EMAIL:
578                 cData.mEmail.add(data);
579                 break;
580             case TYPE_ADDRESS:
581                 cData.mAddress.add(data);
582                 break;
583         }
584         sContactDataset.put(contactId, cData);
585     }
586 
587     /* As per Pbap 1.2 specification, Database Identifies shall be
588      * re-generated when a Folder Version Counter rolls over or starts over.*/
589 
rolloverCounters()590     static void rolloverCounters() {
591         sDbIdentifier.set(Calendar.getInstance().getTimeInMillis());
592         sPrimaryVersionCounter = (sPrimaryVersionCounter < 0) ? 0 : sPrimaryVersionCounter;
593         sSecondaryVersionCounter = (sSecondaryVersionCounter < 0) ? 0 : sSecondaryVersionCounter;
594         if (V) {
595             Log.v(TAG, "DbIdentifier rolled over to:" + sDbIdentifier);
596         }
597     }
598 }
599