Loading core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java 0 → 100644 +380 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 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.server.backup; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.accounts.Account; import android.accounts.AccountManager; import android.app.backup.BackupDataInputStream; import android.app.backup.BackupDataOutput; import android.app.backup.BackupHelper; import android.content.ContentResolver; import android.content.Context; import android.content.SyncAdapterType; import android.content.SyncStatusObserver; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.util.Log; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Helper for backing up account sync settings (whether or not a service should be synced). The * sync settings are backed up as a JSON object containing all the necessary information for * restoring the sync settings later. */ public class AccountSyncSettingsBackupHelper implements BackupHelper { private static final String TAG = "AccountSyncSettingsBackupHelper"; private static final boolean DEBUG = false; private static final int STATE_VERSION = 1; private static final int MD5_BYTE_SIZE = 16; private static final int SYNC_REQUEST_LATCH_TIMEOUT_SECONDS = 1; private static final String JSON_FORMAT_HEADER_KEY = "account_data"; private static final String JSON_FORMAT_ENCODING = "UTF-8"; private static final int JSON_FORMAT_VERSION = 1; private static final String KEY_VERSION = "version"; private static final String KEY_MASTER_SYNC_ENABLED = "masterSyncEnabled"; private static final String KEY_ACCOUNTS = "accounts"; private static final String KEY_ACCOUNT_NAME = "name"; private static final String KEY_ACCOUNT_TYPE = "type"; private static final String KEY_ACCOUNT_AUTHORITIES = "authorities"; private static final String KEY_AUTHORITY_NAME = "name"; private static final String KEY_AUTHORITY_SYNC_STATE = "syncState"; private static final String KEY_AUTHORITY_SYNC_ENABLED = "syncEnabled"; private Context mContext; private AccountManager mAccountManager; public AccountSyncSettingsBackupHelper(Context context) { mContext = context; mAccountManager = AccountManager.get(mContext); } /** * Take a snapshot of the current account sync settings and write them to the given output. */ @Override public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput output, ParcelFileDescriptor newState) { try { JSONObject dataJSON = serializeAccountSyncSettingsToJSON(); if (DEBUG) { Log.d(TAG, "Account sync settings JSON: " + dataJSON); } // Encode JSON data to bytes. byte[] dataBytes = dataJSON.toString().getBytes(JSON_FORMAT_ENCODING); byte[] oldMd5Checksum = readOldMd5Checksum(oldState); byte[] newMd5Checksum = generateMd5Checksum(dataBytes); if (!Arrays.equals(oldMd5Checksum, newMd5Checksum)) { int dataSize = dataBytes.length; output.writeEntityHeader(JSON_FORMAT_HEADER_KEY, dataSize); output.writeEntityData(dataBytes, dataSize); Log.i(TAG, "Backup successful."); } else { Log.i(TAG, "Old and new MD5 checksums match. Skipping backup."); } writeNewMd5Checksum(newState, newMd5Checksum); } catch (JSONException | IOException | NoSuchAlgorithmException e) { Log.e(TAG, "Couldn't backup account sync settings\n" + e); } } /** * Fetch and serialize Account and authority information as a JSON Array. */ private JSONObject serializeAccountSyncSettingsToJSON() throws JSONException { Account[] accounts = mAccountManager.getAccounts(); SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( mContext.getUserId()); // Create a map of Account types to authorities. Later this will make it easier for us to // generate our JSON. HashMap<String, List<String>> accountTypeToAuthorities = new HashMap<String, List<String>>(); for (SyncAdapterType syncAdapter : syncAdapters) { // Skip adapters that aren’t visible to the user. if (!syncAdapter.isUserVisible()) { continue; } if (!accountTypeToAuthorities.containsKey(syncAdapter.accountType)) { accountTypeToAuthorities.put(syncAdapter.accountType, new ArrayList<String>()); } accountTypeToAuthorities.get(syncAdapter.accountType).add(syncAdapter.authority); } // Generate JSON. JSONObject backupJSON = new JSONObject(); backupJSON.put(KEY_VERSION, JSON_FORMAT_VERSION); backupJSON.put(KEY_MASTER_SYNC_ENABLED, ContentResolver.getMasterSyncAutomatically()); JSONArray accountJSONArray = new JSONArray(); for (Account account : accounts) { List<String> authorities = accountTypeToAuthorities.get(account.type); // We ignore Accounts that don't have any authorities because there would be no sync // settings for us to restore. if (authorities == null || authorities.isEmpty()) { continue; } JSONObject accountJSON = new JSONObject(); accountJSON.put(KEY_ACCOUNT_NAME, account.name); accountJSON.put(KEY_ACCOUNT_TYPE, account.type); // Add authorities for this Account type and check whether or not sync is enabled. JSONArray authoritiesJSONArray = new JSONArray(); for (String authority : authorities) { int syncState = ContentResolver.getIsSyncable(account, authority); boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority); JSONObject authorityJSON = new JSONObject(); authorityJSON.put(KEY_AUTHORITY_NAME, authority); authorityJSON.put(KEY_AUTHORITY_SYNC_STATE, syncState); authorityJSON.put(KEY_AUTHORITY_SYNC_ENABLED, syncEnabled); authoritiesJSONArray.put(authorityJSON); } accountJSON.put(KEY_ACCOUNT_AUTHORITIES, authoritiesJSONArray); accountJSONArray.put(accountJSON); } backupJSON.put(KEY_ACCOUNTS, accountJSONArray); return backupJSON; } /** * Read the MD5 checksum from the old state. * * @return the old MD5 checksum */ private byte[] readOldMd5Checksum(ParcelFileDescriptor oldState) throws IOException { DataInputStream dataInput = new DataInputStream( new FileInputStream(oldState.getFileDescriptor())); byte[] oldMd5Checksum = new byte[MD5_BYTE_SIZE]; try { int stateVersion = dataInput.readInt(); if (stateVersion <= STATE_VERSION) { // If the state version is a version we can understand then read the MD5 sum, // otherwise we return an empty byte array for the MD5 sum which will force a // backup. for (int i = 0; i < MD5_BYTE_SIZE; i++) { oldMd5Checksum[i] = dataInput.readByte(); } } else { Log.i(TAG, "Backup state version is: " + stateVersion + " (support only up to version " + STATE_VERSION + ")"); } } catch (EOFException eof) { // Initial state may be empty. } finally { dataInput.close(); } return oldMd5Checksum; } /** * Write the given checksum to the file descriptor. */ private void writeNewMd5Checksum(ParcelFileDescriptor newState, byte[] md5Checksum) throws IOException { DataOutputStream dataOutput = new DataOutputStream( new BufferedOutputStream(new FileOutputStream(newState.getFileDescriptor()))); dataOutput.writeInt(STATE_VERSION); dataOutput.write(md5Checksum); dataOutput.close(); } private byte[] generateMd5Checksum(byte[] data) throws NoSuchAlgorithmException { if (data == null) { return null; } MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(data); } /** * Restore account sync settings from the given data input stream. */ @Override public void restoreEntity(BackupDataInputStream data) { byte[] dataBytes = new byte[data.size()]; try { // Read the data and convert it to a String. data.read(dataBytes); String dataString = new String(dataBytes, JSON_FORMAT_ENCODING); // Convert data to a JSON object. JSONObject dataJSON = new JSONObject(dataString); boolean masterSyncEnabled = dataJSON.getBoolean(KEY_MASTER_SYNC_ENABLED); JSONArray accountJSONArray = dataJSON.getJSONArray(KEY_ACCOUNTS); boolean currentMasterSyncEnabled = ContentResolver.getMasterSyncAutomatically(); if (currentMasterSyncEnabled) { // Disable master sync to prevent any syncs from running. ContentResolver.setMasterSyncAutomatically(false); } try { HashSet<Account> currentAccounts = getAccountsHashSet(); for (int i = 0; i < accountJSONArray.length(); i++) { JSONObject accountJSON = (JSONObject) accountJSONArray.get(i); String accountName = accountJSON.getString(KEY_ACCOUNT_NAME); String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE); Account account = new Account(accountName, accountType); // Check if the account already exists. Accounts that don't exist on the device // yet won't be restored. if (currentAccounts.contains(account)) { restoreExistingAccountSyncSettingsFromJSON(accountJSON); } } } finally { // Set the master sync preference to the value from the backup set. ContentResolver.setMasterSyncAutomatically(masterSyncEnabled); } Log.i(TAG, "Restore successful."); } catch (IOException | JSONException e) { Log.e(TAG, "Couldn't restore account sync settings\n" + e); } } /** * Helper method - fetch accounts and return them as a HashSet. * * @return Accounts in a HashSet. */ private HashSet<Account> getAccountsHashSet() { Account[] accounts = mAccountManager.getAccounts(); HashSet<Account> accountHashSet = new HashSet<Account>(); for (Account account : accounts) { accountHashSet.add(account); } return accountHashSet; } /** * Restore account sync settings using the given JSON. This function won't work if the account * doesn't exist yet. */ private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON) throws JSONException { // Restore authorities. JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES); String accountName = accountJSON.getString(KEY_ACCOUNT_NAME); String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE); final Account account = new Account(accountName, accountType); for (int i = 0; i < authorities.length(); i++) { JSONObject authority = (JSONObject) authorities.get(i); final String authorityName = authority.getString(KEY_AUTHORITY_NAME); boolean syncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED); // Cancel any active syncs. if (ContentResolver.isSyncActive(account, authorityName)) { ContentResolver.cancelSync(account, authorityName); } boolean overwriteSync = true; Bundle initializationExtras = createSyncInitializationBundle(); int currentSyncState = ContentResolver.getIsSyncable(account, authorityName); if (currentSyncState < 0) { // Requesting a sync is an asynchronous operation, so we setup a countdown latch to // wait for it to finish. Initialization syncs are generally very brief and // shouldn't take too much time to finish. final CountDownLatch latch = new CountDownLatch(1); Object syncStatusObserverHandle = ContentResolver.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, new SyncStatusObserver() { @Override public void onStatusChanged(int which) { if (!ContentResolver.isSyncActive(account, authorityName)) { latch.countDown(); } } }); // If we set sync settings for a sync that hasn't been initialized yet, we run the // risk of having our changes overwritten later on when the sync gets initialized. // To prevent this from happening we will manually initiate the sync adapter. We // also explicitly pass in a Bundle with SYNC_EXTRAS_INITIALIZE to prevent a data // sync from running after the initialization sync. Two syncs will be scheduled, but // the second one (data sync) will override the first one (initialization sync) and // still behave as an initialization sync because of the Bundle. ContentResolver.requestSync(account, authorityName, initializationExtras); boolean done = false; try { done = latch.await(SYNC_REQUEST_LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (InterruptedException e) { Log.e(TAG, "CountDownLatch interrupted\n" + e); done = false; } if (!done) { overwriteSync = false; Log.i(TAG, "CountDownLatch timed out, skipping '" + authorityName + "' authority."); } ContentResolver.removeStatusChangeListener(syncStatusObserverHandle); } if (overwriteSync) { ContentResolver.setSyncAutomatically(account, authorityName, syncEnabled); Log.i(TAG, "Set sync automatically for '" + authorityName + "': " + syncEnabled); } } } private Bundle createSyncInitializationBundle() { Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); return extras; } @Override public void writeNewStateDescription(ParcelFileDescriptor newState) { } } core/java/com/android/server/backup/SystemBackupAgent.java +4 −0 Original line number Diff line number Diff line Loading @@ -86,6 +86,8 @@ public class SystemBackupAgent extends BackupAgentHelper { } addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys)); addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this)); addHelper("account_sync_settings", new AccountSyncSettingsBackupHelper(SystemBackupAgent.this)); super.onBackup(oldState, data, newState); } Loading Loading @@ -118,6 +120,8 @@ public class SystemBackupAgent extends BackupAgentHelper { new String[] { WALLPAPER_IMAGE }, new String[] { WALLPAPER_IMAGE_KEY} )); addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this)); addHelper("account_sync_settings", new AccountSyncSettingsBackupHelper(SystemBackupAgent.this)); try { super.onRestore(data, appVersionCode, newState); Loading services/core/java/com/android/server/content/SyncStorageEngine.java +11 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.content; import android.accounts.Account; import android.accounts.AccountAndUser; import android.app.backup.BackupManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; Loading Loading @@ -670,6 +671,7 @@ public class SyncStorageEngine extends Handler { new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); queueBackup(); } public int getIsSyncable(Account account, int userId, String providerName) { Loading Loading @@ -1035,6 +1037,7 @@ public class SyncStorageEngine extends Handler { } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED); queueBackup(); } public boolean getMasterSyncAutomatically(int userId) { Loading Loading @@ -2810,4 +2813,12 @@ public class SyncStorageEngine extends Handler { .append(")\n"); } } /** * Let the BackupManager know that account sync settings have changed. This will trigger * {@link com.android.server.backup.SystemBackupAgent} to run. */ public void queueBackup() { BackupManager.dataChanged("android"); } } Loading
core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java 0 → 100644 +380 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 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.server.backup; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.accounts.Account; import android.accounts.AccountManager; import android.app.backup.BackupDataInputStream; import android.app.backup.BackupDataOutput; import android.app.backup.BackupHelper; import android.content.ContentResolver; import android.content.Context; import android.content.SyncAdapterType; import android.content.SyncStatusObserver; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.util.Log; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Helper for backing up account sync settings (whether or not a service should be synced). The * sync settings are backed up as a JSON object containing all the necessary information for * restoring the sync settings later. */ public class AccountSyncSettingsBackupHelper implements BackupHelper { private static final String TAG = "AccountSyncSettingsBackupHelper"; private static final boolean DEBUG = false; private static final int STATE_VERSION = 1; private static final int MD5_BYTE_SIZE = 16; private static final int SYNC_REQUEST_LATCH_TIMEOUT_SECONDS = 1; private static final String JSON_FORMAT_HEADER_KEY = "account_data"; private static final String JSON_FORMAT_ENCODING = "UTF-8"; private static final int JSON_FORMAT_VERSION = 1; private static final String KEY_VERSION = "version"; private static final String KEY_MASTER_SYNC_ENABLED = "masterSyncEnabled"; private static final String KEY_ACCOUNTS = "accounts"; private static final String KEY_ACCOUNT_NAME = "name"; private static final String KEY_ACCOUNT_TYPE = "type"; private static final String KEY_ACCOUNT_AUTHORITIES = "authorities"; private static final String KEY_AUTHORITY_NAME = "name"; private static final String KEY_AUTHORITY_SYNC_STATE = "syncState"; private static final String KEY_AUTHORITY_SYNC_ENABLED = "syncEnabled"; private Context mContext; private AccountManager mAccountManager; public AccountSyncSettingsBackupHelper(Context context) { mContext = context; mAccountManager = AccountManager.get(mContext); } /** * Take a snapshot of the current account sync settings and write them to the given output. */ @Override public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput output, ParcelFileDescriptor newState) { try { JSONObject dataJSON = serializeAccountSyncSettingsToJSON(); if (DEBUG) { Log.d(TAG, "Account sync settings JSON: " + dataJSON); } // Encode JSON data to bytes. byte[] dataBytes = dataJSON.toString().getBytes(JSON_FORMAT_ENCODING); byte[] oldMd5Checksum = readOldMd5Checksum(oldState); byte[] newMd5Checksum = generateMd5Checksum(dataBytes); if (!Arrays.equals(oldMd5Checksum, newMd5Checksum)) { int dataSize = dataBytes.length; output.writeEntityHeader(JSON_FORMAT_HEADER_KEY, dataSize); output.writeEntityData(dataBytes, dataSize); Log.i(TAG, "Backup successful."); } else { Log.i(TAG, "Old and new MD5 checksums match. Skipping backup."); } writeNewMd5Checksum(newState, newMd5Checksum); } catch (JSONException | IOException | NoSuchAlgorithmException e) { Log.e(TAG, "Couldn't backup account sync settings\n" + e); } } /** * Fetch and serialize Account and authority information as a JSON Array. */ private JSONObject serializeAccountSyncSettingsToJSON() throws JSONException { Account[] accounts = mAccountManager.getAccounts(); SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( mContext.getUserId()); // Create a map of Account types to authorities. Later this will make it easier for us to // generate our JSON. HashMap<String, List<String>> accountTypeToAuthorities = new HashMap<String, List<String>>(); for (SyncAdapterType syncAdapter : syncAdapters) { // Skip adapters that aren’t visible to the user. if (!syncAdapter.isUserVisible()) { continue; } if (!accountTypeToAuthorities.containsKey(syncAdapter.accountType)) { accountTypeToAuthorities.put(syncAdapter.accountType, new ArrayList<String>()); } accountTypeToAuthorities.get(syncAdapter.accountType).add(syncAdapter.authority); } // Generate JSON. JSONObject backupJSON = new JSONObject(); backupJSON.put(KEY_VERSION, JSON_FORMAT_VERSION); backupJSON.put(KEY_MASTER_SYNC_ENABLED, ContentResolver.getMasterSyncAutomatically()); JSONArray accountJSONArray = new JSONArray(); for (Account account : accounts) { List<String> authorities = accountTypeToAuthorities.get(account.type); // We ignore Accounts that don't have any authorities because there would be no sync // settings for us to restore. if (authorities == null || authorities.isEmpty()) { continue; } JSONObject accountJSON = new JSONObject(); accountJSON.put(KEY_ACCOUNT_NAME, account.name); accountJSON.put(KEY_ACCOUNT_TYPE, account.type); // Add authorities for this Account type and check whether or not sync is enabled. JSONArray authoritiesJSONArray = new JSONArray(); for (String authority : authorities) { int syncState = ContentResolver.getIsSyncable(account, authority); boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority); JSONObject authorityJSON = new JSONObject(); authorityJSON.put(KEY_AUTHORITY_NAME, authority); authorityJSON.put(KEY_AUTHORITY_SYNC_STATE, syncState); authorityJSON.put(KEY_AUTHORITY_SYNC_ENABLED, syncEnabled); authoritiesJSONArray.put(authorityJSON); } accountJSON.put(KEY_ACCOUNT_AUTHORITIES, authoritiesJSONArray); accountJSONArray.put(accountJSON); } backupJSON.put(KEY_ACCOUNTS, accountJSONArray); return backupJSON; } /** * Read the MD5 checksum from the old state. * * @return the old MD5 checksum */ private byte[] readOldMd5Checksum(ParcelFileDescriptor oldState) throws IOException { DataInputStream dataInput = new DataInputStream( new FileInputStream(oldState.getFileDescriptor())); byte[] oldMd5Checksum = new byte[MD5_BYTE_SIZE]; try { int stateVersion = dataInput.readInt(); if (stateVersion <= STATE_VERSION) { // If the state version is a version we can understand then read the MD5 sum, // otherwise we return an empty byte array for the MD5 sum which will force a // backup. for (int i = 0; i < MD5_BYTE_SIZE; i++) { oldMd5Checksum[i] = dataInput.readByte(); } } else { Log.i(TAG, "Backup state version is: " + stateVersion + " (support only up to version " + STATE_VERSION + ")"); } } catch (EOFException eof) { // Initial state may be empty. } finally { dataInput.close(); } return oldMd5Checksum; } /** * Write the given checksum to the file descriptor. */ private void writeNewMd5Checksum(ParcelFileDescriptor newState, byte[] md5Checksum) throws IOException { DataOutputStream dataOutput = new DataOutputStream( new BufferedOutputStream(new FileOutputStream(newState.getFileDescriptor()))); dataOutput.writeInt(STATE_VERSION); dataOutput.write(md5Checksum); dataOutput.close(); } private byte[] generateMd5Checksum(byte[] data) throws NoSuchAlgorithmException { if (data == null) { return null; } MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(data); } /** * Restore account sync settings from the given data input stream. */ @Override public void restoreEntity(BackupDataInputStream data) { byte[] dataBytes = new byte[data.size()]; try { // Read the data and convert it to a String. data.read(dataBytes); String dataString = new String(dataBytes, JSON_FORMAT_ENCODING); // Convert data to a JSON object. JSONObject dataJSON = new JSONObject(dataString); boolean masterSyncEnabled = dataJSON.getBoolean(KEY_MASTER_SYNC_ENABLED); JSONArray accountJSONArray = dataJSON.getJSONArray(KEY_ACCOUNTS); boolean currentMasterSyncEnabled = ContentResolver.getMasterSyncAutomatically(); if (currentMasterSyncEnabled) { // Disable master sync to prevent any syncs from running. ContentResolver.setMasterSyncAutomatically(false); } try { HashSet<Account> currentAccounts = getAccountsHashSet(); for (int i = 0; i < accountJSONArray.length(); i++) { JSONObject accountJSON = (JSONObject) accountJSONArray.get(i); String accountName = accountJSON.getString(KEY_ACCOUNT_NAME); String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE); Account account = new Account(accountName, accountType); // Check if the account already exists. Accounts that don't exist on the device // yet won't be restored. if (currentAccounts.contains(account)) { restoreExistingAccountSyncSettingsFromJSON(accountJSON); } } } finally { // Set the master sync preference to the value from the backup set. ContentResolver.setMasterSyncAutomatically(masterSyncEnabled); } Log.i(TAG, "Restore successful."); } catch (IOException | JSONException e) { Log.e(TAG, "Couldn't restore account sync settings\n" + e); } } /** * Helper method - fetch accounts and return them as a HashSet. * * @return Accounts in a HashSet. */ private HashSet<Account> getAccountsHashSet() { Account[] accounts = mAccountManager.getAccounts(); HashSet<Account> accountHashSet = new HashSet<Account>(); for (Account account : accounts) { accountHashSet.add(account); } return accountHashSet; } /** * Restore account sync settings using the given JSON. This function won't work if the account * doesn't exist yet. */ private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON) throws JSONException { // Restore authorities. JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES); String accountName = accountJSON.getString(KEY_ACCOUNT_NAME); String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE); final Account account = new Account(accountName, accountType); for (int i = 0; i < authorities.length(); i++) { JSONObject authority = (JSONObject) authorities.get(i); final String authorityName = authority.getString(KEY_AUTHORITY_NAME); boolean syncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED); // Cancel any active syncs. if (ContentResolver.isSyncActive(account, authorityName)) { ContentResolver.cancelSync(account, authorityName); } boolean overwriteSync = true; Bundle initializationExtras = createSyncInitializationBundle(); int currentSyncState = ContentResolver.getIsSyncable(account, authorityName); if (currentSyncState < 0) { // Requesting a sync is an asynchronous operation, so we setup a countdown latch to // wait for it to finish. Initialization syncs are generally very brief and // shouldn't take too much time to finish. final CountDownLatch latch = new CountDownLatch(1); Object syncStatusObserverHandle = ContentResolver.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, new SyncStatusObserver() { @Override public void onStatusChanged(int which) { if (!ContentResolver.isSyncActive(account, authorityName)) { latch.countDown(); } } }); // If we set sync settings for a sync that hasn't been initialized yet, we run the // risk of having our changes overwritten later on when the sync gets initialized. // To prevent this from happening we will manually initiate the sync adapter. We // also explicitly pass in a Bundle with SYNC_EXTRAS_INITIALIZE to prevent a data // sync from running after the initialization sync. Two syncs will be scheduled, but // the second one (data sync) will override the first one (initialization sync) and // still behave as an initialization sync because of the Bundle. ContentResolver.requestSync(account, authorityName, initializationExtras); boolean done = false; try { done = latch.await(SYNC_REQUEST_LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (InterruptedException e) { Log.e(TAG, "CountDownLatch interrupted\n" + e); done = false; } if (!done) { overwriteSync = false; Log.i(TAG, "CountDownLatch timed out, skipping '" + authorityName + "' authority."); } ContentResolver.removeStatusChangeListener(syncStatusObserverHandle); } if (overwriteSync) { ContentResolver.setSyncAutomatically(account, authorityName, syncEnabled); Log.i(TAG, "Set sync automatically for '" + authorityName + "': " + syncEnabled); } } } private Bundle createSyncInitializationBundle() { Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); return extras; } @Override public void writeNewStateDescription(ParcelFileDescriptor newState) { } }
core/java/com/android/server/backup/SystemBackupAgent.java +4 −0 Original line number Diff line number Diff line Loading @@ -86,6 +86,8 @@ public class SystemBackupAgent extends BackupAgentHelper { } addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys)); addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this)); addHelper("account_sync_settings", new AccountSyncSettingsBackupHelper(SystemBackupAgent.this)); super.onBackup(oldState, data, newState); } Loading Loading @@ -118,6 +120,8 @@ public class SystemBackupAgent extends BackupAgentHelper { new String[] { WALLPAPER_IMAGE }, new String[] { WALLPAPER_IMAGE_KEY} )); addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this)); addHelper("account_sync_settings", new AccountSyncSettingsBackupHelper(SystemBackupAgent.this)); try { super.onRestore(data, appVersionCode, newState); Loading
services/core/java/com/android/server/content/SyncStorageEngine.java +11 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.content; import android.accounts.Account; import android.accounts.AccountAndUser; import android.app.backup.BackupManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; Loading Loading @@ -670,6 +671,7 @@ public class SyncStorageEngine extends Handler { new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); queueBackup(); } public int getIsSyncable(Account account, int userId, String providerName) { Loading Loading @@ -1035,6 +1037,7 @@ public class SyncStorageEngine extends Handler { } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED); queueBackup(); } public boolean getMasterSyncAutomatically(int userId) { Loading Loading @@ -2810,4 +2813,12 @@ public class SyncStorageEngine extends Handler { .append(")\n"); } } /** * Let the BackupManager know that account sync settings have changed. This will trigger * {@link com.android.server.backup.SystemBackupAgent} to run. */ public void queueBackup() { BackupManager.dataChanged("android"); } }