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