Home | History | Annotate | Download | only in provider
      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      */
     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