AccountReconciler.java revision afe097f318a7eeac9d240d2bedbc5caba7640ea8
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.provider; 18 19import android.accounts.AccountManager; 20import android.accounts.AccountManagerFuture; 21import android.accounts.AuthenticatorException; 22import android.accounts.OperationCanceledException; 23import android.content.Context; 24import android.database.Cursor; 25 26import com.android.email.NotificationController; 27import com.android.email.R; 28import com.android.emailcommon.Logging; 29import com.android.emailcommon.provider.Account; 30import com.android.emailcommon.provider.EmailContent; 31import com.android.emailcommon.provider.Mailbox; 32import com.android.mail.utils.LogUtils; 33import com.google.common.collect.ImmutableList; 34 35import java.io.IOException; 36import java.util.Arrays; 37import java.util.Collections; 38import java.util.List; 39 40public class AccountReconciler { 41 /** 42 * Get all AccountManager accounts for all email types. 43 * @param context Our {@link Context}. 44 * @return A list of all {@link android.accounts.Account}s created by our app. 45 */ 46 private static List<android.accounts.Account> getAllAmAccounts(final Context context) { 47 final AccountManager am = AccountManager.get(context); 48 final ImmutableList.Builder<android.accounts.Account> builder = ImmutableList.builder(); 49 // TODO: Consider getting the types programmatically, in case we add more types. 50 builder.addAll(Arrays.asList(am.getAccountsByType( 51 context.getString(R.string.account_manager_type_legacy_imap)))); 52 builder.addAll(Arrays.asList(am.getAccountsByType( 53 context.getString(R.string.account_manager_type_pop3)))); 54 builder.addAll(Arrays.asList(am.getAccountsByType( 55 context.getString(R.string.account_manager_type_exchange)))); 56 return builder.build(); 57 } 58 59 /** 60 * Get a all {@link Account} objects from the {@link EmailProvider}. 61 * @param context Our {@link Context}. 62 * @return A list of all {@link Account}s from the {@link EmailProvider}. 63 */ 64 private static List<Account> getAllEmailProviderAccounts(final Context context) { 65 final Cursor c = context.getContentResolver().query(Account.CONTENT_URI, 66 Account.CONTENT_PROJECTION, null, null, null); 67 if (c == null) { 68 return Collections.emptyList(); 69 } 70 71 final ImmutableList.Builder<Account> builder = ImmutableList.builder(); 72 try { 73 while (c.moveToNext()) { 74 final Account account = new Account(); 75 account.restore(c); 76 builder.add(account); 77 } 78 } finally { 79 c.close(); 80 } 81 return builder.build(); 82 } 83 84 /** 85 * Compare our account list (obtained from EmailProvider) with the account list owned by 86 * AccountManager. If there are any orphans (an account in one list without a corresponding 87 * account in the other list), delete the orphan, as these must remain in sync. 88 * 89 * Note that the duplication of account information is caused by the Email application's 90 * incomplete integration with AccountManager. 91 * 92 * This function may not be called from the main/UI thread, because it makes blocking calls 93 * into the account manager. 94 * 95 * @param context The context in which to operate 96 */ 97 public static void reconcileAccounts(final Context context) { 98 final List<android.accounts.Account> amAccounts = getAllAmAccounts(context); 99 final List<Account> providerAccounts = getAllEmailProviderAccounts(context); 100 reconcileAccountsInternal(context, providerAccounts, amAccounts, true); 101 } 102 103 /** 104 * Check if the AccountManager accounts list contains a specific account. 105 * @param accounts The list of {@link android.accounts.Account} objects. 106 * @param name The name of the account to find. 107 * @return Whether the account is in the list. 108 */ 109 private static boolean hasAmAccount(final List<android.accounts.Account> accounts, 110 final String name) { 111 for (final android.accounts.Account account : accounts) { 112 if (account.name.equalsIgnoreCase(name)) { 113 return true; 114 } 115 } 116 return false; 117 } 118 119 /** 120 * Check if the EmailProvider accounts list contains a specific account. 121 * @param accounts The list of {@link Account} objects. 122 * @param name The name of the account to find. 123 * @return Whether the account is in the list. 124 */ 125 private static boolean hasEpAccount(final List<Account> accounts, final String name) { 126 for (final Account account : accounts) { 127 if (account.mEmailAddress.equalsIgnoreCase(name)) { 128 return true; 129 } 130 } 131 return false; 132 } 133 134 /** 135 * Internal method to actually perform reconciliation, or simply check that it needs to be done 136 * and avoid doing any heavy work, depending on the value of the passed in 137 * {@code performReconciliation}. 138 */ 139 private static boolean reconcileAccountsInternal( 140 final Context context, 141 final List<Account> emailProviderAccounts, 142 final List<android.accounts.Account> accountManagerAccounts, 143 final boolean performReconciliation) { 144 boolean needsReconciling = false; 145 146 // First, look through our EmailProvider accounts to make sure there's a corresponding 147 // AccountManager account 148 for (final Account providerAccount : emailProviderAccounts) { 149 final String providerAccountName = providerAccount.mEmailAddress; 150 if (!hasAmAccount(accountManagerAccounts, providerAccountName)) { 151 if ((providerAccount.mFlags & Account.FLAGS_INCOMPLETE) != 0) { 152 // Do another test before giving up; an incomplete account shouldn't have 153 // any mailboxes (the incomplete flag is used to prevent reconciliation 154 // between the time the EP account is created and when the AM account is 155 // asynchronously created) 156 if (EmailContent.count(context, Mailbox.CONTENT_URI, 157 Mailbox.ACCOUNT_KEY + "=?", 158 new String[] { Long.toString(providerAccount.mId) } ) > 0) { 159 LogUtils.w(Logging.LOG_TAG, 160 "Account reconciler found wrongly incomplete account"); 161 } else { 162 LogUtils.w(Logging.LOG_TAG, 163 "Account reconciler noticed incomplete account; ignoring"); 164 continue; 165 } 166 } 167 168 needsReconciling = true; 169 if (performReconciliation) { 170 // This account has been deleted in the AccountManager! 171 LogUtils.d(Logging.LOG_TAG, 172 "Account deleted in AccountManager; deleting from provider: " + 173 providerAccountName); 174 context.getContentResolver().delete( 175 EmailProvider.uiUri("uiaccount", providerAccount.mId), null, null); 176 177 // Cancel all notifications for this account 178 NotificationController.cancelNotifications(context, providerAccount); 179 } 180 } 181 } 182 // Now, look through AccountManager accounts to make sure we have a corresponding cached EAS 183 // account from EmailProvider 184 for (final android.accounts.Account accountManagerAccount : accountManagerAccounts) { 185 final String accountManagerAccountName = accountManagerAccount.name; 186 if (!hasEpAccount(emailProviderAccounts, accountManagerAccountName)) { 187 // This account has been deleted from the EmailProvider database 188 needsReconciling = true; 189 190 if (performReconciliation) { 191 LogUtils.d(Logging.LOG_TAG, 192 "Account deleted from provider; deleting from AccountManager: " + 193 accountManagerAccountName); 194 // Delete the account 195 AccountManagerFuture<Boolean> blockingResult = AccountManager.get(context) 196 .removeAccount(accountManagerAccount, null, null); 197 try { 198 // Note: All of the potential errors from removeAccount() are simply logged 199 // here, as there is nothing to actually do about them. 200 blockingResult.getResult(); 201 } catch (OperationCanceledException e) { 202 LogUtils.w(Logging.LOG_TAG, e.toString()); 203 } catch (AuthenticatorException e) { 204 LogUtils.w(Logging.LOG_TAG, e.toString()); 205 } catch (IOException e) { 206 LogUtils.w(Logging.LOG_TAG, e.toString()); 207 } 208 } 209 } 210 } 211 212 return needsReconciling; 213 } 214} 215