AccountBackupRestore.java revision f5418f1f93b02e7fab9f15eb201800b65510998e
1/*
2 * Copyright (C) 2011 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.provider;
18
19import com.android.email.Email;
20import com.android.email.Preferences;
21import com.android.emailcommon.Logging;
22import com.android.emailcommon.provider.Account;
23import com.android.emailcommon.provider.EmailContent;
24
25import android.content.ContentResolver;
26import android.content.Context;
27import android.text.TextUtils;
28import android.util.Log;
29
30/**
31 * Helper class to facilitate EmailProvider's account backup/restore facility.
32 *
33 * Account backup/restore was implemented entirely for the purpose of recovering from database
34 * corruption errors that were/are sporadic and of undetermined cause (though the prevailing wisdom
35 * is that this is due to some kind of memory issue).  Rather than have the offending database get
36 * deleted by SQLiteDatabase and forcing the user to recreate his accounts from scratch, it was
37 * decided to backup accounts when created/modified and then restore them if 1) there are no
38 * accounts in the database and 2) there are backup accounts.  This, at least, would cause user's
39 * email data for IMAP/EAS to be re-synced and prevent the worst outcomes from occurring.
40 *
41 * To accomplish backup/restore, we use the facility now built in to EmailProvider to store a
42 * backup version of the Account and HostAuth tables in a second database (EmailProviderBackup.db)
43 *
44 * TODO: We might look into having our own DatabaseErrorHandler that tries to be clever about
45 * determining whether or not a "corrupt" database is truly corrupt; the problem here is that it
46 * has proven impossible to reproduce the bug, and therefore any "solution" of this kind of utterly
47 * impossible to test in the wild.
48 */
49public class AccountBackupRestore {
50    // We only need to do this once, so prevent extra work by remembering this...
51    private static boolean sBackupsChecked = false;
52
53    /**
54     * Backup user Account and HostAuth data into our backup database
55     */
56    public static void backup(Context context) {
57        ContentResolver resolver = context.getContentResolver();
58        int numBackedUp = resolver.update(EmailProvider.ACCOUNT_BACKUP_URI, null, null, null);
59        if (numBackedUp < 0) {
60            Log.e(Logging.LOG_TAG, "Account backup failed!");
61        } else if (Email.DEBUG) {
62            Log.d(Logging.LOG_TAG, "Backed up " + numBackedUp + " accounts...");
63        }
64    }
65
66    /**
67     * Restore user Account and HostAuth data from our backup database
68     */
69    public static void restoreIfNeeded(Context context) {
70        if (sBackupsChecked) return;
71
72        // Check for legacy backup
73        String legacyBackup = Preferences.getLegacyBackupPreference(context);
74        // If there's a legacy backup, create a new-style backup and delete the legacy backup
75        // In the 1:1000000000 chance that the user gets an app update just as his database becomes
76        // corrupt, oh well...
77        if (!TextUtils.isEmpty(legacyBackup)) {
78            backup(context);
79            Preferences.clearLegacyBackupPreference(context);
80            Log.w(Logging.LOG_TAG, "Created new EmailProvider backup database");
81        }
82
83        // If we have accounts, we're done
84        if (EmailContent.count(context, Account.CONTENT_URI) > 0) return;
85        ContentResolver resolver = context.getContentResolver();
86        int numRecovered = resolver.update(EmailProvider.ACCOUNT_RESTORE_URI, null, null, null);
87        if (numRecovered > 0) {
88            Log.e(Logging.LOG_TAG, "Recovered " + numRecovered + " accounts!");
89        } else if (numRecovered < 0) {
90            Log.e(Logging.LOG_TAG, "Account recovery failed?");
91        } else if (Email.DEBUG) {
92            Log.d(Logging.LOG_TAG, "No accounts to restore...");
93        }
94        sBackupsChecked = true;
95    }
96}
97