2011-02-09 01:50:30 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2011-07-19 00:33:38 +00:00
|
|
|
package com.android.email.provider;
|
2011-02-09 01:50:30 +00:00
|
|
|
|
|
|
|
import android.accounts.AccountManager;
|
|
|
|
import android.accounts.AccountManagerFuture;
|
|
|
|
import android.accounts.AuthenticatorException;
|
|
|
|
import android.accounts.OperationCanceledException;
|
2013-12-17 22:24:37 +00:00
|
|
|
import android.content.ComponentName;
|
2014-03-20 20:33:34 +00:00
|
|
|
import android.content.ContentResolver;
|
2011-02-09 01:50:30 +00:00
|
|
|
import android.content.Context;
|
2013-12-17 22:24:37 +00:00
|
|
|
import android.content.pm.PackageManager;
|
2013-07-26 21:18:39 +00:00
|
|
|
import android.database.Cursor;
|
2014-03-20 20:33:34 +00:00
|
|
|
import android.provider.CalendarContract;
|
|
|
|
import android.provider.ContactsContract;
|
2013-09-12 23:04:37 +00:00
|
|
|
import android.text.TextUtils;
|
2011-02-09 01:50:30 +00:00
|
|
|
|
2013-07-26 21:18:39 +00:00
|
|
|
import com.android.email.R;
|
2014-09-07 20:36:33 +00:00
|
|
|
import com.android.email.NotificationController;
|
|
|
|
import com.android.email.NotificationControllerCreatorHolder;
|
2014-08-22 17:03:44 +00:00
|
|
|
import com.android.email.SecurityPolicy;
|
2013-09-12 23:04:37 +00:00
|
|
|
import com.android.email.service.EmailServiceUtils;
|
2014-03-20 20:33:34 +00:00
|
|
|
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
|
2011-07-19 00:33:38 +00:00
|
|
|
import com.android.emailcommon.Logging;
|
|
|
|
import com.android.emailcommon.provider.Account;
|
2013-09-12 23:04:37 +00:00
|
|
|
import com.android.emailcommon.provider.HostAuth;
|
2014-08-28 18:34:35 +00:00
|
|
|
import com.android.emailcommon.utility.MigrationUtils;
|
2013-05-26 04:32:32 +00:00
|
|
|
import com.android.mail.utils.LogUtils;
|
2013-07-26 21:18:39 +00:00
|
|
|
import com.google.common.collect.ImmutableList;
|
2011-07-19 00:33:38 +00:00
|
|
|
|
2011-02-09 01:50:30 +00:00
|
|
|
import java.io.IOException;
|
2013-07-26 21:18:39 +00:00
|
|
|
import java.util.Collections;
|
2014-08-06 11:22:50 +00:00
|
|
|
import java.util.LinkedHashSet;
|
2011-02-09 01:50:30 +00:00
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
public class AccountReconciler {
|
2013-07-26 21:18:39 +00:00
|
|
|
/**
|
|
|
|
* Get all AccountManager accounts for all email types.
|
|
|
|
* @param context Our {@link Context}.
|
|
|
|
* @return A list of all {@link android.accounts.Account}s created by our app.
|
|
|
|
*/
|
|
|
|
private static List<android.accounts.Account> getAllAmAccounts(final Context context) {
|
|
|
|
final AccountManager am = AccountManager.get(context);
|
2014-08-06 11:22:50 +00:00
|
|
|
|
2013-07-26 21:18:39 +00:00
|
|
|
// TODO: Consider getting the types programmatically, in case we add more types.
|
2014-08-06 11:22:50 +00:00
|
|
|
// Some Accounts types can be identical, the set de-duplicates.
|
|
|
|
final LinkedHashSet<String> accountTypes = new LinkedHashSet<String>();
|
|
|
|
accountTypes.add(context.getString(R.string.account_manager_type_legacy_imap));
|
|
|
|
accountTypes.add(context.getString(R.string.account_manager_type_pop3));
|
|
|
|
accountTypes.add(context.getString(R.string.account_manager_type_exchange));
|
|
|
|
|
|
|
|
final ImmutableList.Builder<android.accounts.Account> builder = ImmutableList.builder();
|
|
|
|
for (final String type : accountTypes) {
|
|
|
|
final android.accounts.Account[] accounts = am.getAccountsByType(type);
|
|
|
|
builder.add(accounts);
|
|
|
|
}
|
2013-07-26 21:18:39 +00:00
|
|
|
return builder.build();
|
|
|
|
}
|
2011-09-15 22:18:26 +00:00
|
|
|
|
|
|
|
/**
|
2013-07-26 21:18:39 +00:00
|
|
|
* Get a all {@link Account} objects from the {@link EmailProvider}.
|
|
|
|
* @param context Our {@link Context}.
|
|
|
|
* @return A list of all {@link Account}s from the {@link EmailProvider}.
|
2011-09-15 22:18:26 +00:00
|
|
|
*/
|
2013-07-26 21:18:39 +00:00
|
|
|
private static List<Account> getAllEmailProviderAccounts(final Context context) {
|
|
|
|
final Cursor c = context.getContentResolver().query(Account.CONTENT_URI,
|
|
|
|
Account.CONTENT_PROJECTION, null, null, null);
|
|
|
|
if (c == null) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
2011-09-15 22:18:26 +00:00
|
|
|
|
2013-07-26 21:18:39 +00:00
|
|
|
final ImmutableList.Builder<Account> builder = ImmutableList.builder();
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
final Account account = new Account();
|
|
|
|
account.restore(c);
|
|
|
|
builder.add(account);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
return builder.build();
|
2011-09-15 22:18:26 +00:00
|
|
|
}
|
2011-05-19 21:14:14 +00:00
|
|
|
|
2011-02-09 01:50:30 +00:00
|
|
|
/**
|
|
|
|
* Compare our account list (obtained from EmailProvider) with the account list owned by
|
|
|
|
* AccountManager. If there are any orphans (an account in one list without a corresponding
|
|
|
|
* account in the other list), delete the orphan, as these must remain in sync.
|
|
|
|
*
|
|
|
|
* Note that the duplication of account information is caused by the Email application's
|
|
|
|
* incomplete integration with AccountManager.
|
|
|
|
*
|
|
|
|
* This function may not be called from the main/UI thread, because it makes blocking calls
|
|
|
|
* into the account manager.
|
|
|
|
*
|
|
|
|
* @param context The context in which to operate
|
|
|
|
*/
|
2014-03-20 20:33:34 +00:00
|
|
|
public static synchronized void reconcileAccounts(final Context context) {
|
2013-07-26 21:18:39 +00:00
|
|
|
final List<android.accounts.Account> amAccounts = getAllAmAccounts(context);
|
|
|
|
final List<Account> providerAccounts = getAllEmailProviderAccounts(context);
|
|
|
|
reconcileAccountsInternal(context, providerAccounts, amAccounts, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the AccountManager accounts list contains a specific account.
|
|
|
|
* @param accounts The list of {@link android.accounts.Account} objects.
|
|
|
|
* @param name The name of the account to find.
|
|
|
|
* @return Whether the account is in the list.
|
|
|
|
*/
|
|
|
|
private static boolean hasAmAccount(final List<android.accounts.Account> accounts,
|
2013-09-25 20:34:04 +00:00
|
|
|
final String name, final String type) {
|
2013-07-26 21:18:39 +00:00
|
|
|
for (final android.accounts.Account account : accounts) {
|
2013-09-25 20:34:04 +00:00
|
|
|
if (account.name.equalsIgnoreCase(name) && account.type.equalsIgnoreCase(type)) {
|
2013-07-26 21:18:39 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the EmailProvider accounts list contains a specific account.
|
|
|
|
* @param accounts The list of {@link Account} objects.
|
|
|
|
* @param name The name of the account to find.
|
|
|
|
* @return Whether the account is in the list.
|
|
|
|
*/
|
|
|
|
private static boolean hasEpAccount(final List<Account> accounts, final String name) {
|
|
|
|
for (final Account account : accounts) {
|
|
|
|
if (account.mEmailAddress.equalsIgnoreCase(name)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2011-09-15 22:18:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal method to actually perform reconciliation, or simply check that it needs to be done
|
|
|
|
* and avoid doing any heavy work, depending on the value of the passed in
|
|
|
|
* {@code performReconciliation}.
|
|
|
|
*/
|
|
|
|
private static boolean reconcileAccountsInternal(
|
2013-07-26 21:18:39 +00:00
|
|
|
final Context context,
|
|
|
|
final List<Account> emailProviderAccounts,
|
|
|
|
final List<android.accounts.Account> accountManagerAccounts,
|
|
|
|
final boolean performReconciliation) {
|
2011-09-15 22:18:26 +00:00
|
|
|
boolean needsReconciling = false;
|
2013-12-17 22:24:37 +00:00
|
|
|
int accountsDeleted = 0;
|
2013-09-12 23:04:37 +00:00
|
|
|
boolean exchangeAccountDeleted = false;
|
|
|
|
|
|
|
|
LogUtils.d(Logging.LOG_TAG, "reconcileAccountsInternal");
|
2011-09-15 22:18:26 +00:00
|
|
|
|
2014-08-28 18:34:35 +00:00
|
|
|
if (MigrationUtils.migrationInProgress()) {
|
|
|
|
LogUtils.d(Logging.LOG_TAG, "deferring reconciliation, migration in progress");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if we should have the Eas authenticators enabled.
|
2014-07-24 22:26:47 +00:00
|
|
|
if (!EmailServiceUtils.isServiceAvailable(context,
|
|
|
|
context.getString(R.string.protocol_eas))) {
|
2014-07-28 18:34:25 +00:00
|
|
|
EmailServiceUtils.disableExchangeComponents(context);
|
2014-07-24 22:26:47 +00:00
|
|
|
} else {
|
2014-07-28 18:34:25 +00:00
|
|
|
EmailServiceUtils.enableExchangeComponent(context);
|
2014-07-24 22:26:47 +00:00
|
|
|
}
|
2011-02-09 01:50:30 +00:00
|
|
|
// First, look through our EmailProvider accounts to make sure there's a corresponding
|
|
|
|
// AccountManager account
|
2013-07-26 21:18:39 +00:00
|
|
|
for (final Account providerAccount : emailProviderAccounts) {
|
|
|
|
final String providerAccountName = providerAccount.mEmailAddress;
|
2013-10-29 23:36:59 +00:00
|
|
|
final EmailServiceUtils.EmailServiceInfo infoForAccount = EmailServiceUtils
|
|
|
|
.getServiceInfoForAccount(context, providerAccount.mId);
|
|
|
|
|
|
|
|
// We want to delete the account if there is no matching Account Manager account for it
|
|
|
|
// unless it is flagged as incomplete. We also want to delete it if we can't find
|
|
|
|
// an accountInfo object for it.
|
|
|
|
if (infoForAccount == null || !hasAmAccount(
|
|
|
|
accountManagerAccounts, providerAccountName, infoForAccount.accountType)) {
|
|
|
|
if (infoForAccount != null &&
|
|
|
|
(providerAccount.mFlags & Account.FLAGS_INCOMPLETE) != 0) {
|
2013-09-17 23:03:14 +00:00
|
|
|
LogUtils.w(Logging.LOG_TAG,
|
|
|
|
"Account reconciler noticed incomplete account; ignoring");
|
|
|
|
continue;
|
2011-02-09 01:50:30 +00:00
|
|
|
}
|
2011-09-15 22:18:26 +00:00
|
|
|
|
|
|
|
needsReconciling = true;
|
|
|
|
if (performReconciliation) {
|
|
|
|
// This account has been deleted in the AccountManager!
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG,
|
2011-09-15 22:18:26 +00:00
|
|
|
"Account deleted in AccountManager; deleting from provider: " +
|
|
|
|
providerAccountName);
|
2013-09-12 23:04:37 +00:00
|
|
|
// See if this is an exchange account
|
|
|
|
final HostAuth auth = providerAccount.getOrCreateHostAuthRecv(context);
|
|
|
|
LogUtils.d(Logging.LOG_TAG, "deleted account with hostAuth " + auth);
|
|
|
|
if (auth != null && TextUtils.equals(auth.mProtocol,
|
|
|
|
context.getString(R.string.protocol_eas))) {
|
|
|
|
exchangeAccountDeleted = true;
|
|
|
|
}
|
2013-10-04 19:27:20 +00:00
|
|
|
// Cancel all notifications for this account
|
2014-09-07 20:36:33 +00:00
|
|
|
final NotificationController nc =
|
|
|
|
NotificationControllerCreatorHolder.getInstance(context);
|
|
|
|
if (nc != null) {
|
|
|
|
nc.cancelNotifications(context, providerAccount);
|
|
|
|
}
|
2013-10-04 19:27:20 +00:00
|
|
|
|
2013-07-26 21:18:39 +00:00
|
|
|
context.getContentResolver().delete(
|
|
|
|
EmailProvider.uiUri("uiaccount", providerAccount.mId), null, null);
|
2013-05-23 01:36:07 +00:00
|
|
|
|
2013-12-17 22:24:37 +00:00
|
|
|
accountsDeleted++;
|
2013-09-12 23:04:37 +00:00
|
|
|
|
2011-09-15 22:18:26 +00:00
|
|
|
}
|
2011-02-09 01:50:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now, look through AccountManager accounts to make sure we have a corresponding cached EAS
|
|
|
|
// account from EmailProvider
|
2014-08-22 17:03:44 +00:00
|
|
|
boolean needsPolicyUpdate = false;
|
2013-07-26 21:18:39 +00:00
|
|
|
for (final android.accounts.Account accountManagerAccount : accountManagerAccounts) {
|
|
|
|
final String accountManagerAccountName = accountManagerAccount.name;
|
|
|
|
if (!hasEpAccount(emailProviderAccounts, accountManagerAccountName)) {
|
2011-02-09 01:50:30 +00:00
|
|
|
// This account has been deleted from the EmailProvider database
|
2011-09-15 22:18:26 +00:00
|
|
|
needsReconciling = true;
|
|
|
|
|
|
|
|
if (performReconciliation) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG,
|
2011-09-15 22:18:26 +00:00
|
|
|
"Account deleted from provider; deleting from AccountManager: " +
|
|
|
|
accountManagerAccountName);
|
|
|
|
// Delete the account
|
|
|
|
AccountManagerFuture<Boolean> blockingResult = AccountManager.get(context)
|
2013-07-26 21:18:39 +00:00
|
|
|
.removeAccount(accountManagerAccount, null, null);
|
2011-09-15 22:18:26 +00:00
|
|
|
try {
|
|
|
|
// Note: All of the potential errors from removeAccount() are simply logged
|
|
|
|
// here, as there is nothing to actually do about them.
|
|
|
|
blockingResult.getResult();
|
|
|
|
} catch (OperationCanceledException e) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(Logging.LOG_TAG, e.toString());
|
2011-09-15 22:18:26 +00:00
|
|
|
} catch (AuthenticatorException e) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(Logging.LOG_TAG, e.toString());
|
2011-09-15 22:18:26 +00:00
|
|
|
} catch (IOException e) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(Logging.LOG_TAG, e.toString());
|
2011-09-15 22:18:26 +00:00
|
|
|
}
|
2014-08-22 17:03:44 +00:00
|
|
|
// Just set a flag that our policies need to be updated with device
|
|
|
|
// So we can do the update, one time, at a later point in time.
|
|
|
|
needsPolicyUpdate = true;
|
2011-02-09 01:50:30 +00:00
|
|
|
}
|
2014-03-20 20:33:34 +00:00
|
|
|
} else {
|
|
|
|
// Fix up the Calendar and Contacts syncing. It used to be possible for IMAP and
|
|
|
|
// POP accounts to get calendar and contacts syncing enabled.
|
|
|
|
// See b/11818312
|
|
|
|
final String accountType = accountManagerAccount.type;
|
|
|
|
final String protocol = EmailServiceUtils.getProtocolFromAccountType(
|
|
|
|
context, accountType);
|
|
|
|
final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
|
2014-08-21 23:30:49 +00:00
|
|
|
if (info == null || !info.syncCalendar) {
|
2014-03-20 20:33:34 +00:00
|
|
|
ContentResolver.setIsSyncable(accountManagerAccount,
|
|
|
|
CalendarContract.AUTHORITY, 0);
|
|
|
|
}
|
2014-08-21 23:30:49 +00:00
|
|
|
if (info == null || !info.syncContacts) {
|
2014-03-20 20:33:34 +00:00
|
|
|
ContentResolver.setIsSyncable(accountManagerAccount,
|
|
|
|
ContactsContract.AUTHORITY, 0);
|
|
|
|
}
|
2011-02-09 01:50:30 +00:00
|
|
|
}
|
|
|
|
}
|
2011-09-15 22:18:26 +00:00
|
|
|
|
2014-08-22 17:03:44 +00:00
|
|
|
if (needsPolicyUpdate) {
|
|
|
|
// We have removed accounts from the AccountManager, let's make sure that
|
|
|
|
// our policies are up to date.
|
|
|
|
SecurityPolicy.getInstance(context).policiesUpdated();
|
|
|
|
}
|
|
|
|
|
2014-06-17 10:18:11 +00:00
|
|
|
final String composeActivityName =
|
|
|
|
context.getString(R.string.reconciliation_compose_activity_name);
|
|
|
|
if (!TextUtils.isEmpty(composeActivityName)) {
|
|
|
|
// If there are no accounts remaining after reconciliation, disable the compose activity
|
|
|
|
final boolean enableCompose = emailProviderAccounts.size() - accountsDeleted > 0;
|
|
|
|
final ComponentName componentName = new ComponentName(context, composeActivityName);
|
|
|
|
context.getPackageManager().setComponentEnabledSetting(componentName,
|
|
|
|
enableCompose ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
|
|
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
|
|
PackageManager.DONT_KILL_APP);
|
|
|
|
LogUtils.d(LogUtils.TAG, "Setting compose activity to "
|
|
|
|
+ (enableCompose ? "enabled" : "disabled"));
|
|
|
|
}
|
|
|
|
|
2013-12-17 22:24:37 +00:00
|
|
|
|
2013-09-12 23:04:37 +00:00
|
|
|
// If an account has been deleted, the simplest thing is just to kill our process.
|
|
|
|
// Otherwise we might have a service running trying to do something for the account
|
|
|
|
// which has been deleted, which can get NPEs. It's not as clean is it could be, but
|
|
|
|
// it still works pretty well because there is nowhere in the email app to delete the
|
|
|
|
// account. You have to go to Settings, so it's not user visible that the Email app
|
|
|
|
// has been killed.
|
2013-12-17 22:24:37 +00:00
|
|
|
if (accountsDeleted > 0) {
|
2013-09-12 23:04:37 +00:00
|
|
|
LogUtils.i(Logging.LOG_TAG, "Restarting because account deleted");
|
|
|
|
if (exchangeAccountDeleted) {
|
|
|
|
EmailServiceUtils.killService(context, context.getString(R.string.protocol_eas));
|
|
|
|
}
|
|
|
|
System.exit(-1);
|
|
|
|
}
|
|
|
|
|
2011-09-15 22:18:26 +00:00
|
|
|
return needsReconciling;
|
2011-02-09 01:50:30 +00:00
|
|
|
}
|
|
|
|
}
|