Home | History | Annotate | Download | only in email
      1 /*
      2  * Copyright (C) 2010 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;
     18 
     19 import com.android.email.mail.store.ExchangeStore;
     20 import com.android.email.provider.EmailContent;
     21 
     22 import android.accounts.AccountManagerFuture;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.database.Cursor;
     26 import android.os.Bundle;
     27 import android.provider.Calendar;
     28 import android.provider.ContactsContract;
     29 import android.util.Log;
     30 
     31 /**
     32  * Utility functions to support backup and restore of accounts.
     33  *
     34  * In the short term, this is used to work around local database failures.  In the long term,
     35  * this will also support server-side backups, providing support for automatic account restoration
     36  * when switching or replacing phones.
     37  */
     38 public class AccountBackupRestore {
     39 
     40     /**
     41      * Backup accounts.  Can be called from UI thread (does work in a new thread)
     42      */
     43     public static void backupAccounts(final Context context) {
     44         if (Email.DEBUG) {
     45             Log.v(Email.LOG_TAG, "backupAccounts");
     46         }
     47         // Because we typically call this from the UI, let's do the work in a thread
     48         new Thread() {
     49             @Override
     50             public void run() {
     51                 doBackupAccounts(context, Preferences.getPreferences(context));
     52             }
     53         }.start();
     54     }
     55 
     56     /**
     57      * Restore accounts if needed.  This is blocking, and should only be called in specific
     58      * startup/entry points.
     59      */
     60     public static void restoreAccountsIfNeeded(final Context context) {
     61         // Don't log here;  This is called often.
     62         boolean restored = doRestoreAccounts(context, Preferences.getPreferences(context));
     63         if (restored) {
     64             // after restoring accounts, register services appropriately
     65             Log.w(Email.LOG_TAG, "Register services after restoring accounts");
     66             // update security profile
     67             SecurityPolicy.getInstance(context).updatePolicies(-1);
     68             // enable/disable other email services as necessary
     69             Email.setServicesEnabled(context);
     70             ExchangeUtils.startExchangeService(context);
     71         }
     72     }
     73 
     74     /**
     75      * Non-UI-Thread worker to backup all accounts
     76      *
     77      * @param context used to access the provider
     78      * @param preferences used to access the backups (provided separately for testability)
     79      */
     80     /* package */ synchronized static void doBackupAccounts(Context context,
     81             Preferences preferences) {
     82         // 1.  Wipe any existing backup accounts
     83         Account[] oldBackups = preferences.getAccounts();
     84         for (Account backup : oldBackups) {
     85             backup.delete(preferences);
     86         }
     87 
     88         // 2. Identify the default account (if any).  This is required because setting
     89         // the default account flag is lazy,and sometimes we don't have any flags set.  We'll
     90         // use this to make it explicit (see loop, below).
     91         // This is also the quick check for "no accounts" (the only case in which the returned
     92         // value is -1) and if so, we can exit immediately.
     93         long defaultAccountId = EmailContent.Account.getDefaultAccountId(context);
     94         if (defaultAccountId == -1) {
     95             return;
     96         }
     97 
     98         // 3. Create new backup(s), if any
     99         Cursor c = context.getContentResolver().query(EmailContent.Account.CONTENT_URI,
    100                 EmailContent.Account.CONTENT_PROJECTION, null, null, null);
    101         try {
    102             while (c.moveToNext()) {
    103                 EmailContent.Account fromAccount =
    104                         EmailContent.getContent(c, EmailContent.Account.class);
    105                 if (Email.DEBUG) {
    106                     Log.v(Email.LOG_TAG, "Backing up account:" + fromAccount.getDisplayName());
    107                 }
    108                 Account toAccount = LegacyConversions.makeLegacyAccount(context, fromAccount);
    109 
    110                 // Determine if contacts are also synced, and if so, record that
    111                 if (fromAccount.mHostAuthRecv.mProtocol.equals("eas")) {
    112                     android.accounts.Account acct = new android.accounts.Account(
    113                             fromAccount.mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
    114                     boolean syncContacts = ContentResolver.getSyncAutomatically(acct,
    115                             ContactsContract.AUTHORITY);
    116                     if (syncContacts) {
    117                         toAccount.mBackupFlags |= Account.BACKUP_FLAGS_SYNC_CONTACTS;
    118                     }
    119                     boolean syncCalendar = ContentResolver.getSyncAutomatically(acct,
    120                             Calendar.AUTHORITY);
    121                     if (syncCalendar) {
    122                         toAccount.mBackupFlags |= Account.BACKUP_FLAGS_SYNC_CALENDAR;
    123                     }
    124                 }
    125 
    126                 // If this is the default account, mark it as such
    127                 if (fromAccount.mId == defaultAccountId) {
    128                     toAccount.mBackupFlags |= Account.BACKUP_FLAGS_IS_DEFAULT;
    129                 }
    130 
    131                 // Mark this account as a backup of a Provider account, instead of a legacy
    132                 // account to upgrade
    133                 toAccount.mBackupFlags |= Account.BACKUP_FLAGS_IS_BACKUP;
    134 
    135                 toAccount.save(preferences);
    136             }
    137         } finally {
    138             c.close();
    139         }
    140     }
    141 
    142     /**
    143      * Restore all accounts.  This is blocking.
    144      *
    145      * @param context used to access the provider
    146      * @param preferences used to access the backups (provided separately for testability)
    147      * @return true if accounts were restored (meaning services should be restarted, etc.)
    148      */
    149     /* package */ synchronized static boolean doRestoreAccounts(Context context,
    150             Preferences preferences) {
    151         boolean result = false;
    152 
    153         // 1. Quick check - if we have any accounts, get out
    154         int numAccounts = EmailContent.count(context, EmailContent.Account.CONTENT_URI, null, null);
    155         if (numAccounts > 0) {
    156             return result;
    157         }
    158         // 2. Quick check - if no backup accounts, get out
    159         Account[] backups = preferences.getAccounts();
    160         if (backups.length == 0) {
    161             return result;
    162         }
    163 
    164         Log.w(Email.LOG_TAG, "*** Restoring Email Accounts, found " + backups.length);
    165 
    166         // 3. Possible lost accounts situation - check for any backups, and restore them
    167         for (Account backupAccount : backups) {
    168             // don't back up any leftover legacy accounts (these are migrated elsewhere).
    169             if ((backupAccount.mBackupFlags & Account.BACKUP_FLAGS_IS_BACKUP) == 0) {
    170                 continue;
    171             }
    172             // Restore the account
    173             Log.w(Email.LOG_TAG, "Restoring account:" + backupAccount.getDescription());
    174             EmailContent.Account toAccount =
    175                 LegacyConversions.makeAccount(context, backupAccount);
    176 
    177             // Mark the default account if this is it
    178             if (0 != (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_IS_DEFAULT)) {
    179                 toAccount.setDefaultAccount(true);
    180             }
    181 
    182             // For exchange accounts, handle system account first, then save in provider
    183             if (toAccount.mHostAuthRecv.mProtocol.equals("eas")) {
    184                 // Recreate entry in Account Manager as well, if needed
    185                 // Set "sync contacts/calendar" mode as well, if needed
    186                 boolean alsoSyncContacts =
    187                     (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_SYNC_CONTACTS) != 0;
    188                 boolean alsoSyncCalendar =
    189                     (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_SYNC_CALENDAR) != 0;
    190 
    191                 // Use delete-then-add semantic to simplify handling of update-in-place
    192 //                AccountManagerFuture<Boolean> removeResult = ExchangeStore.removeSystemAccount(
    193 //                        context.getApplicationContext(), toAccount, null);
    194 //                try {
    195 //                    // This call blocks until removeSystemAccount completes.  Result is not used.
    196 //                    removeResult.getResult();
    197 //                } catch (AccountsException e) {
    198 //                    Log.d(Email.LOG_TAG, "removeSystemAccount failed: " + e);
    199 //                    // log and discard - we don't care if remove fails, generally
    200 //                } catch (IOException e) {
    201 //                    Log.d(Email.LOG_TAG, "removeSystemAccount failed: " + e);
    202 //                    // log and discard - we don't care if remove fails, generally
    203 //                }
    204 
    205                 // NOTE: We must use the Application here, rather than the current context, because
    206                 // all future references to AccountManager will use the context passed in here
    207                 // TODO: Need to implement overwrite semantics for an already-installed account
    208                 AccountManagerFuture<Bundle> addAccountResult =
    209                      ExchangeStore.addSystemAccount(context.getApplicationContext(), toAccount,
    210                              alsoSyncContacts, alsoSyncCalendar, null);
    211 //                try {
    212 //                    // This call blocks until addSystemAccount completes.  Result is not used.
    213 //                    addAccountResult.getResult();
    214                     toAccount.save(context);
    215 //                } catch (OperationCanceledException e) {
    216 //                    Log.d(Email.LOG_TAG, "addAccount was canceled");
    217 //                } catch (IOException e) {
    218 //                    Log.d(Email.LOG_TAG, "addAccount failed: " + e);
    219 //                } catch (AuthenticatorException e) {
    220 //                    Log.d(Email.LOG_TAG, "addAccount failed: " + e);
    221 //                }
    222 
    223             } else {
    224                 // non-eas account - save it immediately
    225                 toAccount.save(context);
    226             }
    227             // report that an account was restored
    228             result = true;
    229         }
    230         return result;
    231     }
    232 }
    233