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 17package com.android.email; 18 19import com.android.email.mail.store.ExchangeStore; 20import com.android.email.provider.EmailContent; 21 22import android.accounts.AccountManagerFuture; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.database.Cursor; 26import android.os.Bundle; 27import android.provider.Calendar; 28import android.provider.ContactsContract; 29import 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 */ 38public 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