Skip to content

Commit

Permalink
Enable Android Backup and Restore
Browse files Browse the repository at this point in the history
This enables backup and restore of Chrome's signin state. Most other preferences are not, at present, restored.

BUG=607535

Review-Url: https://codereview.chromium.org/1954143002
Cr-Commit-Position: refs/heads/master@{#393840}
  • Loading branch information
aberent authored and Commit bot committed May 16, 2016
1 parent bac7970 commit 12cf3ae
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 8 deletions.
2 changes: 2 additions & 0 deletions chrome/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ jinja_template("chrome_sync_shell_apk_manifest") {
# GYP: //chrome/android/chrome_apk.gyp:chrome_public_apk_template_resources
jinja_template_resources("chrome_public_apk_template_resources") {
resources = [
"java/res_template/xml/chromebackupscheme.xml",
"java/res_template/xml/searchable.xml",
"java/res_template/xml/syncadapter.xml",
]
Expand All @@ -421,6 +422,7 @@ jinja_template_resources("chrome_public_apk_template_resources") {
# GYP: //chrome/android/chrome_apk.gyp:chrome_sync_shell_apk_template_resources
jinja_template_resources("chrome_sync_shell_apk_template_resources") {
resources = [
"java/res_template/xml/chromebackupscheme.xml",
"java/res_template/xml/searchable.xml",
"java/res_template/xml/syncadapter.xml",
]
Expand Down
4 changes: 3 additions & 1 deletion chrome/android/java/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ by a child template that "extends" this file.
android:icon="@mipmap/app_icon"
android:label="@string/app_name"
android:largeHeap="false"
android:allowBackup="false"
android:allowBackup="true"
android:backupAgent="org.chromium.chrome.browser.ChromeBackupAgent"
android:fullBackupContent="@xml/chromebackupscheme"
android:supportsRtl="true"
{% block extra_application_attributes %}{% endblock %}>

Expand Down
8 changes: 8 additions & 0 deletions chrome/android/java/res_template/xml/chromebackupscheme.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->

<full-backup-content>
<include domain="sharedpref" path="{{manifest_package}}_preferences.xml"/>
</full-backup-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser;

import android.annotation.TargetApi;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;

import org.chromium.base.Log;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferences;
import org.chromium.sync.signin.ChromeSigninController;

import java.io.IOException;
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.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ChromeBackupAgent extends BackupAgent {

private static final String TAG = "ChromeBackupAgent";

// 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 restore all non-device specific aspects of the user's state. This will involve both
// restoring many more Android preferences and many Chrome preferences (in Chrome's JSON
// preference file).
private static final String[] RESTORED_ANDROID_PREFS = {
PrivacyPreferences.PREF_CRASH_DUMP_UPLOAD,
FirstRunStatus.FIRST_RUN_FLOW_COMPLETE,
FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_SETUP,
};

@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
// No implementation needed for Android 6.0 Auto Backup. Used only on older versions of
// Android Backup
}

@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
// No implementation needed for Android 6.0 Auto Backup. Used only on older versions of
// Android Backup
}

@Override
public void onRestoreFinished() {
SharedPreferences sharedPrefs =
PreferenceManager.getDefaultSharedPreferences(ChromeBackupAgent.this);
Set<String> prefNames = sharedPrefs.getAll().keySet();
// Save the user name for later restoration.
String userName = sharedPrefs.getString(ChromeSigninController.SIGNED_IN_ACCOUNT_KEY, null);
SharedPreferences.Editor editor = sharedPrefs.edit();
// Throw away prefs we don't want to restore.
Set<String> restoredPrefs = new HashSet<>(Arrays.asList(RESTORED_ANDROID_PREFS));
for (String pref : prefNames) {
Log.d(TAG, "Checking pref " + pref);
if (!restoredPrefs.contains(pref)) editor.remove(pref);
}
// Because FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_COMPLETE is not restored Chrome
// will sign in the user on first run to the account in FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME
// if any. If the rest of FRE has been completed this will happen silently.
if (userName != null) {
editor.putString(FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME, userName);
}
boolean commitResult = editor.commit();

Log.d(TAG, "onRestoreFinished complete; commit result = " + commitResult);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ public final class FirstRunSignInProcessor {
* SharedPreferences preference names to keep the state of the First Run Experience.
*/
private static final String FIRST_RUN_FLOW_SIGNIN_COMPLETE = "first_run_signin_complete";
private static final String FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME =

// Needed by ChromeBackupAgent
public static final String FIRST_RUN_FLOW_SIGNIN_SETUP = "first_run_signin_setup";
public static final String FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME =
"first_run_signin_account_name";
private static final String FIRST_RUN_FLOW_SIGNIN_SETUP = "first_run_signin_setup";

/**
* Initiates the automatic sign-in process in background.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
*/
public class FirstRunStatus {

private static final String FIRST_RUN_FLOW_COMPLETE = "first_run_flow";
// Needed by ChromeBackupAgent
public static final String FIRST_RUN_FLOW_COMPLETE = "first_run_flow";

/**
* Sets the "main First Run Experience flow complete" preference.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
*/
public class ChromeBrowserInitializer {
private static final String TAG = "BrowserInitializer";
private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "chrome";
private static ChromeBrowserInitializer sChromeBrowserInitiliazer;

private final Handler mHandler;
Expand All @@ -75,6 +74,9 @@ public class ChromeBrowserInitializer {

private MinidumpDirectoryObserver mMinidumpDirectoryObserver;

// Public to allow use in ChromeBackupAgent
public static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "chrome";

/**
* A callback to be executed when there is a new version available in Play Store.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public class PrivacyPreferences extends PreferenceFragment
private static final String PREF_SAFE_BROWSING = "safe_browsing";
private static final String PREF_CONTEXTUAL_SEARCH = "contextual_search";
private static final String PREF_NETWORK_PREDICTIONS = "network_predictions";
private static final String PREF_CRASH_DUMP_UPLOAD = "crash_dump_upload";
private static final String PREF_CRASH_DUMP_UPLOAD_NO_CELLULAR =
"crash_dump_upload_no_cellular";
private static final String PREF_DO_NOT_TRACK = "do_not_track";
Expand All @@ -45,6 +44,9 @@ public class PrivacyPreferences extends PreferenceFragment

private ManagedPreferenceDelegate mManagedPreferenceDelegate;

// Needed for ChromeBackupAgent
public static final String PREF_CRASH_DUMP_UPLOAD = "crash_dump_upload";

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand Down
3 changes: 3 additions & 0 deletions chrome/android/java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/ChromeActivity.java",
"java/src/org/chromium/chrome/browser/ChromeApplication.java",
"java/src/org/chromium/chrome/browser/ChromeBackgroundService.java",
"java/src/org/chromium/chrome/browser/ChromeBackupAgent.java",
"java/src/org/chromium/chrome/browser/ChromeFeatureList.java",
"java/src/org/chromium/chrome/browser/ChromeHttpAuthHandler.java",
"java/src/org/chromium/chrome/browser/ChromeLifetimeController.java",
Expand Down Expand Up @@ -950,6 +951,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java",
"javatests/src/org/chromium/chrome/browser/ChromeActivityTest.java",
"javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java",
"javatests/src/org/chromium/chrome/browser/ChromeBackupIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/ChromeTabbedActivityLollipopAndAboveTest.java",
"javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java",
"javatests/src/org/chromium/chrome/browser/FocusedEditableTextFieldZoomTest.java",
Expand Down Expand Up @@ -1203,6 +1205,7 @@ chrome_test_java_sources = [
]

chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/ChromeBackupAgentTest.java",
"junit/src/org/chromium/chrome/browser/ShortcutHelperTest.java",
"junit/src/org/chromium/chrome/browser/SSLClientCertificateRequestTest.java",
"junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser;

import android.accounts.Account;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.test.suitebuilder.annotation.SmallTest;

import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.signin.AccountIdProvider;
import org.chromium.chrome.test.ChromeTabbedActivityTestBase;
import org.chromium.sync.signin.AccountManagerHelper;
import org.chromium.sync.signin.ChromeSigninController;
import org.chromium.sync.test.util.MockAccountManager;

/**
* Android backup tests.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@CommandLineFlags.Remove({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class ChromeBackupIntegrationTest extends ChromeTabbedActivityTestBase {

private static final String GOOGLE_ACCOUNT_TYPE = "com.google";

@Override
public void startMainActivity() throws InterruptedException {
// Do nothing here, the tests need to do some per-test setup before they start the main
// activity.
}

private static final class MockAccountIdProvider extends AccountIdProvider {
@Override
public String getAccountId(Context ctx, String accountName) {
return accountName;
}

@Override
public boolean canBeUsed(Context ctx) {
return true;
}
}

static class ChromeTestBackupAgent extends ChromeBackupAgent {
ChromeTestBackupAgent(Context context) {
// This is protected in ContextWrapper, so can only be called within a derived
// class.
attachBaseContext(context);
}
}

@SmallTest
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
public void testSimpleRestore() throws InterruptedException {
Context targetContext = getInstrumentation().getTargetContext();

// Fake having previously gone through FRE and signed in.
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(targetContext);
SharedPreferences.Editor preferenceEditor = prefs.edit();
preferenceEditor.putBoolean(FirstRunStatus.FIRST_RUN_FLOW_COMPLETE, true);
preferenceEditor.putBoolean(FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_SETUP, true);
preferenceEditor.putString(ChromeSigninController.SIGNED_IN_ACCOUNT_KEY, "user1@gmail.com");
preferenceEditor.commit();

Account account = new Account("user1@gmail.com", GOOGLE_ACCOUNT_TYPE);
MockAccountManager accountManager =
new MockAccountManager(targetContext, getInstrumentation().getContext(), account);
AccountManagerHelper.overrideAccountManagerHelperForTests(targetContext, accountManager);
AccountIdProvider.setInstanceForTest(new MockAccountIdProvider());

// Run Chrome's restore code.
new ChromeTestBackupAgent(targetContext).onRestoreFinished();

// Start Chrome and check that it signs in.
startMainActivityFromLauncher();

assertTrue(ChromeSigninController.get(targetContext).isSignedIn());
assertEquals("user1@gmail.com",
ChromeSigninController.get(targetContext).getSignedInAccountName());
}

@SmallTest
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
public void testRestoreAccountMissing() throws InterruptedException {
Context targetContext = getInstrumentation().getTargetContext();

// Fake having previously gone through FRE and signed in.
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(targetContext);
SharedPreferences.Editor preferenceEditor = prefs.edit();
preferenceEditor.putBoolean(FirstRunStatus.FIRST_RUN_FLOW_COMPLETE, true);
preferenceEditor.putBoolean(FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_SETUP, true);
preferenceEditor.putString(ChromeSigninController.SIGNED_IN_ACCOUNT_KEY, "user1@gmail.com");
preferenceEditor.commit();

// Create a mock account manager with a different account
Account account = new Account("user2@gmail.com", GOOGLE_ACCOUNT_TYPE);
MockAccountManager accountManager =
new MockAccountManager(targetContext, getInstrumentation().getContext(), account);
AccountManagerHelper.overrideAccountManagerHelperForTests(targetContext, accountManager);
AccountIdProvider.setInstanceForTest(new MockAccountIdProvider());

// Run Chrome's restore code.
new ChromeTestBackupAgent(targetContext).onRestoreFinished();

// Start Chrome.
startMainActivityFromLauncher();

// Since the account didn't exist, Chrome should not be signed in.
assertFalse(ChromeSigninController.get(targetContext).isSignedIn());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;

import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import org.chromium.testing.local.LocalRobolectricTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;

/**
* Unit tests for {@link org.chromium.chrome.browser.ChromeBackupAgent}.
*/
@RunWith(LocalRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ChromeBackupAgentTest {

static class ChromeTestBackupAgent extends ChromeBackupAgent {
ChromeTestBackupAgent() {
// This is protected in ContextWrapper, so can only be called within a derived
// class.
attachBaseContext(Robolectric.application);
}
}

@Test
public void testOnRestoreFinished() {
SharedPreferences sharedPrefs =
PreferenceManager.getDefaultSharedPreferences(Robolectric.application);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putBoolean("crash_dump_upload", false);
editor.putString("google.services.username", "user1");
editor.putString("junk", "junk");
editor.commit();

new ChromeTestBackupAgent().onRestoreFinished();

// Check that we have only restored the correct preferences
assertThat(sharedPrefs.getBoolean("crash_dump_upload", true), equalTo(false));
assertThat(sharedPrefs.getString("google.services.username", null), nullValue());
assertThat(sharedPrefs.getString("junk", null), nullValue());

// Check that the preferences for which there is special code are correct
assertThat(sharedPrefs.getString("first_run_signin_account_name", null), equalTo("user1"));
}

}
Loading

0 comments on commit 12cf3ae

Please sign in to comment.