AccountReconciler.java revision 03cd72805dab0379ed255d151f1c17cc60655fc3
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.util.Log;
25
26import com.android.email.Controller;
27import com.android.emailcommon.Logging;
28import com.android.emailcommon.provider.Account;
29import com.google.common.annotations.VisibleForTesting;
30
31import java.io.IOException;
32import java.util.List;
33
34public 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     */
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     */
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     */
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