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