AccountSyncSettingsBackupHelper.java revision 35f94d02ed59f311ab014e5686156a9d7bf1ea14
1a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul/*
2a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * Copyright (C) 2014 The Android Open Source Project
3a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul *
4a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * Licensed under the Apache License, Version 2.0 (the "License");
5a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * you may not use this file except in compliance with the License.
6a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * You may obtain a copy of the License at
7a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul *
8a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul *      http://www.apache.org/licenses/LICENSE-2.0
9a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul *
10a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * Unless required by applicable law or agreed to in writing, software
11a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * distributed under the License is distributed on an "AS IS" BASIS,
12a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * See the License for the specific language governing permissions and
14a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * limitations under the License.
15a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul */
16a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
17a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulpackage com.android.server.backup;
18a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
19a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport org.json.JSONArray;
20a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport org.json.JSONException;
21a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport org.json.JSONObject;
22a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
23a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.accounts.Account;
24a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.accounts.AccountManager;
25a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.app.backup.BackupDataInputStream;
26a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.app.backup.BackupDataOutput;
27a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.app.backup.BackupHelper;
28a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.content.ContentResolver;
29a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.content.Context;
30a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.content.SyncAdapterType;
31a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.os.ParcelFileDescriptor;
32a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport android.util.Log;
33a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
34a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.io.BufferedOutputStream;
35a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.io.DataInputStream;
36a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.io.DataOutputStream;
37a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.io.EOFException;
38a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.io.FileInputStream;
39a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.io.FileOutputStream;
40a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.io.IOException;
41a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.security.MessageDigest;
42a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.security.NoSuchAlgorithmException;
43a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.util.ArrayList;
44a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.util.Arrays;
45a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.util.HashMap;
46a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.util.HashSet;
47a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulimport java.util.List;
48a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
49a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul/**
50a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * Helper for backing up account sync settings (whether or not a service should be synced). The
51a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * sync settings are backed up as a JSON object containing all the necessary information for
52a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul * restoring the sync settings later.
53a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul */
54a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paulpublic class AccountSyncSettingsBackupHelper implements BackupHelper {
55a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
56a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String TAG = "AccountSyncSettingsBackupHelper";
57a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final boolean DEBUG = false;
58a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
59a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final int STATE_VERSION = 1;
60a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final int MD5_BYTE_SIZE = 16;
61a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final int SYNC_REQUEST_LATCH_TIMEOUT_SECONDS = 1;
62a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
63a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String JSON_FORMAT_HEADER_KEY = "account_data";
64a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String JSON_FORMAT_ENCODING = "UTF-8";
65a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final int JSON_FORMAT_VERSION = 1;
66a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
67a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_VERSION = "version";
68a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_MASTER_SYNC_ENABLED = "masterSyncEnabled";
69a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_ACCOUNTS = "accounts";
70a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_ACCOUNT_NAME = "name";
71a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_ACCOUNT_TYPE = "type";
72a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_ACCOUNT_AUTHORITIES = "authorities";
73a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_AUTHORITY_NAME = "name";
74a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_AUTHORITY_SYNC_STATE = "syncState";
75a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private static final String KEY_AUTHORITY_SYNC_ENABLED = "syncEnabled";
76a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
77a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private Context mContext;
78a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private AccountManager mAccountManager;
79a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
80a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    public AccountSyncSettingsBackupHelper(Context context) {
81a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        mContext = context;
82a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        mAccountManager = AccountManager.get(mContext);
83a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
84a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
85a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    /**
86a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * Take a snapshot of the current account sync settings and write them to the given output.
87a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     */
88a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    @Override
89a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput output,
90a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            ParcelFileDescriptor newState) {
91a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        try {
92a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            JSONObject dataJSON = serializeAccountSyncSettingsToJSON();
93a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
94a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            if (DEBUG) {
95a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                Log.d(TAG, "Account sync settings JSON: " + dataJSON);
96a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
97a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
98a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // Encode JSON data to bytes.
99a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            byte[] dataBytes = dataJSON.toString().getBytes(JSON_FORMAT_ENCODING);
100a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            byte[] oldMd5Checksum = readOldMd5Checksum(oldState);
101a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            byte[] newMd5Checksum = generateMd5Checksum(dataBytes);
102a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            if (!Arrays.equals(oldMd5Checksum, newMd5Checksum)) {
103a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                int dataSize = dataBytes.length;
104a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                output.writeEntityHeader(JSON_FORMAT_HEADER_KEY, dataSize);
105a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                output.writeEntityData(dataBytes, dataSize);
106a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
107a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                Log.i(TAG, "Backup successful.");
108a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            } else {
109a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                Log.i(TAG, "Old and new MD5 checksums match. Skipping backup.");
110a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
111a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
112a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            writeNewMd5Checksum(newState, newMd5Checksum);
113a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        } catch (JSONException | IOException | NoSuchAlgorithmException e) {
114a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            Log.e(TAG, "Couldn't backup account sync settings\n" + e);
115a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
116a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
117a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
118a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    /**
119a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * Fetch and serialize Account and authority information as a JSON Array.
120a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     */
121a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private JSONObject serializeAccountSyncSettingsToJSON() throws JSONException {
122a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        Account[] accounts = mAccountManager.getAccounts();
123a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
124a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                mContext.getUserId());
125a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
126a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        // Create a map of Account types to authorities. Later this will make it easier for us to
127a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        // generate our JSON.
128a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        HashMap<String, List<String>> accountTypeToAuthorities = new HashMap<String,
129a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                List<String>>();
130a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        for (SyncAdapterType syncAdapter : syncAdapters) {
131a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // Skip adapters that aren’t visible to the user.
132a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            if (!syncAdapter.isUserVisible()) {
133a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                continue;
134a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
135a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            if (!accountTypeToAuthorities.containsKey(syncAdapter.accountType)) {
136a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                accountTypeToAuthorities.put(syncAdapter.accountType, new ArrayList<String>());
137a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
138a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            accountTypeToAuthorities.get(syncAdapter.accountType).add(syncAdapter.authority);
139a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
140a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
141a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        // Generate JSON.
142a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        JSONObject backupJSON = new JSONObject();
143a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        backupJSON.put(KEY_VERSION, JSON_FORMAT_VERSION);
144a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        backupJSON.put(KEY_MASTER_SYNC_ENABLED, ContentResolver.getMasterSyncAutomatically());
145a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
146a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        JSONArray accountJSONArray = new JSONArray();
147a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        for (Account account : accounts) {
148a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            List<String> authorities = accountTypeToAuthorities.get(account.type);
149a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
150a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // We ignore Accounts that don't have any authorities because there would be no sync
151a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // settings for us to restore.
152a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            if (authorities == null || authorities.isEmpty()) {
153a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                continue;
154a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
155a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
156a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            JSONObject accountJSON = new JSONObject();
157a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            accountJSON.put(KEY_ACCOUNT_NAME, account.name);
158a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            accountJSON.put(KEY_ACCOUNT_TYPE, account.type);
159a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
160a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // Add authorities for this Account type and check whether or not sync is enabled.
161a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            JSONArray authoritiesJSONArray = new JSONArray();
162a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            for (String authority : authorities) {
163a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                int syncState = ContentResolver.getIsSyncable(account, authority);
164a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
165a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
166a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                JSONObject authorityJSON = new JSONObject();
167a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                authorityJSON.put(KEY_AUTHORITY_NAME, authority);
168a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                authorityJSON.put(KEY_AUTHORITY_SYNC_STATE, syncState);
169a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                authorityJSON.put(KEY_AUTHORITY_SYNC_ENABLED, syncEnabled);
170a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                authoritiesJSONArray.put(authorityJSON);
171a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
172a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            accountJSON.put(KEY_ACCOUNT_AUTHORITIES, authoritiesJSONArray);
173a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
174a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            accountJSONArray.put(accountJSON);
175a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
176a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        backupJSON.put(KEY_ACCOUNTS, accountJSONArray);
177a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
178a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        return backupJSON;
179a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
180a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
181a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    /**
182a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * Read the MD5 checksum from the old state.
183a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     *
184a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * @return the old MD5 checksum
185a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     */
186a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private byte[] readOldMd5Checksum(ParcelFileDescriptor oldState) throws IOException {
187a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        DataInputStream dataInput = new DataInputStream(
188a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                new FileInputStream(oldState.getFileDescriptor()));
189a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
190a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        byte[] oldMd5Checksum = new byte[MD5_BYTE_SIZE];
191a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        try {
192a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            int stateVersion = dataInput.readInt();
193a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            if (stateVersion <= STATE_VERSION) {
194a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                // If the state version is a version we can understand then read the MD5 sum,
195a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                // otherwise we return an empty byte array for the MD5 sum which will force a
196a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                // backup.
197a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                for (int i = 0; i < MD5_BYTE_SIZE; i++) {
198a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    oldMd5Checksum[i] = dataInput.readByte();
199a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                }
200a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            } else {
201a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                Log.i(TAG, "Backup state version is: " + stateVersion
202a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                        + " (support only up to version " + STATE_VERSION + ")");
203a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
204a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        } catch (EOFException eof) {
205a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // Initial state may be empty.
206a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
20735f94d02ed59f311ab014e5686156a9d7bf1ea14Ritesh Reddy        // We explicitly don't close 'dataInput' because we must not close the backing fd.
208a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        return oldMd5Checksum;
209a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
210a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
211a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    /**
212a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * Write the given checksum to the file descriptor.
213a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     */
214a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private void writeNewMd5Checksum(ParcelFileDescriptor newState, byte[] md5Checksum)
215a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            throws IOException {
216a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        DataOutputStream dataOutput = new DataOutputStream(
217a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                new BufferedOutputStream(new FileOutputStream(newState.getFileDescriptor())));
218a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
219a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        dataOutput.writeInt(STATE_VERSION);
220a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        dataOutput.write(md5Checksum);
22135f94d02ed59f311ab014e5686156a9d7bf1ea14Ritesh Reddy
22235f94d02ed59f311ab014e5686156a9d7bf1ea14Ritesh Reddy        // We explicitly don't close 'dataOutput' because we must not close the backing fd.
22335f94d02ed59f311ab014e5686156a9d7bf1ea14Ritesh Reddy        // The FileOutputStream will not close it implicitly.
22435f94d02ed59f311ab014e5686156a9d7bf1ea14Ritesh Reddy
225a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
226a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
227a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private byte[] generateMd5Checksum(byte[] data) throws NoSuchAlgorithmException {
228a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        if (data == null) {
229a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            return null;
230a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
231a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
232a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        MessageDigest md5 = MessageDigest.getInstance("MD5");
233a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        return md5.digest(data);
234a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
235a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
236a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    /**
237a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * Restore account sync settings from the given data input stream.
238a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     */
239a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    @Override
240a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    public void restoreEntity(BackupDataInputStream data) {
241a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        byte[] dataBytes = new byte[data.size()];
242a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        try {
243a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // Read the data and convert it to a String.
244a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            data.read(dataBytes);
245a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            String dataString = new String(dataBytes, JSON_FORMAT_ENCODING);
246a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
247a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            // Convert data to a JSON object.
248a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            JSONObject dataJSON = new JSONObject(dataString);
249a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            boolean masterSyncEnabled = dataJSON.getBoolean(KEY_MASTER_SYNC_ENABLED);
250a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            JSONArray accountJSONArray = dataJSON.getJSONArray(KEY_ACCOUNTS);
251a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
252a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            boolean currentMasterSyncEnabled = ContentResolver.getMasterSyncAutomatically();
253a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            if (currentMasterSyncEnabled) {
254a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                // Disable master sync to prevent any syncs from running.
255a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                ContentResolver.setMasterSyncAutomatically(false);
256a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
257a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
258a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            try {
259a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                HashSet<Account> currentAccounts = getAccountsHashSet();
260a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                for (int i = 0; i < accountJSONArray.length(); i++) {
261a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    JSONObject accountJSON = (JSONObject) accountJSONArray.get(i);
262a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
263a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);
264a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
265a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    Account account = new Account(accountName, accountType);
266a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
267a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    // Check if the account already exists. Accounts that don't exist on the device
268a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    // yet won't be restored.
269a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    if (currentAccounts.contains(account)) {
270a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                        restoreExistingAccountSyncSettingsFromJSON(accountJSON);
27153abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                    } else {
27253abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                        // TODO:
27353abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                        // Stash the data to a file that the SyncManager can read from to restore
27453abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                        // settings at a later date.
275a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                    }
276a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                }
277a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            } finally {
278a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                // Set the master sync preference to the value from the backup set.
279a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul                ContentResolver.setMasterSyncAutomatically(masterSyncEnabled);
280a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
281a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
282a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            Log.i(TAG, "Restore successful.");
283a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        } catch (IOException | JSONException e) {
284a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            Log.e(TAG, "Couldn't restore account sync settings\n" + e);
285a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
286a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
287a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
288a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    /**
289a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * Helper method - fetch accounts and return them as a HashSet.
290a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     *
291a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * @return Accounts in a HashSet.
292a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     */
293a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private HashSet<Account> getAccountsHashSet() {
294a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        Account[] accounts = mAccountManager.getAccounts();
295a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        HashSet<Account> accountHashSet = new HashSet<Account>();
296a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        for (Account account : accounts) {
297a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            accountHashSet.add(account);
298a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
299a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        return accountHashSet;
300a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
301a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
302a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    /**
303a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * Restore account sync settings using the given JSON. This function won't work if the account
304a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     * doesn't exist yet.
30553abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * This function will only be called during Setup Wizard, where we are guaranteed that there
30653abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * are no active syncs.
30753abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * There are 2 pieces of data to restore -
30853abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     *      isSyncable (corresponds to {@link ContentResolver#getIsSyncable(Account, String)}
30953abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     *      syncEnabled (corresponds to {@link ContentResolver#getSyncAutomatically(Account, String)}
31053abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * <strong>The restore favours adapters that were enabled on the old device, and doesn't care
31153abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * about adapters that were disabled.</strong>
31253abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     *
31353abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * syncEnabled=true in restore data.
31453abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * syncEnabled will be true on this device. isSyncable will be left as the default in order to
31553abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * give the enabled adapter the chance to run an initialization sync.
31653abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     *
31753abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * syncEnabled=false in restore data.
31853abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * syncEnabled will be false on this device. isSyncable will be set to 2, unless it was 0 on the
31953abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * old device in which case it will be set to 0 on this device. This is because isSyncable=0 is
32053abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * a rare state and was probably set to 0 for good reason (historically isSyncable is a way by
32153abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * which adapters control their own sync state independently of sync settings which is
32253abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * toggleable by the user).
32353abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * isSyncable=2 is a new isSyncable state we introduced specifically to allow adapters that are
32453abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * disabled after a restore to run initialization logic when the adapter is later enabled.
32553abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * See com.android.server.content.SyncStorageEngine#setSyncAutomatically
32653abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     *
32753abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * The end result is that an adapter that the user had on will be turned on and get an
32853abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * initialization sync, while an adapter that the user had off will be off until the user
32953abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams     * enables it on this device at which point it will get an initialization sync.
330a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul     */
331a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON)
332a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            throws JSONException {
333a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        // Restore authorities.
334a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES);
335a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
336a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);
33753abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams
338a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        final Account account = new Account(accountName, accountType);
339a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        for (int i = 0; i < authorities.length(); i++) {
340a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            JSONObject authority = (JSONObject) authorities.get(i);
341a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            final String authorityName = authority.getString(KEY_AUTHORITY_NAME);
34253abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams            boolean wasSyncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED);
34353abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams            int wasSyncable = authority.getInt(KEY_AUTHORITY_SYNC_STATE);
34453abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams
34553abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams            ContentResolver.setSyncAutomaticallyAsUser(
34653abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                    account, authorityName, wasSyncEnabled, 0 /* user Id */);
34753abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams
34853abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams            if (!wasSyncEnabled) {
34953abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                ContentResolver.setIsSyncable(
35053abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                        account,
35153abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                        authorityName,
35253abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                        wasSyncable == 0 ?
35353abfdb86c2bf834777dbda61fc46083a93a4a83Matthew Williams                                0 /* not syncable */ : 2 /* syncable but needs initialization */);
354a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul            }
355a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul        }
356a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
357a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
358a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    @Override
359a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    public void writeNewStateDescription(ParcelFileDescriptor newState) {
360a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul
361a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul    }
362a6533256ac1de9d0860688ca743aa7a2468470ccMarvin Paul}
363