| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.email; |
| |
| import com.android.email.mail.store.ExchangeStore; |
| import com.android.email.provider.EmailContent; |
| import com.android.exchange.Eas; |
| |
| import android.accounts.AccountManagerFuture; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.os.Bundle; |
| import android.provider.Calendar; |
| import android.provider.ContactsContract; |
| import android.util.Log; |
| |
| /** |
| * Utility functions to support backup and restore of accounts. |
| * |
| * In the short term, this is used to work around local database failures. In the long term, |
| * this will also support server-side backups, providing support for automatic account restoration |
| * when switching or replacing phones. |
| */ |
| public class AccountBackupRestore { |
| |
| /** |
| * Backup accounts. Can be called from UI thread (does work in a new thread) |
| */ |
| public static void backupAccounts(final Context context) { |
| if (Email.DEBUG) { |
| Log.v(Email.LOG_TAG, "backupAccounts"); |
| } |
| // Because we typically call this from the UI, let's do the work in a thread |
| new Thread() { |
| @Override |
| public void run() { |
| doBackupAccounts(context, Preferences.getPreferences(context)); |
| } |
| }.start(); |
| } |
| |
| /** |
| * Restore accounts if needed. This is blocking, and should only be called in specific |
| * startup/entry points. |
| */ |
| public static void restoreAccountsIfNeeded(final Context context) { |
| // Don't log here; This is called often. |
| boolean restored = doRestoreAccounts(context, Preferences.getPreferences(context)); |
| if (restored) { |
| // after restoring accounts, register services appropriately |
| Log.w(Email.LOG_TAG, "Register services after restoring accounts"); |
| Email.setServicesEnabled(context); |
| context.startService(new Intent(context.getApplicationContext(), |
| com.android.exchange.SyncManager.class)); |
| } |
| } |
| |
| /** |
| * Non-UI-Thread worker to backup all accounts |
| * |
| * @param context used to access the provider |
| * @param preferences used to access the backups (provided separately for testability) |
| */ |
| /* package */ synchronized static void doBackupAccounts(Context context, |
| Preferences preferences) { |
| // 1. Wipe any existing backup accounts |
| Account[] oldBackups = preferences.getAccounts(); |
| for (Account backup : oldBackups) { |
| backup.delete(preferences); |
| } |
| |
| // 2. Identify the default account (if any). This is required because setting |
| // the default account flag is lazy,and sometimes we don't have any flags set. We'll |
| // use this to make it explicit (see loop, below). |
| // This is also the quick check for "no accounts" (the only case in which the returned |
| // value is -1) and if so, we can exit immediately. |
| long defaultAccountId = EmailContent.Account.getDefaultAccountId(context); |
| if (defaultAccountId == -1) { |
| return; |
| } |
| |
| // 3. Create new backup(s), if any |
| Cursor c = context.getContentResolver().query(EmailContent.Account.CONTENT_URI, |
| EmailContent.Account.CONTENT_PROJECTION, null, null, null); |
| try { |
| while (c.moveToNext()) { |
| EmailContent.Account fromAccount = |
| EmailContent.getContent(c, EmailContent.Account.class); |
| if (Email.DEBUG) { |
| Log.v(Email.LOG_TAG, "Backing up account:" + fromAccount.getDisplayName()); |
| } |
| Account toAccount = LegacyConversions.makeLegacyAccount(context, fromAccount); |
| |
| // Determine if contacts are also synced, and if so, record that |
| if (fromAccount.mHostAuthRecv.mProtocol.equals("eas")) { |
| android.accounts.Account acct = new android.accounts.Account( |
| fromAccount.mEmailAddress, Eas.ACCOUNT_MANAGER_TYPE); |
| boolean syncContacts = ContentResolver.getSyncAutomatically(acct, |
| ContactsContract.AUTHORITY); |
| if (syncContacts) { |
| toAccount.mBackupFlags |= Account.BACKUP_FLAGS_SYNC_CONTACTS; |
| } |
| boolean syncCalendar = ContentResolver.getSyncAutomatically(acct, |
| Calendar.AUTHORITY); |
| if (syncCalendar) { |
| toAccount.mBackupFlags |= Account.BACKUP_FLAGS_SYNC_CALENDAR; |
| } |
| } |
| |
| // If this is the default account, mark it as such |
| if (fromAccount.mId == defaultAccountId) { |
| toAccount.mBackupFlags |= Account.BACKUP_FLAGS_IS_DEFAULT; |
| } |
| |
| // Mark this account as a backup of a Provider account, instead of a legacy |
| // account to upgrade |
| toAccount.mBackupFlags |= Account.BACKUP_FLAGS_IS_BACKUP; |
| |
| toAccount.save(preferences); |
| } |
| } finally { |
| c.close(); |
| } |
| } |
| |
| /** |
| * Restore all accounts. This is blocking. |
| * |
| * @param context used to access the provider |
| * @param preferences used to access the backups (provided separately for testability) |
| * @return true if accounts were restored (meaning services should be restarted, etc.) |
| */ |
| /* package */ synchronized static boolean doRestoreAccounts(Context context, |
| Preferences preferences) { |
| boolean result = false; |
| |
| // 1. Quick check - if we have any accounts, get out |
| int numAccounts = EmailContent.count(context, EmailContent.Account.CONTENT_URI, null, null); |
| if (numAccounts > 0) { |
| return result; |
| } |
| // 2. Quick check - if no backup accounts, get out |
| Account[] backups = preferences.getAccounts(); |
| if (backups.length == 0) { |
| return result; |
| } |
| |
| Log.w(Email.LOG_TAG, "*** Restoring Email Accounts, found " + backups.length); |
| |
| // 3. Possible lost accounts situation - check for any backups, and restore them |
| for (Account backupAccount : backups) { |
| // don't back up any leftover legacy accounts (these are migrated elsewhere). |
| if ((backupAccount.mBackupFlags & Account.BACKUP_FLAGS_IS_BACKUP) == 0) { |
| continue; |
| } |
| // Restore the account |
| Log.w(Email.LOG_TAG, "Restoring account:" + backupAccount.getDescription()); |
| EmailContent.Account toAccount = |
| LegacyConversions.makeAccount(context, backupAccount); |
| |
| // Mark the default account if this is it |
| if (0 != (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_IS_DEFAULT)) { |
| toAccount.setDefaultAccount(true); |
| } |
| |
| // For exchange accounts, handle system account first, then save in provider |
| if (toAccount.mHostAuthRecv.mProtocol.equals("eas")) { |
| // Recreate entry in Account Manager as well, if needed |
| // Set "sync contacts/calendar" mode as well, if needed |
| boolean alsoSyncContacts = |
| (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_SYNC_CONTACTS) != 0; |
| boolean alsoSyncCalendar = |
| (backupAccount.mBackupFlags & Account.BACKUP_FLAGS_SYNC_CALENDAR) != 0; |
| |
| // Use delete-then-add semantic to simplify handling of update-in-place |
| // AccountManagerFuture<Boolean> removeResult = ExchangeStore.removeSystemAccount( |
| // context.getApplicationContext(), toAccount, null); |
| // try { |
| // // This call blocks until removeSystemAccount completes. Result is not used. |
| // removeResult.getResult(); |
| // } catch (AccountsException e) { |
| // Log.d(Email.LOG_TAG, "removeSystemAccount failed: " + e); |
| // // log and discard - we don't care if remove fails, generally |
| // } catch (IOException e) { |
| // Log.d(Email.LOG_TAG, "removeSystemAccount failed: " + e); |
| // // log and discard - we don't care if remove fails, generally |
| // } |
| |
| // NOTE: We must use the Application here, rather than the current context, because |
| // all future references to AccountManager will use the context passed in here |
| // TODO: Need to implement overwrite semantics for an already-installed account |
| AccountManagerFuture<Bundle> addAccountResult = |
| ExchangeStore.addSystemAccount(context.getApplicationContext(), toAccount, |
| alsoSyncContacts, alsoSyncCalendar, null); |
| // try { |
| // // This call blocks until addSystemAccount completes. Result is not used. |
| // addAccountResult.getResult(); |
| toAccount.save(context); |
| // } catch (OperationCanceledException e) { |
| // Log.d(Email.LOG_TAG, "addAccount was canceled"); |
| // } catch (IOException e) { |
| // Log.d(Email.LOG_TAG, "addAccount failed: " + e); |
| // } catch (AuthenticatorException e) { |
| // Log.d(Email.LOG_TAG, "addAccount failed: " + e); |
| // } |
| |
| } else { |
| // non-eas account - save it immediately |
| toAccount.save(context); |
| } |
| // report that an account was restored |
| result = true; |
| } |
| return result; |
| } |
| } |