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 {