Synchronize getSyncKey/setSyncKey in Calendar and Contacts sync

* When the sync state of Calendar/Contacts is changed, a number of observer calls
  are triggered.  In addition, we might have a running sync.
* The syncKey operations need to be synchronized, because we may otherwise
  inadvertently use stale data when syncing, which would cause symptoms
  as seen in the referenced bug

Bug: 2561864
Change-Id: I03db58fe01c45778d271fad34d8d4940edefe8fe
diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java
index f3d3902..043411b 100644
--- a/src/com/android/exchange/SyncManager.java
+++ b/src/com/android/exchange/SyncManager.java
@@ -683,7 +683,7 @@
         }
 
         @Override
-        public void onChange(boolean selfChange) {
+        public synchronized void onChange(boolean selfChange) {
             // See if the user has changed syncing of our calendar
             if (!selfChange) {
                 new Thread(new Runnable() {
diff --git a/src/com/android/exchange/adapter/CalendarSyncAdapter.java b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
index df0296b..a25f83b 100644
--- a/src/com/android/exchange/adapter/CalendarSyncAdapter.java
+++ b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
@@ -105,6 +105,8 @@
     private static final Uri sExtendedPropertiesUri = asSyncAdapter(ExtendedProperties.CONTENT_URI);
     private static final Uri sRemindersUri = asSyncAdapter(Reminders.CONTENT_URI);
 
+    private static final Object sSyncKeyLock = new Object();
+
     private long mCalendarId = -1;
 
     private ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
@@ -173,20 +175,24 @@
      */
     @Override
     public String getSyncKey() throws IOException {
-        ContentProviderClient client =
-            mService.mContentResolver.acquireContentProviderClient(Calendar.CONTENT_URI);
-        try {
-            byte[] data = SyncStateContract.Helpers.get(client,
-                    asSyncAdapter(Calendar.SyncState.CONTENT_URI), mAccountManagerAccount);
-            if (data == null || data.length == 0) {
-                // Initialize the SyncKey
-                setSyncKey("0", false);
-                return "0";
-            } else {
-                return new String(data);
+        synchronized (sSyncKeyLock) {
+            ContentProviderClient client = mService.mContentResolver
+                    .acquireContentProviderClient(Calendar.CONTENT_URI);
+            try {
+                byte[] data = SyncStateContract.Helpers.get(client,
+                        asSyncAdapter(Calendar.SyncState.CONTENT_URI), mAccountManagerAccount);
+                if (data == null || data.length == 0) {
+                    // Initialize the SyncKey
+                    setSyncKey("0", false);
+                    return "0";
+                } else {
+                    String syncKey = new String(data);
+                    userLog("SyncKey retrieved as ", syncKey, " from CalendarProvider");
+                    return syncKey;
+                }
+            } catch (RemoteException e) {
+                throw new IOException("Can't get SyncKey from CalendarProvider");
             }
-        } catch (RemoteException e) {
-            throw new IOException("Can't get SyncKey from ContactsProvider");
         }
     }
 
@@ -196,19 +202,21 @@
      */
     @Override
     public void setSyncKey(String syncKey, boolean inCommands) throws IOException {
-        if ("0".equals(syncKey) || !inCommands) {
-            ContentProviderClient client =
-                mService.mContentResolver
-                    .acquireContentProviderClient(Calendar.CONTENT_URI);
-            try {
-                SyncStateContract.Helpers.set(client, asSyncAdapter(Calendar.SyncState.CONTENT_URI),
-                        mAccountManagerAccount, syncKey.getBytes());
-                userLog("SyncKey set to ", syncKey, " in CalendarProvider");
-           } catch (RemoteException e) {
-                throw new IOException("Can't set SyncKey in CalendarProvider");
+        synchronized (sSyncKeyLock) {
+            if ("0".equals(syncKey) || !inCommands) {
+                ContentProviderClient client = mService.mContentResolver
+                        .acquireContentProviderClient(Calendar.CONTENT_URI);
+                try {
+                    SyncStateContract.Helpers.set(client,
+                            asSyncAdapter(Calendar.SyncState.CONTENT_URI), mAccountManagerAccount,
+                            syncKey.getBytes());
+                    userLog("SyncKey set to ", syncKey, " in CalendarProvider");
+                } catch (RemoteException e) {
+                    throw new IOException("Can't set SyncKey in CalendarProvider");
+                }
             }
+            mMailbox.mSyncKey = syncKey;
         }
-        mMailbox.mSyncKey = syncKey;
     }
 
     class EasCalendarSyncParser extends AbstractSyncParser {
diff --git a/src/com/android/exchange/adapter/ContactsSyncAdapter.java b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
index 1b9b1f7..8b4adba 100644
--- a/src/com/android/exchange/adapter/ContactsSyncAdapter.java
+++ b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
@@ -118,6 +118,8 @@
     private static final int[] HOME_PHONE_TAGS = new int[] {Tags.CONTACTS_HOME_TELEPHONE_NUMBER,
         Tags.CONTACTS_HOME2_TELEPHONE_NUMBER};
 
+    private static final Object sSyncKeyLock = new Object();
+
     ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
     ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
 
@@ -156,26 +158,29 @@
      */
     @Override
     public String getSyncKey() throws IOException {
-        ContentProviderClient client =
-            mService.mContentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
-        try {
-            byte[] data = SyncStateContract.Helpers.get(client,
-                    ContactsContract.SyncState.CONTENT_URI, mAccountManagerAccount);
-            if (data == null || data.length == 0) {
-                // Initialize the SyncKey
-                setSyncKey("0", false);
-                // Make sure ungrouped contacts for Exchange are defaultly visible
-                ContentValues cv = new ContentValues();
-                cv.put(Groups.ACCOUNT_NAME, mAccount.mEmailAddress);
-                cv.put(Groups.ACCOUNT_TYPE, com.android.email.Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
-                cv.put(Settings.UNGROUPED_VISIBLE, true);
-                client.insert(addCallerIsSyncAdapterParameter(Settings.CONTENT_URI), cv);
-                return "0";
-            } else {
-                return new String(data);
+        synchronized (sSyncKeyLock) {
+            ContentProviderClient client = mService.mContentResolver
+                    .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
+            try {
+                byte[] data = SyncStateContract.Helpers.get(client,
+                        ContactsContract.SyncState.CONTENT_URI, mAccountManagerAccount);
+                if (data == null || data.length == 0) {
+                    // Initialize the SyncKey
+                    setSyncKey("0", false);
+                    // Make sure ungrouped contacts for Exchange are defaultly visible
+                    ContentValues cv = new ContentValues();
+                    cv.put(Groups.ACCOUNT_NAME, mAccount.mEmailAddress);
+                    cv.put(Groups.ACCOUNT_TYPE,
+                            com.android.email.Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
+                    cv.put(Settings.UNGROUPED_VISIBLE, true);
+                    client.insert(addCallerIsSyncAdapterParameter(Settings.CONTENT_URI), cv);
+                    return "0";
+                } else {
+                    return new String(data);
+                }
+            } catch (RemoteException e) {
+                throw new IOException("Can't get SyncKey from ContactsProvider");
             }
-        } catch (RemoteException e) {
-            throw new IOException("Can't get SyncKey from ContactsProvider");
         }
     }
 
@@ -185,19 +190,20 @@
      */
     @Override
     public void setSyncKey(String syncKey, boolean inCommands) throws IOException {
-        if ("0".equals(syncKey) || !inCommands) {
-            ContentProviderClient client =
-                mService.mContentResolver
-                    .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
-            try {
-                SyncStateContract.Helpers.set(client, ContactsContract.SyncState.CONTENT_URI,
-                        mAccountManagerAccount, syncKey.getBytes());
-                userLog("SyncKey set to ", syncKey, " in ContactsProvider");
-           } catch (RemoteException e) {
-                throw new IOException("Can't set SyncKey in ContactsProvider");
+        synchronized (sSyncKeyLock) {
+            if ("0".equals(syncKey) || !inCommands) {
+                ContentProviderClient client = mService.mContentResolver
+                        .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
+                try {
+                    SyncStateContract.Helpers.set(client, ContactsContract.SyncState.CONTENT_URI,
+                            mAccountManagerAccount, syncKey.getBytes());
+                    userLog("SyncKey set to ", syncKey, " in ContactsProvider");
+                } catch (RemoteException e) {
+                    throw new IOException("Can't set SyncKey in ContactsProvider");
+                }
             }
+            mMailbox.mSyncKey = syncKey;
         }
-        mMailbox.mSyncKey = syncKey;
     }
 
     public static final class EasChildren {