Complete rewrite of account backup/restore code

* Use EmailProvider to backup/restore into a backup database
* Remove all of the old AccountBackupRestore code
* Get rid of the legacy Account class and all of the Preferences
  crap that referenced it
* Remove corresponding tests

Change-Id: I2de75aafdacc87246174303961e58547303f641e
This commit is contained in:
Marc Blank 2011-05-06 12:07:39 -07:00
parent ae993bda32
commit 0993190caf
22 changed files with 422 additions and 1684 deletions

View File

@ -1461,7 +1461,7 @@ public abstract class EmailContent {
* for desktop shortcuts.
*
* <p>We don't want to store _id in shortcuts, because
* {@link com.android.email.AccountBackupRestore} won't preserve it.
* {@link com.android.email.provider.AccountBackupRestore} won't preserve it.
*/
public Uri getShortcutSafeUri() {
return getShortcutSafeUriFromUuid(mCompatibilityUuid);

View File

@ -1,480 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email;
import com.android.email.mail.Store;
import com.android.emailcommon.service.SyncWindow;
import com.android.emailcommon.utility.Utility;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import java.util.Arrays;
import java.util.UUID;
/**
* Account stores all of the settings for a single account defined by the user. It is able to save
* and delete itself given a Preferences to work with. Each account is defined by a UUID.
*/
public class Account {
public static final int DELETE_POLICY_NEVER = 0;
public static final int DELETE_POLICY_7DAYS = 1;
public static final int DELETE_POLICY_ON_DELETE = 2;
public static final int CHECK_INTERVAL_NEVER = -1;
public static final int CHECK_INTERVAL_PUSH = -2;
// These flags will never be seen in a "real" (legacy) account
public static final int BACKUP_FLAGS_IS_BACKUP = 1;
public static final int BACKUP_FLAGS_SYNC_CONTACTS = 2;
public static final int BACKUP_FLAGS_IS_DEFAULT = 4;
public static final int BACKUP_FLAGS_SYNC_CALENDAR = 8;
// Since email sync has always been "on" prior to the creation of this flag, it's sense is
// reversed to avoid legacy issues.
public static final int BACKUP_FLAGS_DONT_SYNC_EMAIL = 16;
public static final int BACKUP_FLAGS_BACKGROUND_ATTACHMENTS = 32;
// serialized values
String mUuid;
String mStoreUri;
String mLocalStoreUri;
String mSenderUri;
String mDescription;
String mName;
String mEmail;
int mAutomaticCheckIntervalMinutes;
long mLastAutomaticCheckTime;
boolean mNotifyNewMail;
String mDraftsFolderName;
String mSentFolderName;
String mTrashFolderName;
String mOutboxFolderName;
int mAccountNumber;
boolean mVibrate; // true: Always vibrate. false: Only when mVibrateWhenSilent.
boolean mVibrateWhenSilent; // true: Vibrate even if !mVibrate. False: Require mVibrate.
String mRingtoneUri;
int mSyncWindow;
int mBackupFlags; // for account backups only
String mProtocolVersion; // for account backups only
long mSecurityFlags; // for account backups only
String mSignature; // for account backups only
/**
* <pre>
* 0 Never
* 1 After 7 days
* 2 When I delete from inbox
* </pre>
*/
int mDeletePolicy;
/**
* All new fields should have named keys
*/
private static final String KEY_SYNC_WINDOW = ".syncWindow";
private static final String KEY_BACKUP_FLAGS = ".backupFlags";
private static final String KEY_PROTOCOL_VERSION = ".protocolVersion";
private static final String KEY_SECURITY_FLAGS = ".securityFlags";
private static final String KEY_SIGNATURE = ".signature";
private static final String KEY_VIBRATE_WHEN_SILENT = ".vibrateWhenSilent";
public Account(Context context) {
// TODO Change local store path to something readable / recognizable
mUuid = UUID.randomUUID().toString();
mLocalStoreUri = "local://localhost/" + context.getDatabasePath(mUuid + ".db");
mAutomaticCheckIntervalMinutes = -1;
mAccountNumber = -1;
mNotifyNewMail = true;
mVibrate = false;
mVibrateWhenSilent = false;
mRingtoneUri = "content://settings/system/notification_sound";
mSyncWindow = SyncWindow.SYNC_WINDOW_USER; // IMAP & POP3
mBackupFlags = 0;
mProtocolVersion = null;
mSecurityFlags = 0;
mSignature = null;
}
Account(Preferences preferences, String uuid) {
this.mUuid = uuid;
refresh(preferences);
}
/**
* Refresh the account from the stored settings.
*/
public void refresh(Preferences preferences) {
mStoreUri = Utility.base64Decode(preferences.mSharedPreferences.getString(mUuid
+ ".storeUri", null));
mLocalStoreUri = preferences.mSharedPreferences.getString(mUuid + ".localStoreUri", null);
String senderText = preferences.mSharedPreferences.getString(mUuid + ".senderUri", null);
if (senderText == null) {
// Preference ".senderUri" was called ".transportUri" in earlier versions, so we'll
// do a simple upgrade here when necessary.
senderText = preferences.mSharedPreferences.getString(mUuid + ".transportUri", null);
}
mSenderUri = Utility.base64Decode(senderText);
mDescription = preferences.mSharedPreferences.getString(mUuid + ".description", null);
mName = preferences.mSharedPreferences.getString(mUuid + ".name", mName);
mEmail = preferences.mSharedPreferences.getString(mUuid + ".email", mEmail);
mAutomaticCheckIntervalMinutes = preferences.mSharedPreferences.getInt(mUuid
+ ".automaticCheckIntervalMinutes", -1);
mLastAutomaticCheckTime = preferences.mSharedPreferences.getLong(mUuid
+ ".lastAutomaticCheckTime", 0);
mNotifyNewMail = preferences.mSharedPreferences.getBoolean(mUuid + ".notifyNewMail",
false);
// delete policy was incorrectly set on earlier versions, so we'll upgrade it here.
// rule: if IMAP account and policy = 0 ("never"), change policy to 2 ("on delete")
mDeletePolicy = preferences.mSharedPreferences.getInt(mUuid + ".deletePolicy", 0);
if (mDeletePolicy == DELETE_POLICY_NEVER &&
mStoreUri != null && mStoreUri.toString().startsWith(Store.STORE_SCHEME_IMAP)) {
mDeletePolicy = DELETE_POLICY_ON_DELETE;
}
mDraftsFolderName = preferences.mSharedPreferences.getString(mUuid + ".draftsFolderName",
"Drafts");
mSentFolderName = preferences.mSharedPreferences.getString(mUuid + ".sentFolderName",
"Sent");
mTrashFolderName = preferences.mSharedPreferences.getString(mUuid + ".trashFolderName",
"Trash");
mOutboxFolderName = preferences.mSharedPreferences.getString(mUuid + ".outboxFolderName",
"Outbox");
mAccountNumber = preferences.mSharedPreferences.getInt(mUuid + ".accountNumber", 0);
mVibrate = preferences.mSharedPreferences.getBoolean(mUuid + ".vibrate", false);
mVibrateWhenSilent = preferences.mSharedPreferences.getBoolean(mUuid +
KEY_VIBRATE_WHEN_SILENT, false);
mRingtoneUri = preferences.mSharedPreferences.getString(mUuid + ".ringtone",
"content://settings/system/notification_sound");
mSyncWindow = preferences.mSharedPreferences.getInt(mUuid + KEY_SYNC_WINDOW,
SyncWindow.SYNC_WINDOW_USER);
mBackupFlags = preferences.mSharedPreferences.getInt(mUuid + KEY_BACKUP_FLAGS, 0);
mProtocolVersion = preferences.mSharedPreferences.getString(mUuid + KEY_PROTOCOL_VERSION,
null);
// Wrap this in a try/catch, as this preference was formerly saved as an int (the value no
// longer fits in an int, and is now stored as a long)
try {
mSecurityFlags = preferences.mSharedPreferences.getLong(mUuid + KEY_SECURITY_FLAGS, 0);
} catch (ClassCastException e) {
mSecurityFlags = preferences.mSharedPreferences.getInt(mUuid + KEY_SECURITY_FLAGS, 0);
}
mSignature = preferences.mSharedPreferences.getString(mUuid + KEY_SIGNATURE, null);
}
public String getUuid() {
return mUuid;
}
public String getStoreUri() {
return mStoreUri;
}
public void setStoreUri(String storeUri) {
this.mStoreUri = storeUri;
}
public String getSenderUri() {
return mSenderUri;
}
public void setSenderUri(String senderUri) {
this.mSenderUri = senderUri;
}
public String getDescription() {
return mDescription;
}
public void setDescription(String description) {
this.mDescription = description;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public String getEmail() {
return mEmail;
}
public void setEmail(String email) {
this.mEmail = email;
}
public boolean isVibrate() {
return mVibrate;
}
public void setVibrate(boolean vibrate) {
mVibrate = vibrate;
}
public boolean isVibrateWhenSilent() {
return mVibrateWhenSilent;
}
public void setVibrateWhenSilent(boolean vibrateWhenSilent) {
mVibrateWhenSilent = vibrateWhenSilent;
}
public String getRingtone() {
return mRingtoneUri;
}
public void setRingtone(String ringtoneUri) {
mRingtoneUri = ringtoneUri;
}
public void delete(Preferences preferences) {
String[] uuids = preferences.mSharedPreferences.getString("accountUuids", "").split(",");
StringBuffer sb = new StringBuffer();
for (int i = 0, length = uuids.length; i < length; i++) {
if (!uuids[i].equals(mUuid)) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(uuids[i]);
}
}
String accountUuids = sb.toString();
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
editor.putString("accountUuids", accountUuids);
editor.remove(mUuid + ".storeUri");
editor.remove(mUuid + ".localStoreUri");
editor.remove(mUuid + ".senderUri");
editor.remove(mUuid + ".description");
editor.remove(mUuid + ".name");
editor.remove(mUuid + ".email");
editor.remove(mUuid + ".automaticCheckIntervalMinutes");
editor.remove(mUuid + ".lastAutomaticCheckTime");
editor.remove(mUuid + ".notifyNewMail");
editor.remove(mUuid + ".deletePolicy");
editor.remove(mUuid + ".draftsFolderName");
editor.remove(mUuid + ".sentFolderName");
editor.remove(mUuid + ".trashFolderName");
editor.remove(mUuid + ".outboxFolderName");
editor.remove(mUuid + ".accountNumber");
editor.remove(mUuid + ".vibrate");
editor.remove(mUuid + KEY_VIBRATE_WHEN_SILENT);
editor.remove(mUuid + ".ringtone");
editor.remove(mUuid + KEY_SYNC_WINDOW);
editor.remove(mUuid + KEY_BACKUP_FLAGS);
editor.remove(mUuid + KEY_PROTOCOL_VERSION);
editor.remove(mUuid + KEY_SECURITY_FLAGS);
editor.remove(mUuid + KEY_SIGNATURE);
// also delete any deprecated fields
editor.remove(mUuid + ".transportUri");
editor.apply();
}
public void save(Preferences preferences) {
if (!preferences.mSharedPreferences.getString("accountUuids", "").contains(mUuid)) {
/*
* When the account is first created we assign it a unique account number. The
* account number will be unique to that account for the lifetime of the account.
* So, we get all the existing account numbers, sort them ascending, loop through
* the list and check if the number is greater than 1 + the previous number. If so
* we use the previous number + 1 as the account number. This refills gaps.
* mAccountNumber starts as -1 on a newly created account. It must be -1 for this
* algorithm to work.
*
* I bet there is a much smarter way to do this. Anyone like to suggest it?
*/
Account[] accounts = preferences.getAccounts();
int[] accountNumbers = new int[accounts.length];
for (int i = 0; i < accounts.length; i++) {
accountNumbers[i] = accounts[i].getAccountNumber();
}
Arrays.sort(accountNumbers);
for (int accountNumber : accountNumbers) {
if (accountNumber > mAccountNumber + 1) {
break;
}
mAccountNumber = accountNumber;
}
mAccountNumber++;
String accountUuids = preferences.mSharedPreferences.getString("accountUuids", "");
accountUuids += (accountUuids.length() != 0 ? "," : "") + mUuid;
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
editor.putString("accountUuids", accountUuids);
editor.apply();
}
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
editor.putString(mUuid + ".storeUri", Utility.base64Encode(mStoreUri));
editor.putString(mUuid + ".localStoreUri", mLocalStoreUri);
editor.putString(mUuid + ".senderUri", Utility.base64Encode(mSenderUri));
editor.putString(mUuid + ".description", mDescription);
editor.putString(mUuid + ".name", mName);
editor.putString(mUuid + ".email", mEmail);
editor.putInt(mUuid + ".automaticCheckIntervalMinutes", mAutomaticCheckIntervalMinutes);
editor.putLong(mUuid + ".lastAutomaticCheckTime", mLastAutomaticCheckTime);
editor.putBoolean(mUuid + ".notifyNewMail", mNotifyNewMail);
editor.putInt(mUuid + ".deletePolicy", mDeletePolicy);
editor.putString(mUuid + ".draftsFolderName", mDraftsFolderName);
editor.putString(mUuid + ".sentFolderName", mSentFolderName);
editor.putString(mUuid + ".trashFolderName", mTrashFolderName);
editor.putString(mUuid + ".outboxFolderName", mOutboxFolderName);
editor.putInt(mUuid + ".accountNumber", mAccountNumber);
editor.putBoolean(mUuid + ".vibrate", mVibrate);
editor.putBoolean(mUuid + KEY_VIBRATE_WHEN_SILENT, mVibrateWhenSilent);
editor.putString(mUuid + ".ringtone", mRingtoneUri);
editor.putInt(mUuid + KEY_SYNC_WINDOW, mSyncWindow);
editor.putInt(mUuid + KEY_BACKUP_FLAGS, mBackupFlags);
editor.putString(mUuid + KEY_PROTOCOL_VERSION, mProtocolVersion);
editor.putLong(mUuid + KEY_SECURITY_FLAGS, mSecurityFlags);
editor.putString(mUuid + KEY_SIGNATURE, mSignature);
// The following fields are *not* written because they need to be more fine-grained
// and not risk rewriting with old data.
// editor.putString(mUuid + PREF_TAG_STORE_PERSISTENT, mStorePersistent);
// also delete any deprecated fields
editor.remove(mUuid + ".transportUri");
editor.apply();
}
@Override
public String toString() {
return mDescription;
}
public Uri getContentUri() {
return Uri.parse("content://accounts/" + getUuid());
}
public String getLocalStoreUri() {
return mLocalStoreUri;
}
public void setLocalStoreUri(String localStoreUri) {
this.mLocalStoreUri = localStoreUri;
}
/**
* Returns -1 for never.
*/
public int getAutomaticCheckIntervalMinutes() {
return mAutomaticCheckIntervalMinutes;
}
/**
* @param automaticCheckIntervalMinutes or -1 for never.
*/
public void setAutomaticCheckIntervalMinutes(int automaticCheckIntervalMinutes) {
this.mAutomaticCheckIntervalMinutes = automaticCheckIntervalMinutes;
}
public long getLastAutomaticCheckTime() {
return mLastAutomaticCheckTime;
}
public void setLastAutomaticCheckTime(long lastAutomaticCheckTime) {
this.mLastAutomaticCheckTime = lastAutomaticCheckTime;
}
public boolean isNotifyNewMail() {
return mNotifyNewMail;
}
public void setNotifyNewMail(boolean notifyNewMail) {
this.mNotifyNewMail = notifyNewMail;
}
public int getDeletePolicy() {
return mDeletePolicy;
}
public void setDeletePolicy(int deletePolicy) {
this.mDeletePolicy = deletePolicy;
}
public String getDraftsFolderName() {
return mDraftsFolderName;
}
public void setDraftsFolderName(String draftsFolderName) {
mDraftsFolderName = draftsFolderName;
}
public String getSentFolderName() {
return mSentFolderName;
}
public void setSentFolderName(String sentFolderName) {
mSentFolderName = sentFolderName;
}
public String getTrashFolderName() {
return mTrashFolderName;
}
public void setTrashFolderName(String trashFolderName) {
mTrashFolderName = trashFolderName;
}
public String getOutboxFolderName() {
return mOutboxFolderName;
}
public void setOutboxFolderName(String outboxFolderName) {
mOutboxFolderName = outboxFolderName;
}
public int getAccountNumber() {
return mAccountNumber;
}
public int getSyncWindow() {
return mSyncWindow;
}
public void setSyncWindow(int window) {
mSyncWindow = window;
}
public int getBackupFlags() {
return mBackupFlags;
}
public void setBackupFlags(int flags) {
mBackupFlags = flags;
}
@Override
public boolean equals(Object o) {
if (o instanceof Account) {
return ((Account)o).mUuid.equals(mUuid);
}
return super.equals(o);
}
}

View File

@ -1,226 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email;
import com.android.email.service.MailService;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.CalendarProviderStub;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.util.Log;
/**
* Utility functions to support backup and restore of accounts.
*
* In the short term, this is used to work around local database failures. In the long term,
* this will also support server-side backups, providing support for automatic account restoration
* when switching or replacing phones.
*/
public class AccountBackupRestore {
// This reduces the need for expensive checks to just the first time only/
// synchronize on AccountBackupRestore.class
private static boolean sBackupsChecked = false;
/**
* Backup accounts. Can be called from UI thread (does work in a new thread)
*/
public static void backupAccounts(final Context context) {
if (Email.DEBUG) {
Log.v(Logging.LOG_TAG, "backupAccounts");
}
// Because we typically call this from the UI, let's do the work in a thread
new Thread() {
@Override
public void run() {
doBackupAccounts(context, Preferences.getPreferences(context));
}
}.start();
}
/**
* Restore accounts if needed. This is blocking, and should only be called in specific
* startup/entry points.
*/
public static synchronized void restoreAccountsIfNeeded(final Context context) {
// Quick exit if possible
if (sBackupsChecked) return;
// Don't log here; This is called often.
boolean restored = doRestoreAccounts(context, Preferences.getPreferences(context), false);
if (restored) {
// after restoring accounts, register services appropriately
Log.w(Logging.LOG_TAG, "Register services after restoring accounts");
// update security profile
SecurityPolicy.getInstance(context).policiesUpdated(-1);
// enable/disable other email services as necessary
Email.setServicesEnabledSync(context);
ExchangeUtils.startExchangeService(context);
}
sBackupsChecked = true;
}
/**
* Non-UI-Thread worker to backup all accounts
*
* @param context used to access the provider
* @param preferences used to access the backups (provided separately for testability)
*/
/* package */ synchronized static void doBackupAccounts(Context context,
Preferences preferences) {
// 1. Wipe any existing backup accounts
Account[] oldBackups = preferences.getAccounts();
for (Account backup : oldBackups) {
backup.delete(preferences);
}
// 2. Identify the default account (if any). This is required because setting
// the default account flag is lazy,and sometimes we don't have any flags set. We'll
// use this to make it explicit (see loop, below).
// This is also the quick check for "no accounts" (the only case in which the returned
// value is -1) and if so, we can exit immediately.
long defaultAccountId = EmailContent.Account.getDefaultAccountId(context);
if (defaultAccountId == -1) {
return;
}
// 3. Create new backup(s), if any
Cursor c = context.getContentResolver().query(EmailContent.Account.CONTENT_URI,
EmailContent.Account.CONTENT_PROJECTION, null, null, null);
try {
while (c.moveToNext()) {
EmailContent.Account fromAccount =
EmailContent.getContent(c, EmailContent.Account.class);
if (Email.DEBUG) {
Log.v(Logging.LOG_TAG, "Backing up account:" + fromAccount.getDisplayName());
}
Account toAccount = LegacyConversions.makeLegacyAccount(context, fromAccount);
// Determine if contacts are also synced, and if so, record that
if (fromAccount.isEasAccount(context)) {
android.accounts.Account acct = new android.accounts.Account(
fromAccount.mEmailAddress, AccountManagerTypes.TYPE_EXCHANGE);
boolean syncContacts = ContentResolver.getSyncAutomatically(acct,
ContactsContract.AUTHORITY);
if (syncContacts) {
toAccount.mBackupFlags |= Account.BACKUP_FLAGS_SYNC_CONTACTS;
}
boolean syncCalendar = ContentResolver.getSyncAutomatically(acct,
CalendarProviderStub.AUTHORITY);
if (syncCalendar) {
toAccount.mBackupFlags |= Account.BACKUP_FLAGS_SYNC_CALENDAR;
}
boolean syncEmail = ContentResolver.getSyncAutomatically(acct,
EmailContent.AUTHORITY);
if (!syncEmail) {
toAccount.mBackupFlags |= Account.BACKUP_FLAGS_DONT_SYNC_EMAIL;
}
}
// Attachment downloading
if ((fromAccount.mFlags & EmailContent.Account.FLAGS_BACKGROUND_ATTACHMENTS) != 0) {
toAccount.mBackupFlags |= Account.BACKUP_FLAGS_BACKGROUND_ATTACHMENTS;
}
// If this is the default account, mark it as such
if (fromAccount.mId == defaultAccountId) {
toAccount.mBackupFlags |= Account.BACKUP_FLAGS_IS_DEFAULT;
}
// Mark this account as a backup of a Provider account, instead of a legacy
// account to upgrade
toAccount.mBackupFlags |= Account.BACKUP_FLAGS_IS_BACKUP;
toAccount.save(preferences);
}
} finally {
c.close();
}
}
/**
* Restore all accounts. This is blocking.
*
* @param context used to access the provider
* @param preferences used to access the backups (provided separately for testability)
* @return true if accounts were restored (meaning services should be restarted, etc.)
*/
/* package */ synchronized static boolean doRestoreAccounts(Context context,
Preferences preferences, boolean unitTest) {
boolean result = false;
// 1. Quick check - if we have any accounts, get out
int numAccounts = EmailContent.count(context, EmailContent.Account.CONTENT_URI, null, null);
if (numAccounts > 0) {
return result;
}
// 2. Quick check - if no backup accounts, get out
Account[] backups = preferences.getAccounts();
if (backups.length == 0) {
return result;
}
Log.w(Logging.LOG_TAG, "*** Restoring Email Accounts, found " + backups.length);
// 3. Possible lost accounts situation - check for any backups, and restore them
for (Account backupAccount : backups) {
// don't back up any leftover legacy accounts (these are migrated elsewhere).
if ((backupAccount.mBackupFlags & Account.BACKUP_FLAGS_IS_BACKUP) == 0) {
continue;
}
// Restore the account
Log.w(Logging.LOG_TAG, "Restoring account:" + backupAccount.getDescription());
EmailContent.Account toAccount = LegacyConversions.makeAccount(context, backupAccount);
// Mark the default account if this is it
if (0 != (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_IS_DEFAULT)) {
toAccount.setDefaultAccount(true);
}
// Restore attachment flag
if (0 != (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_BACKGROUND_ATTACHMENTS)) {
toAccount.mFlags |= EmailContent.Account.FLAGS_BACKGROUND_ATTACHMENTS;
}
// Note that the sense of the email flag is opposite that of contacts/calendar flags
boolean email =
(backupAccount.mBackupFlags & Account.BACKUP_FLAGS_DONT_SYNC_EMAIL) == 0;
boolean contacts = false;
boolean calendar = false;
// Handle system account first, then save in provider
if (toAccount.isEasAccount(context)) {
contacts = (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_SYNC_CONTACTS) != 0;
calendar = (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_SYNC_CALENDAR) != 0;
}
toAccount.save(context);
// Don't simulate AccountManager in unit tests; this results in an NPE
// The unit tests only check EmailProvider based functionality
if (!unitTest) {
MailService.setupAccountManagerAccount(context, toAccount, email, calendar,
contacts, null);
}
result = true;
}
return result;
}
}

View File

@ -18,6 +18,7 @@ package com.android.email;
import com.android.email.mail.Store;
import com.android.email.mail.store.Pop3Store.Pop3Message;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Api;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.AuthenticationFailedException;
@ -1013,7 +1014,7 @@ public class Controller {
*/
@VisibleForTesting
protected void backupAccounts(Context context) {
AccountBackupRestore.backupAccounts(context);
AccountBackupRestore.backup(context);
}
/**

View File

@ -32,10 +32,8 @@ import com.android.emailcommon.mail.Part;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
import com.android.emailcommon.provider.EmailContent.HostAuth;
import com.android.emailcommon.provider.EmailContent.Mailbox;
import com.android.emailcommon.utility.AttachmentUtilities;
import com.android.emailcommon.utility.Utility;
import org.apache.commons.io.IOUtils;
@ -50,7 +48,6 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@ -434,109 +431,6 @@ public class LegacyConversions {
mp.addBodyPart(bp);
}
/**
* Conversion from provider account to legacy account
*
* Used for backup/restore.
*
* @param context application context
* @param fromAccount the provider account to be backed up (including transient hostauth's)
* @return a legacy Account object ready to be committed to preferences
*/
/* package */ static Account makeLegacyAccount(Context context,
EmailContent.Account fromAccount) {
Account result = new Account(context);
result.setDescription(fromAccount.getDisplayName());
result.setEmail(fromAccount.getEmailAddress());
// fromAccount.mSyncKey - assume lost if restoring
result.setSyncWindow(fromAccount.getSyncLookback());
result.setAutomaticCheckIntervalMinutes(fromAccount.getSyncInterval());
// fromAccount.mHostAuthKeyRecv - id not saved; will be reassigned when restoring
// fromAccount.mHostAuthKeySend - id not saved; will be reassigned when restoring
// Provider Account flags, and how they are mapped.
// FLAGS_NOTIFY_NEW_MAIL -> mNotifyNewMail
// FLAGS_VIBRATE_ALWAYS -> mVibrate
// FLAGS_VIBRATE_WHEN_SILENT -> mVibrateWhenSilent
// DELETE_POLICY_NEVER -> mDeletePolicy
// DELETE_POLICY_7DAYS
// DELETE_POLICY_ON_DELETE
result.setNotifyNewMail(0 !=
(fromAccount.getFlags() & EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL));
result.setVibrate(0 !=
(fromAccount.getFlags() & EmailContent.Account.FLAGS_VIBRATE_ALWAYS));
result.setVibrateWhenSilent(0 !=
(fromAccount.getFlags() & EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT));
result.setDeletePolicy(fromAccount.getDeletePolicy());
result.mUuid = fromAccount.getUuid();
result.setName(fromAccount.mSenderName);
result.setRingtone(fromAccount.mRingtoneUri);
result.mProtocolVersion = fromAccount.mProtocolVersion;
// int fromAccount.mNewMessageCount = will be reset on next sync
result.mSignature = fromAccount.mSignature;
// Use the existing conversions from HostAuth <-> Uri
result.setStoreUri(fromAccount.getStoreUri(context));
result.setSenderUri(fromAccount.getSenderUri(context));
return result;
}
/**
* Conversion from legacy account to provider account
*
* Used for backup/restore.
*
* @param context application context
* @param fromAccount the legacy account to convert to modern format
* @return an Account ready to be committed to provider
*/
public static EmailContent.Account makeAccount(Context context, Account fromAccount) {
EmailContent.Account result = new EmailContent.Account();
result.setDisplayName(fromAccount.getDescription());
result.setEmailAddress(fromAccount.getEmail());
result.mSyncKey = null;
result.setSyncLookback(fromAccount.getSyncWindow());
result.setSyncInterval(fromAccount.getAutomaticCheckIntervalMinutes());
// result.mHostAuthKeyRecv; -- will be set when object is saved
// result.mHostAuthKeySend; -- will be set when object is saved
int flags = 0;
if (fromAccount.isNotifyNewMail()) flags |= EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL;
if (fromAccount.isVibrate()) flags |= EmailContent.Account.FLAGS_VIBRATE_ALWAYS;
if (fromAccount.isVibrateWhenSilent())
flags |= EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT;
result.setFlags(flags);
result.setDeletePolicy(fromAccount.getDeletePolicy());
// result.setDefaultAccount(); -- will be set by caller, if needed
result.mCompatibilityUuid = fromAccount.getUuid();
result.setSenderName(fromAccount.getName());
result.setRingtone(fromAccount.getRingtone());
result.mProtocolVersion = fromAccount.mProtocolVersion;
result.mNewMessageCount = 0;
result.mSecuritySyncKey = null;
result.mPolicyKey = 0;
result.mSignature = fromAccount.mSignature;
try {
HostAuth recvAuth = result.getOrCreateHostAuthRecv(context);
Utility.setHostAuthFromString(recvAuth, fromAccount.getStoreUri());
} catch (URISyntaxException e) {
result.mHostAuthRecv = new HostAuth();
Log.w(Logging.LOG_TAG, e);
}
try {
HostAuth sendAuth = result.getOrCreateHostAuthSend(context);
Utility.setHostAuthFromString(sendAuth, fromAccount.getSenderUri());
} catch (URISyntaxException e) {
result.mHostAuthSend = new HostAuth();
Log.w(Logging.LOG_TAG, e);
}
return result;
}
/**
* Infer mailbox type from mailbox name. Used by MessagingController (for live folder sync).

View File

@ -20,7 +20,6 @@ import com.android.emailcommon.Logging;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
import java.util.UUID;
@ -32,7 +31,6 @@ public class Preferences {
// Preferences field names
private static final String ACCOUNT_UUIDS = "accountUuids";
private static final String DEFAULT_ACCOUNT_UUID = "defaultAccountUuid";
private static final String ENABLE_DEBUG_LOGGING = "enableDebugLogging";
private static final String ENABLE_EXCHANGE_LOGGING = "enableExchangeLogging";
private static final String ENABLE_EXCHANGE_FILE_LOGGING = "enableExchangeFileLogging";
@ -81,78 +79,12 @@ public class Preferences {
return sPreferences;
}
/**
* Returns an array of the accounts on the system. If no accounts are
* registered the method returns an empty array.
*/
public Account[] getAccounts() {
String accountUuids = mSharedPreferences.getString(ACCOUNT_UUIDS, null);
if (accountUuids == null || accountUuids.length() == 0) {
return new Account[] {};
}
String[] uuids = accountUuids.split(",");
Account[] accounts = new Account[uuids.length];
for (int i = 0, length = uuids.length; i < length; i++) {
accounts[i] = new Account(this, uuids[i]);
}
return accounts;
public static String getLegacyBackupPreference(Context context) {
return getPreferences(context).mSharedPreferences.getString(ACCOUNT_UUIDS, null);
}
/**
* Get an account object by Uri, or return null if no account exists
* TODO: Merge hardcoded strings with the same strings in Account.java
*/
public Account getAccountByContentUri(Uri uri) {
if (!"content".equals(uri.getScheme()) || !"accounts".equals(uri.getAuthority())) {
return null;
}
String uuid = uri.getPath().substring(1);
if (uuid == null) {
return null;
}
String accountUuids = mSharedPreferences.getString(ACCOUNT_UUIDS, null);
if (accountUuids == null || accountUuids.length() == 0) {
return null;
}
String[] uuids = accountUuids.split(",");
for (int i = 0, length = uuids.length; i < length; i++) {
if (uuid.equals(uuids[i])) {
return new Account(this, uuid);
}
}
return null;
}
/**
* Returns the Account marked as default. If no account is marked as default
* the first account in the list is marked as default and then returned. If
* there are no accounts on the system the method returns null.
*/
public Account getDefaultAccount() {
String defaultAccountUuid = mSharedPreferences.getString(DEFAULT_ACCOUNT_UUID, null);
Account defaultAccount = null;
Account[] accounts = getAccounts();
if (defaultAccountUuid != null) {
for (Account account : accounts) {
if (account.getUuid().equals(defaultAccountUuid)) {
defaultAccount = account;
break;
}
}
}
if (defaultAccount == null) {
if (accounts.length > 0) {
defaultAccount = accounts[0];
setDefaultAccount(defaultAccount);
}
}
return defaultAccount;
}
public void setDefaultAccount(Account account) {
mSharedPreferences.edit().putString(DEFAULT_ACCOUNT_UUID, account.getUuid()).apply();
public static void clearLegacyBackupPreference(Context context) {
getPreferences(context).mSharedPreferences.edit().remove(ACCOUNT_UUIDS).apply();
}
public void setEnableDebugLogging(boolean value) {

View File

@ -16,10 +16,10 @@
package com.android.email.activity;
import com.android.email.AccountBackupRestore;
import com.android.email.Email;
import com.android.email.ExchangeUtils;
import com.android.email.activity.setup.AccountSetupBasics;
import com.android.email.provider.AccountBackupRestore;
import com.android.email.service.MailService;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
@ -166,7 +166,7 @@ public class Welcome extends Activity {
// 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.restoreAccountsIfNeeded(this);
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

View File

@ -16,9 +16,9 @@
package com.android.email.activity.setup;
import com.android.email.AccountBackupRestore;
import com.android.email.R;
import com.android.email.VendorPolicyLoader;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
@ -55,7 +55,7 @@ public class AccountSettingsUtils {
account.update(context, cv);
}
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(context);
AccountBackupRestore.backup(context);
}
/**

View File

@ -16,12 +16,12 @@
package com.android.email.activity.setup;
import com.android.email.AccountBackupRestore;
import com.android.email.Email;
import com.android.email.ExchangeUtils;
import com.android.email.R;
import com.android.email.activity.UiUtilities;
import com.android.email.mail.Store;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Device;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
@ -342,7 +342,7 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment
// Nothing to be done if this fails
}
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(mContext);
AccountBackupRestore.backup(mContext);
}
/**

View File

@ -16,11 +16,11 @@
package com.android.email.activity.setup;
import com.android.email.AccountBackupRestore;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.activity.UiUtilities;
import com.android.email.mail.Store;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.HostAuth;
@ -405,7 +405,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment {
account.update(mContext, account.toContentValues());
account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(mContext);
AccountBackupRestore.backup(mContext);
}
/**

View File

@ -16,11 +16,11 @@
package com.android.email.activity.setup;
import com.android.email.AccountBackupRestore;
import com.android.email.R;
import com.android.email.activity.ActivityHelper;
import com.android.email.activity.UiUtilities;
import com.android.email.activity.Welcome;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
@ -226,7 +226,7 @@ public class AccountSetupNames extends AccountSetupActivity implements OnClickLi
mAccount.update(mContext, cv);
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(AccountSetupNames.this);
AccountBackupRestore.backup(AccountSetupNames.this);
return Account.isSecurityHold(mContext, mAccount.mId);
}

View File

@ -16,10 +16,10 @@
package com.android.email.activity.setup;
import com.android.email.AccountBackupRestore;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.activity.UiUtilities;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
@ -344,7 +344,7 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment
Account account = SetupData.getAccount();
account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(mContext);
AccountBackupRestore.backup(mContext);
}
/**

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
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.
*
* Account backup/restore was implemented entirely for the purpose of recovering from database
* corruption errors that were/are sporadic and of undetermined cause (though the prevailing wisdom
* is that this is due to some kind of memory issue). Rather than have the offending database get
* deleted by SQLiteDatabase and forcing the user to recreate his accounts from scratch, it was
* decided to backup accounts when created/modified and then restore them if 1) there are no
* accounts in the database and 2) there are backup accounts. This, at least, would cause user's
* email data for IMAP/EAS to be re-synced and prevent the worst outcomes from occurring.
*
* To accomplish backup/restore, we use the facility now built in to EmailProvider to store a
* backup version of the Account and HostAuth tables in a second database (EmailProviderBackup.db)
*
* TODO: We might look into having our own DatabaseErrorHandler that tries to be clever about
* determining whether or not a "corrupt" database is truly corrupt; the problem here is that it
* has proven impossible to reproduce the bug, and therefore any "solution" of this kind of utterly
* impossible to test in the wild.
*/
public class AccountBackupRestore {
// We only need to do this once, so prevent extra work by remembering this...
private static boolean sBackupsChecked = false;
/**
* Backup user Account and HostAuth data into our backup database
*/
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;
}
}

View File

@ -68,6 +68,7 @@ public class EmailProvider extends ContentProvider {
protected static final String DATABASE_NAME = "EmailProvider.db";
protected static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
protected static final String BACKUP_DATABASE_NAME = "EmailProviderBackup.db";
public static final String ACTION_ATTACHMENT_UPDATED = "com.android.email.ATTACHMENT_UPDATED";
public static final String ATTACHMENT_UPDATED_EXTRA_FLAGS =
@ -80,6 +81,10 @@ public class EmailProvider extends ContentProvider {
public static final Uri INTEGRITY_CHECK_URI =
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";
@ -1549,12 +1554,144 @@ public class EmailProvider extends ContentProvider {
return sb.toString();
}
/**
* Restore a HostAuth from a database, given its unique id
* @param db the database
* @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) {
Cursor c = db.query(HostAuth.TABLE_NAME, HostAuth.CONTENT_PROJECTION,
HostAuth.RECORD_ID + "=?", new String[] {Long.toString(id)}, null, null, null);
try {
if (c.moveToFirst()) {
HostAuth hostAuth = new HostAuth();
hostAuth.restore(c);
return hostAuth;
}
return null;
} finally {
c.close();
}
}
/**
* Copy the Account and HostAuth tables from one database to another
* @param fromDatabase the source database
* @param toDatabase the destination database
* @return the number of accounts copied, or -1 if an error occurred
*/
private int copyAccountTables(SQLiteDatabase fromDatabase, SQLiteDatabase toDatabase) {
if (fromDatabase == null || toDatabase == null) return -1;
int copyCount = 0;
try {
// Lock both databases; for the "from" database, we don't want anyone changing it from
// under us; for the "to" database, we want to make the operation atomic
fromDatabase.beginTransaction();
toDatabase.beginTransaction();
// Delete anything hanging around here
toDatabase.delete(Account.TABLE_NAME, null, null);
toDatabase.delete(HostAuth.TABLE_NAME, null, null);
// Get our account cursor
Cursor c = fromDatabase.query(Account.TABLE_NAME, Account.CONTENT_PROJECTION,
null, null, null, null, null);
boolean noErrors = true;
try {
// Loop through accounts, copying them and associated host auth's
while (c.moveToNext()) {
Account account = new Account();
account.restore(c);
// Clear security sync key and sync key, as these were specific to the state of
// the account, and we've reset that...
// Clear policy key so that we can re-establish policies from the server
// TODO This is pretty EAS specific, but there's a lot of that around
account.mSecuritySyncKey = null;
account.mSyncKey = null;
account.mPolicyKey = 0;
// Copy host auth's and update foreign keys
HostAuth hostAuth = restoreHostAuth(fromDatabase, account.mHostAuthKeyRecv);
// The account might have gone away, though very unlikely
if (hostAuth == null) continue;
account.mHostAuthKeyRecv = toDatabase.insert(HostAuth.TABLE_NAME, null,
hostAuth.toContentValues());
// EAS accounts have no send HostAuth
if (account.mHostAuthKeySend > 0) {
hostAuth = restoreHostAuth(fromDatabase, account.mHostAuthKeySend);
// Belt and suspenders; I can't imagine that this is possible, since we
// checked the validity of the account above, and the database is now locked
if (hostAuth == null) continue;
account.mHostAuthKeySend = toDatabase.insert(HostAuth.TABLE_NAME, null,
hostAuth.toContentValues());
}
// Now, create the account in the "to" database
toDatabase.insert(Account.TABLE_NAME, null, account.toContentValues());
copyCount++;
}
} catch (SQLiteException e) {
noErrors = false;
copyCount = -1;
} finally {
fromDatabase.endTransaction();
if (noErrors) {
// Say it's ok to commit
toDatabase.setTransactionSuccessful();
}
toDatabase.endTransaction();
c.close();
}
} catch (SQLiteException e) {
copyCount = -1;
}
return copyCount;
}
private SQLiteDatabase getBackupDatabase(Context context) {
DatabaseHelper helper = new DatabaseHelper(context, BACKUP_DATABASE_NAME);
return helper.getWritableDatabase();
}
/**
* Backup account data, returning the number of accounts backed up
*/
private int backupAccounts() {
Context context = getContext();
SQLiteDatabase backupDatabase = getBackupDatabase(context);
try {
return copyAccountTables(getDatabase(context), backupDatabase);
} finally {
if (backupDatabase != null) {
backupDatabase.close();
}
}
}
/**
* Restore account data, returning the number of accounts restored
*/
private int restoreAccounts() {
Context context = getContext();
SQLiteDatabase backupDatabase = getBackupDatabase(context);
try {
return copyAccountTables(backupDatabase, getDatabase(context));
} finally {
if (backupDatabase != null) {
backupDatabase.close();
}
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// Handle this special case the fastest possible way
if (uri == INTEGRITY_CHECK_URI) {
checkDatabases();
return 0;
} else if (uri == ACCOUNT_BACKUP_URI) {
return backupAccounts();
} else if (uri == ACCOUNT_RESTORE_URI) {
return restoreAccounts();
}
// Notify all existing cursors, except for ACCOUNT_RESET_NEW_COUNT(_ID)

View File

@ -16,12 +16,12 @@
package com.android.email.service;
import com.android.email.AccountBackupRestore;
import com.android.email.Email;
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;
@ -61,7 +61,7 @@ public class AccountService extends Service {
@Override
public void restoreAccountsIfNeeded() {
AccountBackupRestore.restoreAccountsIfNeeded(mContext);
AccountBackupRestore.restoreIfNeeded(mContext);
}
@Override

View File

@ -16,13 +16,13 @@
package com.android.email.service;
import com.android.email.AccountBackupRestore;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.NotificationController;
import com.android.email.Preferences;
import com.android.email.SecurityPolicy;
import com.android.email.SingleRunningTask;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.EmailContent;
@ -55,7 +55,6 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;
/**
* Background service for refreshing non-push email accounts.
@ -192,7 +191,7 @@ public class MailService extends Service {
sMailService = this;
// Restore accounts, if it has not happened already
AccountBackupRestore.restoreAccountsIfNeeded(this);
AccountBackupRestore.restoreIfNeeded(this);
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
@ -825,7 +824,7 @@ public class MailService extends Service {
* @param context the caller's context
*/
public static void accountDeleted(Context context) {
AccountBackupRestore.backupAccounts(context);
AccountBackupRestore.backup(context);
SecurityPolicy.getInstance(context).reducePolicies();
Email.setNotifyUiAccountsChanged(true);
MailService.actionReschedule(context);

View File

@ -1,335 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import com.android.emailcommon.provider.EmailContent;
import android.content.Context;
import android.database.Cursor;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
/**
* This is a series of unit tests for backup/restore of the Account class.
*
* Technically these are functional because they use the underlying preferences framework.
*
* NOTE: These tests are destructive of any "legacy" accounts that might be lying around.
*
* You can run this entire test case with:
* runtest -c com.android.email.AccountBackupRestoreTests email
*/
@MediumTest
public class AccountBackupRestoreTests extends ProviderTestCase2<EmailProvider> {
private Preferences mPreferences;
private Context mMockContext;
public AccountBackupRestoreTests() {
super(EmailProvider.class, EmailContent.AUTHORITY);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mMockContext = getMockContext();
// Note: preferences are not supported by this mock context, so we must
// explicitly use (and clean out) the real ones for now.
mPreferences = Preferences.getPreferences(mContext);
}
/**
* Delete any dummy accounts we set up for this test
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
deleteLegacyAccounts();
}
/**
* Delete *all* legacy accounts
*/
private void deleteLegacyAccounts() {
Account[] oldAccounts = mPreferences.getAccounts();
for (Account oldAccount : oldAccounts) {
oldAccount.delete(mPreferences);
}
}
/**
* Test backup with no accounts
*/
public void testNoAccountBackup() {
// create some "old" backups or legacy accounts
Account backupAccount = new Account(mMockContext);
backupAccount.save(mPreferences);
// confirm they are there
Account[] oldBackups = mPreferences.getAccounts();
assertTrue(oldBackups.length >= 1);
// make sure there are no accounts in the provider
int numAccounts = EmailContent.count(mMockContext, EmailContent.Account.CONTENT_URI,
null, null);
assertEquals(0, numAccounts);
// run backups
AccountBackupRestore.doBackupAccounts(mMockContext, mPreferences);
// confirm there are no backups made
Account[] backups = mPreferences.getAccounts();
assertEquals(0, backups.length);
}
/**
* Test backup with accounts
*/
public void testBackup() {
// Clear the decks
deleteLegacyAccounts();
// Create real accounts in need of backup
EmailContent.Account liveAccount1 =
ProviderTestUtils.setupAccount("testBackup1", false, mMockContext);
liveAccount1.mHostAuthRecv =
ProviderTestUtils.setupHostAuth("legacy-recv", 0, false, mMockContext);
liveAccount1.mHostAuthSend =
ProviderTestUtils.setupHostAuth("legacy-send", 0, false, mMockContext);
liveAccount1.setDefaultAccount(true);
liveAccount1.save(mMockContext);
EmailContent.Account liveAccount2 =
ProviderTestUtils.setupAccount("testBackup2", false, mMockContext);
liveAccount2.mHostAuthRecv =
ProviderTestUtils.setupHostAuth("legacy-recv", 0, false, mMockContext);
liveAccount2.mHostAuthSend =
ProviderTestUtils.setupHostAuth("legacy-send", 0, false, mMockContext);
liveAccount2.setDefaultAccount(false);
liveAccount2.save(mMockContext);
// run backups
AccountBackupRestore.doBackupAccounts(mMockContext, mPreferences);
// Confirm we have two backups now
// Deep inspection is not performed here - see LegacyConversionsTests
// We just check for basic identity & flags
Account[] backups = mPreferences.getAccounts();
assertEquals(2, backups.length);
for (Account backup : backups) {
if ("testBackup1".equals(backup.getDescription())) {
assertTrue(0 != (backup.mBackupFlags & Account.BACKUP_FLAGS_IS_DEFAULT));
} else if ("testBackup2".equals(backup.getDescription())) {
assertFalse(0 != (backup.mBackupFlags & Account.BACKUP_FLAGS_IS_DEFAULT));
} else {
fail("unexpected backup name=" + backup.getDescription());
}
}
Account backup1 = backups[0];
assertTrue(0 != (backup1.mBackupFlags & Account.BACKUP_FLAGS_IS_BACKUP));
assertEquals(liveAccount1.getDisplayName(), backup1.getDescription());
}
/**
* TODO: Test backup EAS accounts, with and without contacts sync
*
* Blocker: We need to inject the dependency on ContentResolver.getSyncAutomatically()
* so we can make our fake accounts appear to be syncable or non-syncable
*/
/**
* Test no-restore with accounts found
*/
public void testNoAccountRestore1() {
// make sure there are no real backups
deleteLegacyAccounts();
// make sure there are test backups available
Account backupAccount1 = setupLegacyBackupAccount("backup1");
backupAccount1.save(mPreferences);
Account backupAccount2 = setupLegacyBackupAccount("backup2");
backupAccount2.save(mPreferences);
// make sure there are accounts
EmailContent.Account existing =
ProviderTestUtils.setupAccount("existing", true, mMockContext);
// run the restore
boolean anyRestored =
AccountBackupRestore.doRestoreAccounts(mMockContext, mPreferences, true);
assertFalse(anyRestored);
// make sure accounts still there
int numAccounts = EmailContent.count(mMockContext, EmailContent.Account.CONTENT_URI,
null, null);
assertEquals(1, numAccounts);
}
/**
* Test no-restore with no accounts & no backups
*/
public void testNoAccountRestore2() {
// make sure there are no real backups
deleteLegacyAccounts();
// make sure there are no accounts
int numAccounts = EmailContent.count(mMockContext, EmailContent.Account.CONTENT_URI,
null, null);
assertEquals(0, numAccounts);
// run the restore
boolean anyRestored =
AccountBackupRestore.doRestoreAccounts(mMockContext, mPreferences, true);
assertFalse(anyRestored);
// make sure accounts still there
numAccounts = EmailContent.count(mMockContext, EmailContent.Account.CONTENT_URI,
null, null);
assertEquals(0, numAccounts);
}
/**
* Test restore with 2 accounts.
* Repeats test to verify restore of default account
*/
public void testAccountRestore() {
// make sure there are no real backups
deleteLegacyAccounts();
// create test backups
Account backupAccount1 = setupLegacyBackupAccount("backup1");
backupAccount1.mBackupFlags |= Account.BACKUP_FLAGS_IS_DEFAULT;
backupAccount1.save(mPreferences);
Account backupAccount2 = setupLegacyBackupAccount("backup2");
backupAccount2.save(mPreferences);
// run the restore
boolean anyRestored =
AccountBackupRestore.doRestoreAccounts(mMockContext, mPreferences, true);
assertTrue(anyRestored);
// Check the restored accounts
// Deep inspection is not performed here - see LegacyConversionsTests for that
// We just check for basic identity & flags
Cursor c = mMockContext.getContentResolver().query(EmailContent.Account.CONTENT_URI,
EmailContent.Account.CONTENT_PROJECTION, null, null, null);
try {
assertEquals(2, c.getCount());
while (c.moveToNext()) {
EmailContent.Account restored =
EmailContent.getContent(c, EmailContent.Account.class);
if ("backup1".equals(restored.getDisplayName())) {
assertTrue(restored.mIsDefault);
} else if ("backup2".equals(restored.getDisplayName())) {
assertFalse(restored.mIsDefault);
} else {
fail("Unexpected restore account name=" + restored.getDisplayName());
}
checkRestoredTransientValues(restored);
}
} finally {
c.close();
}
// clear out the backups & accounts and try again
deleteLegacyAccounts();
mMockContext.getContentResolver().delete(EmailContent.Account.CONTENT_URI, null, null);
Account backupAccount3 = setupLegacyBackupAccount("backup3");
backupAccount3.save(mPreferences);
Account backupAccount4 = setupLegacyBackupAccount("backup4");
backupAccount4.mBackupFlags |= Account.BACKUP_FLAGS_IS_DEFAULT;
backupAccount4.save(mPreferences);
// run the restore
AccountBackupRestore.doRestoreAccounts(mMockContext, mPreferences, true);
// Check the restored accounts
// Deep inspection is not performed here - see LegacyConversionsTests for that
// We just check for basic identity & flags
c = mMockContext.getContentResolver().query(EmailContent.Account.CONTENT_URI,
EmailContent.Account.CONTENT_PROJECTION, null, null, null);
try {
assertEquals(2, c.getCount());
while (c.moveToNext()) {
EmailContent.Account restored =
EmailContent.getContent(c, EmailContent.Account.class);
if ("backup3".equals(restored.getDisplayName())) {
assertFalse(restored.mIsDefault);
} else if ("backup4".equals(restored.getDisplayName())) {
assertTrue(restored.mIsDefault);
} else {
fail("Unexpected restore account name=" + restored.getDisplayName());
}
checkRestoredTransientValues(restored);
}
} finally {
c.close();
}
}
/**
* Check a given restored account to make sure that transient (non-backed-up) values
* are initialized to reasonable values.
*/
private void checkRestoredTransientValues(EmailContent.Account restored) {
// sync key == null
assertNull(restored.mSyncKey);
// hostauth id's are no longer zero or -1
assertTrue(restored.mHostAuthKeyRecv > 0);
assertTrue(restored.mHostAuthKeySend > 0);
// protocol version == null or non-empty string
assertTrue(restored.mProtocolVersion == null || restored.mProtocolVersion.length() > 0);
}
/**
* TODO: Test restore EAS accounts, with and without contacts sync
*
* Blocker: We need to inject the dependency on account manager to catch the calls to it
*/
/**
* Setup a legacy backup account with many fields prefilled.
*/
private Account setupLegacyBackupAccount(String name) {
Account backup = new Account(mMockContext);
// fill in useful fields
backup.mUuid = "test-uid-" + name;
backup.mStoreUri = "store://test/" + name;
backup.mLocalStoreUri = "local://localhost/" + name;
backup.mSenderUri = "sender://test/" + name;
backup.mDescription = name;
backup.mName = "name " + name;
backup.mEmail = "email " + name;
backup.mAutomaticCheckIntervalMinutes = 100;
backup.mLastAutomaticCheckTime = 200;
backup.mNotifyNewMail = true;
backup.mDraftsFolderName = "drafts " + name;
backup.mSentFolderName = "sent " + name;
backup.mTrashFolderName = "trash " + name;
backup.mOutboxFolderName = "outbox " + name;
backup.mAccountNumber = 300;
backup.mVibrate = true;
backup.mVibrateWhenSilent = false;
backup.mRingtoneUri = "ringtone://test/" + name;
backup.mSyncWindow = 400;
backup.mBackupFlags = Account.BACKUP_FLAGS_IS_BACKUP;
backup.mProtocolVersion = "proto version" + name;
backup.mDeletePolicy = Account.DELETE_POLICY_NEVER;
backup.mSecurityFlags = 500;
return backup;
}
}

View File

@ -1,198 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email;
import com.android.emailcommon.utility.Utility;
import android.content.SharedPreferences;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
/**
* This is a series of unit tests for the Account class.
*
* Technically these are functional because they use the underlying preferences framework.
*/
@MediumTest
public class AccountUnitTests extends AndroidTestCase {
private Preferences mPreferences;
private String mUuid;
private Account mAccount;
@Override
protected void setUp() throws Exception {
super.setUp();
mPreferences = Preferences.getPreferences(getContext());
}
/**
* Delete any dummy accounts we set up for this test
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
if (mAccount != null && mPreferences != null) {
mAccount.delete(mPreferences);
}
}
/**
* Test the update path from .transportUri to .senderUri
*/
public void testTransportToSenderUpdate() {
final String TEST_VALUE = "This Is The Sender Uri";
// Create a dummy account
createTestAccount();
// Tweak it to look like an old account (with ".transportUri")
SharedPreferences.Editor editor = mPreferences.mSharedPreferences.edit();
editor.remove(mUuid + ".senderUri");
editor.putString(mUuid + ".transportUri", Utility.base64Encode(TEST_VALUE));
editor.commit();
// Read it, see if we get back the string as a sender string
mAccount.refresh(mPreferences);
assertEquals(TEST_VALUE, mAccount.getSenderUri());
// Update it - this will automatically convert it to the newer name
mAccount.save(mPreferences);
// Confirm that the field was replaced with the new form
String newString = mPreferences.mSharedPreferences.getString(mUuid + ".senderUri", null);
assertEquals(TEST_VALUE, Utility.base64Decode(newString));
String oldString = mPreferences.mSharedPreferences.getString(mUuid + ".transportUri", null);
assertNull(oldString);
}
/**
* Test the update path for old IMAP accounts that didn't have DELETE_POLICY_ON_DELETE
* properly preset.
*/
public void testImapDeletePolicyUpdate() {
final String STORE_URI_IMAP = "imap://user:pass@imap-server.com";
final String STORE_URI_POP3 = "pop3://user:pass@pop3-server.com";
// Test 1: try it with a POP3 account - no update should occur
// create a dummy account
createTestAccount();
// set up a minimal POP3 account with default value
SharedPreferences.Editor editor = mPreferences.mSharedPreferences.edit();
editor.putString(mUuid + ".storeUri", Utility.base64Encode(STORE_URI_POP3));
editor.putInt(mUuid + ".deletePolicy", Account.DELETE_POLICY_NEVER);
editor.commit();
// read it in and confirm that we get the default value
mAccount.refresh(mPreferences);
assertEquals(Account.DELETE_POLICY_NEVER, mAccount.getDeletePolicy());
// flush it and confirm that we don't change the database
mAccount.save(mPreferences);
int storedPolicy = mPreferences.mSharedPreferences.getInt(mUuid + ".deletePolicy", -1);
assertEquals(Account.DELETE_POLICY_NEVER, storedPolicy);
// Test 2: try it with an IMAP account - this time we should see an auto-update
// create a dummy account
mAccount.delete(mPreferences);
createTestAccount();
// tweak it to have the wrong settings - this is what IMAP accounts look like
// with manual setup, in earlier versions
editor = mPreferences.mSharedPreferences.edit();
editor.putString(mUuid + ".storeUri", Utility.base64Encode(STORE_URI_IMAP));
editor.putInt(mUuid + ".deletePolicy", Account.DELETE_POLICY_NEVER);
editor.commit();
// Now read it in and confirm that we get the properly updated value
mAccount.refresh(mPreferences);
assertEquals(Account.DELETE_POLICY_ON_DELETE, mAccount.getDeletePolicy());
// Now flush it and confirm that we fixed the database
mAccount.save(mPreferences);
storedPolicy = mPreferences.mSharedPreferences.getInt(mUuid + ".deletePolicy", -1);
assertEquals(Account.DELETE_POLICY_ON_DELETE, storedPolicy);
}
/**
* Test new flags field (added only for backups - not used by real/legacy accounts)
*/
public void testFlagsField() {
createTestAccount();
assertEquals(0, mAccount.mBackupFlags);
mAccount.save(mPreferences);
mAccount.mBackupFlags = -1;
mAccount.refresh(mPreferences);
assertEquals(0, mAccount.mBackupFlags);
mAccount.mBackupFlags = Account.BACKUP_FLAGS_IS_BACKUP;
mAccount.save(mPreferences);
mAccount.mBackupFlags = -1;
mAccount.refresh(mPreferences);
assertEquals(Account.BACKUP_FLAGS_IS_BACKUP, mAccount.mBackupFlags);
mAccount.mBackupFlags = Account.BACKUP_FLAGS_SYNC_CONTACTS;
mAccount.save(mPreferences);
mAccount.mBackupFlags = -1;
mAccount.refresh(mPreferences);
assertEquals(Account.BACKUP_FLAGS_SYNC_CONTACTS, mAccount.mBackupFlags);
mAccount.mBackupFlags = Account.BACKUP_FLAGS_IS_DEFAULT;
mAccount.save(mPreferences);
mAccount.mBackupFlags = -1;
mAccount.refresh(mPreferences);
assertEquals(Account.BACKUP_FLAGS_IS_DEFAULT, mAccount.mBackupFlags);
mAccount.mBackupFlags = Account.BACKUP_FLAGS_SYNC_CALENDAR;
mAccount.save(mPreferences);
mAccount.mBackupFlags = -1;
mAccount.refresh(mPreferences);
assertEquals(Account.BACKUP_FLAGS_SYNC_CALENDAR, mAccount.mBackupFlags);
mAccount.mBackupFlags = Account.BACKUP_FLAGS_DONT_SYNC_EMAIL;
mAccount.save(mPreferences);
mAccount.mBackupFlags = -1;
mAccount.refresh(mPreferences);
assertEquals(Account.BACKUP_FLAGS_DONT_SYNC_EMAIL, mAccount.mBackupFlags);
mAccount.mBackupFlags = Account.BACKUP_FLAGS_BACKGROUND_ATTACHMENTS;
mAccount.save(mPreferences);
mAccount.mBackupFlags = -1;
mAccount.refresh(mPreferences);
assertEquals(Account.BACKUP_FLAGS_BACKGROUND_ATTACHMENTS, mAccount.mBackupFlags);
}
/**
* Create a dummy account with minimal fields
*/
private void createTestAccount() {
mAccount = new Account(getContext());
mAccount.save(mPreferences);
mUuid = mAccount.getUuid();
}
}

View File

@ -49,11 +49,11 @@ import java.util.Date;
/**
* Tests of the Legacy Conversions code (used by MessagingController).
*
*
* NOTE: It would probably make sense to rewrite this using a MockProvider, instead of the
* ProviderTestCase (which is a real provider running on a temp database). This would be more of
* a true "unit test".
*
*
* You can run this entire test case with:
* runtest -c com.android.email.LegacyConversionsTests email
*/
@ -72,7 +72,6 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
EmailProvider mProvider;
Context mProviderContext;
Context mContext;
Account mLegacyAccount = null;
Preferences mPreferences = null;
public LegacyConversionsTests() {
@ -86,14 +85,6 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
mContext = getContext();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
if (mLegacyAccount != null) {
mLegacyAccount.delete(mPreferences);
}
}
/**
* TODO: basic Legacy -> Provider Message conversions
* TODO: basic Legacy -> Provider Body conversions
@ -591,142 +582,4 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
// cv.put("attachment_count", attachments.size());
}
/**
* Test conversion of a legacy account to a provider account
*/
public void testMakeProviderAccount() throws MessagingException {
setupLegacyAccount("testMakeProviderAccount", true);
EmailContent.Account toAccount =
LegacyConversions.makeAccount(mProviderContext, mLegacyAccount);
checkProviderAccount("testMakeProviderAccount", mLegacyAccount, toAccount);
}
/**
* Test conversion of a provider account to a legacy account
*/
public void testMakeLegacyAccount() throws MessagingException {
EmailContent.Account fromAccount = ProviderTestUtils.setupAccount("convert-to-legacy",
false, mProviderContext);
fromAccount.mHostAuthRecv =
ProviderTestUtils.setupHostAuth("legacy-recv", 0, false, mProviderContext);
fromAccount.mHostAuthSend =
ProviderTestUtils.setupHostAuth("legacy-send", 0, false, mProviderContext);
fromAccount.save(mProviderContext);
Account toAccount = LegacyConversions.makeLegacyAccount(mProviderContext, fromAccount);
checkLegacyAccount("testMakeLegacyAccount", fromAccount, toAccount);
}
/**
* Setup a legacy account in mLegacyAccount with many fields prefilled.
*/
private void setupLegacyAccount(String name, boolean saveIt) {
// prefs & legacy account are saved for cleanup (it's stored in the real prefs file)
mPreferences = Preferences.getPreferences(mProviderContext);
mLegacyAccount = new Account(mProviderContext);
// fill in useful fields
mLegacyAccount.mUuid = "test-uid-" + name;
mLegacyAccount.mStoreUri = "store://test/" + name;
mLegacyAccount.mLocalStoreUri = "local://localhost/" + name;
mLegacyAccount.mSenderUri = "sender://test/" + name;
mLegacyAccount.mDescription = "description " + name;
mLegacyAccount.mName = "name " + name;
mLegacyAccount.mEmail = "email " + name;
mLegacyAccount.mAutomaticCheckIntervalMinutes = 100;
mLegacyAccount.mLastAutomaticCheckTime = 200;
mLegacyAccount.mNotifyNewMail = true;
mLegacyAccount.mDraftsFolderName = "drafts " + name;
mLegacyAccount.mSentFolderName = "sent " + name;
mLegacyAccount.mTrashFolderName = "trash " + name;
mLegacyAccount.mOutboxFolderName = "outbox " + name;
mLegacyAccount.mAccountNumber = 300;
mLegacyAccount.mVibrate = true;
mLegacyAccount.mVibrateWhenSilent = false;
mLegacyAccount.mRingtoneUri = "ringtone://test/" + name;
mLegacyAccount.mSyncWindow = 400;
mLegacyAccount.mBackupFlags = 0;
mLegacyAccount.mDeletePolicy = Account.DELETE_POLICY_NEVER;
mLegacyAccount.mSecurityFlags = 500;
mLegacyAccount.mSignature = "signature " + name;
if (saveIt) {
mLegacyAccount.save(mPreferences);
}
}
/**
* Compare a provider account to the legacy account it was created from
*/
private void checkProviderAccount(String tag, Account expect, EmailContent.Account actual)
throws MessagingException {
assertEquals(tag + " description", expect.getDescription(), actual.mDisplayName);
assertEquals(tag + " email", expect.getEmail(), actual.mEmailAddress);
assertEquals(tag + " sync key", null, actual.mSyncKey);
assertEquals(tag + " lookback", expect.getSyncWindow(), actual.mSyncLookback);
assertEquals(tag + " sync intvl", expect.getAutomaticCheckIntervalMinutes(),
actual.mSyncInterval);
// These asserts are checking mHostAuthKeyRecv & mHostAuthKeySend
assertEquals(tag + " store", expect.getStoreUri(), actual.getStoreUri(mProviderContext));
assertEquals(tag + " sender", expect.getSenderUri(), actual.getSenderUri(mProviderContext));
// Synthesize & check flags
int expectFlags = 0;
if (expect.mNotifyNewMail) expectFlags |= EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL;
if (expect.mVibrate) expectFlags |= EmailContent.Account.FLAGS_VIBRATE_ALWAYS;
if (expect.mVibrateWhenSilent)
expectFlags |= EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT;
expectFlags |=
(expect.mDeletePolicy << EmailContent.Account.FLAGS_DELETE_POLICY_SHIFT)
& EmailContent.Account.FLAGS_DELETE_POLICY_MASK;
assertEquals(tag + " flags", expectFlags, actual.mFlags);
assertEquals(tag + " default", false, actual.mIsDefault);
assertEquals(tag + " uuid", expect.getUuid(), actual.mCompatibilityUuid);
assertEquals(tag + " name", expect.getName(), actual.mSenderName);
assertEquals(tag + " ringtone", expect.getRingtone(), actual.mRingtoneUri);
assertEquals(tag + " proto vers", expect.mProtocolVersion, actual.mProtocolVersion);
assertEquals(tag + " new count", 0, actual.mNewMessageCount);
assertEquals(tag + " sec sync key", null, actual.mSecuritySyncKey);
assertEquals(tag + " signature", expect.mSignature, actual.mSignature);
}
/**
* Compare a legacy account to the provider account it was created from
*/
private void checkLegacyAccount(String tag, EmailContent.Account expect, Account actual)
throws MessagingException {
int expectFlags = expect.getFlags();
assertEquals(tag + " uuid", expect.mCompatibilityUuid, actual.mUuid);
assertEquals(tag + " store", expect.getStoreUri(mProviderContext), actual.mStoreUri);
assertTrue(actual.mLocalStoreUri.startsWith("local://localhost"));
assertEquals(tag + " sender", expect.getSenderUri(mProviderContext), actual.mSenderUri);
assertEquals(tag + " description", expect.getDisplayName(), actual.mDescription);
assertEquals(tag + " name", expect.getSenderName(), actual.mName);
assertEquals(tag + " email", expect.getEmailAddress(), actual.mEmail);
assertEquals(tag + " checkintvl", expect.getSyncInterval(),
actual.mAutomaticCheckIntervalMinutes);
assertEquals(tag + " checktime", 0, actual.mLastAutomaticCheckTime);
assertEquals(tag + " notify",
(expectFlags & EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL) != 0,
actual.mNotifyNewMail);
assertEquals(tag + " drafts", null, actual.mDraftsFolderName);
assertEquals(tag + " sent", null, actual.mSentFolderName);
assertEquals(tag + " trash", null, actual.mTrashFolderName);
assertEquals(tag + " outbox", null, actual.mOutboxFolderName);
assertEquals(tag + " acct #", -1, actual.mAccountNumber);
assertEquals(tag + " vibrate",
(expectFlags & EmailContent.Account.FLAGS_VIBRATE_ALWAYS) != 0,
actual.mVibrate);
assertEquals(tag + " vibrateSilent",
(expectFlags & EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT) != 0,
actual.mVibrateWhenSilent);
assertEquals(tag + " ", expect.getRingtone(), actual.mRingtoneUri);
assertEquals(tag + " sync window", expect.getSyncLookback(), actual.mSyncWindow);
assertEquals(tag + " backup flags", 0, actual.mBackupFlags);
assertEquals(tag + " proto vers", expect.mProtocolVersion, actual.mProtocolVersion);
assertEquals(tag + " delete policy", expect.getDeletePolicy(), actual.getDeletePolicy());
assertEquals(tag + " signature", expect.mSignature, actual.mSignature);
}
}

View File

@ -1,97 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
/**
* This is a series of unit tests for the Preferences class.
*
* Technically these are functional because they use the underlying preferences framework. It
* would be a really good idea if we could inject our own underlying preferences storage, to better
* test cases like zero accounts behavior (right now, we have to allow for any number of accounts
* already being on the device, and not trashing any.)
*/
@SmallTest
public class PreferencesUnitTests extends AndroidTestCase {
private Preferences mPreferences;
private Account mAccount;
@Override
protected void setUp() throws Exception {
super.setUp();
mPreferences = Preferences.getPreferences(getContext());
}
/**
* Delete any dummy accounts we set up for this test
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
if (mAccount != null && mPreferences != null) {
mAccount.delete(mPreferences);
}
}
/**
* Test the new getAccountByContentUri() API. This should return null if no
* accounts are configured, or the Uri doesn't match, and it should return a desired account
* otherwise.
*
* TODO: Not actually testing the no-accounts case
*/
public void testGetAccountByContentUri() {
// Create a dummy account
createTestAccount();
// test sunny-day lookup by Uri
Uri testAccountUri = mAccount.getContentUri();
Account lookup = mPreferences.getAccountByContentUri(testAccountUri);
assertEquals(mAccount, lookup);
// now make it a bogus Uri - bad scheme, good path, good UUID
testAccountUri = Uri.parse("bogus://accounts/" + mAccount.getUuid());
lookup = mPreferences.getAccountByContentUri(testAccountUri);
assertNull(lookup);
// now make it a bogus Uri - good scheme, bad path, good UUID
testAccountUri = Uri.parse("content://bogus/" + mAccount.getUuid());
lookup = mPreferences.getAccountByContentUri(testAccountUri);
assertNull(lookup);
// now make it a bogus Uri - good scheme/path, bad UUID
testAccountUri = Uri.parse("content://accounts/" + mAccount.getUuid() + "-bogus");
lookup = mPreferences.getAccountByContentUri(testAccountUri);
assertNull(lookup);
}
/**
* Create a dummy account with minimal fields
*/
private void createTestAccount() {
mAccount = new Account(getContext());
mAccount.save(mPreferences);
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.provider;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.HostAuth;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
/**
* This is a series of unit tests for backup/restore of the Account class.
*
* You can run this entire test case with:
* runtest -c com.android.email.provider.AccountBackupRestoreTests email
*/
@MediumTest
public class AccountBackupRestoreTests extends ProviderTestCase2<EmailProvider> {
private Context mMockContext;
public AccountBackupRestoreTests() {
super(EmailProvider.class, EmailContent.AUTHORITY);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mMockContext = getMockContext();
}
/**
* Delete any dummy accounts we set up for this test
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
public static void assertRestoredAccountEqual(Account expect, Account actual) {
assertEquals(" mDisplayName", expect.mDisplayName, actual.mDisplayName);
assertEquals(" mEmailAddress", expect.mEmailAddress, actual.mEmailAddress);
assertEquals(" mSyncLookback", expect.mSyncLookback, actual.mSyncLookback);
assertEquals(" mSyncInterval", expect.mSyncInterval, actual.mSyncInterval);
assertEquals(" mFlags", expect.mFlags, actual.mFlags);
assertEquals(" mIsDefault", expect.mIsDefault, actual.mIsDefault);
assertEquals(" mSenderName", expect.mSenderName, actual.mSenderName);
assertEquals(" mRingtoneUri", expect.mRingtoneUri, actual.mRingtoneUri);
assertEquals(" mProtocolVersion", expect.mProtocolVersion,
actual.mProtocolVersion);
assertEquals(" mNewMessageCount", expect.mNewMessageCount,
actual.mNewMessageCount);
assertEquals(" mSignature", expect.mSignature, actual.mSignature);
// Nulled out by backup
assertEquals(0, actual.mPolicyKey);
assertNull(actual.mSyncKey);
assertNull(actual.mSecuritySyncKey);
}
/**
* Test backup with accounts
*/
public void testBackupAndRestore() {
// Create real accounts in need of backup
Account saved1 =
ProviderTestUtils.setupAccount("testBackup1", false, mMockContext);
saved1.mHostAuthRecv =
ProviderTestUtils.setupHostAuth("legacy-recv", 0, false, mMockContext);
saved1.mHostAuthSend =
ProviderTestUtils.setupHostAuth("legacy-send", 0, false, mMockContext);
saved1.setDefaultAccount(true);
saved1.save(mMockContext);
Account saved2 =
ProviderTestUtils.setupAccount("testBackup2", false, mMockContext);
saved2.mHostAuthRecv =
ProviderTestUtils.setupHostAuth("legacy-recv", 0, false, mMockContext);
saved2.mHostAuthSend =
ProviderTestUtils.setupHostAuth("legacy-send", 0, false, mMockContext);
saved2.setDefaultAccount(false);
saved2.save(mMockContext);
// Make sure they're in the database
assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI));
assertEquals(4, EmailContent.count(mMockContext, HostAuth.CONTENT_URI));
// Backup the accounts
AccountBackupRestore.backup(mMockContext);
// Delete the accounts
ContentResolver cr = mMockContext.getContentResolver();
cr.delete(Account.CONTENT_URI, null, null);
cr.delete(HostAuth.CONTENT_URI, null, null);
// Make sure they're no longer in the database
assertEquals(0, EmailContent.count(mMockContext, Account.CONTENT_URI));
assertEquals(0, EmailContent.count(mMockContext, HostAuth.CONTENT_URI));
// Restore the accounts
AccountBackupRestore.restoreIfNeeded(mMockContext);
// Make sure there are two accounts and four host auths
assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI));
assertEquals(4, EmailContent.count(mMockContext, HostAuth.CONTENT_URI));
// Get a cursor to our accounts, from earliest to latest (same order as saved1/saved2)
Cursor c = cr.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, "_id ASC");
assertNotNull(c);
assertTrue(c.moveToNext());
// Restore the account
Account restored = new Account();
restored.restore(c);
// And the host auth's
HostAuth recv = HostAuth.restoreHostAuthWithId(mMockContext, restored.mHostAuthKeyRecv);
assertNotNull(recv);
HostAuth send = HostAuth.restoreHostAuthWithId(mMockContext, restored.mHostAuthKeySend);
assertNotNull(send);
// The host auth's should be equal (except id)
ProviderTestUtils.assertHostAuthEqual("backup", saved1.mHostAuthRecv, recv, false);
ProviderTestUtils.assertHostAuthEqual("backup", saved1.mHostAuthSend, send, false);
assertRestoredAccountEqual(saved1, restored);
assertTrue(c.moveToNext());
// Restore the account
restored = new Account();
restored.restore(c);
// And the host auth's
recv = HostAuth.restoreHostAuthWithId(mMockContext, restored.mHostAuthKeyRecv);
assertNotNull(recv);
send = HostAuth.restoreHostAuthWithId(mMockContext, restored.mHostAuthKeySend);
assertNotNull(send);
// The host auth's should be equal (except id)
ProviderTestUtils.assertHostAuthEqual("backup", saved2.mHostAuthRecv, recv, false);
ProviderTestUtils.assertHostAuthEqual("backup", saved2.mHostAuthSend, send, false);
assertRestoredAccountEqual(saved2, restored);
}
}

View File

@ -329,11 +329,18 @@ public class ProviderTestUtils extends Assert {
* Compare two hostauth records for equality
*/
public static void assertHostAuthEqual(String caller, HostAuth expect, HostAuth actual) {
assertHostAuthEqual(caller, expect, actual, true);
}
public static void assertHostAuthEqual(String caller, HostAuth expect, HostAuth actual,
boolean testEmailContent) {
if (expect == actual) {
return;
}
assertEmailContentEqual(caller, expect, actual);
if (testEmailContent) {
assertEmailContentEqual(caller, expect, actual);
}
assertEquals(caller + " mProtocol", expect.mProtocol, actual.mProtocol);
assertEquals(caller + " mAddress", expect.mAddress, actual.mAddress);
assertEquals(caller + " mPort", expect.mPort, actual.mPort);