From: uazo Date: Thu, 21 Oct 2021 07:06:08 +0000 Subject: [wip] enable android backup --- chrome/android/chrome_public_apk_tmpl.gni | 1 + .../java/res/xml/about_chrome_preferences.xml | 11 ++ .../chrome/browser/ChromeBackupAgentImpl.java | 122 ++++++++++++++++-- .../about_settings/AboutChromeSettings.java | 70 +++++++++- chrome/browser/android/chrome_backup_agent.cc | 97 ++++++++++++++ .../strings/android_chrome_strings.grd | 12 ++ components/prefs/json_pref_store.cc | 5 + components/prefs/json_pref_store.h | 2 + components/prefs/persistent_pref_store.h | 3 + components/prefs/pref_service.cc | 4 + components/prefs/pref_service.h | 2 + 11 files changed, 316 insertions(+), 13 deletions(-) diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni --- a/chrome/android/chrome_public_apk_tmpl.gni +++ b/chrome/android/chrome_public_apk_tmpl.gni @@ -22,6 +22,7 @@ default_chrome_public_jinja_variables = [ "channel=$android_channel", "enable_vr=$enable_vr", "include_arcore_manifest_flag=false", + "backup_key=unused" # see https://developer.android.com/guide/topics/data/keyvaluebackup#BackupManifest ] # Enable stack unwinding only on official build with specific channels. It is diff --git a/chrome/android/java/res/xml/about_chrome_preferences.xml b/chrome/android/java/res/xml/about_chrome_preferences.xml --- a/chrome/android/java/res/xml/about_chrome_preferences.xml +++ b/chrome/android/java/res/xml/about_chrome_preferences.xml @@ -19,4 +19,15 @@ android:fragment="org.chromium.chrome.browser.about_settings.LegalInformationSettings" android:key="legal_information" android:title="@string/legal_information_title" /> + + + diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgentImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgentImpl.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgentImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackupAgentImpl.java @@ -48,6 +48,9 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.Map; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; /** * Backup agent for Chrome, using Android key/value backup. @@ -57,6 +60,11 @@ 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 NATIVE_LOCALSTATE_PREF_JSON = "localstate_prefs_json"; + private static final String NATIVE_PROFILE_PREF_JSON = "profile_prefs_json"; + private static final String SHARED_PREFS_PREFIX = "shared."; + private static final String PREF_ALLOW_ANDROID_BACKUP = "allow_android_backup"; + private static final String TAG = "ChromeBackupAgent"; @VisibleForTesting @@ -175,9 +183,30 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { return bytes[0] != 0; } + private static byte[] convertToBytes(Object object) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bos)) { + out.writeObject(object); + return bos.toByteArray(); + } + } + + private static Object convertFromBytes(byte[] bytes) throws IOException, ClassNotFoundException { + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream in = new ObjectInputStream(bis)) { + return in.readObject(); + } + } + @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { + SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences(); + if (sharedPrefs.getBoolean(PREF_ALLOW_ANDROID_BACKUP, false) == false) { + Log.i(TAG, "Backup disabled by user"); + return; + } + final ArrayList backupNames = new ArrayList<>(); final ArrayList backupValues = new ArrayList<>(); final AtomicReference syncAccount = new AtomicReference<>(); @@ -203,9 +232,20 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { for (boolean val : nativeBackupValues) { backupValues.add(booleanToBytes(val)); } + + // serialize local state and prefs as json string + String jsonPrefs = ChromeBackupAgentImplJni.get().getLocalStatePrefJson(this); + Log.i(TAG, "--- local state prefs %s", jsonPrefs); + backupNames.add(NATIVE_LOCALSTATE_PREF_JSON); + backupValues.add(jsonPrefs.getBytes()); + + jsonPrefs = ChromeBackupAgentImplJni.get().getProfilePrefsJson(this); + Log.i(TAG, "--- prefs %s", jsonPrefs); + backupNames.add(NATIVE_PROFILE_PREF_JSON); + backupValues.add(jsonPrefs.getBytes()); + return true; }); - SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences(); if (!nativePrefsRead) { // Something went wrong reading the native preferences, skip the backup, but try again @@ -244,6 +284,17 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { } } + // Serialize all java SharedPreferences + Map allValues = sharedPrefs.getAll(); + for (Map.Entry entry : allValues.entrySet()) { + String prefName = entry.getKey(); + Object value = entry.getValue(); + byte[] serialized = convertToBytes(value); + + backupNames.add(SHARED_PREFS_PREFIX + prefName); + backupValues.add(serialized); + } + // Finally add the user id. backupNames.add(ANDROID_DEFAULT_PREFIX + SIGNED_IN_ACCOUNT_KEY); backupValues.add(ApiCompatibilityUtils.getBytesUtf8( @@ -286,10 +337,11 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { // 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"); + if (sharedPrefs.getBoolean(PREF_ALLOW_ANDROID_BACKUP, false) == false) { + // this is currently a problem. + // when the application is reinstalled, this method is launched automatically, and, since the default flag is false, I exit. + // a possible alternative is to check the flag in the backup. + Log.i(TAG, "Restore disabled by user"); return; } @@ -347,13 +399,6 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { return; } - // If the user hasn't signed in, or can't sign in, then don't restore anything. - if (!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 nativeBackupNames = new ArrayList<>(); @@ -363,9 +408,34 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { for (int i = 0; i < backupNames.size(); i++) { String name = backupNames.get(i); if (name.startsWith(NATIVE_PREF_PREFIX)) { + Log.i(TAG, "Restore attempted for %s", name); nativeBackupNames.add(name.substring(prefixLength)); nativeBackupValues[count] = bytesToBoolean(backupValues.get(i)); count++; + } else if (name.equals(NATIVE_LOCALSTATE_PREF_JSON)) { + try { + String value = new String(backupValues.get(i)); + if (ChromeBackupAgentImplJni.get().setLocalStatePrefJson(this, + value) == false) { + Log.e(TAG, "Failed to restore local state from native."); + } else { + Log.i(TAG, "Local state restored."); + } + } catch (Exception e) { + Log.e(TAG, "Failed to restore local state: %s", e.toString()); + } + } else if (name.equals(NATIVE_PROFILE_PREF_JSON)) { + try { + String value = new String(backupValues.get(i)); + if (ChromeBackupAgentImplJni.get().setProfilePrefsJson(this, + value) == false) { + Log.e(TAG, "Failed to restore profile prefs from native."); + } else { + Log.i(TAG, "Profile prefs restored."); + } + } catch (Exception e) { + Log.e(TAG, "Failed to restore profile prefs: %s", e.toString()); + } } } ChromeBackupAgentImplJni.get().setBoolBackupPrefs(this, @@ -386,6 +456,30 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { editor.putBoolean( name.substring(prefixLength), bytesToBoolean(backupValues.get(i))); } + else if (name.startsWith(SHARED_PREFS_PREFIX)) { + String prefName = name.substring(SHARED_PREFS_PREFIX.length()); + try { + if (sharedPrefs.contains(prefName)) { + Log.i(TAG, "Restoring %s", prefName); + Object value = convertFromBytes(backupValues.get(i)); + if (value instanceof String) { + editor.putString(prefName, (String)value); + } else if (value instanceof Integer) { + editor.putInt(prefName, (int)value); + } else if (value instanceof Long) { + editor.putLong(prefName, (long)value); + } else if (value instanceof Float) { + editor.putFloat(prefName, (float)value); + } else if (value instanceof Boolean) { + editor.putBoolean(prefName, (boolean)value); + } else { + Log.e(TAG, "Unknow type for %s", prefName); + } + } + } catch (Exception e) { + Log.e(TAG, "Failed to restore shared pref %s: %s", prefName, e.toString()); + } + } } // Because FirstRunSignInProcessor.FIRST_RUN_FLOW_SIGNIN_COMPLETE is not restored Chrome @@ -467,5 +561,9 @@ public class ChromeBackupAgentImpl extends ChromeBackupAgent.Impl { String[] getBoolBackupNames(ChromeBackupAgentImpl caller); boolean[] getBoolBackupValues(ChromeBackupAgentImpl caller); void setBoolBackupPrefs(ChromeBackupAgentImpl caller, String[] name, boolean[] value); + String getLocalStatePrefJson(ChromeBackupAgentImpl caller); + boolean setLocalStatePrefJson(ChromeBackupAgentImpl caller, String json); + String getProfilePrefsJson(ChromeBackupAgentImpl caller); + boolean setProfilePrefsJson(ChromeBackupAgentImpl caller, String json); } } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java --- a/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java @@ -4,6 +4,17 @@ package org.chromium.chrome.browser.about_settings; +import android.app.backup.BackupManager; +import android.app.backup.RestoreObserver; +import org.chromium.base.ContextUtils; +import org.chromium.base.StrictModeContext; +import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; +import org.chromium.chrome.browser.ui.messages.snackbar.INeedSnackbarManager; +import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; +import org.chromium.chrome.browser.ApplicationLifetime; +import android.os.Build; +import android.os.Build.VERSION_CODES; + import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; @@ -30,13 +41,15 @@ import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; */ public class AboutChromeSettings extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener, - Preference.OnPreferenceChangeListener { + Preference.OnPreferenceChangeListener, + INeedSnackbarManager { private static final int TAPS_FOR_DEVELOPER_SETTINGS = 7; private static final String PREF_APPLICATION_VERSION = "application_version"; private static final String PREF_ALLOW_INLINE_UPDATE = "allow_inline_update"; private static final String PREF_OS_VERSION = "os_version"; private static final String PREF_LEGAL_INFORMATION = "legal_information"; + private static final String PREF_ALLOW_ANDROID_BACKUP = "allow_android_backup"; // Non-translated strings: private static final String MSG_DEVELOPER_ENABLE_COUNTDOWN = @@ -51,6 +64,9 @@ public class AboutChromeSettings DeveloperSettings.shouldShowDeveloperSettings() ? -1 : TAPS_FOR_DEVELOPER_SETTINGS; private Toast mToast; + private SnackbarManager mSnackbarManager; + private Snackbar mSnackbar; + @Override public void onCreatePreferences(Bundle bundle, String s) { getActivity().setTitle(R.string.prefs_about_chrome); @@ -72,6 +88,58 @@ public class AboutChromeSettings OmahaBase.getSharedPreferences() .getBoolean(OmahaBase.PREF_ALLOW_INLINE_UPDATE, false)); allowInlineUpdate.setOnPreferenceChangeListener(this); + + mSnackbar = Snackbar.make(getActivity().getString(R.string.ui_relaunch_notice), + new SnackbarManager.SnackbarController() { + @Override + public void onDismissNoAction(Object actionData) { } + + @Override + public void onAction(Object actionData) { + ApplicationLifetime.terminate(true); + } + }, Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_UNKNOWN) + .setSingleLine(false) + .setAction(getActivity().getString(R.string.relaunch), + /*actionData*/null) + .setDuration(/*durationMs*/70000); + + Preference backupNow = findPreference("backup_now"); + backupNow.setOnPreferenceClickListener(preference -> { + // make a backup request + BackupManager backupManager = new BackupManager(ContextUtils.getApplicationContext()); + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + // note: this is a request + // it is not possible to know when android decides to actually launch the backup + backupManager.dataChanged(); + } + mToast = Toast.makeText(getActivity(), "Backup requested.", Toast.LENGTH_LONG); + mToast.show(); + return true; + }); + + Preference restoreNow = findPreference("restore_now"); + restoreNow.setOnPreferenceClickListener(preference -> { + // make a restore request + BackupManager backupManager = new BackupManager(ContextUtils.getApplicationContext()); + backupManager.requestRestore( + new RestoreObserver() { + public void restoreFinished(int error) { + if (!mSnackbarManager.isShowing()) + mSnackbarManager.showSnackbar(mSnackbar); + } + } + ); + return true; + }); + + // Since Android P app can no longer request restoring of its backup. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + restoreNow.getParent().removePreference(restoreNow); + } + + public void setSnackbarManager(SnackbarManager manager) { + mSnackbarManager = manager; } /** diff --git a/chrome/browser/android/chrome_backup_agent.cc b/chrome/browser/android/chrome_backup_agent.cc --- a/chrome/browser/android/chrome_backup_agent.cc +++ b/chrome/browser/android/chrome_backup_agent.cc @@ -15,6 +15,10 @@ #include "components/prefs/pref_service.h" #include "components/sync/base/pref_names.h" +#include "base/android/jni_string.h" +#include "base/json/json_string_value_serializer.h" +#include "chrome/browser/browser_process.h" + namespace { const char* backed_up_preferences_[] = { @@ -77,6 +81,99 @@ static void JNI_ChromeBackupAgentImpl_SetBoolBackupPrefs( prefs->CommitPendingWrite(); } +static base::android::ScopedJavaLocalRef + JNI_ChromeBackupAgentImpl_GetLocalStatePrefJson( + JNIEnv* env, + const base::android::JavaParamRef& jcaller) +{ + // Extract the local state and serialize to a string. + base::Value local_state = + g_browser_process->local_state()->GetPreferenceValues( + PrefService::EXCLUDE_DEFAULTS); + std::string serialized_settings; + JSONStringValueSerializer serializer(&serialized_settings); + serializer.set_pretty_print(false); + if (!serializer.Serialize(local_state)) + return base::android::ConvertUTF8ToJavaString(env, {}); + + return base::android::ConvertUTF8ToJavaString(env, serialized_settings); +} + +static jboolean JNI_ChromeBackupAgentImpl_SetLocalStatePrefJson( + JNIEnv* env, + const base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& json_java) +{ + std::string json = base::android::ConvertJavaStringToUTF8(json_java); + + JSONStringValueDeserializer deserializer(json); + + int error_code = 0; + std::string error_message; + std::unique_ptr value = + deserializer.Deserialize(&error_code, &error_message); + if (error_code != 0) { + LOG(ERROR) << "Failed to deserialize json: " << error_code + << ": " << error_message; + return false; + } + + auto unfiltered_prefs = std::make_unique(); + unfiltered_prefs.reset( + static_cast(value.release())); + g_browser_process->local_state()->OverridePrefs( + std::move(unfiltered_prefs)); + + return true; +} + +static base::android::ScopedJavaLocalRef + JNI_ChromeBackupAgentImpl_GetProfilePrefsJson( + JNIEnv* env, + const base::android::JavaParamRef& jcaller) +{ + // Extract the profile prefs and serialize to a string. + PrefService* prefs = + ProfileManager::GetLastUsedProfile()->GetOriginalProfile()->GetPrefs(); + base::Value prefs_state = + prefs->GetPreferenceValues( + PrefService::EXCLUDE_DEFAULTS); + std::string serialized_settings; + JSONStringValueSerializer serializer(&serialized_settings); + if (!serializer.Serialize(prefs_state)) + return base::android::ConvertUTF8ToJavaString(env, {}); + + return base::android::ConvertUTF8ToJavaString(env, serialized_settings); +} + +static jboolean JNI_ChromeBackupAgentImpl_SetProfilePrefsJson( + JNIEnv* env, + const base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& json_java) +{ + std::string json = base::android::ConvertJavaStringToUTF8(json_java); + + JSONStringValueDeserializer deserializer(json); + + int error_code = 0; + std::string error_message; + std::unique_ptr value = + deserializer.Deserialize(&error_code, &error_message); + if (error_code != 0) { + LOG(ERROR) << "Failed to deserialize json: " << error_code + << ": " << error_message; + return false; + } + + auto unfiltered_prefs = std::make_unique(); + unfiltered_prefs.reset( + static_cast(value.release())); + ProfileManager::GetLastUsedProfile()->GetOriginalProfile() + ->GetPrefs()->OverridePrefs(std::move(unfiltered_prefs)); + + return true; +} + namespace android { std::vector GetBackupPrefNames() { diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd @@ -1669,6 +1669,18 @@ Your Google account may have other forms of browsing history like searches and a Check for updates by contacting the Bromite repo + + Allow Android Backup + + + Activate Android backup manager + + + Backup now + + + Restore now + diff --git a/components/prefs/json_pref_store.cc b/components/prefs/json_pref_store.cc --- a/components/prefs/json_pref_store.cc +++ b/components/prefs/json_pref_store.cc @@ -558,6 +558,11 @@ void JsonPrefStore::FinalizeFileRead( return; } +void JsonPrefStore::OverridePrefs( + std::unique_ptr new_values) { + prefs_ = std::move(new_values); +} + void JsonPrefStore::ScheduleWrite(uint32_t flags) { if (read_only_) return; diff --git a/components/prefs/json_pref_store.h b/components/prefs/json_pref_store.h --- a/components/prefs/json_pref_store.h +++ b/components/prefs/json_pref_store.h @@ -123,6 +123,8 @@ class COMPONENTS_PREFS_EXPORT JsonPrefStore void OnStoreDeletionFromDisk() override; + void OverridePrefs(std::unique_ptr new_values) override; + #if defined(UNIT_TEST) base::ImportantFileWriter& get_writer() { return writer_; } #endif diff --git a/components/prefs/persistent_pref_store.h b/components/prefs/persistent_pref_store.h --- a/components/prefs/persistent_pref_store.h +++ b/components/prefs/persistent_pref_store.h @@ -8,6 +8,7 @@ #include "base/callback.h" #include "components/prefs/prefs_export.h" #include "components/prefs/writeable_pref_store.h" +#include "base/values.h" // This interface is complementary to the PrefStore interface, declaring // additional functionality that adds support for setting values and persisting @@ -89,6 +90,8 @@ class COMPONENTS_PREFS_EXPORT PersistentPrefStore : public WriteablePrefStore { // Cleans preference data that may have been saved outside of the store. virtual void OnStoreDeletionFromDisk() = 0; + virtual void OverridePrefs(std::unique_ptr new_values) {} + // TODO(crbug.com/942491) Remove this after fixing the bug. virtual bool IsInMemoryPrefStore() const; diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc --- a/components/prefs/pref_service.cc +++ b/components/prefs/pref_service.cc @@ -405,6 +405,10 @@ void PrefService::OnStoreDeletionFromDisk() { user_pref_store_->OnStoreDeletionFromDisk(); } +void PrefService::OverridePrefs(std::unique_ptr new_values) { + user_pref_store_->OverridePrefs(std::move(new_values)); +} + void PrefService::ChangePrefValueStore( PrefStore* managed_prefs, PrefStore* supervised_user_prefs, diff --git a/components/prefs/pref_service.h b/components/prefs/pref_service.h --- a/components/prefs/pref_service.h +++ b/components/prefs/pref_service.h @@ -380,6 +380,8 @@ class COMPONENTS_PREFS_EXPORT PrefService { void AddPrefObserverAllPrefs(PrefObserver* obs); void RemovePrefObserverAllPrefs(PrefObserver* obs); + void OverridePrefs(std::unique_ptr new_values); + #if defined(OS_ANDROID) base::android::ScopedJavaLocalRef GetJavaObject(); #endif -- 2.17.1