230 lines
10 KiB
Java
230 lines
10 KiB
Java
/*
|
|
* 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 android.accounts.AccountManagerFuture;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
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);
|
|
ExchangeUtils.startExchangeService(context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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, Email.EXCHANGE_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;
|
|
}
|
|
}
|