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