Merge "Move restore account to EmailProvider."

This commit is contained in:
Makoto Onuki 2011-06-20 18:46:09 -07:00 committed by Android (Google) Code Review
commit 09a0e9b5dc
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;
@ -62,6 +63,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;
@ -88,8 +90,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";
@ -750,7 +750,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;
@ -771,6 +772,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
@ -781,10 +787,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() {
@ -867,7 +904,7 @@ public class EmailProvider extends ContentProvider {
}
}
private class DatabaseHelper extends SQLiteOpenHelper {
private static class DatabaseHelper extends SQLiteOpenHelper {
Context mContext;
DatabaseHelper(Context context, String name) {
@ -1619,7 +1656,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 {
@ -1640,7 +1677,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 {
@ -1706,7 +1743,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();
}
@ -1714,11 +1751,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();
@ -1729,11 +1774,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();
@ -1748,9 +1803,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)
@ -2006,7 +2059,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));