Move account delete logic to EmailProvider

* This functionality had to move away from Controller

Change-Id: I557918a325eab8c83a9728fa1ce33dde8b86158f
This commit is contained in:
Marc Blank 2012-03-09 11:52:45 -08:00
parent ab6321e2c4
commit ebb79619e8
8 changed files with 127 additions and 115 deletions

View File

@ -965,5 +965,4 @@ public final class Account extends EmailContent implements AccountColumns, Parce
sb.append(']');
return sb.toString();
}
}

View File

@ -637,4 +637,8 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
return new Mailbox[size];
}
};
public String toString() {
return "[Mailbox " + mId + ": " + mDisplayName + "]";
}
}

View File

@ -27,7 +27,6 @@ import android.os.RemoteException;
import android.util.Log;
import com.android.email.mail.store.Pop3Store.Pop3Message;
import com.android.email.provider.AccountBackupRestore;
import com.android.email.provider.Utilities;
import com.android.email.service.EmailServiceUtils;
import com.android.emailcommon.Logging;
@ -92,12 +91,6 @@ public class Controller {
};
private static final int MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID = 1;
private static final String MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?";
private static final String MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION =
MAILBOXES_FOR_ACCOUNT_SELECTION + " AND " + MailboxColumns.TYPE + "!=" +
Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
private static final String MESSAGES_FOR_ACCOUNT_SELECTION = MessageColumns.ACCOUNT_KEY + "=?";
// Service callbacks as set up via setCallback
private static RemoteCallbackList<IEmailServiceCallback> sCallbackList =
new RemoteCallbackList<IEmailServiceCallback>();
@ -971,99 +964,6 @@ public class Controller {
return getServiceForAccount(message.mAccountKey);
}
/**
* Delete an account.
*/
public void deleteAccount(final long accountId) {
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
deleteAccountSync(accountId, mProviderContext);
}
});
}
/**
* Delete an account synchronously.
*/
public void deleteAccountSync(long accountId, Context context) {
try {
mLegacyControllerMap.remove(accountId);
// Get the account URI.
final Account account = Account.restoreAccountWithId(context, accountId);
if (account == null) {
return; // Already deleted?
}
// Delete account data, attachments, PIM data, etc.
deleteSyncedDataSync(accountId);
// Now delete the account itself
Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
context.getContentResolver().delete(uri, null, null);
// For unit tests, don't run backup, security, and ui pieces.
if (mInUnitTests) {
return;
}
// Clean up
AccountBackupRestore.backup(context);
SecurityPolicy.getInstance(context).reducePolicies();
Email.setServicesEnabledSync(context);
Email.setNotifyUiAccountsChanged(true);
} catch (Exception e) {
Log.w(Logging.LOG_TAG, "Exception while deleting account", e);
}
}
/**
* Delete all synced data, but don't delete the actual account. This is used when security
* policy requirements are not met, and we don't want to reveal any synced data, but we do
* wish to keep the account configured (e.g. to accept remote wipe commands).
*
* The only mailbox not deleted is the account mailbox (if any)
* Also, clear the sync keys on the remaining account, since the data is gone.
*
* SYNCHRONOUS - do not call from UI thread.
*
* @param accountId The account to wipe.
*/
public void deleteSyncedDataSync(long accountId) {
try {
// Delete synced attachments
AttachmentUtilities.deleteAllAccountAttachmentFiles(mProviderContext,
accountId);
// Delete synced email, leaving only an empty inbox. We do this in two phases:
// 1. Delete all non-inbox mailboxes (which will delete all of their messages)
// 2. Delete all remaining messages (which will be the inbox messages)
ContentResolver resolver = mProviderContext.getContentResolver();
String[] accountIdArgs = new String[] { Long.toString(accountId) };
resolver.delete(Mailbox.CONTENT_URI,
MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION,
accountIdArgs);
resolver.delete(Message.CONTENT_URI, MESSAGES_FOR_ACCOUNT_SELECTION, accountIdArgs);
// Delete sync keys on remaining items
ContentValues cv = new ContentValues();
cv.putNull(Account.SYNC_KEY);
resolver.update(Account.CONTENT_URI, cv, Account.ID_SELECTION, accountIdArgs);
cv.clear();
cv.putNull(Mailbox.SYNC_KEY);
resolver.update(Mailbox.CONTENT_URI, cv,
MAILBOXES_FOR_ACCOUNT_SELECTION, accountIdArgs);
// Delete PIM data (contacts, calendar), stop syncs, etc. if applicable
IEmailService service = getServiceForAccount(accountId);
if (service != null) {
service.deleteAccountPIMData(accountId);
}
} catch (Exception e) {
Log.w(Logging.LOG_TAG, "Exception while deleting account synced data", e);
}
}
/**
* Simple callback for synchronous commands. For many commands, this can be largely ignored
* and the result is observed via provider cursors. The callback will *not* necessarily be

View File

@ -28,9 +28,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import com.android.email.provider.EmailProvider;
import com.android.email.service.EmailBroadcastProcessorService;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
@ -582,7 +584,8 @@ public class SecurityPolicy {
NotificationController.getInstance(mContext).showSecurityUnsupportedNotification(
account);
// Erase data
Controller.getInstance(mContext).deleteSyncedDataSync(accountId);
Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
mContext.getContentResolver().delete(uri, null, null);
} else if (isActive(policy)) {
if (policyChanged) {
Log.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName + " changed.");
@ -661,8 +664,9 @@ public class SecurityPolicy {
Log.w(TAG, "Email administration disabled; deleting " + c.getCount() +
" secured account(s)");
while (c.moveToNext()) {
Controller.getInstance(context).deleteAccountSync(
c.getLong(EmailContent.ID_PROJECTION_COLUMN), context);
long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
cr.delete(uri, null, null);
}
} finally {
c.close();
@ -754,7 +758,8 @@ public class SecurityPolicy {
// Mark the account as "on hold".
setAccountHoldFlag(context, account, true);
// Erase data
controller.deleteSyncedDataSync(accountId);
Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
context.getContentResolver().delete(uri, null, null);
// Report one or more were found
result = true;
}

View File

@ -37,11 +37,11 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import com.android.email.Controller;
import com.android.email.R;
import com.android.email.activity.ActivityHelper;
import com.android.email.mail.Sender;
import com.android.email.mail.Store;
import com.android.email.provider.EmailProvider;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
@ -678,11 +678,16 @@ public class AccountSettings extends PreferenceActivity {
/**
* Delete the selected account
*/
public void deleteAccount(Account account) {
public void deleteAccount(final Account account) {
// Kick off the work to actually delete the account
// Delete the account (note, this is async. Would be nice to get a callback.
Controller.getInstance(this).deleteAccount(account.mId);
new Thread(new Runnable() {
@Override
public void run() {
Uri uri = EmailProvider.uiUri("uiaccount", account.mId);
getContentResolver().delete(uri, null, null);
}}).start();
// TODO: Remove ui glue for unified
// Then update the UI as appropriate:
// If single pane, return to the header list. If multi, rebuild header list
if (onIsMultiPane()) {

View File

@ -21,9 +21,9 @@ import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.android.email.Controller;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.google.common.annotations.VisibleForTesting;
@ -119,8 +119,8 @@ public class AccountReconciler {
Log.d(Logging.LOG_TAG,
"Account deleted in AccountManager; deleting from provider: " +
providerAccountName);
Controller.getInstance(context).deleteAccountSync(providerAccount.mId,
providerContext);
Uri uri = EmailProvider.uiUri("uiaccount", providerAccount.mId);
context.getContentResolver().delete(uri, null, null);
}
}
}

View File

@ -41,6 +41,7 @@ import com.android.common.content.ProjectionMap;
import com.android.email.Email;
import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.SecurityPolicy;
import com.android.email.provider.ContentCache.CacheToken;
import com.android.email.service.AttachmentDownloadService;
import com.android.email.service.EmailServiceUtils;
@ -62,6 +63,7 @@ import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.provider.QuickResponse;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.IEmailService;
import com.android.emailcommon.service.IEmailServiceCallback;
import com.android.emailcommon.service.SearchParams;
import com.android.emailcommon.utility.AttachmentUtilities;
@ -212,6 +214,7 @@ public class EmailProvider extends ContentProvider {
private static final int UI_ATTACHMENTS = UI_BASE + 14;
private static final int UI_ATTACHMENT = UI_BASE + 15;
private static final int UI_SEARCH = UI_BASE + 16;
private static final int UI_ACCOUNT_DATA = UI_BASE + 17;
// MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS
private static final int LAST_EMAIL_PROVIDER_DB_BASE = UI_BASE;
@ -426,6 +429,7 @@ public class EmailProvider extends ContentProvider {
matcher.addURI(EmailContent.AUTHORITY, "uiattachments/#", UI_ATTACHMENTS);
matcher.addURI(EmailContent.AUTHORITY, "uiattachment/#", UI_ATTACHMENT);
matcher.addURI(EmailContent.AUTHORITY, "uisearch/#", UI_SEARCH);
matcher.addURI(EmailContent.AUTHORITY, "uiaccountdata/#", UI_ACCOUNT_DATA);
}
/**
@ -446,6 +450,14 @@ public class EmailProvider extends ContentProvider {
private SQLiteDatabase mDatabase;
private SQLiteDatabase mBodyDatabase;
public static Uri uiUri(String type, long id) {
return Uri.parse(uiUriString(type, id));
}
public static String uiUriString(String type, long id) {
return "content://" + EmailContent.AUTHORITY + "/" + type + ((id == -1) ? "" : ("/" + id));
}
/**
* Orphan record deletion utility. Generates a sqlite statement like:
* delete from <table> where <column> not in (select <foreignColumn> from <foreignTable>)
@ -723,6 +735,10 @@ public class EmailProvider extends ContentProvider {
switch (match) {
case UI_MESSAGE:
return uiDeleteMessage(uri);
case UI_ACCOUNT_DATA:
return uiDeleteAccountData(uri);
case UI_ACCOUNT:
return uiDeleteAccount(uri);
// These are cases in which one or more Messages might get deleted, either by
// cascade or explicitly
case MAILBOX_ID:
@ -2241,7 +2257,7 @@ outer:
long mailboxId = Mailbox.findMailboxOfType(getContext(), accountId, Mailbox.TYPE_INBOX);
if (mailboxId != Mailbox.NO_MAILBOX) {
values.put(UIProvider.SettingsColumns.DEFAULT_INBOX,
"content://" + EmailContent.AUTHORITY + "/uifolder/" + mailboxId);
uiUriString("uifolder", mailboxId));
}
StringBuilder sb = genSelect(sAccountSettingsMap, uiProjection, values);
sb.append(" FROM " + Account.TABLE_NAME + " WHERE " + AccountColumns.ID + "=?");
@ -2520,7 +2536,7 @@ outer:
} catch (RemoteException e) {
}
}
return Uri.parse("content://" + EmailContent.AUTHORITY + "/uimessage/" + msg.mId);
return uiUri("uimessage", msg.mId);
}
/**
@ -2913,4 +2929,86 @@ outer:
return uiQuery(UI_FOLDER, ContentUris.withAppendedId(Mailbox.CONTENT_URI,
searchMailbox.mId), projection);
}
private static final String MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?";
private static final String MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION =
MAILBOXES_FOR_ACCOUNT_SELECTION + " AND " + MailboxColumns.TYPE + "!=" +
Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
private static final String MESSAGES_FOR_ACCOUNT_SELECTION = MessageColumns.ACCOUNT_KEY + "=?";
/**
* Delete an account and clean it up
*/
private int uiDeleteAccount(Uri uri) {
Context context = getContext();
long accountId = Long.parseLong(uri.getLastPathSegment());
try {
// Get the account URI.
final Account account = Account.restoreAccountWithId(context, accountId);
if (account == null) {
return 0; // Already deleted?
}
deleteAccountData(context, accountId);
// Now delete the account itself
uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
context.getContentResolver().delete(uri, null, null);
// Clean up
AccountBackupRestore.backup(context);
SecurityPolicy.getInstance(context).reducePolicies();
Email.setServicesEnabledSync(context);
return 1;
} catch (Exception e) {
Log.w(Logging.LOG_TAG, "Exception while deleting account", e);
}
return 0;
}
private int uiDeleteAccountData(Uri uri) {
Context context = getContext();
long accountId = Long.parseLong(uri.getLastPathSegment());
// Get the account URI.
final Account account = Account.restoreAccountWithId(context, accountId);
if (account == null) {
return 0; // Already deleted?
}
deleteAccountData(context, accountId);
return 1;
}
private void deleteAccountData(Context context, long accountId) {
// Delete synced attachments
AttachmentUtilities.deleteAllAccountAttachmentFiles(context, accountId);
// Delete synced email, leaving only an empty inbox. We do this in two phases:
// 1. Delete all non-inbox mailboxes (which will delete all of their messages)
// 2. Delete all remaining messages (which will be the inbox messages)
ContentResolver resolver = context.getContentResolver();
String[] accountIdArgs = new String[] { Long.toString(accountId) };
resolver.delete(Mailbox.CONTENT_URI,
MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION,
accountIdArgs);
resolver.delete(Message.CONTENT_URI, MESSAGES_FOR_ACCOUNT_SELECTION, accountIdArgs);
// Delete sync keys on remaining items
ContentValues cv = new ContentValues();
cv.putNull(Account.SYNC_KEY);
resolver.update(Account.CONTENT_URI, cv, Account.ID_SELECTION, accountIdArgs);
cv.clear();
cv.putNull(Mailbox.SYNC_KEY);
resolver.update(Mailbox.CONTENT_URI, cv,
MAILBOXES_FOR_ACCOUNT_SELECTION, accountIdArgs);
// Delete PIM data (contacts, calendar), stop syncs, etc. if applicable
IEmailService service = EmailServiceUtils.getServiceForAccount(context, null, accountId);
if (service != null) {
try {
service.deleteAccountPIMData(accountId);
} catch (RemoteException e) {
// Can't do anything about this
}
}
}
}

View File

@ -424,7 +424,8 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
long message4Id = message.mId;
// Now wipe account 1's data
mTestController.deleteSyncedDataSync(account1Id);
Uri uri = EmailProvider.uiUri("uiaccount", account1Id);
mProviderContext.getContentResolver().delete(uri, null, null);
// Confirm: Mailboxes gone (except account box), all messages gone, account survives
assertNull(Mailbox.restoreMailboxWithId(mProviderContext, box1Id));