fix bug where sync settings set lost upon upgrade from donut and eclair
to froyo

- intepret a missing syncavble attribute from donut as "unsynced"
  rather than the traditional "true"
- copy the sync settings from the authorities "contacts" and "calendar"
  to "com.android.contacts" and "com.android.calendar" if the latter
  don't already have settings
- delay the database cleanup until after boot completed, which will give
  the GoogleLoginService accounts migration code a chance to run; this
  was causing all the settings to get removed upon a donut to froyo upgrade

Change-Id: I8795e97ba0c9b930d1a50784229ca9ab15dff9d2
http://b/issue?id=2531359
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index df48f04..5c8ee18 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -215,7 +215,9 @@
         // the accounts are not set yet
         sendCheckAlarmsMessage();
 
-        mSyncStorageEngine.doDatabaseCleanup(accounts);
+        if (mBootCompleted) {
+            mSyncStorageEngine.doDatabaseCleanup(accounts);
+        }
 
         if (accounts.length > 0) {
             // If this is the first time this was called after a bootup then
@@ -1317,6 +1319,7 @@
         private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
         public void onBootCompleted() {
             mBootCompleted = true;
+            mSyncStorageEngine.doDatabaseCleanup(AccountManager.get(mContext).getAccounts());
             if (mReadyToRunLatch != null) {
                 mReadyToRunLatch.countDown();
             }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 03e606f..daad95c 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -122,7 +122,15 @@
     private static final boolean SYNC_ENABLED_DEFAULT = false;
 
     // the version of the accounts xml file format
-    private static final int ACCOUNTS_VERSION = 1;
+    private static final int ACCOUNTS_VERSION = 2;
+
+    private static HashMap<String, String> sAuthorityRenames;
+
+    static {
+        sAuthorityRenames = new HashMap<String, String>();
+        sAuthorityRenames.put("contacts", "com.android.contacts");
+        sAuthorityRenames.put("calendar", "com.android.calendar");
+    }
 
     public static class PendingOperation {
         final Account account;
@@ -1281,7 +1289,9 @@
     private void removeAuthorityLocked(Account account, String authorityName) {
         AccountInfo accountInfo = mAccounts.get(account);
         if (accountInfo != null) {
-            if (accountInfo.authorities.remove(authorityName) != null) {
+            final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
+            if (authorityInfo != null) {
+                mAuthorities.remove(authorityInfo.ident);
                 writeAccountInfoLocked();
             }
         }
@@ -1407,11 +1417,61 @@
             }
         }
 
+        if (maybeMigrateSettingsForRenamedAuthorities()) {
+            writeNeeded = true;
+        }
+
         if (writeNeeded) {
             writeAccountInfoLocked();
         }
     }
 
+    /**
+     * some authority names have changed. copy over their settings and delete the old ones
+     * @return true if a change was made
+     */
+    private boolean maybeMigrateSettingsForRenamedAuthorities() {
+        boolean writeNeeded = false;
+
+        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
+        final int N = mAuthorities.size();
+        for (int i=0; i<N; i++) {
+            AuthorityInfo authority = mAuthorities.valueAt(i);
+            // skip this authority if it isn't one of the renamed ones
+            final String newAuthorityName = sAuthorityRenames.get(authority.authority);
+            if (newAuthorityName == null) {
+                continue;
+            }
+
+            // remember this authority so we can remove it later. we can't remove it
+            // now without messing up this loop iteration
+            authoritiesToRemove.add(authority);
+
+            // this authority isn't enabled, no need to copy it to the new authority name since
+            // the default is "disabled"
+            if (!authority.enabled) {
+                continue;
+            }
+
+            // if we already have a record of this new authority then don't copy over the settings
+            if (getAuthorityLocked(authority.account, newAuthorityName, "cleanup") != null) {
+                continue;
+            }
+
+            AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
+                    newAuthorityName, -1 /* ident */, false /* doWrite */);
+            newAuthority.enabled = true;
+            writeNeeded = true;
+        }
+
+        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
+            removeAuthorityLocked(authorityInfo.account, authorityInfo.authority);
+            writeNeeded = true;
+        }
+
+        return writeNeeded;
+    }
+
     private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
         AuthorityInfo authority = null;
         int id = -1;
