summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHemant Gupta <hemantg@codeaurora.org>2017-03-09 20:30:54 +0530
committerAndre Eisenbach <eisenbach@google.com>2017-06-09 18:22:19 +0000
commit91e595b5bef1b092975992e529b2c581eb1be08b (patch)
treeb3fc786470702ad9097dc22fbf6046200ac3ed37
parentcecda8201dad0b6d2007f6c7e31b1113729d7850 (diff)
downloadBluetooth-91e595b5bef1b092975992e529b2c581eb1be08b.tar.gz
PBAP: Implement Folder Version Counter(s)
Primary version counter should be updated on any change to contact. Secondary version counter should not update when there is change to fields other than F, FN, TEL, EMAIL or ADDRESS. There is no default API available in android to determine which field is updated as required for updating secondary version counter. Implemented logic to increment the secondary version counter by comparing updated contacts with old cached contacts of the contact database. Test: Verified that folder version counter gets incremented for change(s) in fields of Contacts as per PBAP specification. TestTracker: 89053 Bug: 33011817 Change-Id: I90a1ced3713c369bcde0edd627bbde57a9676708 (cherry picked from commit cf92c2759b709461acf7c36c08f4eef9c8004c85)
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java28
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapService.java75
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapUtils.java428
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java2
4 files changed, 490 insertions, 43 deletions
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index 092c196fa..4aeea0e4f 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -365,6 +365,8 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
+ /* TODO: block Get request if contacts are not completely loaded locally */
+
if (V) logHeader(request);
if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name);
@@ -1009,6 +1011,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
if (needSendBody != NEED_SEND_BODY) {
+ op.noBodyHeader();
return needSendBody;
}
@@ -1118,6 +1121,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name);
if (needSendBody != NEED_SEND_BODY) {
+ op.noBodyHeader();
return needSendBody;
}
@@ -1334,34 +1338,18 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
ByteBuffer pvc = ByteBuffer.allocate(16);
pvc.putLong(primaryVcMsb);
- Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapService.primaryVersionCounter);
-
- updatePBSecondaryFolderVersion(BluetoothPbapService.primaryVersionCounter);
- pvc.putLong(BluetoothPbapService.primaryVersionCounter);
- BluetoothPbapService.primaryVersionCounter = 0;
+ Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.primaryVersionCounter);
+ pvc.putLong(BluetoothPbapUtils.primaryVersionCounter);
return pvc.array();
}
- private void updatePBSecondaryFolderVersion(long counterValue) {
- // Workaround for passing PTS test case TC_PSE_PBD_BV_9_C
- if (SystemProperties.getBoolean("ro.bluetooth.pbap.sec", false)) {
- Log.d(TAG, "Reset secondary folder version bit for PTS case");
- BluetoothPbapService.secondaryVersionCounter = 0;
- } else {
- BluetoothPbapService.secondaryVersionCounter =
- BluetoothPbapService.primaryVersionCounter;
- }
- }
-
private byte[] getPBSecondaryFolderVersion() {
long secondaryVcMsb = 0;
ByteBuffer svc = ByteBuffer.allocate(16);
svc.putLong(secondaryVcMsb);
- Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapService.secondaryVersionCounter);
-
- svc.putLong(BluetoothPbapService.secondaryVersionCounter);
- BluetoothPbapService.secondaryVersionCounter = 0;
+ Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.secondaryVersionCounter);
+ svc.putLong(BluetoothPbapUtils.secondaryVersionCounter);
return svc.array();
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index e4e8d4d88..8462b65b6 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -74,6 +74,7 @@ import com.android.bluetooth.util.DevicePolicyUtils;
import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.HashMap;
import javax.obex.ServerSession;
@@ -146,6 +147,14 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
private static final int AUTH_TIMEOUT = 3;
+ private static final int SHUTDOWN = 4;
+
+ protected static final int LOAD_CONTACTS = 5;
+
+ private static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
+
+ protected static final int ROLLOVER_COUNTERS = 7;
+
private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
@@ -201,15 +210,9 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
private boolean mSdpSearchInitiated = false;
- private static AtomicLong mDbIndetifier = new AtomicLong();
-
- static long primaryVersionCounter = 0;
-
- static long secondaryVersionCounter = 0;
-
private boolean isRegisteredObserver = false;
- private static final int SHUTDOWN = 4;
+ protected Context mContext;
// package and class name to which we send intent to check phone book access permission
private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
@@ -224,7 +227,12 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, " onChange on contact uri ");
- primaryVersionCounter++;
+ if (BluetoothPbapUtils.contactsLoaded) {
+ if (!mSessionStatusHandler.hasMessages(CHECK_SECONDARY_VERSION_COUNTER)) {
+ mSessionStatusHandler.sendMessage(
+ mSessionStatusHandler.obtainMessage(CHECK_SECONDARY_VERSION_COUNTER));
+ }
+ }
}
}
@@ -232,6 +240,7 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
public BluetoothPbapService() {
mState = BluetoothPbap.STATE_DISCONNECTED;
+ mContext = this;
}
// process the intent from receiver
@@ -250,11 +259,11 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
}
// Release all resources
closeService();
- return;
} else if (state == BluetoothAdapter.STATE_ON) {
// start RFCOMM listener
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
}
+ return;
}
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) {
@@ -416,6 +425,11 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
private final void closeService() {
if (VERBOSE) Log.v(TAG, "Pbap Service closeService in");
+ BluetoothPbapUtils.savePbapParams(this, BluetoothPbapUtils.primaryVersionCounter,
+ BluetoothPbapUtils.secondaryVersionCounter, BluetoothPbapUtils.mDbIdentifier.get(),
+ BluetoothPbapUtils.contactsLastUpdated, BluetoothPbapUtils.totalFields,
+ BluetoothPbapUtils.totalSvcFields, BluetoothPbapUtils.totalContacts);
+
// exit initSocket early
mInterrupted = true;
if (mWakeLock != null) {
@@ -475,7 +489,6 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
private void stopObexServerSession() {
if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
-
mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
// Release the wake lock if obex transaction is over
@@ -626,7 +639,7 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
}
}
- private final Handler mSessionStatusHandler = new Handler() {
+ protected final Handler mSessionStatusHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
@@ -690,6 +703,15 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
case SHUTDOWN:
closeService();
break;
+ case LOAD_CONTACTS:
+ BluetoothPbapUtils.loadAllContacts(mContext, this);
+ break;
+ case CHECK_SECONDARY_VERSION_COUNTER:
+ BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, this);
+ break;
+ case ROLLOVER_COUNTERS:
+ BluetoothPbapUtils.rolloverCounters();
+ break;
default:
break;
}
@@ -802,9 +824,17 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
if (mContactChangeObserver == null) {
registerReceiver(mPbapReceiver, filter);
- mContactChangeObserver = new BluetoothPbapContentObserver();
- getContentResolver().registerContentObserver(
- DevicePolicyUtils.getEnterprisePhoneUri(this), false, mContactChangeObserver);
+ try {
+ if (DEBUG) Log.d(TAG, "Registering observer");
+ mContactChangeObserver = new BluetoothPbapContentObserver();
+ getContentResolver().registerContentObserver(
+ DevicePolicyUtils.getEnterprisePhoneUri(this), false,
+ mContactChangeObserver);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "SQLite exception: " + e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Illegal state exception, content observer is already registered");
+ }
}
return true;
}
@@ -937,18 +967,15 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
"OBEX Phonebook Access Server", mServerSockets.getRfcommChannel(),
mServerSockets.getL2capPsm(), SDP_PBAP_SERVER_VERSION,
SDP_PBAP_SUPPORTED_REPOSITORIES, SDP_PBAP_SUPPORTED_FEATURES);
- /* Here we might have changed crucial data, hence reset DB identifier */
- updateDbIdentifier();
+ // fetch Pbap Params to check if significant change has happened to Database
+ BluetoothPbapUtils.fetchPbapParams(mContext);
+
if (DEBUG) Log.d(TAG, "PBAP server with handle:" + mSdpHandle);
}
}
- private void updateDbIdentifier() {
- mDbIndetifier.set(Calendar.getInstance().getTime().getTime());
- }
-
long getDbIdentifier() {
- return mDbIndetifier.get();
+ return BluetoothPbapUtils.mDbIdentifier.get();
}
private void setUserTimeoutAlarm() {
@@ -985,6 +1012,12 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
} catch (IOException ex) {
Log.e(TAG, "Caught exception starting obex server session" + ex.toString());
}
+
+ if (!BluetoothPbapUtils.contactsLoaded) {
+ mSessionStatusHandler.sendMessage(
+ mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
+ }
+
} else if (permission == BluetoothDevice.ACCESS_REJECTED) {
if (DEBUG) {
Log.d(TAG, "incoming connection rejected from: " + sRemoteDeviceName
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
index d489a7a78..45f2e210b 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
@@ -18,11 +18,24 @@
package com.android.bluetooth.pbap;
import android.content.Context;
+import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.RawContactsEntity;
+
import android.util.Log;
import com.android.vcard.VCardComposer;
@@ -33,14 +46,65 @@ import com.android.bluetooth.pbap.BluetoothPbapService;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.lang.Math;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
public class BluetoothPbapUtils {
- private static final String TAG = "FilterUtils";
+ private static final String TAG = "BluetoothPbapUtils";
private static final boolean V = BluetoothPbapService.VERBOSE;
public static int FILTER_PHOTO = 3;
public static int FILTER_TEL = 7;
public static int FILTER_NICKNAME = 23;
+ private static final long QUERY_CONTACT_RETRY_INTERVAL = 4000;
+
+ protected static AtomicLong mDbIdentifier = new AtomicLong();
+
+ protected static long primaryVersionCounter = 0;
+ protected static long secondaryVersionCounter = 0;
+ public static long totalContacts = 0;
+
+ /* totalFields and totalSvcFields used to update primary/secondary version
+ * counter between pbap sessions*/
+ public static long totalFields = 0;
+ public static long totalSvcFields = 0;
+ public static long contactsLastUpdated = 0;
+ public static boolean contactsLoaded = false;
+
+ private static class ContactData {
+ private String name;
+ private ArrayList<String> email;
+ private ArrayList<String> phone;
+ private ArrayList<String> address;
+
+ public ContactData() {
+ phone = new ArrayList<String>();
+ email = new ArrayList<String>();
+ address = new ArrayList<String>();
+ }
+
+ public ContactData(String name, ArrayList<String> phone, ArrayList<String> email,
+ ArrayList<String> address) {
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ }
+ }
+
+ private static HashMap<String, ContactData> contactDataset = new HashMap<String, ContactData>();
+
+ private static HashSet<String> ContactSet = new HashSet<String>();
+
+ private static final String TYPE_NAME = "name";
+ private static final String TYPE_PHONE = "phone";
+ private static final String TYPE_EMAIL = "email";
+ private static final String TYPE_ADDRESS = "address";
public static boolean hasFilter(byte[] filter) {
return filter != null && filter.length > 0;
@@ -185,4 +249,366 @@ public class BluetoothPbapUtils {
Utils.safeCloseStream(os);
return success;
}
+
+ protected static void savePbapParams(Context ctx, long primaryCounter, long secondaryCounter,
+ long dbIdentifier, long lastUpdatedTimestamp, long totalFields, long totalSvcFields,
+ long totalContacts) {
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);
+ Editor edit = pref.edit();
+ edit.putLong("primary", primaryCounter);
+ edit.putLong("secondary", secondaryCounter);
+ edit.putLong("dbIdentifier", dbIdentifier);
+ edit.putLong("totalContacts", totalContacts);
+ edit.putLong("lastUpdatedTimestamp", lastUpdatedTimestamp);
+ edit.putLong("totalFields", totalFields);
+ edit.putLong("totalSvcFields", totalSvcFields);
+ edit.apply();
+
+ if (V)
+ Log.v(TAG, "Saved Primary:" + primaryCounter + ", Secondary:" + secondaryCounter
+ + ", Database Identifier: " + dbIdentifier);
+ }
+
+ /* fetchPbapParams() loads preserved value of Database Identifiers and folder
+ * version counters. Servers using a database identifier 0 or regenerating
+ * one at each connection will not benefit from the resulting performance and
+ * user experience improvements. So database identifier is set with current
+ * timestamp and updated on rollover of folder version counter.*/
+ protected static void fetchPbapParams(Context ctx) {
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);
+ long timeStamp = Calendar.getInstance().getTimeInMillis();
+ BluetoothPbapUtils.mDbIdentifier.set(pref.getLong("mDbIdentifier", timeStamp));
+ BluetoothPbapUtils.primaryVersionCounter = pref.getLong("primary", 0);
+ BluetoothPbapUtils.secondaryVersionCounter = pref.getLong("secondary", 0);
+ BluetoothPbapUtils.totalFields = pref.getLong("totalContacts", 0);
+ BluetoothPbapUtils.contactsLastUpdated = pref.getLong("lastUpdatedTimestamp", timeStamp);
+ BluetoothPbapUtils.totalFields = pref.getLong("totalFields", 0);
+ BluetoothPbapUtils.totalSvcFields = pref.getLong("totalSvcFields", 0);
+ if (V) Log.v(TAG, " fetchPbapParams " + pref.getAll());
+ }
+
+ /* loadAllContacts() fetches data like name,phone,email or addrees related to
+ * all contacts. It is required to determine which field of the contact is
+ * added/updated/deleted to increment secondary version counter accordingly.*/
+ protected static void loadAllContacts(Context mContext, Handler mHandler) {
+ if (V) Log.v(TAG, "Loading Contacts ...");
+
+ try {
+ String[] projection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
+ int contactCount = 0;
+ if ((contactCount = fetchAndSetContacts(
+ mContext, mHandler, projection, null, null, true))
+ < 0)
+ return;
+ totalContacts = contactCount; // to set total contacts count fetched on Connect
+ contactsLoaded = true;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception occurred in load contacts: " + e);
+ }
+ }
+
+ protected static void updateSecondaryVersionCounter(Context mContext, Handler mHandler) {
+ try {
+ /* updated_list stores list of contacts which are added/updated after
+ * the time when contacts were last updated. (contactsLastUpdated
+ * indicates the time when contact/contacts were last updated and
+ * corresponding changes were reflected in Folder Version Counters).*/
+ ArrayList<String> updated_list = new ArrayList<String>();
+ HashSet<String> currentContactSet = new HashSet<String>();
+ int currentContactCount = 0;
+
+ String[] projection = {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP};
+ Cursor c = mContext.getContentResolver().query(
+ Contacts.CONTENT_URI, projection, null, null, null);
+
+ if (c == null) {
+ Log.d(TAG, "Failed to fetch data from contact database");
+ return;
+ }
+ while (c.moveToNext()) {
+ String contactId = c.getString(0);
+ long lastUpdatedTime = c.getLong(1);
+ if (lastUpdatedTime > contactsLastUpdated) {
+ updated_list.add(contactId);
+ }
+ currentContactSet.add(contactId);
+ }
+ currentContactCount = c.getCount();
+ c.close();
+
+ if (V) Log.v(TAG, "updated list =" + updated_list);
+ String[] dataProjection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
+
+ String whereClause = Data.CONTACT_ID + "=?";
+
+ /* code to check if new contact/contacts are added */
+ if (currentContactCount > totalContacts) {
+ for (int i = 0; i < updated_list.size(); i++) {
+ String[] selectionArgs = {updated_list.get(i)};
+ fetchAndSetContacts(
+ mContext, mHandler, dataProjection, whereClause, selectionArgs, false);
+ secondaryVersionCounter++;
+ primaryVersionCounter++;
+ totalContacts = currentContactCount;
+ }
+ /* When contact/contacts are deleted */
+ } else if (currentContactCount < totalContacts) {
+ totalContacts = currentContactCount;
+ ArrayList<String> svcFields = new ArrayList<String>(
+ Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
+ Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE));
+ HashSet<String> deletedContacts = new HashSet<String>(ContactSet);
+ deletedContacts.removeAll(currentContactSet);
+ primaryVersionCounter += deletedContacts.size();
+ secondaryVersionCounter += deletedContacts.size();
+ if (V) Log.v(TAG, "Deleted Contacts : " + deletedContacts);
+
+ // to decrement totalFields and totalSvcFields count
+ for (String deletedContact : deletedContacts) {
+ ContactSet.remove(deletedContact);
+ String[] selectionArgs = {deletedContact};
+ Cursor dataCursor = mContext.getContentResolver().query(
+ Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
+
+ if (dataCursor == null) {
+ Log.d(TAG, "Failed to fetch data from contact database");
+ return;
+ }
+
+ while (dataCursor.moveToNext()) {
+ if (svcFields.contains(
+ dataCursor.getString(dataCursor.getColumnIndex(Data.MIMETYPE))))
+ totalSvcFields--;
+ totalFields--;
+ }
+ dataCursor.close();
+ }
+
+ /* When contacts are updated. i.e. Fields of existing contacts are
+ * added/updated/deleted */
+ } else {
+ for (int i = 0; i < updated_list.size(); i++) {
+ primaryVersionCounter++;
+ ArrayList<String> phone_tmp = new ArrayList<String>();
+ ArrayList<String> email_tmp = new ArrayList<String>();
+ ArrayList<String> address_tmp = new ArrayList<String>();
+ String name_tmp = null, updatedCID = updated_list.get(i);
+ boolean updated = false;
+
+ String[] selectionArgs = {updated_list.get(i)};
+ Cursor dataCursor = mContext.getContentResolver().query(
+ Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
+
+ if (dataCursor == null) {
+ Log.d(TAG, "Failed to fetch data from contact database");
+ return;
+ }
+ // fetch all updated contacts and compare with cached copy of contacts
+ int indexData = dataCursor.getColumnIndex(Data.DATA1);
+ int indexMimeType = dataCursor.getColumnIndex(Data.MIMETYPE);
+ String data;
+ String mimeType;
+ while (dataCursor.moveToNext()) {
+ data = dataCursor.getString(indexData);
+ mimeType = dataCursor.getString(indexMimeType);
+ switch (mimeType) {
+ case Email.CONTENT_ITEM_TYPE:
+ email_tmp.add(data);
+ break;
+ case Phone.CONTENT_ITEM_TYPE:
+ phone_tmp.add(data);
+ break;
+ case StructuredPostal.CONTENT_ITEM_TYPE:
+ address_tmp.add(data);
+ break;
+ case StructuredName.CONTENT_ITEM_TYPE:
+ name_tmp = new String(data);
+ break;
+ }
+ }
+ ContactData cData =
+ new ContactData(name_tmp, phone_tmp, email_tmp, address_tmp);
+ dataCursor.close();
+
+ if ((name_tmp == null && contactDataset.get(updatedCID).name != null)
+ || (name_tmp != null && contactDataset.get(updatedCID).name == null)
+ || (!(name_tmp == null && contactDataset.get(updatedCID).name == null)
+ && !name_tmp.equals(contactDataset.get(updatedCID).name))) {
+ updated = true;
+ } else if (checkFieldUpdates(contactDataset.get(updatedCID).phone, phone_tmp)) {
+ updated = true;
+ } else if (checkFieldUpdates(contactDataset.get(updatedCID).email, email_tmp)) {
+ updated = true;
+ } else if (checkFieldUpdates(
+ contactDataset.get(updatedCID).address, address_tmp)) {
+ updated = true;
+ }
+
+ if (updated) {
+ secondaryVersionCounter++;
+ contactDataset.put(updatedCID, cData);
+ }
+ }
+ }
+
+ Log.d(TAG, "primaryVersionCounter = " + primaryVersionCounter
+ + ", secondaryVersionCounter=" + secondaryVersionCounter);
+
+ // check if Primary/Secondary version Counter has rolled over
+ if (secondaryVersionCounter < 0 || primaryVersionCounter < 0)
+ mHandler.sendMessage(
+ mHandler.obtainMessage(BluetoothPbapService.ROLLOVER_COUNTERS));
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while updating secondary version counter:" + e);
+ }
+ }
+
+ /* checkFieldUpdates checks update contact fields of a particular contact.
+ * Field update can be a field updated/added/deleted in an existing contact.
+ * Returns true if any contact field is updated else return false. */
+ protected static boolean checkFieldUpdates(
+ ArrayList<String> oldFields, ArrayList<String> newFields) {
+ if (newFields != null && oldFields != null) {
+ if (newFields.size() != oldFields.size()) {
+ totalSvcFields += Math.abs(newFields.size() - oldFields.size());
+ totalFields += Math.abs(newFields.size() - oldFields.size());
+ return true;
+ }
+ for (int i = 0; i < newFields.size(); i++) {
+ if (!oldFields.contains(newFields.get(i))) {
+ return true;
+ }
+ }
+ /* when all fields of type(phone/email/address) are deleted in a given contact*/
+ } else if (newFields == null && oldFields != null && oldFields.size() > 0) {
+ totalSvcFields += oldFields.size();
+ totalFields += oldFields.size();
+ return true;
+
+ /* when new fields are added for a type(phone/email/address) in a contact
+ * for which there were no fields of this type earliar.*/
+ } else if (oldFields == null && newFields != null && newFields.size() > 0) {
+ totalSvcFields += newFields.size();
+ totalFields += newFields.size();
+ return true;
+ }
+ return false;
+ }
+
+ /* fetchAndSetContacts reads contacts and caches them
+ * isLoad = true indicates its loading all contacts
+ * isLoad = false indiacates its caching recently added contact in database*/
+ protected static int fetchAndSetContacts(Context mContext, Handler mHandler,
+ String[] projection, String whereClause, String[] selectionArgs, boolean isLoad) {
+ long currentTotalFields = 0, currentSvcFieldCount = 0;
+ Cursor c = mContext.getContentResolver().query(
+ Data.CONTENT_URI, projection, whereClause, selectionArgs, null);
+
+ /* send delayed message to loadContact when ContentResolver is unable
+ * to fetch data from contact database using the specified URI at that
+ * moment (Case: immediate Pbap connect on system boot with BT ON)*/
+ if (c == null) {
+ Log.d(TAG, "Failed to fetch contacts data from database..");
+ if (isLoad)
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(BluetoothPbapService.LOAD_CONTACTS),
+ QUERY_CONTACT_RETRY_INTERVAL);
+ return -1;
+ }
+
+ int indexCId = c.getColumnIndex(Data.CONTACT_ID);
+ int indexData = c.getColumnIndex(Data.DATA1);
+ int indexMimeType = c.getColumnIndex(Data.MIMETYPE);
+ String contactId, data, mimeType;
+ while (c.moveToNext()) {
+ contactId = c.getString(indexCId);
+ data = c.getString(indexData);
+ mimeType = c.getString(indexMimeType);
+ /* fetch phone/email/address/name information of the contact */
+ switch (mimeType) {
+ case Phone.CONTENT_ITEM_TYPE:
+ setContactFields(TYPE_PHONE, contactId, data);
+ currentSvcFieldCount++;
+ break;
+ case Email.CONTENT_ITEM_TYPE:
+ setContactFields(TYPE_EMAIL, contactId, data);
+ currentSvcFieldCount++;
+ break;
+ case StructuredPostal.CONTENT_ITEM_TYPE:
+ setContactFields(TYPE_ADDRESS, contactId, data);
+ currentSvcFieldCount++;
+ break;
+ case StructuredName.CONTENT_ITEM_TYPE:
+ setContactFields(TYPE_NAME, contactId, data);
+ currentSvcFieldCount++;
+ break;
+ }
+ ContactSet.add(contactId);
+ currentTotalFields++;
+ }
+ c.close();
+
+ /* This code checks if there is any update in contacts after last pbap
+ * disconnect has happenned (even if BT is turned OFF during this time)*/
+ if (isLoad && currentTotalFields != totalFields) {
+ primaryVersionCounter += Math.abs(totalContacts - ContactSet.size());
+
+ if (currentSvcFieldCount != totalSvcFields)
+ if (totalContacts != ContactSet.size())
+ secondaryVersionCounter += Math.abs(totalContacts - ContactSet.size());
+ else
+ secondaryVersionCounter++;
+ if (primaryVersionCounter < 0 || secondaryVersionCounter < 0) rolloverCounters();
+
+ totalFields = currentTotalFields;
+ totalSvcFields = currentSvcFieldCount;
+ contactsLastUpdated = System.currentTimeMillis();
+ Log.d(TAG, "Contacts updated between last BT OFF and current"
+ + "Pbap Connect, primaryVersionCounter=" + primaryVersionCounter
+ + ", secondaryVersionCounter=" + secondaryVersionCounter);
+ } else if (!isLoad) {
+ totalFields++;
+ totalSvcFields++;
+ }
+ return ContactSet.size();
+ }
+
+ /* setContactFields() is used to store contacts data in local cache (phone,
+ * email or address which is required for updating Secondary Version counter).
+ * contactsFieldData - List of field data for phone/email/address.
+ * contactId - Contact ID, data1 - field value from data table for phone/email/address*/
+
+ protected static void setContactFields(String fieldType, String contactId, String data) {
+ ContactData cData = null;
+ if (contactDataset.containsKey(contactId))
+ cData = contactDataset.get(contactId);
+ else
+ cData = new ContactData();
+
+ switch (fieldType) {
+ case TYPE_NAME:
+ cData.name = data;
+ break;
+ case TYPE_PHONE:
+ cData.phone.add(data);
+ break;
+ case TYPE_EMAIL:
+ cData.email.add(data);
+ break;
+ case TYPE_ADDRESS:
+ cData.address.add(data);
+ break;
+ }
+ contactDataset.put(contactId, cData);
+ }
+
+ /* As per Pbap 1.2 specification, Database Identifies shall be
+ * re-generated when a Folder Version Counter rolls over or starts over.*/
+
+ protected static void rolloverCounters() {
+ mDbIdentifier.set(Calendar.getInstance().getTimeInMillis());
+ primaryVersionCounter = (primaryVersionCounter < 0) ? 0 : primaryVersionCounter;
+ secondaryVersionCounter = (secondaryVersionCounter < 0) ? 0 : secondaryVersionCounter;
+ if (V) Log.v(TAG, "mDbIdentifier rolled over to:" + mDbIdentifier);
+ }
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index 2675f8d5c..b28ec77f8 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -458,7 +458,7 @@ public class BluetoothPbapVcardManager {
Log.d(TAG, "getCallHistoryPrimaryFolderVersion count is " + count + " type is " + type);
ByteBuffer pvc = ByteBuffer.allocate(16);
pvc.putLong(primaryVcMsb);
- Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapService.primaryVersionCounter);
+ Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.primaryVersionCounter);
pvc.putLong(count);
return pvc.array();
}