• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
backupAccounts(final Context context)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      */
restoreAccountsIfNeeded(final Context context)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      */
doBackupAccounts(Context context, Preferences preferences)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      */
doRestoreAccounts(Context context, Preferences preferences)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