Move restore account to EmailProvider.

In order to reduce the UI initialization code, move
let EmailProvider restore accounts when it opens the DB.

Backup can be moved too, but I just leave it as a TODO.

Change-Id: Id5c1810904db6abaecbfecbaa8d2d53834ebf07b
This commit is contained in:
Makoto Onuki 2011-06-20 18:10:10 -07:00
parent 99401ebea7
commit 9dad9ad973
6 changed files with 81 additions and 77 deletions

View File

@ -162,13 +162,6 @@ public class Welcome extends Activity {
// Reset the "accounts changed" notification, now that we're here
Email.setNotifyUiAccountsChanged(false);
// Restore accounts, if it has not happened already
// NOTE: This is blocking, which it should not be (in the UI thread)
// We're going to live with this for the short term and replace with something
// smarter. Long-term fix: Move this, and most of the code below, to an AsyncTask
// and do the DB work in a thread. Then post handler to finish() as appropriate.
AccountBackupRestore.restoreIfNeeded(this);
// Because the app could be reloaded (for debugging, etc.), we need to make sure that
// ExchangeService gets a chance to start. There is no harm to starting it if it has
// already been started

View File

@ -16,16 +16,8 @@
package com.android.email.provider;
import com.android.email.Email;
import com.android.email.Preferences;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import android.content.ContentResolver;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
/**
* Helper class to facilitate EmailProvider's account backup/restore facility.
@ -52,45 +44,11 @@ public class AccountBackupRestore {
/**
* Backup user Account and HostAuth data into our backup database
*
* TODO Make EmailProvider do this automatically.
*/
public static void backup(Context context) {
ContentResolver resolver = context.getContentResolver();
int numBackedUp = resolver.update(EmailProvider.ACCOUNT_BACKUP_URI, null, null, null);
if (numBackedUp < 0) {
Log.e(Logging.LOG_TAG, "Account backup failed!");
} else if (Email.DEBUG) {
Log.d(Logging.LOG_TAG, "Backed up " + numBackedUp + " accounts...");
}
}
/**
* Restore user Account and HostAuth data from our backup database
*/
public static void restoreIfNeeded(Context context) {
if (sBackupsChecked) return;
// Check for legacy backup
String legacyBackup = Preferences.getLegacyBackupPreference(context);
// If there's a legacy backup, create a new-style backup and delete the legacy backup
// In the 1:1000000000 chance that the user gets an app update just as his database becomes
// corrupt, oh well...
if (!TextUtils.isEmpty(legacyBackup)) {
backup(context);
Preferences.clearLegacyBackupPreference(context);
Log.w(Logging.LOG_TAG, "Created new EmailProvider backup database");
}
// If we have accounts, we're done
if (EmailContent.count(context, Account.CONTENT_URI) > 0) return;
ContentResolver resolver = context.getContentResolver();
int numRecovered = resolver.update(EmailProvider.ACCOUNT_RESTORE_URI, null, null, null);
if (numRecovered > 0) {
Log.e(Logging.LOG_TAG, "Recovered " + numRecovered + " accounts!");
} else if (numRecovered < 0) {
Log.e(Logging.LOG_TAG, "Account recovery failed?");
} else if (Email.DEBUG) {
Log.d(Logging.LOG_TAG, "No accounts to restore...");
}
sBackupsChecked = true;
resolver.update(EmailProvider.ACCOUNT_BACKUP_URI, null, null, null);
}
}

View File