@@ -1424,14 +1484,15 @@
             Log.e(TAG, "the id of the authority is null", e);
         }
         if (id >= 0) {
+            String authorityName = parser.getAttributeValue(null, "authority");
+            String enabled = parser.getAttributeValue(null, "enabled");
+            String syncable = parser.getAttributeValue(null, "syncable");
             String accountName = parser.getAttributeValue(null, "account");
             String accountType = parser.getAttributeValue(null, "type");
             if (accountType == null) {
                 accountType = "com.google";
+                syncable = "unknown";
             }
-            String authorityName = parser.getAttributeValue(null, "authority");
-            String enabled = parser.getAttributeValue(null, "enabled");
-            String syncable = parser.getAttributeValue(null, "syncable");
             authority = mAuthorities.get(id);
             if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
                     + accountName + " auth=" + authorityName
@@ -1456,7 +1517,7 @@
                     authority.syncable = -1;
                 } else {
                     authority.syncable =
-                            (syncable == null || Boolean.parseBoolean(enabled)) ? 1 : 0;
+                            (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
                 }
             } else {
                 Log.w(TAG, "Failure adding authority: account="
@@ -1546,13 +1607,11 @@
                 out.attribute(null, "account", authority.account.name);
                 out.attribute(null, "type", authority.account.type);
                 out.attribute(null, "authority", authority.authority);
-                if (!authority.enabled) {
-                    out.attribute(null, "enabled", "false");
-                }
+                out.attribute(null, "enabled", Boolean.toString(authority.enabled));
                 if (authority.syncable < 0) {
                     out.attribute(null, "syncable", "unknown");
-                } else if (authority.syncable == 0) {
-                    out.attribute(null, "syncable", "false");
+                } else {
+                    out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
                 }
                 for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
                     out.startTag(null, "periodicSync");
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index 7028d1a..48fe765 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -214,7 +214,6 @@
         MockContentResolver mockResolver = new MockContentResolver();
 
         final TestContext testContext = new TestContext(mockResolver, getContext());
-        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
         byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 + "<accounts>\n"
@@ -230,7 +229,7 @@
         fos.write(accountsFileData);
         accountInfoFile.finishWrite(fos);
 
-        engine.clearAndReadState();
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
         List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority1);
         assertEquals(1, syncs.size());
@@ -245,7 +244,7 @@
         assertEquals(sync3, syncs.get(0));
 
         accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<accounts version=\"1\">\n"
+                + "<accounts version=\"2\">\n"
                 + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
                 + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
                 + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
@@ -268,7 +267,7 @@
         assertEquals(0, syncs.size());
 
         accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<accounts version=\"1\">\n"
+                + "<accounts version=\"2\">\n"
                 + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
                 + "<periodicSync period=\"1000\" />\n"
                 + "</authority>"
@@ -299,6 +298,89 @@
         assertEquals(1, syncs.size());
         assertEquals(sync3s, syncs.get(0));
     }
+
+    @SmallTest
+    public void testAuthorityRenaming() throws Exception {
+        final Account account1 = new Account("acc1", "type1");
+        final Account account2 = new Account("acc2", "type2");
+        final String authorityContacts = "contacts";
+        final String authorityCalendar = "calendar";
+        final String authorityOther = "other";
+        final String authorityContactsNew = "com.android.contacts";
+        final String authorityCalendarNew = "com.android.calendar";
+
+        MockContentResolver mockResolver = new MockContentResolver();
+
+        final TestContext testContext = new TestContext(mockResolver, getContext());
+
+        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<accounts>\n"
+                + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
+                + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
+                + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
+                + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
+                + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
+                + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
+                + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+                + " authority=\"com.android.calendar\" />\n"
+                + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+                + " authority=\"com.android.contacts\" />\n"
+                + "</accounts>\n").getBytes();
+
+        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+        syncDir.mkdirs();
+        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        FileOutputStream fos = accountInfoFile.startWrite();
+        fos.write(accountsFileData);
+        accountInfoFile.finishWrite(fos);
+
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+        assertEquals(false, engine.getSyncAutomatically(account1, authorityContacts));
+        assertEquals(false, engine.getSyncAutomatically(account1, authorityCalendar));
+        assertEquals(true, engine.getSyncAutomatically(account1, authorityOther));
+        assertEquals(true, engine.getSyncAutomatically(account1, authorityContactsNew));
+        assertEquals(true, engine.getSyncAutomatically(account1, authorityCalendarNew));
+
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityContacts));
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendar));
+        assertEquals(true, engine.getSyncAutomatically(account2, authorityOther));
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityContactsNew));
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendarNew));
+    }
+
+    @SmallTest
+    public void testSyncableMigration() throws Exception {
+        final Account account = new Account("acc", "type");
+
+        MockContentResolver mockResolver = new MockContentResolver();
+
+        final TestContext testContext = new TestContext(mockResolver, getContext());
+
+        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<accounts>\n"
+                + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
+                + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
+                + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
+                + " authority=\"other3\" />\n"
+                + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
+                + " authority=\"other4\" />\n"
+                + "</accounts>\n").getBytes();
+
+        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+        syncDir.mkdirs();
+        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        FileOutputStream fos = accountInfoFile.startWrite();
+        fos.write(accountsFileData);
+        accountInfoFile.finishWrite(fos);
+
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+        assertEquals(-1, engine.getIsSyncable(account, "other1"));
+        assertEquals(1, engine.getIsSyncable(account, "other2"));
+        assertEquals(0, engine.getIsSyncable(account, "other3"));
+        assertEquals(1, engine.getIsSyncable(account, "other4"));
+    }
 }
 
 class TestContext extends ContextWrapper {