Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgent.java

Issue 2047473003: [Android backup] Sync settings, and new to old version restore (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix findbugs issues Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgent.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgent.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgent.java
index 360b0dbcbe99e70439b78089a227c5d89672ba31..865f12d4ca2f0301427938cc14072f52e209c5e0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgent.java
@@ -10,30 +10,40 @@ import android.annotation.TargetApi;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
+import org.chromium.base.StreamUtil;
import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.SuppressFBWarnings;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
+import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferences;
import org.chromium.sync.signin.ChromeSigninController;
+import org.json.JSONException;
+import org.json.JSONObject;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Backup agent for Chrome, filters the restored backup to remove preferences that should not have
- * been restored.
- *
- * Note: Nothing in this class can depend on the ChromeApplication instance having been created.
- * During restore Android creates a special instance of the Chrome application with its own
- * Android defined application class, which is not derived from ChromeApplication.
+ * been restored. Note: Nothing in this class can depend on the ChromeApplication instance having
+ * been created. During restore Android creates a special instance of the Chrome application with
+ * its own Android defined application class, which is not derived from ChromeApplication.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ChromeBackupAgent extends BackupAgent {
@@ -42,17 +52,69 @@ public class ChromeBackupAgent extends BackupAgent {
// Lists of preferences that should be restored unchanged.
- // TODO(aberent): At present this only restores the signed in user, and the FRE settings
- // (whether is has been completed, and whether the user disabled crash dump reporting). It
- // should also restore all the user's sync choices. This will involve restoring a number of
- // Chrome preferences (in Chrome's JSON preference file) in addition to the Android preferences
- // currently restored.
private static final String[] RESTORED_ANDROID_PREFS = {
PrivacyPreferences.PREF_CRASH_DUMP_UPLOAD,
FirstRunStatus.FIRST_RUN_FLOW_COMPLETE,
FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_SETUP,
};
+ // Sync preferences, all in C++ sync_driver::prefs namespace.
+ //
+ // TODO(aberent): These should ideally use the constants that are used to access the preferences
+ // elsewhere, but those are currently only exist in C++, so doing so would require some
+ // reorganization.
+ private static final String[][] RESTORED_CHROME_PREFS = {
+ // kSyncFirstSetupComplete
+ {"sync", "has_setup_completed"},
+ // kSyncKeepEverythingSynced
+ {"sync", "keep_everything_synced"},
+ // kSyncAutofillProfile
+ {"sync", "autofill_profile"},
+ // kSyncAutofillWallet
+ {"sync", "autofill_wallet"},
+ // kSyncAutofillWalletMetadata
+ {"sync", "autofill_wallet_metadata"},
+ // kSyncAutofill
+ {"sync", "autofill"},
+ // kSyncBookmarks
+ {"sync", "bookmarks"},
+ // kSyncDeviceInfo
+ {"sync", "device_info"},
+ // kSyncFaviconImages
+ {"sync", "favicon_images"},
+ // kSyncFaviconTracking
+ {"sync", "favicon_tracking"},
+ // kSyncHistoryDeleteDirectives
+ {"sync", "history_delete_directives"},
+ // kSyncPasswords
+ {"sync", "passwords"},
+ // kSyncPreferences
+ {"sync", "preferences"},
+ // kSyncPriorityPreferences
+ {"sync", "priority_preferences"},
+ // kSyncSessions
+ {"sync", "sessions"},
+ // kSyncSupervisedUserSettings
+ {"sync", "managed_user_settings"},
+ // kSyncSupervisedUserSharedSettings
+ {"sync", "managed_user_shared_settings"},
+ // kSyncSupervisedUserWhitelists
+ {"sync", "managed_user_whitelists"},
+ // kSyncTabs
+ {"sync", "tabs"},
+ // kSyncTypedUrls
+ {"sync", "typed_urls"},
+ // kSyncSuppressStart
+ {"sync", "suppress_start"},
+ };
+
+ private static final String[] DEFAULT_JSON_PREFS_FILE = {
+ // chrome::kInitialProfile
+ "Default",
+ // chrome::kPreferencesFilename
+ "Preferences",
+ };
+
private static boolean sAllowChromeApplication = false;
@Override
@@ -78,7 +140,7 @@ public class ChromeBackupAgent extends BackupAgent {
private boolean accountExistsOnDevice(String userName) {
// This cannot use AccountManagerHelper, since that depends on ChromeApplication.
- for (Account account: getAccounts()) {
+ for (Account account : getAccounts()) {
if (account.name.equals(userName)) return true;
}
return false;
@@ -95,18 +157,42 @@ public class ChromeBackupAgent extends BackupAgent {
// This is running without a ChromeApplication instance, so this has to be done here.
ContextUtils.initApplicationContext(getApplicationContext());
SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
- Set<String> prefNames = sharedPrefs.getAll().keySet();
// Save the user name for later restoration.
String userName = sharedPrefs.getString(ChromeSigninController.SIGNED_IN_ACCOUNT_KEY, null);
Log.d(TAG, "Previous signed in user name = " + userName);
+
+ File prefsFile = this.getDir(ChromeBrowserInitializer.PRIVATE_DATA_DIRECTORY_SUFFIX,
+ Context.MODE_PRIVATE);
+ for (String name : DEFAULT_JSON_PREFS_FILE) {
+ prefsFile = new File(prefsFile, name);
+ }
+
// If the user hasn't signed in, or can't sign in, then don't restore anything.
if (userName == null || !accountExistsOnDevice(userName)) {
- boolean commitResult = sharedPrefs.edit().clear().commit();
- Log.d(TAG, "onRestoreFinished complete, nothing restored; commit result = %s",
- commitResult);
+ clearAllPrefs(sharedPrefs, prefsFile);
+ Log.d(TAG, "onRestoreFinished complete, nothing restored");
+ return;
+ }
+
+ // Check that the file has been restored.
+ if (!filterChromePrefs(prefsFile)) {
+ // The preferences are corrupt, for safety delete all of them
+ clearAllPrefs(sharedPrefs, prefsFile);
+ Log.d(TAG, "onRestoreFinished failed");
return;
}
+ restoreAndroidPrefs(sharedPrefs, userName);
+ Log.d(TAG, "onRestoreFinished complete");
+ }
+
+ private void clearAllPrefs(SharedPreferences sharedPrefs, File prefsFile) {
+ deleteFileIfPossible(prefsFile);
+ sharedPrefs.edit().clear().commit();
+ }
+
+ private boolean restoreAndroidPrefs(SharedPreferences sharedPrefs, String userName) {
+ Set<String> prefNames = sharedPrefs.getAll().keySet();
SharedPreferences.Editor editor = sharedPrefs.edit();
// Throw away prefs we don't want to restore.
Set<String> restoredPrefs = new HashSet<>(Arrays.asList(RESTORED_ANDROID_PREFS));
@@ -118,12 +204,82 @@ public class ChromeBackupAgent extends BackupAgent {
// if any. If the rest of FRE has been completed this will happen silently.
editor.putString(FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME, userName);
boolean commitResult = editor.commit();
+ return commitResult;
+ }
+
+ private boolean filterChromePrefs(File prefsFile) {
+ InputStream inputStream = null;
+ OutputStream outputStream = null;
+ try {
+ inputStream = openInputStream(prefsFile);
+ int fileLength = (int) getFileLength(prefsFile);
+ byte[] buffer = new byte[fileLength];
+ if (inputStream.read(buffer) != fileLength) return false;
+ JSONObject jsonInput = new JSONObject(new String(buffer, "UTF-8"));
+ JSONObject jsonOutput = new JSONObject();
+ for (String[] pref : RESTORED_CHROME_PREFS) {
+ Object prefValue = readChromePref(jsonInput, pref);
+ if (prefValue != null) writeChromePref(jsonOutput, pref, prefValue);
+ }
+ byte[] outputBytes = jsonOutput.toString().getBytes("UTF-8");
+ outputStream = openOutputStream(prefsFile);
+ outputStream.write(outputBytes);
+ return true;
+ } catch (IOException | JSONException e) {
+ Log.d(TAG, "Filtering preferences failed with %s", e.getMessage());
+ return false;
+ } finally {
+ StreamUtil.closeQuietly(inputStream);
+ StreamUtil.closeQuietly(outputStream);
+ }
+ }
+
+ @VisibleForTesting
+ protected long getFileLength(File prefsFile) {
+ return prefsFile.length();
+ }
+
+ @VisibleForTesting
+ protected InputStream openInputStream(File prefsFile) throws FileNotFoundException {
+ return new FileInputStream(prefsFile);
+ }
+
+ @VisibleForTesting
+ protected OutputStream openOutputStream(File prefsFile) throws FileNotFoundException {
+ return new FileOutputStream(prefsFile);
+ }
+
+ private Object readChromePref(JSONObject json, String pref[]) {
+ JSONObject finalParent = json;
+ for (int i = 0; i < pref.length - 1; i++) {
+ finalParent = finalParent.optJSONObject(pref[i]);
+ if (finalParent == null) return null;
+ }
+ return finalParent.opt(pref[pref.length - 1]);
+ }
+
+ private void writeChromePref(JSONObject json, String[] prefPath, Object value)
+ throws JSONException {
+ JSONObject finalParent = json;
+ for (int i = 0; i < prefPath.length - 1; i++) {
+ JSONObject prevParent = finalParent;
+ finalParent = prevParent.optJSONObject(prefPath[i]);
+ if (finalParent == null) {
+ finalParent = new JSONObject();
+ prevParent.put(prefPath[i], finalParent);
+ }
+ }
+ finalParent.put(prefPath[prefPath.length - 1], value);
+ }
- Log.d(TAG, "onRestoreFinished complete; commit result = %s", commitResult);
+ @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
+ private void deleteFileIfPossible(File file) {
+ // Ignore result. There is nothing else we can do if the delete fails.
+ file.delete();
}
@VisibleForTesting
static void allowChromeApplicationForTesting() {
- sAllowChromeApplication = true;
+ sAllowChromeApplication = true;
}
}

Powered by Google App Engine
This is Rietveld 408576698