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