@ -17,6 +17,7 @@
package com.android.email.provider;
import com.android.email.Email;
import com.android.email.Preferences;
import com.android.email.provider.ContentCache.CacheToken;
import com.android.email.service.AttachmentDownloadService;
import com.android.emailcommon.AccountManagerTypes;
@ -60,6 +61,7 @@ import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
@ -86,8 +88,6 @@ public class EmailProvider extends ContentProvider {
Uri.parse("content://" + EmailContent.AUTHORITY + "/integrityCheck");
public static final Uri ACCOUNT_BACKUP_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
public static final Uri ACCOUNT_RESTORE_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountRestore");
/** Appended to the notification URI for delete operations */
public static final String NOTIFICATION_OP_DELETE = "delete";
@ -724,7 +724,8 @@ public class EmailProvider extends ContentProvider {
private SQLiteDatabase mDatabase;
private SQLiteDatabase mBodyDatabase;
public synchronized SQLiteDatabase getDatabase(Context context) {
@VisibleForTesting
synchronized SQLiteDatabase getDatabase(Context context) {
// Always return the cached database, if we've got one
if (mDatabase != null) {
return mDatabase;
@ -745,6 +746,11 @@ public class EmailProvider extends ContentProvider {
String bodyFileName = mBodyDatabase.getPath();
mDatabase.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase");
}
// Restore accounts if the database is corrupted...
restoreIfNeeded(context, mDatabase);
} else {
Log.w(TAG, "getWritableDatabase returned null!");
}
// Check for any orphaned Messages in the updated/deleted tables
@ -755,10 +761,41 @@ public class EmailProvider extends ContentProvider {
}
/*package*/ static SQLiteDatabase getReadableDatabase(Context context) {
DatabaseHelper helper = new EmailProvider().new DatabaseHelper(context, DATABASE_NAME);
DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME);
return helper.getReadableDatabase();
}
/**
* Restore user Account and HostAuth data from our backup database
*/
public static void restoreIfNeeded(Context context, SQLiteDatabase mainDatabase) {
if (Email.DEBUG) {
Log.w(TAG, "restoreIfNeeded...");
}
// Check for legacy backup
String legacyBackup = Preferences.getLegacyBackupPreference(context);
// If there's a legacy backup, create a new-style backup and delete the legacy backup
// In the 1:1000000000 chance that the user gets an app update just as his database becomes
// corrupt, oh well...
if (!TextUtils.isEmpty(legacyBackup)) {
backupAccounts(context, mainDatabase);
Preferences.clearLegacyBackupPreference(context);
Log.w(TAG, "Created new EmailProvider backup database");
return;
}
// If we have accounts, we're done
Cursor c = mainDatabase.query(Account.TABLE_NAME, EmailContent.ID_PROJECTION, null, null,
null, null, null);
if (c.moveToFirst()) {
if (Email.DEBUG) {
Log.w(TAG, "restoreIfNeeded: Account exists.");
}
return; // At least one account exists.
}
restoreAccounts(context, mainDatabase);
}
/** {@inheritDoc} */
@Override
public void shutdown() {
@ -841,7 +878,7 @@ public class EmailProvider extends ContentProvider {
}
}
private class DatabaseHelper extends SQLiteOpenHelper {
private static class DatabaseHelper extends SQLiteOpenHelper {
Context mContext;
DatabaseHelper(Context context, String name) {
@ -1577,7 +1614,7 @@ public class EmailProvider extends ContentProvider {
* @param id the unique id (_id) of the row
* @return a fully populated HostAuth or null if the row does not exist
*/
private HostAuth restoreHostAuth(SQLiteDatabase db, long id) {
private static HostAuth restoreHostAuth(SQLiteDatabase db, long id) {
Cursor c = db.query(HostAuth.TABLE_NAME, HostAuth.CONTENT_PROJECTION,
HostAuth.RECORD_ID + "=?", new String[] {Long.toString(id)}, null, null, null);
try {
@ -1598,7 +1635,7 @@ public class EmailProvider extends ContentProvider {
* @param toDatabase the destination database
* @return the number of accounts copied, or -1 if an error occurred
*/
private int copyAccountTables(SQLiteDatabase fromDatabase, SQLiteDatabase toDatabase) {
private static int copyAccountTables(SQLiteDatabase fromDatabase, SQLiteDatabase toDatabase) {
if (fromDatabase == null || toDatabase == null) return -1;
int copyCount = 0;
try {
@ -1664,7 +1701,7 @@ public class EmailProvider extends ContentProvider {
return copyCount;
}
private SQLiteDatabase getBackupDatabase(Context context) {
private static SQLiteDatabase getBackupDatabase(Context context) {
DatabaseHelper helper = new DatabaseHelper(context, BACKUP_DATABASE_NAME);
return helper.getWritableDatabase();
}
@ -1672,11 +1709,19 @@ public class EmailProvider extends ContentProvider {
/**
* Backup account data, returning the number of accounts backed up
*/
private int backupAccounts() {
Context context = getContext();
private static int backupAccounts(Context context, SQLiteDatabase mainDatabase) {
if (Email.DEBUG) {
Log.d(TAG, "backupAccounts...");
}
SQLiteDatabase backupDatabase = getBackupDatabase(context);
try {
return copyAccountTables(getDatabase(context), backupDatabase);
int numBackedUp = copyAccountTables(mainDatabase, backupDatabase);
if (numBackedUp < 0) {
Log.e(TAG, "Account backup failed!");
} else if (Email.DEBUG) {
Log.d(TAG, "Backed up " + numBackedUp + " accounts...");
}
return numBackedUp;
} finally {
if (backupDatabase != null) {
backupDatabase.close();
@ -1687,11 +1732,21 @@ public class EmailProvider extends ContentProvider {
/**
* Restore account data, returning the number of accounts restored
*/
private int restoreAccounts() {
Context context = getContext();
private static int restoreAccounts(Context context, SQLiteDatabase mainDatabase) {
if (Email.DEBUG) {
Log.d(TAG, "restoreAccounts...");
}
SQLiteDatabase backupDatabase = getBackupDatabase(context);
try {
return copyAccountTables(backupDatabase, getDatabase(context));
int numRecovered = copyAccountTables(backupDatabase, mainDatabase);
if (numRecovered > 0) {
Log.e(TAG, "Recovered " + numRecovered + " accounts!");
} else if (numRecovered < 0) {
Log.e(TAG, "Account recovery failed?");
} else if (Email.DEBUG) {
Log.d(TAG, "No accounts to restore...");
}
return numRecovered;
} finally {
if (backupDatabase != null) {
backupDatabase.close();
@ -1706,9 +1761,7 @@ public class EmailProvider extends ContentProvider {
checkDatabases();
return 0;
} else if (uri == ACCOUNT_BACKUP_URI) {
return backupAccounts();
} else if (uri == ACCOUNT_RESTORE_URI) {
return restoreAccounts();
return backupAccounts(getContext(), getDatabase(getContext()));
}
// Notify all existing cursors, except for ACCOUNT_RESET_NEW_COUNT(_ID)
@ -1963,7 +2016,7 @@ public class EmailProvider extends ContentProvider {
@VisibleForTesting
@SuppressWarnings("deprecation")
void convertPolicyFlagsToPolicyTable(SQLiteDatabase db) {
static void convertPolicyFlagsToPolicyTable(SQLiteDatabase db) {
Cursor c = db.query(Account.TABLE_NAME,
new String[] {EmailContent.RECORD_ID /*0*/, AccountColumns.SECURITY_FLAGS /*1*/},
AccountColumns.SECURITY_FLAGS + ">0", null, null, null, null);

View File

@ -21,7 +21,6 @@ import com.android.email.ExchangeUtils;
import com.android.email.NotificationController;
import com.android.email.ResourceHelper;
import com.android.email.VendorPolicyLoader;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Configuration;
import com.android.emailcommon.Device;
import com.android.emailcommon.service.IAccountService;
@ -54,7 +53,9 @@ public class AccountService extends Service {
@Override
public void restoreAccountsIfNeeded() {
AccountBackupRestore.restoreIfNeeded(mContext);
// Obsolete -- this is done 100% transparently in EmailProvider.
// We leave the method because we don't want to change the service interface.
// (It may be okay to remove it, but we're not sure at this point.)
}
@Override

View File

@ -141,9 +141,6 @@ public class MailService extends Service {
public int onStartCommand(final Intent intent, int flags, final int startId) {
super.onStartCommand(intent, flags, startId);
// Restore accounts, if it has not happened already
AccountBackupRestore.restoreIfNeeded(this);
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {

View File

@ -114,8 +114,10 @@ public class AccountBackupRestoreTests extends ProviderTestCase2<EmailProvider>
assertEquals(0, EmailContent.count(mMockContext, Account.CONTENT_URI));
assertEquals(0, EmailContent.count(mMockContext, HostAuth.CONTENT_URI));
// Restore the accounts
AccountBackupRestore.restoreIfNeeded(mMockContext);
// Because we restore accounts at the db open time, we first need to close the db
// explicitly here.
// Accounts will be restored next time we touch the db.
getProvider().shutdown();
// Make sure there are two accounts and four host auths
assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI));