AccountReconciler.java revision 78b8c04898c04890b757fbd83df9666e0a9068f5
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.net.Uri; 25import android.util.Log; 26 27import com.android.emailcommon.Logging; 28import com.android.emailcommon.provider.Account; 29import com.android.emailcommon.provider.EmailContent; 30import com.android.emailcommon.provider.Mailbox; 31import com.google.common.annotations.VisibleForTesting; 32 33import java.io.IOException; 34import java.util.List; 35 36public class AccountReconciler { 37 // AccountManager accounts with a name beginning with this constant are ignored for purposes 38 // of reconcilation. This is for unit test purposes only; the caller may NOT be in the same 39 // package as this class, so we make the constant public. 40 @VisibleForTesting 41 static final String ACCOUNT_MANAGER_ACCOUNT_TEST_PREFIX = " _"; 42 43 /** 44 * Checks two account lists to see if there is any reconciling to be done. Can be done on the 45 * UI thread. 46 * @param context the app context 47 * @param emailProviderAccounts accounts as reported in the Email provider 48 * @param accountManagerAccounts accounts as reported by the system account manager, for the 49 * particular protocol types that match emailProviderAccounts 50 */ 51 public static boolean accountsNeedReconciling( 52 final Context context, 53 List<Account> emailProviderAccounts, 54 android.accounts.Account[] accountManagerAccounts) { 55 56 return reconcileAccountsInternal( 57 context, emailProviderAccounts, accountManagerAccounts, 58 context, false /* performReconciliation */); 59 } 60 61 /** 62 * Compare our account list (obtained from EmailProvider) with the account list owned by 63 * AccountManager. If there are any orphans (an account in one list without a corresponding 64 * account in the other list), delete the orphan, as these must remain in sync. 65 * 66 * Note that the duplication of account information is caused by the Email application's 67 * incomplete integration with AccountManager. 68 * 69 * This function may not be called from the main/UI thread, because it makes blocking calls 70 * into the account manager. 71 * 72 * @param context The context in which to operate 73 * @param emailProviderAccounts the exchange provider accounts to work from 74 * @param accountManagerAccounts The account manager accounts to work from 75 * @param providerContext application provider context 76 */ 77 public static void reconcileAccounts( 78 Context context, 79 List<Account> emailProviderAccounts, 80 android.accounts.Account[] accountManagerAccounts, 81 Context providerContext) { 82 reconcileAccountsInternal( 83 context, emailProviderAccounts, accountManagerAccounts, 84 providerContext, true /* performReconciliation */); 85 } 86 87 /** 88 * Internal method to actually perform reconciliation, or simply check that it needs to be done 89 * and avoid doing any heavy work, depending on the value of the passed in 90 * {@code performReconciliation}. 91 */ 92 private static boolean reconcileAccountsInternal( 93 Context context, 94 List<Account> emailProviderAccounts, 95 android.accounts.Account[] accountManagerAccounts, 96 Context providerContext, 97 boolean performReconciliation) { 98 boolean needsReconciling = false; 99 100 // First, look through our EmailProvider accounts to make sure there's a corresponding 101 // AccountManager account 102 for (Account providerAccount: emailProviderAccounts) { 103 String providerAccountName = providerAccount.mEmailAddress; 104 boolean found = false; 105 for (android.accounts.Account accountManagerAccount: accountManagerAccounts) { 106 if (accountManagerAccount.name.equalsIgnoreCase(providerAccountName)) { 107 found = true; 108 break; 109 } 110 } 111 if (!found) { 112 if ((providerAccount.mFlags & Account.FLAGS_INCOMPLETE) != 0) { 113 // Do another test before giving up; an incomplete account shouldn't have 114 // any mailboxes (the incomplete flag is used to prevent reconciliation 115 // between the time the EP account is created and when the AM account is 116 // asynchronously created) 117 if (EmailContent.count(providerContext, Mailbox.CONTENT_URI, 118 Mailbox.ACCOUNT_KEY + "=?", 119 new String[] { Long.toString(providerAccount.mId) } ) > 0) { 120 Log.w(Logging.LOG_TAG, 121 "Account reconciler found wrongly incomplete account"); 122 } else { 123 Log.w(Logging.LOG_TAG, 124 "Account reconciler noticed incomplete account; ignoring"); 125 continue; 126 } 127 } 128 129 needsReconciling = true; 130 if (performReconciliation) { 131 // This account has been deleted in the AccountManager! 132 Log.d(Logging.LOG_TAG, 133 "Account deleted in AccountManager; deleting from provider: " + 134 providerAccountName); 135 Uri uri = EmailProvider.uiUri("uiaccount", providerAccount.mId); 136 context.getContentResolver().delete(uri, null, null); 137 } 138 } 139 } 140 // Now, look through AccountManager accounts to make sure we have a corresponding cached EAS 141 // account from EmailProvider 142 for (android.accounts.Account accountManagerAccount: accountManagerAccounts) { 143 String accountManagerAccountName = accountManagerAccount.name; 144 boolean found = false; 145 for (Account cachedEasAccount: emailProviderAccounts) { 146 if (cachedEasAccount.mEmailAddress.equalsIgnoreCase(accountManagerAccountName)) { 147 found = true; 148 } 149 } 150 if (accountManagerAccountName.startsWith(ACCOUNT_MANAGER_ACCOUNT_TEST_PREFIX)) { 151 found = true; 152 } 153 if (!found) { 154 // This account has been deleted from the EmailProvider database 155 needsReconciling = true; 156 157 if (performReconciliation) { 158 Log.d(Logging.LOG_TAG, 159 "Account deleted from provider; deleting from AccountManager: " + 160 accountManagerAccountName); 161 // Delete the account 162 AccountManagerFuture<Boolean> blockingResult = AccountManager.get(context) 163 .removeAccount(accountManagerAccount, null, null); 164 try { 165 // Note: All of the potential errors from removeAccount() are simply logged 166 // here, as there is nothing to actually do about them. 167 blockingResult.getResult(); 168 } catch (OperationCanceledException e) { 169 Log.w(Logging.LOG_TAG, e.toString()); 170 } catch (AuthenticatorException e) { 171 Log.w(Logging.LOG_TAG, e.toString()); 172 } catch (IOException e) { 173 Log.w(Logging.LOG_TAG, e.toString()); 174 } 175 } 176 } 177 } 178 179 return needsReconciling; 180 } 181} 182