Commit 074761b1 authored by aberent's avatar aberent Committed by Commit bot

[Android backup] Restore nothing if signed in user isn't valid

If the user was previously not signed in, or if the account doesn't
exist on the new device, then restore nothing.

BUG=613147

Review-Url: https://codereview.chromium.org/1990233002
Cr-Commit-Position: refs/heads/master@{#397135}
parent 928ffadc
......@@ -102,8 +102,10 @@ by a child template that "extends" this file.
android:label="@string/app_name"
android:largeHeap="false"
android:allowBackup="true"
android:backupAgent="org.chromium.chrome.browser.ChromeBackupAgent"
android:fullBackupContent="@xml/chromebackupscheme"
{% block android_backup_agent %}
android:backupAgent="org.chromium.chrome.browser.ChromeBackupAgent"
{% endblock %}
android:supportsRtl="true"
{% block extra_application_attributes %}{% endblock %}>
......
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.TargetApi;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
......@@ -14,6 +16,7 @@ import android.os.ParcelFileDescriptor;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferences;
......@@ -27,6 +30,10 @@ 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.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ChromeBackupAgent extends BackupAgent {
......@@ -37,15 +44,17 @@ public class ChromeBackupAgent extends BackupAgent {
// 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).
// 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,
};
private static boolean sAllowChromeApplication = false;
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
......@@ -60,27 +69,61 @@ public class ChromeBackupAgent extends BackupAgent {
// Android Backup
}
// May be overriden by downstream products that access account information in a different way.
protected Account[] getAccounts() {
Log.d(TAG, "Getting accounts from AccountManager");
AccountManager manager = (AccountManager) getSystemService(ACCOUNT_SERVICE);
return manager.getAccounts();
}
private boolean accountExistsOnDevice(String userName) {
// This cannot use AccountManagerHelper, since that depends on ChromeApplication.
for (Account account: getAccounts()) {
if (account.name.equals(userName)) return true;
}
return false;
}
@Override
public void onRestoreFinished() {
if (getApplicationContext() instanceof ChromeApplication && !sAllowChromeApplication) {
// This should never happen in real use, but will happen during testing if Chrome is
// already running (even in background, started to provide a service, for example).
Log.w(TAG, "Running with wrong type of Application class");
return;
}
// 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);
// 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);
return;
}
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);
}
editor.putString(FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME, userName);
boolean commitResult = editor.commit();
Log.d(TAG, "onRestoreFinished complete; commit result = " + commitResult);
Log.d(TAG, "onRestoreFinished complete; commit result = %s", commitResult);
}
@VisibleForTesting
static void allowChromeApplicationForTesting() {
sAllowChromeApplication = true;
}
}
......@@ -30,6 +30,9 @@ import org.chromium.sync.test.util.MockAccountManager;
public class ChromeBackupIntegrationTest extends ChromeTabbedActivityTestBase {
private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
private static final String TEST_ACCOUNT_1 = "user1@gmail.com";
private static final String TEST_ACCOUNT_2 = "user2@gmail.com";
private Context mTargetContext;
@Override
public void startMainActivity() throws InterruptedException {
......@@ -55,66 +58,79 @@ public class ChromeBackupIntegrationTest extends ChromeTabbedActivityTestBase {
// class.
attachBaseContext(context);
}
@Override
protected Account[] getAccounts() {
// ChromeBackupAgent can't use Chrome's account manager, so we override this to mock
// the existence of the account.
return new Account[]{new Account(TEST_ACCOUNT_1, GOOGLE_ACCOUNT_TYPE)};
}
}
@Override
public void setUp() throws Exception {
super.setUp();
clearAppData();
mTargetContext = getInstrumentation().getTargetContext();
// Create a mock account manager. Although this isn't used by ChromeBackupAgent it is used
// when we test the result by starting Chrome.
ChromeBackupAgent.allowChromeApplicationForTesting();
Account account = new Account(TEST_ACCOUNT_1, GOOGLE_ACCOUNT_TYPE);
MockAccountManager accountManager =
new MockAccountManager(mTargetContext, getInstrumentation().getContext(), account);
AccountManagerHelper.overrideAccountManagerHelperForTests(mTargetContext, accountManager);
AccountIdProvider.setInstanceForTest(new MockAccountIdProvider());
}
@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 = ContextUtils.getAppSharedPreferences();
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());
// Set up the mocked account as the signed in account.
preferenceEditor.putString(ChromeSigninController.SIGNED_IN_ACCOUNT_KEY, TEST_ACCOUNT_1);
preferenceEditor.commit();
// Run Chrome's restore code.
new ChromeTestBackupAgent(targetContext).onRestoreFinished();
new ChromeTestBackupAgent(mTargetContext).onRestoreFinished();
// Start Chrome and check that it signs in.
startMainActivityFromLauncher();
assertTrue(ChromeSigninController.get(targetContext).isSignedIn());
assertEquals("user1@gmail.com",
ChromeSigninController.get(targetContext).getSignedInAccountName());
assertTrue(ChromeSigninController.get(mTargetContext).isSignedIn());
assertEquals(TEST_ACCOUNT_1,
ChromeSigninController.get(mTargetContext).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 = ContextUtils.getAppSharedPreferences();
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());
// Use an account that hasn't been created by the mocks as the signed in account.
preferenceEditor.putString(ChromeSigninController.SIGNED_IN_ACCOUNT_KEY, TEST_ACCOUNT_2);
preferenceEditor.commit();
// Run Chrome's restore code.
new ChromeTestBackupAgent(targetContext).onRestoreFinished();
new ChromeTestBackupAgent(mTargetContext).onRestoreFinished();
// Start Chrome.
startMainActivityFromLauncher();
// Since the account didn't exist, Chrome should not be signed in.
assertFalse(ChromeSigninController.get(targetContext).isSignedIn());
assertFalse(ChromeSigninController.get(mTargetContext).isSignedIn());
}
}
......@@ -8,6 +8,9 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.SharedPreferences;
import org.chromium.base.ContextUtils;
......@@ -36,6 +39,10 @@ public class ChromeBackupAgentTest {
@Before
public void setUp() throws Exception {
ContextUtils.initApplicationContextForTests(Robolectric.application);
AccountManager manager =
(AccountManager) Robolectric.application.getSystemService(Context.ACCOUNT_SERVICE);
manager.addAccountExplicitly(new Account("user1", "dummy"), null, null);
manager.addAccountExplicitly(new Account("user2", "dummy"), null, null);
}
@Test
......@@ -58,4 +65,38 @@ public class ChromeBackupAgentTest {
assertThat(sharedPrefs.getString("first_run_signin_account_name", null), equalTo("user1"));
}
@Test
public void testOnRestoreFinishedNoUser() {
SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putBoolean("crash_dump_upload", false);
editor.putString("junk", "junk");
editor.commit();
new ChromeTestBackupAgent().onRestoreFinished();
// Check that we haven't restored any preferences
assertThat(sharedPrefs.getBoolean("crash_dump_upload", true), equalTo(true));
assertThat(sharedPrefs.getString("google.services.username", null), nullValue());
assertThat(sharedPrefs.getString("junk", null), nullValue());
assertThat(sharedPrefs.getString("first_run_signin_account_name", null), nullValue());
}
@Test
public void testOnRestoreFinishedWrongUser() {
SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putBoolean("crash_dump_upload", false);
editor.putString("google.services.username", "wrong_user");
editor.putString("junk", "junk");
editor.commit();
new ChromeTestBackupAgent().onRestoreFinished();
// Check that we haven't restored any preferences
assertThat(sharedPrefs.getBoolean("crash_dump_upload", true), equalTo(true));
assertThat(sharedPrefs.getString("google.services.username", null), nullValue());
assertThat(sharedPrefs.getString("junk", null), nullValue());
assertThat(sharedPrefs.getString("first_run_signin_account_name", null), nullValue());
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment