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