• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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