Clean up account reconciliation
* Move AccountReconciler to the Email app (from EmailCommon) * Ensure that Controller.deleteAccountSync() performs ALL actions needed to clean up after an account deletion (delete attachment files, reset policies, refresh the UI, etc.) * Add reconcileAccounts() API to AccountService * Remove accountDeleted() and restoreAccountsIfNeede() from the AccountService API * Remove unused callback Bug: 4883073 Bug: 4767084 Change-Id: I43ffaf009db1a6f306bb0f2a74fb4dd3b2c4b966
This commit is contained in:
parent
ecf8672df2
commit
4e4aba9ebc
@ -65,25 +65,13 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accountDeleted() {
|
||||
public void reconcileAccounts(final String protocol, final String accountManagerType) {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
mService.accountDeleted();
|
||||
mService.reconcileAccounts(protocol, accountManagerType);
|
||||
}
|
||||
}, "accountDeleted");
|
||||
}
|
||||
|
||||
// The following call is synchronous, and should not be made from the UI thread
|
||||
@Override
|
||||
public void restoreAccountsIfNeeded() {
|
||||
setTask(new ProxyTask() {
|
||||
@Override
|
||||
public void run() throws RemoteException {
|
||||
mService.restoreAccountsIfNeeded();
|
||||
}
|
||||
}, "restoreAccountsIfNeeded");
|
||||
waitForCompletion();
|
||||
}, "reconcileAccounts");
|
||||
}
|
||||
|
||||
// The following call is synchronous, and should not be made from the UI thread
|
||||
|
@ -22,8 +22,7 @@ interface IAccountService {
|
||||
oneway void notifyLoginFailed(long accountId);
|
||||
oneway void notifyLoginSucceeded(long accountId);
|
||||
|
||||
void accountDeleted();
|
||||
void restoreAccountsIfNeeded();
|
||||
void reconcileAccounts(String protocol, String accountManagerType);
|
||||
|
||||
int getAccountColor(long accountId);
|
||||
|
||||
|
@ -28,11 +28,13 @@ import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.test.IsolatedContext;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.mail.store.Pop3Store.Pop3Message;
|
||||
import com.android.email.provider.AccountBackupRestore;
|
||||
import com.android.email.service.EmailServiceUtils;
|
||||
import com.android.email.service.MailService;
|
||||
import com.android.emailcommon.Api;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
@ -1119,22 +1121,14 @@ public class Controller {
|
||||
* Delete an account.
|
||||
*/
|
||||
public void deleteAccount(final long accountId) {
|
||||
Utility.runAsync(new Runnable() {
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deleteAccountSync(accountId, mProviderContext);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup our accounts; define this here so that unit tests can override the behavior
|
||||
* @param context the caller's context
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected void backupAccounts(Context context) {
|
||||
AccountBackupRestore.backup(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an account synchronously.
|
||||
*/
|
||||
@ -1154,20 +1148,17 @@ public class Controller {
|
||||
Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
|
||||
context.getContentResolver().delete(uri, null, null);
|
||||
|
||||
backupAccounts(context);
|
||||
// For unit tests, don't run backup, security, and ui pieces
|
||||
if (context instanceof IsolatedContext) return;
|
||||
|
||||
// Release or relax device administration, if relevant
|
||||
// Clean up
|
||||
AccountBackupRestore.backup(context);
|
||||
SecurityPolicy.getInstance(context).reducePolicies();
|
||||
|
||||
Email.setServicesEnabledSync(context);
|
||||
Email.setNotifyUiAccountsChanged(true);
|
||||
MailService.actionReschedule(context);
|
||||
} catch (Exception e) {
|
||||
Log.w(Logging.LOG_TAG, "Exception while deleting account", e);
|
||||
} finally {
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
l.deleteAccountCallback(accountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1329,12 +1320,6 @@ public class Controller {
|
||||
public void sendMailCallback(MessagingException result, long accountId,
|
||||
long messageId, int progress) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from {@link Controller#deleteAccount}.
|
||||
*/
|
||||
public void deleteAccountCallback(long accountId) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,13 +16,13 @@
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.email.Controller.Result;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
/**
|
||||
* A {@link Result} that wraps another {@link Result} and makes sure methods gets called back
|
||||
* on the UI thread.
|
||||
@ -129,14 +129,5 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccountCallback(final long accountId) {
|
||||
run(new Runnable() {
|
||||
public void run() {
|
||||
if (!isRegistered()) return;
|
||||
mWrappee.deleteAccountCallback(accountId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -14,21 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.emailcommon.utility;
|
||||
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
package com.android.email.provider;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@ -57,7 +56,7 @@ public class AccountReconciler {
|
||||
*/
|
||||
public static boolean reconcileAccounts(Context context,
|
||||
List<Account> emailProviderAccounts, android.accounts.Account[] accountManagerAccounts,
|
||||
ContentResolver resolver) {
|
||||
Context providerContext) {
|
||||
// First, look through our EmailProvider accounts to make sure there's a corresponding
|
||||
// AccountManager account
|
||||
boolean accountsDeleted = false;
|
||||
@ -80,9 +79,8 @@ public class AccountReconciler {
|
||||
Log.d(Logging.LOG_TAG,
|
||||
"Account deleted in AccountManager; deleting from provider: " +
|
||||
providerAccountName);
|
||||
// TODO This will orphan downloaded attachments; need to handle this
|
||||
resolver.delete(ContentUris.withAppendedId(Account.CONTENT_URI,
|
||||
providerAccount.mId), null, null);
|
||||
Controller.getInstance(context).deleteAccountSync(providerAccount.mId,
|
||||
providerContext);
|
||||
accountsDeleted = true;
|
||||
}
|
||||
}
|
@ -16,22 +16,27 @@
|
||||
|
||||
package com.android.email.service;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.ResourceHelper;
|
||||
import com.android.email.VendorPolicyLoader;
|
||||
import com.android.email.provider.AccountReconciler;
|
||||
import com.android.emailcommon.Configuration;
|
||||
import com.android.emailcommon.Device;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.service.IAccountService;
|
||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AccountService extends Service {
|
||||
|
||||
@ -50,16 +55,33 @@ public class AccountService extends Service {
|
||||
NotificationController.getInstance(mContext).cancelLoginFailedNotification(accountId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreAccountsIfNeeded() {
|
||||
// Obsolete -- this is done 100% transparently in EmailProvider.
|
||||
// We leave the method because we don't want to change the service interface.
|
||||
// (It may be okay to remove it, but we're not sure at this point.)
|
||||
private ArrayList<Account> getAccountList(String forProtocol) {
|
||||
ArrayList<Account> providerAccounts = new ArrayList<Account>();
|
||||
Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
|
||||
Account.ID_PROJECTION, null, null, null);
|
||||
try {
|
||||
while (c.moveToNext()) {
|
||||
long accountId = c.getLong(Account.CONTENT_ID_COLUMN);
|
||||
String protocol = Account.getProtocol(mContext, accountId);
|
||||
if ((protocol != null) && forProtocol.equals(protocol)) {
|
||||
Account account = Account.restoreAccountWithId(mContext, accountId);
|
||||
if (account != null) {
|
||||
providerAccounts.add(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return providerAccounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accountDeleted() {
|
||||
MailService.accountDeleted(mContext);
|
||||
public void reconcileAccounts(String protocol, String accountManagerType) {
|
||||
ArrayList<Account> providerList = getAccountList(protocol);
|
||||
android.accounts.Account[] accountMgrList =
|
||||
AccountManager.get(mContext).getAccountsByType(accountManagerType);
|
||||
AccountReconciler.reconcileAccounts(mContext, providerList, accountMgrList, mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -38,16 +38,14 @@ import android.util.Log;
|
||||
import com.android.email.Controller;
|
||||
import com.android.email.Email;
|
||||
import com.android.email.Preferences;
|
||||
import com.android.email.SecurityPolicy;
|
||||
import com.android.email.SingleRunningTask;
|
||||
import com.android.email.provider.AccountBackupRestore;
|
||||
import com.android.email.provider.AccountReconciler;
|
||||
import com.android.emailcommon.AccountManagerTypes;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.utility.AccountReconciler;
|
||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
@ -703,7 +701,7 @@ public class MailService extends Service {
|
||||
.getAccountsByType(AccountManagerTypes.TYPE_POP_IMAP);
|
||||
ArrayList<Account> providerAccounts = getPopImapAccountList(context);
|
||||
MailService.reconcileAccountsWithAccountManager(context, providerAccounts,
|
||||
accountManagerAccounts, false, context.getContentResolver());
|
||||
accountManagerAccounts, context);
|
||||
|
||||
}
|
||||
};
|
||||
@ -715,38 +713,20 @@ public class MailService extends Service {
|
||||
sReconcilePopImapAccountsSyncExecutor.run(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a variety of cleanup actions that must be performed when an account has been deleted.
|
||||
* This includes triggering an account backup, ensuring that security policies are properly
|
||||
* reset, if necessary, notifying the UI of the change, and resetting scheduled syncs and
|
||||
* notifications.
|
||||
* @param context the caller's context
|
||||
*/
|
||||
public static void accountDeleted(Context context) {
|
||||
AccountBackupRestore.backup(context);
|
||||
SecurityPolicy.getInstance(context).reducePolicies();
|
||||
Email.setNotifyUiAccountsChanged(true);
|
||||
MailService.actionReschedule(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* See Utility.reconcileAccounts for details
|
||||
* @param context The context in which to operate
|
||||
* @param emailProviderAccounts the exchange provider accounts to work from
|
||||
* @param accountManagerAccounts The account manager accounts to work from
|
||||
* @param blockExternalChanges FOR TESTING ONLY - block backups, security changes, etc.
|
||||
* @param resolver the content resolver for making provider updates (injected for testability)
|
||||
* @param providerContext the provider's context (in unit tests, this may differ from context)
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void reconcileAccountsWithAccountManager(Context context,
|
||||
List<Account> emailProviderAccounts, android.accounts.Account[] accountManagerAccounts,
|
||||
boolean blockExternalChanges, ContentResolver resolver) {
|
||||
boolean accountsDeleted = AccountReconciler.reconcileAccounts(context,
|
||||
emailProviderAccounts, accountManagerAccounts, resolver);
|
||||
// If we changed the list of accounts, refresh the backup & security settings
|
||||
if (!blockExternalChanges && accountsDeleted) {
|
||||
accountDeleted(context);
|
||||
}
|
||||
Context providerContext) {
|
||||
AccountReconciler.reconcileAccounts(context, emailProviderAccounts, accountManagerAccounts,
|
||||
providerContext);
|
||||
}
|
||||
|
||||
public static void setupAccountManagerAccount(Context context, Account account,
|
||||
|
@ -307,17 +307,6 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
||||
assertEquals(Account.FLAGS_VIBRATE_ALWAYS, a2b.mFlags);
|
||||
}
|
||||
|
||||
private static class MockController extends Controller {
|
||||
protected MockController(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void backupAccounts(Context context) {
|
||||
// For testing, we don't want to back up our accounts
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the response to disabling DeviceAdmin status
|
||||
*/
|
||||
@ -352,11 +341,6 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
||||
assertNull(a3a.mSecuritySyncKey);
|
||||
assertTrue(a3a.mPolicyKey == 0);
|
||||
|
||||
// Simulate revoke of device admin; directly call deleteSecuredAccounts, which is normally
|
||||
// called from a background thread
|
||||
MockController mockController = new MockController(mMockContext);
|
||||
Controller.injectMockControllerForTest(mockController);
|
||||
try {
|
||||
mSecurityPolicy.deleteSecuredAccounts(mMockContext);
|
||||
Policy after2 = mSecurityPolicy.getAggregatePolicy();
|
||||
assertEquals(EMPTY_POLICY, after2);
|
||||
@ -366,9 +350,6 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
||||
assertNull(a2b);
|
||||
Account a3b = Account.restoreAccountWithId(mMockContext, a3.mId);
|
||||
assertNull(a3b.mSecuritySyncKey);
|
||||
} finally {
|
||||
Controller.injectMockControllerForTest(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,7 +50,6 @@ import com.android.emailcommon.provider.EmailContent.PolicyColumns;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.provider.Policy;
|
||||
import com.android.emailcommon.utility.AccountReconciler;
|
||||
import com.android.emailcommon.utility.TextUtilities;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
|
@ -68,11 +68,6 @@ public class MailServiceTests extends AccountTestCase {
|
||||
super.tearDown();
|
||||
// Delete any test accounts we might have created earlier
|
||||
deleteTemporaryAccountManagerAccounts();
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
pm.setComponentEnabledSetting(
|
||||
new ComponentName(getContext(), EasTestAuthenticatorService.class),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,7 +142,7 @@ public class MailServiceTests extends AccountTestCase {
|
||||
// Run the reconciler
|
||||
ContentResolver resolver = mMockContext.getContentResolver();
|
||||
MailService.reconcileAccountsWithAccountManager(context,
|
||||
makeExchangeServiceAccountList(), accountManagerAccounts, true, resolver);
|
||||
makeExchangeServiceAccountList(), accountManagerAccounts, mMockContext);
|
||||
|
||||
// There should now be only two EmailProvider accounts
|
||||
assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null));
|
||||
@ -160,7 +155,7 @@ public class MailServiceTests extends AccountTestCase {
|
||||
|
||||
// Run the reconciler
|
||||
MailService.reconcileAccountsWithAccountManager(context,
|
||||
makeExchangeServiceAccountList(), accountManagerAccounts, true, resolver);
|
||||
makeExchangeServiceAccountList(), accountManagerAccounts, mMockContext);
|
||||
|
||||
// There should now be only one AccountManager account
|
||||
accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
|
||||
|
Loading…
Reference in New Issue
Block a user