Commit 97a0fbce authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

Convert ChromeBackupAgent to split compat

Review from Patchset 1 to get the right diffs on the changes in
ChromeBackupAgentImpl.

Bug: 1126301
Change-Id: I441e3dc4cf098212d6170db7f3913ef2edbc2066
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2461221Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815445}
parent 0fb73c35
...@@ -2069,8 +2069,10 @@ android_library("base_module_java") { ...@@ -2069,8 +2069,10 @@ android_library("base_module_java") {
sources = [ sources = [
"java/src/com/google/ipc/invalidation/ticl/android2/channel/GcmRegistrationTaskService.java", "java/src/com/google/ipc/invalidation/ticl/android2/channel/GcmRegistrationTaskService.java",
"java/src/org/chromium/chrome/browser/ChromeBackgroundService.java", "java/src/org/chromium/chrome/browser/ChromeBackgroundService.java",
"java/src/org/chromium/chrome/browser/ChromeBackupAgent.java",
"java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java", "java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java", "java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatBackupAgent.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatCustomTabsService.java", "java/src/org/chromium/chrome/browser/base/SplitCompatCustomTabsService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatGcmListenerService.java", "java/src/org/chromium/chrome/browser/base/SplitCompatGcmListenerService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatGcmTaskService.java", "java/src/org/chromium/chrome/browser/base/SplitCompatGcmTaskService.java",
...@@ -3066,7 +3068,7 @@ generate_jni("chrome_jni_headers") { ...@@ -3066,7 +3068,7 @@ generate_jni("chrome_jni_headers") {
"java/src/org/chromium/chrome/browser/AfterStartupTaskUtils.java", "java/src/org/chromium/chrome/browser/AfterStartupTaskUtils.java",
"java/src/org/chromium/chrome/browser/AppHooks.java", "java/src/org/chromium/chrome/browser/AppHooks.java",
"java/src/org/chromium/chrome/browser/ApplicationLifetime.java", "java/src/org/chromium/chrome/browser/ApplicationLifetime.java",
"java/src/org/chromium/chrome/browser/ChromeBackupAgent.java", "java/src/org/chromium/chrome/browser/ChromeBackupAgentImpl.java",
"java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java", "java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java",
"java/src/org/chromium/chrome/browser/DevToolsServer.java", "java/src/org/chromium/chrome/browser/DevToolsServer.java",
"java/src/org/chromium/chrome/browser/IntentHandler.java", "java/src/org/chromium/chrome/browser/IntentHandler.java",
......
...@@ -16,7 +16,7 @@ chrome_java_sources = [ ...@@ -16,7 +16,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java", "java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java",
"java/src/org/chromium/chrome/browser/ChromeApplication.java", "java/src/org/chromium/chrome/browser/ChromeApplication.java",
"java/src/org/chromium/chrome/browser/ChromeBackgroundServiceImpl.java", "java/src/org/chromium/chrome/browser/ChromeBackgroundServiceImpl.java",
"java/src/org/chromium/chrome/browser/ChromeBackupAgent.java", "java/src/org/chromium/chrome/browser/ChromeBackupAgentImpl.java",
"java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java", "java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java",
"java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java", "java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java",
"java/src/org/chromium/chrome/browser/ChromeInactivityTracker.java", "java/src/org/chromium/chrome/browser/ChromeInactivityTracker.java",
......
// Copyright 2016 The Chromium Authors. All rights reserved. // Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser; package org.chromium.chrome.browser;
import android.app.backup.BackupAgent; import org.chromium.chrome.browser.base.SplitCompatBackupAgent;
import android.app.backup.BackupDataInput; import org.chromium.chrome.browser.base.SplitCompatUtils;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupManager;
import android.content.SharedPreferences;
import android.os.ParcelFileDescriptor;
import androidx.annotation.IntDef; /** See {@link ChromeBackupAgentImpl}. */
import androidx.annotation.VisibleForTesting; public class ChromeBackupAgent extends SplitCompatBackupAgent {
public ChromeBackupAgent() {
import org.chromium.base.ApiCompatibilityUtils; super(SplitCompatUtils.getIdentifierName(
import org.chromium.base.ContextUtils; "org.chromium.chrome.browser.ChromeBackupAgentImpl"));
import org.chromium.base.Log;
import org.chromium.base.PathUtils;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.init.AsyncInitTaskRunner;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.IdentityServicesProvider;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.AccountUtils;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.content_public.common.ContentProcessInfo;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Backup agent for Chrome, using Android key/value backup.
*/
@SuppressWarnings("UseSharedPreferencesManagerFromChromeCheck")
public class ChromeBackupAgent extends BackupAgent {
private static final String ANDROID_DEFAULT_PREFIX = "AndroidDefault.";
private static final String NATIVE_PREF_PREFIX = "native.";
private static final String TAG = "ChromeBackupAgent";
@VisibleForTesting
static final String HISTOGRAM_ANDROID_RESTORE_RESULT = "Android.RestoreResult";
// Restore status is used to pass the result of any restore to Chrome's first run, so that
// it can be recorded as a histogram.
@IntDef({RestoreStatus.NO_RESTORE, RestoreStatus.RESTORE_COMPLETED,
RestoreStatus.RESTORE_AFTER_FIRST_RUN, RestoreStatus.BROWSER_STARTUP_FAILED,
RestoreStatus.NOT_SIGNED_IN, RestoreStatus.RESTORE_STATUS_RECORDED})
@Retention(RetentionPolicy.SOURCE)
public @interface RestoreStatus {
// Values must match those in histogram.xml AndroidRestoreResult.
int NO_RESTORE = 0;
int RESTORE_COMPLETED = 1;
int RESTORE_AFTER_FIRST_RUN = 2;
int BROWSER_STARTUP_FAILED = 3;
int NOT_SIGNED_IN = 4;
int NUM_ENTRIES = 5;
// Set RESTORE_STATUS_RECORDED when the histogram has been recorded; so that it is only
// recorded once.
int RESTORE_STATUS_RECORDED = 5;
}
private static final String RESTORE_STATUS = "android_restore_status";
// Keep track of backup failures, so that we give up in the end on persistent problems.
@VisibleForTesting
static final String BACKUP_FAILURE_COUNT = "android_backup_failure_count";
@VisibleForTesting
static final int MAX_BACKUP_FAILURES = 5;
// List of preferences that should be restored unchanged.
static final String[] BACKUP_ANDROID_BOOL_PREFS = {
ChromePreferenceKeys.DATA_REDUCTION_ENABLED,
ChromePreferenceKeys.FIRST_RUN_CACHED_TOS_ACCEPTED,
ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE,
ChromePreferenceKeys.FIRST_RUN_LIGHTWEIGHT_FLOW_COMPLETE,
ChromePreferenceKeys.FIRST_RUN_FLOW_SIGNIN_SETUP,
ChromePreferenceKeys.PRIVACY_METRICS_REPORTING,
};
// Key used to store the email of the signed in account. This email is obtained from
// IdentityManager during the backup.
static final String SIGNED_IN_ACCOUNT_KEY = "google.services.username";
// Timeout for running the background tasks, needs to be quite long since they may be doing
// network access, but must be less than the 1 minute restore timeout to be useful.
private static final long BACKGROUND_TASK_TIMEOUT_SECS = 20;
/**
* Class to save and restore the backup state, used to decide if backups are needed. Since the
* backup data is small, and stored as private data by the backup service, this can simply store
* and compare a copy of the data.
*/
private static final class BackupState {
private ArrayList<String> mNames;
private ArrayList<byte[]> mValues;
@SuppressWarnings("unchecked")
public BackupState(ParcelFileDescriptor parceledState) throws IOException {
if (parceledState == null) return;
try {
FileInputStream instream = new FileInputStream(parceledState.getFileDescriptor());
ObjectInputStream in = new ObjectInputStream(instream);
mNames = (ArrayList<String>) in.readObject();
mValues = (ArrayList<byte[]>) in.readObject();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public BackupState(ArrayList<String> names, ArrayList<byte[]> values) {
mNames = names;
mValues = values;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof BackupState)) return false;
BackupState otherBackupState = (BackupState) other;
return mNames.equals(otherBackupState.mNames)
&& Arrays.deepEquals(mValues.toArray(), otherBackupState.mValues.toArray());
}
public void save(ParcelFileDescriptor parceledState) throws IOException {
FileOutputStream outstream = new FileOutputStream(parceledState.getFileDescriptor());
ObjectOutputStream out = new ObjectOutputStream(outstream);
out.writeObject(mNames);
out.writeObject(mValues);
}
}
@VisibleForTesting
protected boolean accountExistsOnDevice(String userName) {
return AccountUtils.findAccountByName(
AccountManagerFacadeProvider.getInstance().tryGetGoogleAccounts(), userName)
!= null;
}
// TODO (aberent) Refactor the tests to use a mocked ChromeBrowserInitializer, and make this
// private again.
@VisibleForTesting
boolean initializeBrowser() {
// Workaround for https://crbug.com/718166. The backup agent is sometimes being started in a
// child process, before the child process loads its native library. If backup then loads
// the native library the child process is left in a very confused state and crashes.
if (ContentProcessInfo.inChildProcess()) {
Log.e(TAG, "Backup agent started from child process");
return false;
}
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
return true;
}
private static byte[] booleanToBytes(boolean value) {
return value ? new byte[] {1} : new byte[] {0};
}
private static boolean bytesToBoolean(byte[] bytes) {
return bytes[0] != 0;
}
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
final ArrayList<String> backupNames = new ArrayList<>();
final ArrayList<byte[]> backupValues = new ArrayList<>();
final AtomicReference<CoreAccountInfo> syncAccount = new AtomicReference<>();
// The native preferences can only be read on the UI thread.
Boolean nativePrefsRead = PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
// Start the browser if necessary, so that Chrome can access the native
// preferences. Although Chrome requests the backup, it doesn't happen
// immediately, so by the time it does Chrome may not be running.
if (!initializeBrowser()) return false;
syncAccount.set(IdentityServicesProvider.get()
.getIdentityManager(Profile.getLastUsedRegularProfile())
.getPrimaryAccountInfo(ConsentLevel.SYNC));
String[] nativeBackupNames = ChromeBackupAgentJni.get().getBoolBackupNames(this);
boolean[] nativeBackupValues = ChromeBackupAgentJni.get().getBoolBackupValues(this);
assert nativeBackupNames.length == nativeBackupValues.length;
for (String name : nativeBackupNames) {
backupNames.add(NATIVE_PREF_PREFIX + name);
}
for (boolean val : nativeBackupValues) {
backupValues.add(booleanToBytes(val));
}
return true;
});
SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
if (!nativePrefsRead) {
// Something went wrong reading the native preferences, skip the backup, but try again
// later.
int backupFailureCount = sharedPrefs.getInt(BACKUP_FAILURE_COUNT, 0) + 1;
if (backupFailureCount >= MAX_BACKUP_FAILURES) {
// Too many re-tries, give up and force an unconditional backup next time one is
// requested.
return;
}
sharedPrefs.edit().putInt(BACKUP_FAILURE_COUNT, backupFailureCount).apply();
if (oldState != null) {
try {
// Copy the old state to the new state, so that next time Chrome only does a
// backup if necessary.
BackupState state = new BackupState(oldState);
state.save(newState);
} catch (Exception e) {
// There was no old state, or it was corrupt; leave the newState unwritten,
// hence forcing an unconditional backup on the next attempt.
}
}
// Ask Android to schedule a retry.
new BackupManager(this).dataChanged();
return;
}
// The backup is going to work, clear the failure count.
sharedPrefs.edit().remove(BACKUP_FAILURE_COUNT).apply();
// Add the Android boolean prefs.
for (String prefName : BACKUP_ANDROID_BOOL_PREFS) {
if (sharedPrefs.contains(prefName)) {
backupNames.add(ANDROID_DEFAULT_PREFIX + prefName);
backupValues.add(booleanToBytes(sharedPrefs.getBoolean(prefName, false)));
}
}
// Finally add the user id.
backupNames.add(ANDROID_DEFAULT_PREFIX + SIGNED_IN_ACCOUNT_KEY);
backupValues.add(ApiCompatibilityUtils.getBytesUtf8(
syncAccount.get() == null ? "" : syncAccount.get().getEmail()));
BackupState newBackupState = new BackupState(backupNames, backupValues);
// Check if a backup is actually needed.
try {
BackupState oldBackupState = new BackupState(oldState);
if (newBackupState.equals(oldBackupState)) {
Log.i(TAG, "Nothing has changed since the last backup. Backup skipped.");
newBackupState.save(newState);
return;
}
} catch (IOException e) {
// This will happen if Chrome has never written backup data, or if the backup status is
// corrupt. Create a new backup in either case.
Log.i(TAG, "Can't read backup status file");
}
// Write the backup data
for (int i = 0; i < backupNames.size(); i++) {
data.writeEntityHeader(backupNames.get(i), backupValues.get(i).length);
data.writeEntityData(backupValues.get(i), backupValues.get(i).length);
}
// Remember the backup state.
newBackupState.save(newState);
Log.i(TAG, "Backup complete");
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
// TODO(aberent) Check that this is not running on the UI thread. Doing so, however, makes
// testing difficult since the test code runs on the UI thread.
// Check that the user hasn't already seen FRE (not sure if this can ever happen, but if it
// does then restoring the backup will overwrite the user's choices).
SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
if (FirstRunStatus.getFirstRunFlowComplete()
|| FirstRunStatus.getLightweightFirstRunFlowComplete()) {
setRestoreStatus(RestoreStatus.RESTORE_AFTER_FIRST_RUN);
Log.w(TAG, "Restore attempted after first run");
return;
}
final ArrayList<String> backupNames = new ArrayList<>();
final ArrayList<byte[]> backupValues = new ArrayList<>();
String restoredUserName = null;
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
byte[] buffer = new byte[dataSize];
data.readEntityData(buffer, 0, dataSize);
if (key.equals(ANDROID_DEFAULT_PREFIX + SIGNED_IN_ACCOUNT_KEY)) {
restoredUserName = new String(buffer);
} else {
backupNames.add(key);
backupValues.add(buffer);
}
}
// Start and wait for the Async init tasks. This loads the library, and attempts to load the
// first run variations seed. Since these are both slow it makes sense to run them in
// parallel as Android AsyncTasks, reusing some of Chrome's async startup logic.
//
// Note that this depends on onRestore being run from a background thread, since
// if it were called from the UI thread the broadcast would not be received until after it
// exited.
final CountDownLatch latch = new CountDownLatch(1);
PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
// Chrome library loading depends on PathUtils.
PathUtils.setPrivateDataDirectorySuffix(
ChromeApplication.PRIVATE_DATA_DIRECTORY_SUFFIX);
createAsyncInitTaskRunner(latch).startBackgroundTasks(
false /* allocateChildConnection */, true /* initVariationSeed */);
});
try {
// Ignore result. It will only be false if it times out. Problems with fetching the
// variation seed can be ignored, and other problems will either recover or be repeated
// when Chrome is started synchronously.
latch.await(BACKGROUND_TASK_TIMEOUT_SECS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// Should never happen, but can be ignored (as explained above) anyway.
}
// Chrome has to be running before it can check if the account exists. Because the native
// library is already loaded Chrome startup should be fast.
boolean browserStarted = PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
// Start the browser if necessary.
return initializeBrowser();
});
if (!browserStarted) {
// Something went wrong starting Chrome, skip the restore.
setRestoreStatus(RestoreStatus.BROWSER_STARTUP_FAILED);
return;
}
// If the user hasn't signed in, or can't sign in, then don't restore anything.
if (restoredUserName == null || !accountExistsOnDevice(restoredUserName)) {
setRestoreStatus(RestoreStatus.NOT_SIGNED_IN);
Log.i(TAG, "Chrome was not signed in with a known account name, not restoring");
return;
}
// Restore the native preferences on the UI thread
PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
ArrayList<String> nativeBackupNames = new ArrayList<>();
boolean[] nativeBackupValues = new boolean[backupNames.size()];
int count = 0;
int prefixLength = NATIVE_PREF_PREFIX.length();
for (int i = 0; i < backupNames.size(); i++) {
String name = backupNames.get(i);
if (name.startsWith(NATIVE_PREF_PREFIX)) {
nativeBackupNames.add(name.substring(prefixLength));
nativeBackupValues[count] = bytesToBoolean(backupValues.get(i));
count++;
}
}
ChromeBackupAgentJni.get().setBoolBackupPrefs(this,
nativeBackupNames.toArray(new String[count]),
Arrays.copyOf(nativeBackupValues, count));
});
// Now that everything looks good so restore the Android preferences.
SharedPreferences.Editor editor = sharedPrefs.edit();
// Only restore preferences that we know about.
int prefixLength = ANDROID_DEFAULT_PREFIX.length();
for (int i = 0; i < backupNames.size(); i++) {
String name = backupNames.get(i);
if (name.startsWith(ANDROID_DEFAULT_PREFIX)
&& Arrays.asList(BACKUP_ANDROID_BOOL_PREFS)
.contains(name.substring(prefixLength))) {
editor.putBoolean(
name.substring(prefixLength), bytesToBoolean(backupValues.get(i)));
}
}
// 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.
editor.putString(ChromePreferenceKeys.FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME, restoredUserName);
editor.apply();
// The silent first run will change things, so there is no point in trying to prevent
// additional backups at this stage. Don't write anything to |newState|.
setRestoreStatus(RestoreStatus.RESTORE_COMPLETED);
Log.i(TAG, "Restore complete");
}
@VisibleForTesting
AsyncInitTaskRunner createAsyncInitTaskRunner(final CountDownLatch latch) {
return new AsyncInitTaskRunner() {
@Override
protected void onSuccess() {
latch.countDown();
}
@Override
protected void onFailure(Exception failureCause) {
// Ignore failure. Problems with the variation seed can be ignored, and other
// problems will either recover or be repeated when Chrome is started synchronously.
latch.countDown();
}
};
}
/**
* Get the saved result of any restore that may have happened.
*
* @return the restore status, a RestoreStatus value.
*/
@VisibleForTesting
@RestoreStatus
static int getRestoreStatus() {
return ContextUtils.getAppSharedPreferences().getInt(
RESTORE_STATUS, RestoreStatus.NO_RESTORE);
}
/**
* Save the restore status for later transfer to a histogram.
*
* @param status the status.
*/
@VisibleForTesting
static void setRestoreStatus(@RestoreStatus int status) {
ContextUtils.getAppSharedPreferences().edit().putInt(RESTORE_STATUS, status).apply();
}
/**
* Record the restore histogram. To be called from Chrome itself once it is running.
*/
public static void recordRestoreHistogram() {
@RestoreStatus
int restoreStatus = getRestoreStatus();
// Ensure restore status is only recorded once
if (restoreStatus != RestoreStatus.RESTORE_STATUS_RECORDED) {
RecordHistogram.recordEnumeratedHistogram(
HISTOGRAM_ANDROID_RESTORE_RESULT, restoreStatus, RestoreStatus.NUM_ENTRIES);
setRestoreStatus(RestoreStatus.RESTORE_STATUS_RECORDED);
}
}
@NativeMethods
interface Natives {
String[] getBoolBackupNames(ChromeBackupAgent caller);
boolean[] getBoolBackupValues(ChromeBackupAgent caller);
void setBoolBackupPrefs(ChromeBackupAgent caller, String[] name, boolean[] value);
} }
} }
// 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.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupManager;
import android.content.SharedPreferences;
import android.os.ParcelFileDescriptor;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.PathUtils;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.init.AsyncInitTaskRunner;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.IdentityServicesProvider;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.AccountUtils;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.content_public.common.ContentProcessInfo;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Backup agent for Chrome, using Android key/value backup.
*/
@SuppressWarnings("UseSharedPreferencesManagerFromChromeCheck")
public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl {
private static final String ANDROID_DEFAULT_PREFIX = "AndroidDefault.";
private static final String NATIVE_PREF_PREFIX = "native.";
private static final String TAG = "ChromeBackupAgent";
@VisibleForTesting
static final String HISTOGRAM_ANDROID_RESTORE_RESULT = "Android.RestoreResult";
// Restore status is used to pass the result of any restore to Chrome's first run, so that
// it can be recorded as a histogram.
@IntDef({RestoreStatus.NO_RESTORE, RestoreStatus.RESTORE_COMPLETED,
RestoreStatus.RESTORE_AFTER_FIRST_RUN, RestoreStatus.BROWSER_STARTUP_FAILED,
RestoreStatus.NOT_SIGNED_IN, RestoreStatus.RESTORE_STATUS_RECORDED})
@Retention(RetentionPolicy.SOURCE)
public @interface RestoreStatus {
// Values must match those in histogram.xml AndroidRestoreResult.
int NO_RESTORE = 0;
int RESTORE_COMPLETED = 1;
int RESTORE_AFTER_FIRST_RUN = 2;
int BROWSER_STARTUP_FAILED = 3;
int NOT_SIGNED_IN = 4;
int NUM_ENTRIES = 5;
// Set RESTORE_STATUS_RECORDED when the histogram has been recorded; so that it is only
// recorded once.
int RESTORE_STATUS_RECORDED = 5;
}
private static final String RESTORE_STATUS = "android_restore_status";
// Keep track of backup failures, so that we give up in the end on persistent problems.
@VisibleForTesting
static final String BACKUP_FAILURE_COUNT = "android_backup_failure_count";
@VisibleForTesting
static final int MAX_BACKUP_FAILURES = 5;
// List of preferences that should be restored unchanged.
static final String[] BACKUP_ANDROID_BOOL_PREFS = {
ChromePreferenceKeys.DATA_REDUCTION_ENABLED,
ChromePreferenceKeys.FIRST_RUN_CACHED_TOS_ACCEPTED,
ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE,
ChromePreferenceKeys.FIRST_RUN_LIGHTWEIGHT_FLOW_COMPLETE,
ChromePreferenceKeys.FIRST_RUN_FLOW_SIGNIN_SETUP,
ChromePreferenceKeys.PRIVACY_METRICS_REPORTING,
};
// Key used to store the email of the signed in account. This email is obtained from
// IdentityManager during the backup.
static final String SIGNED_IN_ACCOUNT_KEY = "google.services.username";
// Timeout for running the background tasks, needs to be quite long since they may be doing
// network access, but must be less than the 1 minute restore timeout to be useful.
private static final long BACKGROUND_TASK_TIMEOUT_SECS = 20;
/**
* Class to save and restore the backup state, used to decide if backups are needed. Since the
* backup data is small, and stored as private data by the backup service, this can simply store
* and compare a copy of the data.
*/
private static final class BackupState {
private ArrayList<String> mNames;
private ArrayList<byte[]> mValues;
@SuppressWarnings("unchecked")
public BackupState(ParcelFileDescriptor parceledState) throws IOException {
if (parceledState == null) return;
try {
FileInputStream instream = new FileInputStream(parceledState.getFileDescriptor());
ObjectInputStream in = new ObjectInputStream(instream);
mNames = (ArrayList<String>) in.readObject();
mValues = (ArrayList<byte[]>) in.readObject();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public BackupState(ArrayList<String> names, ArrayList<byte[]> values) {
mNames = names;
mValues = values;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof BackupState)) return false;
BackupState otherBackupState = (BackupState) other;
return mNames.equals(otherBackupState.mNames)
&& Arrays.deepEquals(mValues.toArray(), otherBackupState.mValues.toArray());
}
public void save(ParcelFileDescriptor parceledState) throws IOException {
FileOutputStream outstream = new FileOutputStream(parceledState.getFileDescriptor());
ObjectOutputStream out = new ObjectOutputStream(outstream);
out.writeObject(mNames);
out.writeObject(mValues);
}
}
@VisibleForTesting
protected boolean accountExistsOnDevice(String userName) {
return AccountUtils.findAccountByName(
AccountManagerFacadeProvider.getInstance().tryGetGoogleAccounts(), userName)
!= null;
}
// TODO (aberent) Refactor the tests to use a mocked ChromeBrowserInitializer, and make this
// private again.
@VisibleForTesting
boolean initializeBrowser() {
// Workaround for https://crbug.com/718166. The backup agent is sometimes being started in a
// child process, before the child process loads its native library. If backup then loads
// the native library the child process is left in a very confused state and crashes.
if (ContentProcessInfo.inChildProcess()) {
Log.e(TAG, "Backup agent started from child process");
return false;
}
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
return true;
}
private static byte[] booleanToBytes(boolean value) {
return value ? new byte[] {1} : new byte[] {0};
}
private static boolean bytesToBoolean(byte[] bytes) {
return bytes[0] != 0;
}
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
final ArrayList<String> backupNames = new ArrayList<>();
final ArrayList<byte[]> backupValues = new ArrayList<>();
final AtomicReference<CoreAccountInfo> syncAccount = new AtomicReference<>();
// The native preferences can only be read on the UI thread.
Boolean nativePrefsRead = PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
// Start the browser if necessary, so that Chrome can access the native
// preferences. Although Chrome requests the backup, it doesn't happen
// immediately, so by the time it does Chrome may not be running.
if (!initializeBrowser()) return false;
syncAccount.set(IdentityServicesProvider.get()
.getIdentityManager(Profile.getLastUsedRegularProfile())
.getPrimaryAccountInfo(ConsentLevel.SYNC));
String[] nativeBackupNames = ChromeBackupAgentImplJni.get().getBoolBackupNames(this);
boolean[] nativeBackupValues = ChromeBackupAgentImplJni.get().getBoolBackupValues(this);
assert nativeBackupNames.length == nativeBackupValues.length;
for (String name : nativeBackupNames) {
backupNames.add(NATIVE_PREF_PREFIX + name);
}
for (boolean val : nativeBackupValues) {
backupValues.add(booleanToBytes(val));
}
return true;
});
SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
if (!nativePrefsRead) {
// Something went wrong reading the native preferences, skip the backup, but try again
// later.
int backupFailureCount = sharedPrefs.getInt(BACKUP_FAILURE_COUNT, 0) + 1;
if (backupFailureCount >= MAX_BACKUP_FAILURES) {
// Too many re-tries, give up and force an unconditional backup next time one is
// requested.
return;
}
sharedPrefs.edit().putInt(BACKUP_FAILURE_COUNT, backupFailureCount).apply();
if (oldState != null) {
try {
// Copy the old state to the new state, so that next time Chrome only does a
// backup if necessary.
BackupState state = new BackupState(oldState);
state.save(newState);
} catch (Exception e) {
// There was no old state, or it was corrupt; leave the newState unwritten,
// hence forcing an unconditional backup on the next attempt.
}
}
// Ask Android to schedule a retry.
new BackupManager(getBackupAgent()).dataChanged();
return;
}
// The backup is going to work, clear the failure count.
sharedPrefs.edit().remove(BACKUP_FAILURE_COUNT).apply();
// Add the Android boolean prefs.
for (String prefName : BACKUP_ANDROID_BOOL_PREFS) {
if (sharedPrefs.contains(prefName)) {
backupNames.add(ANDROID_DEFAULT_PREFIX + prefName);
backupValues.add(booleanToBytes(sharedPrefs.getBoolean(prefName, false)));
}
}
// Finally add the user id.
backupNames.add(ANDROID_DEFAULT_PREFIX + SIGNED_IN_ACCOUNT_KEY);
backupValues.add(ApiCompatibilityUtils.getBytesUtf8(
syncAccount.get() == null ? "" : syncAccount.get().getEmail()));
BackupState newBackupState = new BackupState(backupNames, backupValues);
// Check if a backup is actually needed.
try {
BackupState oldBackupState = new BackupState(oldState);
if (newBackupState.equals(oldBackupState)) {
Log.i(TAG, "Nothing has changed since the last backup. Backup skipped.");
newBackupState.save(newState);
return;
}
} catch (IOException e) {
// This will happen if Chrome has never written backup data, or if the backup status is
// corrupt. Create a new backup in either case.
Log.i(TAG, "Can't read backup status file");
}
// Write the backup data
for (int i = 0; i < backupNames.size(); i++) {
data.writeEntityHeader(backupNames.get(i), backupValues.get(i).length);
data.writeEntityData(backupValues.get(i), backupValues.get(i).length);
}
// Remember the backup state.
newBackupState.save(newState);
Log.i(TAG, "Backup complete");
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
// TODO(aberent) Check that this is not running on the UI thread. Doing so, however, makes
// testing difficult since the test code runs on the UI thread.
// Check that the user hasn't already seen FRE (not sure if this can ever happen, but if it
// does then restoring the backup will overwrite the user's choices).
SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
if (FirstRunStatus.getFirstRunFlowComplete()
|| FirstRunStatus.getLightweightFirstRunFlowComplete()) {
setRestoreStatus(RestoreStatus.RESTORE_AFTER_FIRST_RUN);
Log.w(TAG, "Restore attempted after first run");
return;
}
final ArrayList<String> backupNames = new ArrayList<>();
final ArrayList<byte[]> backupValues = new ArrayList<>();
String restoredUserName = null;
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
byte[] buffer = new byte[dataSize];
data.readEntityData(buffer, 0, dataSize);
if (key.equals(ANDROID_DEFAULT_PREFIX + SIGNED_IN_ACCOUNT_KEY)) {
restoredUserName = new String(buffer);
} else {
backupNames.add(key);
backupValues.add(buffer);
}
}
// Start and wait for the Async init tasks. This loads the library, and attempts to load the
// first run variations seed. Since these are both slow it makes sense to run them in
// parallel as Android AsyncTasks, reusing some of Chrome's async startup logic.
//
// Note that this depends on onRestore being run from a background thread, since
// if it were called from the UI thread the broadcast would not be received until after it
// exited.
final CountDownLatch latch = new CountDownLatch(1);
PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
// Chrome library loading depends on PathUtils.
PathUtils.setPrivateDataDirectorySuffix(
ChromeApplication.PRIVATE_DATA_DIRECTORY_SUFFIX);
createAsyncInitTaskRunner(latch).startBackgroundTasks(
false /* allocateChildConnection */, true /* initVariationSeed */);
});
try {
// Ignore result. It will only be false if it times out. Problems with fetching the
// variation seed can be ignored, and other problems will either recover or be repeated
// when Chrome is started synchronously.
latch.await(BACKGROUND_TASK_TIMEOUT_SECS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// Should never happen, but can be ignored (as explained above) anyway.
}
// Chrome has to be running before it can check if the account exists. Because the native
// library is already loaded Chrome startup should be fast.
boolean browserStarted = PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
// Start the browser if necessary.
return initializeBrowser();
});
if (!browserStarted) {
// Something went wrong starting Chrome, skip the restore.
setRestoreStatus(RestoreStatus.BROWSER_STARTUP_FAILED);
return;
}
// If the user hasn't signed in, or can't sign in, then don't restore anything.
if (restoredUserName == null || !accountExistsOnDevice(restoredUserName)) {
setRestoreStatus(RestoreStatus.NOT_SIGNED_IN);
Log.i(TAG, "Chrome was not signed in with a known account name, not restoring");
return;
}
// Restore the native preferences on the UI thread
PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
ArrayList<String> nativeBackupNames = new ArrayList<>();
boolean[] nativeBackupValues = new boolean[backupNames.size()];
int count = 0;
int prefixLength = NATIVE_PREF_PREFIX.length();
for (int i = 0; i < backupNames.size(); i++) {
String name = backupNames.get(i);
if (name.startsWith(NATIVE_PREF_PREFIX)) {
nativeBackupNames.add(name.substring(prefixLength));
nativeBackupValues[count] = bytesToBoolean(backupValues.get(i));
count++;
}
}
ChromeBackupAgentImplJni.get().setBoolBackupPrefs(this,
nativeBackupNames.toArray(new String[count]),
Arrays.copyOf(nativeBackupValues, count));
});
// Now that everything looks good so restore the Android preferences.
SharedPreferences.Editor editor = sharedPrefs.edit();
// Only restore preferences that we know about.
int prefixLength = ANDROID_DEFAULT_PREFIX.length();
for (int i = 0; i < backupNames.size(); i++) {
String name = backupNames.get(i);
if (name.startsWith(ANDROID_DEFAULT_PREFIX)
&& Arrays.asList(BACKUP_ANDROID_BOOL_PREFS)
.contains(name.substring(prefixLength))) {
editor.putBoolean(
name.substring(prefixLength), bytesToBoolean(backupValues.get(i)));
}
}
// 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.
editor.putString(ChromePreferenceKeys.FIRST_RUN_FLOW_SIGNIN_ACCOUNT_NAME, restoredUserName);
editor.apply();
// The silent first run will change things, so there is no point in trying to prevent
// additional backups at this stage. Don't write anything to |newState|.
setRestoreStatus(RestoreStatus.RESTORE_COMPLETED);
Log.i(TAG, "Restore complete");
}
@VisibleForTesting
AsyncInitTaskRunner createAsyncInitTaskRunner(final CountDownLatch latch) {
return new AsyncInitTaskRunner() {
@Override
protected void onSuccess() {
latch.countDown();
}
@Override
protected void onFailure(Exception failureCause) {
// Ignore failure. Problems with the variation seed can be ignored, and other
// problems will either recover or be repeated when Chrome is started synchronously.
latch.countDown();
}
};
}
/**
* Get the saved result of any restore that may have happened.
*
* @return the restore status, a RestoreStatus value.
*/
@VisibleForTesting
@RestoreStatus
static int getRestoreStatus() {
return ContextUtils.getAppSharedPreferences().getInt(
RESTORE_STATUS, RestoreStatus.NO_RESTORE);
}
/**
* Save the restore status for later transfer to a histogram.
*
* @param status the status.
*/
@VisibleForTesting
static void setRestoreStatus(@RestoreStatus int status) {
ContextUtils.getAppSharedPreferences().edit().putInt(RESTORE_STATUS, status).apply();
}
/**
* Record the restore histogram. To be called from Chrome itself once it is running.
*/
public static void recordRestoreHistogram() {
@RestoreStatus
int restoreStatus = getRestoreStatus();
// Ensure restore status is only recorded once
if (restoreStatus != RestoreStatus.RESTORE_STATUS_RECORDED) {
RecordHistogram.recordEnumeratedHistogram(
HISTOGRAM_ANDROID_RESTORE_RESULT, restoreStatus, RestoreStatus.NUM_ENTRIES);
setRestoreStatus(RestoreStatus.RESTORE_STATUS_RECORDED);
}
}
@NativeMethods
interface Natives {
String[] getBoolBackupNames(ChromeBackupAgentImpl caller);
boolean[] getBoolBackupValues(ChromeBackupAgentImpl caller);
void setBoolBackupPrefs(ChromeBackupAgentImpl caller, String[] name, boolean[] value);
}
}
...@@ -50,7 +50,7 @@ public class ChromeBackupWatcher { ...@@ -50,7 +50,7 @@ public class ChromeBackupWatcher {
} }
sharedPrefs.addObserver((key) -> { sharedPrefs.addObserver((key) -> {
// Update the backup if any of the backed up Android preferences change. // Update the backup if any of the backed up Android preferences change.
for (String pref : ChromeBackupAgent.BACKUP_ANDROID_BOOL_PREFS) { for (String pref : ChromeBackupAgentImpl.BACKUP_ANDROID_BOOL_PREFS) {
if (key.equals(pref)) { if (key.equals(pref)) {
onBackupPrefsChanged(); onBackupPrefsChanged();
return; return;
......
// Copyright 2020 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.base;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import java.io.IOException;
/**
* BackupAgent base class which will call through to the given {@link Impl}. This class must be
* present in the base module, while the Impl can be in the chrome module.
*/
public class SplitCompatBackupAgent extends BackupAgent {
private String mBackupAgentClassName;
private Impl mImpl;
public SplitCompatBackupAgent(String backupAgentClassName) {
mBackupAgentClassName = backupAgentClassName;
}
@Override
protected void attachBaseContext(Context context) {
context = SplitCompatUtils.createChromeContext(context);
mImpl = (Impl) SplitCompatUtils.newInstance(context, mBackupAgentClassName);
mImpl.setBackupAgent(this);
super.attachBaseContext(context);
}
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
mImpl.onBackup(oldState, data, newState);
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
mImpl.onRestore(data, appVersionCode, newState);
}
/**
* Holds the implementation of backup agent logic. Will be called by {@link
* SplitCompatBackupAgent}.
*/
public abstract static class Impl {
private SplitCompatBackupAgent mBackupAgent;
protected final void setBackupAgent(SplitCompatBackupAgent backupAgent) {
mBackupAgent = backupAgent;
}
protected final BackupAgent getBackupAgent() {
return mBackupAgent;
}
public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException;
public abstract void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException;
}
}
...@@ -32,7 +32,7 @@ import org.chromium.chrome.browser.AfterStartupTaskUtils; ...@@ -32,7 +32,7 @@ import org.chromium.chrome.browser.AfterStartupTaskUtils;
import org.chromium.chrome.browser.AppHooks; import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.ChromeActivitySessionTracker; import org.chromium.chrome.browser.ChromeActivitySessionTracker;
import org.chromium.chrome.browser.ChromeApplication; import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.ChromeBackupAgent; import org.chromium.chrome.browser.ChromeBackupAgentImpl;
import org.chromium.chrome.browser.DefaultBrowserInfo; import org.chromium.chrome.browser.DefaultBrowserInfo;
import org.chromium.chrome.browser.DeferredStartupHandler; import org.chromium.chrome.browser.DeferredStartupHandler;
import org.chromium.chrome.browser.DevToolsServer; import org.chromium.chrome.browser.DevToolsServer;
...@@ -360,7 +360,7 @@ public class ProcessInitializationHandler { ...@@ -360,7 +360,7 @@ public class ProcessInitializationHandler {
@Override @Override
public void run() { public void run() {
// Record the saved restore state in a histogram // Record the saved restore state in a histogram
ChromeBackupAgent.recordRestoreHistogram(); ChromeBackupAgentImpl.recordRestoreHistogram();
} }
}); });
......
...@@ -41,34 +41,35 @@ public class RestoreHistogramTest { ...@@ -41,34 +41,35 @@ public class RestoreHistogramTest {
@SmallTest @SmallTest
public void testHistogramWriter() { public void testHistogramWriter() {
LibraryLoader.getInstance().ensureInitialized(); LibraryLoader.getInstance().ensureInitialized();
MetricsUtils.HistogramDelta noRestoreDelta = MetricsUtils.HistogramDelta noRestoreDelta = new MetricsUtils.HistogramDelta(
new MetricsUtils.HistogramDelta(ChromeBackupAgent.HISTOGRAM_ANDROID_RESTORE_RESULT, ChromeBackupAgentImpl.HISTOGRAM_ANDROID_RESTORE_RESULT,
ChromeBackupAgent.RestoreStatus.NO_RESTORE); ChromeBackupAgentImpl.RestoreStatus.NO_RESTORE);
MetricsUtils.HistogramDelta restoreCompletedDelta = MetricsUtils.HistogramDelta restoreCompletedDelta = new MetricsUtils.HistogramDelta(
new MetricsUtils.HistogramDelta(ChromeBackupAgent.HISTOGRAM_ANDROID_RESTORE_RESULT, ChromeBackupAgentImpl.HISTOGRAM_ANDROID_RESTORE_RESULT,
ChromeBackupAgent.RestoreStatus.RESTORE_COMPLETED); ChromeBackupAgentImpl.RestoreStatus.RESTORE_COMPLETED);
MetricsUtils.HistogramDelta restoreStatusRecorded = MetricsUtils.HistogramDelta restoreStatusRecorded = new MetricsUtils.HistogramDelta(
new MetricsUtils.HistogramDelta(ChromeBackupAgent.HISTOGRAM_ANDROID_RESTORE_RESULT, ChromeBackupAgentImpl.HISTOGRAM_ANDROID_RESTORE_RESULT,
ChromeBackupAgent.RestoreStatus.RESTORE_STATUS_RECORDED); ChromeBackupAgentImpl.RestoreStatus.RESTORE_STATUS_RECORDED);
// Check behavior with no preference set // Check behavior with no preference set
ChromeBackupAgent.recordRestoreHistogram(); ChromeBackupAgentImpl.recordRestoreHistogram();
Assert.assertEquals(1, noRestoreDelta.getDelta()); Assert.assertEquals(1, noRestoreDelta.getDelta());
Assert.assertEquals(0, restoreCompletedDelta.getDelta()); Assert.assertEquals(0, restoreCompletedDelta.getDelta());
Assert.assertEquals(ChromeBackupAgent.RestoreStatus.RESTORE_STATUS_RECORDED, Assert.assertEquals(ChromeBackupAgentImpl.RestoreStatus.RESTORE_STATUS_RECORDED,
ChromeBackupAgent.getRestoreStatus()); ChromeBackupAgentImpl.getRestoreStatus());
// Check behavior with a restore status // Check behavior with a restore status
ChromeBackupAgent.setRestoreStatus(ChromeBackupAgent.RestoreStatus.RESTORE_COMPLETED); ChromeBackupAgentImpl.setRestoreStatus(
ChromeBackupAgent.recordRestoreHistogram(); ChromeBackupAgentImpl.RestoreStatus.RESTORE_COMPLETED);
ChromeBackupAgentImpl.recordRestoreHistogram();
Assert.assertEquals(1, noRestoreDelta.getDelta()); Assert.assertEquals(1, noRestoreDelta.getDelta());
Assert.assertEquals(1, restoreCompletedDelta.getDelta()); Assert.assertEquals(1, restoreCompletedDelta.getDelta());
Assert.assertEquals(ChromeBackupAgent.RestoreStatus.RESTORE_STATUS_RECORDED, Assert.assertEquals(ChromeBackupAgentImpl.RestoreStatus.RESTORE_STATUS_RECORDED,
ChromeBackupAgent.getRestoreStatus()); ChromeBackupAgentImpl.getRestoreStatus());
// Second call should record nothing (note this assumes it doesn't record something totally // Second call should record nothing (note this assumes it doesn't record something totally
// random) // random)
ChromeBackupAgent.recordRestoreHistogram(); ChromeBackupAgentImpl.recordRestoreHistogram();
Assert.assertEquals(0, restoreStatusRecorded.getDelta()); Assert.assertEquals(0, restoreStatusRecorded.getDelta());
} }
...@@ -82,9 +83,9 @@ public class RestoreHistogramTest { ...@@ -82,9 +83,9 @@ public class RestoreHistogramTest {
@SmallTest @SmallTest
public void testWritingHistogramAtStartup() throws InterruptedException { public void testWritingHistogramAtStartup() throws InterruptedException {
LibraryLoader.getInstance().ensureInitialized(); LibraryLoader.getInstance().ensureInitialized();
MetricsUtils.HistogramDelta noRestoreDelta = MetricsUtils.HistogramDelta noRestoreDelta = new MetricsUtils.HistogramDelta(
new MetricsUtils.HistogramDelta(ChromeBackupAgent.HISTOGRAM_ANDROID_RESTORE_RESULT, ChromeBackupAgentImpl.HISTOGRAM_ANDROID_RESTORE_RESULT,
ChromeBackupAgent.RestoreStatus.NO_RESTORE); ChromeBackupAgentImpl.RestoreStatus.NO_RESTORE);
// Histogram should be written the first time the activity is started. // Histogram should be written the first time the activity is started.
mActivityTestRule.startMainActivityOnBlankPage(); mActivityTestRule.startMainActivityOnBlankPage();
......
...@@ -95,13 +95,13 @@ public class ChromeBackupAgentTest { ...@@ -95,13 +95,13 @@ public class ChromeBackupAgentTest {
@Rule @Rule
public JniMocker mocker = new JniMocker(); public JniMocker mocker = new JniMocker();
@Mock @Mock
private ChromeBackupAgent.Natives mChromeBackupAgentJniMock; private ChromeBackupAgentImpl.Natives mChromeBackupAgentJniMock;
@Mock @Mock
private IdentityManager mIdentityManagerMock; private IdentityManager mIdentityManagerMock;
@Mock @Mock
private Profile mProfile; private Profile mProfile;
private ChromeBackupAgent mAgent; private ChromeBackupAgentImpl mAgent;
private AsyncInitTaskRunner mTaskRunner; private AsyncInitTaskRunner mTaskRunner;
private final CoreAccountInfo mAccountInfo = private final CoreAccountInfo mAccountInfo =
...@@ -119,7 +119,7 @@ public class ChromeBackupAgentTest { ...@@ -119,7 +119,7 @@ public class ChromeBackupAgentTest {
public void setUp() { public void setUp() {
// Create the agent to test; override fetching the task runner, and spy on the agent to // Create the agent to test; override fetching the task runner, and spy on the agent to
// allow us to validate calls to these methods. // allow us to validate calls to these methods.
mAgent = spy(new ChromeBackupAgent() { mAgent = spy(new ChromeBackupAgentImpl() {
@Override @Override
AsyncInitTaskRunner createAsyncInitTaskRunner(CountDownLatch latch) { AsyncInitTaskRunner createAsyncInitTaskRunner(CountDownLatch latch) {
latch.countDown(); latch.countDown();
...@@ -129,7 +129,7 @@ public class ChromeBackupAgentTest { ...@@ -129,7 +129,7 @@ public class ChromeBackupAgentTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
Profile.setLastUsedProfileForTesting(mProfile); Profile.setLastUsedProfileForTesting(mProfile);
mocker.mock(ChromeBackupAgentJni.TEST_HOOKS, mChromeBackupAgentJniMock); mocker.mock(ChromeBackupAgentImplJni.TEST_HOOKS, mChromeBackupAgentJniMock);
when(mChromeBackupAgentJniMock.getBoolBackupNames(mAgent)) when(mChromeBackupAgentJniMock.getBoolBackupNames(mAgent))
.thenReturn(new String[] {"pref1"}); .thenReturn(new String[] {"pref1"});
...@@ -184,7 +184,7 @@ public class ChromeBackupAgentTest { ...@@ -184,7 +184,7 @@ public class ChromeBackupAgentTest {
verify(backupData).writeEntityData(new byte[] {0}, 1); verify(backupData).writeEntityData(new byte[] {0}, 1);
byte[] unameBytes = ApiCompatibilityUtils.getBytesUtf8(mAccountInfo.getEmail()); byte[] unameBytes = ApiCompatibilityUtils.getBytesUtf8(mAccountInfo.getEmail());
verify(backupData) verify(backupData)
.writeEntityHeader("AndroidDefault." + ChromeBackupAgent.SIGNED_IN_ACCOUNT_KEY, .writeEntityHeader("AndroidDefault." + ChromeBackupAgentImpl.SIGNED_IN_ACCOUNT_KEY,
unameBytes.length); unameBytes.length);
verify(backupData).writeEntityData(unameBytes, unameBytes.length); verify(backupData).writeEntityData(unameBytes, unameBytes.length);
...@@ -199,7 +199,7 @@ public class ChromeBackupAgentTest { ...@@ -199,7 +199,7 @@ public class ChromeBackupAgentTest {
names, hasItem("AndroidDefault." + ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE)); names, hasItem("AndroidDefault." + ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE));
assertThat(names, assertThat(names,
hasItem("AndroidDefault." + ChromePreferenceKeys.FIRST_RUN_FLOW_SIGNIN_SETUP)); hasItem("AndroidDefault." + ChromePreferenceKeys.FIRST_RUN_FLOW_SIGNIN_SETUP));
assertThat(names, hasItem("AndroidDefault." + ChromeBackupAgent.SIGNED_IN_ACCOUNT_KEY)); assertThat(names, hasItem("AndroidDefault." + ChromeBackupAgentImpl.SIGNED_IN_ACCOUNT_KEY));
ArrayList<byte[]> values = (ArrayList<byte[]>) newStateStream.readObject(); ArrayList<byte[]> values = (ArrayList<byte[]>) newStateStream.readObject();
assertThat(values.size(), equalTo(4)); assertThat(values.size(), equalTo(4));
assertThat(values, hasItem(unameBytes)); assertThat(values, hasItem(unameBytes));
...@@ -356,12 +356,12 @@ public class ChromeBackupAgentTest { ...@@ -356,12 +356,12 @@ public class ChromeBackupAgentTest {
verifyNoMoreInteractions(backupData); verifyNoMoreInteractions(backupData);
verifyNoMoreInteractions(mockState); verifyNoMoreInteractions(mockState);
SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
assertThat(prefs.getInt(ChromeBackupAgent.BACKUP_FAILURE_COUNT, 0), equalTo(1)); assertThat(prefs.getInt(ChromeBackupAgentImpl.BACKUP_FAILURE_COUNT, 0), equalTo(1));
// Check that the backup agent gives up retrying after too many failures // Check that the backup agent gives up retrying after too many failures
prefs.edit() prefs.edit()
.putInt(ChromeBackupAgent.BACKUP_FAILURE_COUNT, .putInt(ChromeBackupAgentImpl.BACKUP_FAILURE_COUNT,
ChromeBackupAgent.MAX_BACKUP_FAILURES) ChromeBackupAgentImpl.MAX_BACKUP_FAILURES)
.apply(); .apply();
mAgent.onBackup(null, backupData, mockState); mAgent.onBackup(null, backupData, mockState);
assertThat(BackupManagerShadow.getDataChangedCalls(), equalTo(1)); assertThat(BackupManagerShadow.getDataChangedCalls(), equalTo(1));
...@@ -374,7 +374,7 @@ public class ChromeBackupAgentTest { ...@@ -374,7 +374,7 @@ public class ChromeBackupAgentTest {
ParcelFileDescriptor.open(stateFile, ParcelFileDescriptor.parseMode("w")); ParcelFileDescriptor.open(stateFile, ParcelFileDescriptor.parseMode("w"));
mAgent.onBackup(null, backupData, newState); mAgent.onBackup(null, backupData, newState);
assertThat(prefs.getInt(ChromeBackupAgent.BACKUP_FAILURE_COUNT, 0), equalTo(0)); assertThat(prefs.getInt(ChromeBackupAgentImpl.BACKUP_FAILURE_COUNT, 0), equalTo(0));
} }
private BackupDataInput createMockBackupData() throws IOException { private BackupDataInput createMockBackupData() throws IOException {
...@@ -383,7 +383,8 @@ public class ChromeBackupAgentTest { ...@@ -383,7 +383,8 @@ public class ChromeBackupAgentTest {
final String[] keys = {"native.pref1", "native.pref2", final String[] keys = {"native.pref1", "native.pref2",
"AndroidDefault." + ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, "AndroidDefault." + ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE,
"AndroidDefault.junk", "AndroidDefault." + ChromeBackupAgent.SIGNED_IN_ACCOUNT_KEY}; "AndroidDefault.junk",
"AndroidDefault." + ChromeBackupAgentImpl.SIGNED_IN_ACCOUNT_KEY};
byte[] unameBytes = ApiCompatibilityUtils.getBytesUtf8(mAccountInfo.getEmail()); byte[] unameBytes = ApiCompatibilityUtils.getBytesUtf8(mAccountInfo.getEmail());
final byte[][] values = {{0}, {1}, {1}, {23, 42}, unameBytes}; final byte[][] values = {{0}, {1}, {1}, {23, 42}, unameBytes};
when(backupData.getKey()).thenAnswer(new Answer<String>() { when(backupData.getKey()).thenAnswer(new Answer<String>() {
...@@ -457,8 +458,8 @@ public class ChromeBackupAgentTest { ...@@ -457,8 +458,8 @@ public class ChromeBackupAgentTest {
false /* allocateChildConnection */, true /* initVariationSeed */); false /* allocateChildConnection */, true /* initVariationSeed */);
// Test that the status of the restore has been recorded. // Test that the status of the restore has been recorded.
assertThat(ChromeBackupAgent.getRestoreStatus(), assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgent.RestoreStatus.RESTORE_COMPLETED)); equalTo(ChromeBackupAgentImpl.RestoreStatus.RESTORE_COMPLETED));
} }
/** /**
...@@ -488,8 +489,8 @@ public class ChromeBackupAgentTest { ...@@ -488,8 +489,8 @@ public class ChromeBackupAgentTest {
false /* allocateChildConnection */, true /* initVariationSeed */); false /* allocateChildConnection */, true /* initVariationSeed */);
// Test that the status of the restore has been recorded. // Test that the status of the restore has been recorded.
assertThat(ChromeBackupAgent.getRestoreStatus(), assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgent.RestoreStatus.NOT_SIGNED_IN)); equalTo(ChromeBackupAgentImpl.RestoreStatus.NOT_SIGNED_IN));
} }
/** /**
...@@ -513,8 +514,8 @@ public class ChromeBackupAgentTest { ...@@ -513,8 +514,8 @@ public class ChromeBackupAgentTest {
assertFalse(prefs.contains(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE)); assertFalse(prefs.contains(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE));
// Test that the status of the restore has been recorded. // Test that the status of the restore has been recorded.
assertThat(ChromeBackupAgent.getRestoreStatus(), assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgent.RestoreStatus.BROWSER_STARTUP_FAILED)); equalTo(ChromeBackupAgentImpl.RestoreStatus.BROWSER_STARTUP_FAILED));
} }
/** /**
...@@ -538,8 +539,8 @@ public class ChromeBackupAgentTest { ...@@ -538,8 +539,8 @@ public class ChromeBackupAgentTest {
assertTrue(prefs.contains(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE)); assertTrue(prefs.contains(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE));
// Test that the status of the restore has been recorded. // Test that the status of the restore has been recorded.
assertThat(ChromeBackupAgent.getRestoreStatus(), assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgent.RestoreStatus.RESTORE_AFTER_FIRST_RUN)); equalTo(ChromeBackupAgentImpl.RestoreStatus.RESTORE_AFTER_FIRST_RUN));
} }
/** /**
...@@ -548,35 +549,39 @@ public class ChromeBackupAgentTest { ...@@ -548,35 +549,39 @@ public class ChromeBackupAgentTest {
@Test @Test
public void testGetRestoreStatus() { public void testGetRestoreStatus() {
// Test default value // Test default value
assertThat(ChromeBackupAgent.getRestoreStatus(), assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgent.RestoreStatus.NO_RESTORE)); equalTo(ChromeBackupAgentImpl.RestoreStatus.NO_RESTORE));
// Test that the value can be changed // Test that the value can be changed
ChromeBackupAgent.setRestoreStatus(ChromeBackupAgent.RestoreStatus.RESTORE_AFTER_FIRST_RUN); ChromeBackupAgentImpl.setRestoreStatus(
assertThat(ChromeBackupAgent.getRestoreStatus(), ChromeBackupAgentImpl.RestoreStatus.RESTORE_AFTER_FIRST_RUN);
equalTo(ChromeBackupAgent.RestoreStatus.RESTORE_AFTER_FIRST_RUN)); assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgentImpl.RestoreStatus.RESTORE_AFTER_FIRST_RUN));
// Prove that the value equalTo held in the app preferences (and not, for example, in a // Prove that the value equalTo held in the app preferences (and not, for example, in a
// static). // static).
ContextUtils.getAppSharedPreferences().edit().clear().apply(); ContextUtils.getAppSharedPreferences().edit().clear().apply();
assertThat(ChromeBackupAgent.getRestoreStatus(), assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgent.RestoreStatus.NO_RESTORE)); equalTo(ChromeBackupAgentImpl.RestoreStatus.NO_RESTORE));
// Test that ChromeBackupAgent.setRestoreStatus really looks at the argument. // Test that ChromeBackupAgentImpl.setRestoreStatus really looks at the argument.
ChromeBackupAgent.setRestoreStatus(ChromeBackupAgent.RestoreStatus.BROWSER_STARTUP_FAILED); ChromeBackupAgentImpl.setRestoreStatus(
assertThat(ChromeBackupAgent.getRestoreStatus(), ChromeBackupAgentImpl.RestoreStatus.BROWSER_STARTUP_FAILED);
equalTo(ChromeBackupAgent.RestoreStatus.BROWSER_STARTUP_FAILED)); assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgentImpl.RestoreStatus.BROWSER_STARTUP_FAILED));
// Test the remaining values are implemented // Test the remaining values are implemented
ChromeBackupAgent.setRestoreStatus(ChromeBackupAgent.RestoreStatus.NOT_SIGNED_IN); ChromeBackupAgentImpl.setRestoreStatus(ChromeBackupAgentImpl.RestoreStatus.NOT_SIGNED_IN);
assertThat(ChromeBackupAgent.getRestoreStatus(), assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgent.RestoreStatus.NOT_SIGNED_IN)); equalTo(ChromeBackupAgentImpl.RestoreStatus.NOT_SIGNED_IN));
ChromeBackupAgent.setRestoreStatus(ChromeBackupAgent.RestoreStatus.RESTORE_COMPLETED); ChromeBackupAgentImpl.setRestoreStatus(
assertThat(ChromeBackupAgent.getRestoreStatus(), ChromeBackupAgentImpl.RestoreStatus.RESTORE_COMPLETED);
equalTo(ChromeBackupAgent.RestoreStatus.RESTORE_COMPLETED)); assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
ChromeBackupAgent.setRestoreStatus(ChromeBackupAgent.RestoreStatus.RESTORE_STATUS_RECORDED); equalTo(ChromeBackupAgentImpl.RestoreStatus.RESTORE_COMPLETED));
assertThat(ChromeBackupAgent.getRestoreStatus(), ChromeBackupAgentImpl.setRestoreStatus(
equalTo(ChromeBackupAgent.RestoreStatus.RESTORE_STATUS_RECORDED)); ChromeBackupAgentImpl.RestoreStatus.RESTORE_STATUS_RECORDED);
assertThat(ChromeBackupAgentImpl.getRestoreStatus(),
equalTo(ChromeBackupAgentImpl.RestoreStatus.RESTORE_STATUS_RECORDED));
} }
/** /**
...@@ -587,7 +592,7 @@ public class ChromeBackupAgentTest { ...@@ -587,7 +592,7 @@ public class ChromeBackupAgentTest {
*/ */
@Test @Test
public void testInitializeBrowser_normal() { public void testInitializeBrowser_normal() {
ChromeBackupAgent agent = new ChromeBackupAgent(); ChromeBackupAgentImpl agent = new ChromeBackupAgentImpl();
ChromeBrowserInitializer initializer = mock(ChromeBrowserInitializer.class); ChromeBrowserInitializer initializer = mock(ChromeBrowserInitializer.class);
ChromeBrowserInitializer.setForTesting(initializer); ChromeBrowserInitializer.setForTesting(initializer);
assertTrue(agent.initializeBrowser()); assertTrue(agent.initializeBrowser());
...@@ -600,7 +605,7 @@ public class ChromeBackupAgentTest { ...@@ -600,7 +605,7 @@ public class ChromeBackupAgentTest {
@Test @Test
public void testInitializeBrowser_childProcess() { public void testInitializeBrowser_childProcess() {
ContentProcessInfo.setInChildProcess(true); ContentProcessInfo.setInChildProcess(true);
ChromeBackupAgent agent = new ChromeBackupAgent(); ChromeBackupAgentImpl agent = new ChromeBackupAgentImpl();
ChromeBrowserInitializer initializer = mock(ChromeBrowserInitializer.class); ChromeBrowserInitializer initializer = mock(ChromeBrowserInitializer.class);
ChromeBrowserInitializer.setForTesting(initializer); ChromeBrowserInitializer.setForTesting(initializer);
assertFalse(agent.initializeBrowser()); assertFalse(agent.initializeBrowser());
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include "base/android/jni_array.h" #include "base/android/jni_array.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "chrome/android/chrome_jni_headers/ChromeBackupAgent_jni.h" #include "chrome/android/chrome_jni_headers/ChromeBackupAgentImpl_jni.h"
#include "chrome/browser/android/chrome_backup_agent.h" #include "chrome/browser/android/chrome_backup_agent.h"
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h" #include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h"
...@@ -33,7 +33,7 @@ const char* backed_up_preferences_[] = { ...@@ -33,7 +33,7 @@ const char* backed_up_preferences_[] = {
} // namespace } // namespace
static base::android::ScopedJavaLocalRef<jobjectArray> static base::android::ScopedJavaLocalRef<jobjectArray>
JNI_ChromeBackupAgent_GetBoolBackupNames( JNI_ChromeBackupAgentImpl_GetBoolBackupNames(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) { const base::android::JavaParamRef<jobject>& jcaller) {
return base::android::ToJavaArrayOfStrings(env, return base::android::ToJavaArrayOfStrings(env,
...@@ -41,7 +41,7 @@ JNI_ChromeBackupAgent_GetBoolBackupNames( ...@@ -41,7 +41,7 @@ JNI_ChromeBackupAgent_GetBoolBackupNames(
} }
static base::android::ScopedJavaLocalRef<jbooleanArray> static base::android::ScopedJavaLocalRef<jbooleanArray>
JNI_ChromeBackupAgent_GetBoolBackupValues( JNI_ChromeBackupAgentImpl_GetBoolBackupValues(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) { const base::android::JavaParamRef<jobject>& jcaller) {
PrefService* prefs = ProfileManager::GetLastUsedProfile()->GetPrefs(); PrefService* prefs = ProfileManager::GetLastUsedProfile()->GetPrefs();
...@@ -56,7 +56,7 @@ JNI_ChromeBackupAgent_GetBoolBackupValues( ...@@ -56,7 +56,7 @@ JNI_ChromeBackupAgent_GetBoolBackupValues(
return base::android::ScopedJavaLocalRef<jbooleanArray>(env, array); return base::android::ScopedJavaLocalRef<jbooleanArray>(env, array);
} }
static void JNI_ChromeBackupAgent_SetBoolBackupPrefs( static void JNI_ChromeBackupAgentImpl_SetBoolBackupPrefs(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller, const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobjectArray>& names, const base::android::JavaParamRef<jobjectArray>& names,
...@@ -87,13 +87,13 @@ std::vector<std::string> GetBackupPrefNames() { ...@@ -87,13 +87,13 @@ std::vector<std::string> GetBackupPrefNames() {
base::android::ScopedJavaLocalRef<jobjectArray> GetBoolBackupNamesForTesting( base::android::ScopedJavaLocalRef<jobjectArray> GetBoolBackupNamesForTesting(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) { const base::android::JavaParamRef<jobject>& jcaller) {
return JNI_ChromeBackupAgent_GetBoolBackupNames(env, jcaller); return JNI_ChromeBackupAgentImpl_GetBoolBackupNames(env, jcaller);
} }
base::android::ScopedJavaLocalRef<jbooleanArray> GetBoolBackupValuesForTesting( base::android::ScopedJavaLocalRef<jbooleanArray> GetBoolBackupValuesForTesting(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) { const base::android::JavaParamRef<jobject>& jcaller) {
return JNI_ChromeBackupAgent_GetBoolBackupValues(env, jcaller); return JNI_ChromeBackupAgentImpl_GetBoolBackupValues(env, jcaller);
} }
void SetBoolBackupPrefsForTesting( void SetBoolBackupPrefsForTesting(
...@@ -101,7 +101,7 @@ void SetBoolBackupPrefsForTesting( ...@@ -101,7 +101,7 @@ void SetBoolBackupPrefsForTesting(
const base::android::JavaParamRef<jobject>& jcaller, const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobjectArray>& names, const base::android::JavaParamRef<jobjectArray>& names,
const base::android::JavaParamRef<jbooleanArray>& values) { const base::android::JavaParamRef<jbooleanArray>& values) {
JNI_ChromeBackupAgent_SetBoolBackupPrefs(env, jcaller, names, values); JNI_ChromeBackupAgentImpl_SetBoolBackupPrefs(env, jcaller, names, values);
} }
} // namespace android } // namespace android
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