Home | History | Annotate | Download | only in pbap
      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 
     85         public ContactData() {
     86             phone = new ArrayList<String>();
     87             email = new ArrayList<String>();
     88             address = new ArrayList<String>();
     89         }
     90 
     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 
    109     public static boolean hasFilter(byte[] filter) {
    110         return filter != null && filter.length > 0;
    111     }
    112 
    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 
    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 
    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 
    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 
    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     }
    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 
    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 
    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.*/
    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.*/
    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 
    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. */
    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*/
    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 
    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 
    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