package com.google.android.libraries.backup.shadow; import static android.content.Context.MODE_PRIVATE; import android.app.backup.SharedPreferencesBackupHelper; import android.content.Context; import android.content.SharedPreferences.Editor; import android.util.Log; import com.google.android.libraries.backup.PersistentBackupAgentHelper; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.lang.reflect.Field; import java.util.Map; import java.util.Set; /** * Representation of {@link SharedPreferencesBackupHelper} configuration used for testing. This * class simulates backing up and restoring shared preferences by storing them in memory. * *

{@see BackupAgentHelperShadow} */ public class SharedPreferencesBackupHelperSimulator extends BackupHelperSimulator { private static final String TAG = "SharedPreferencesBackup"; /** Shared preferences file names which should be backed up/restored. */ private final Set prefGroups; private SharedPreferencesBackupHelperSimulator(String keyPrefix, Set prefGroups) { super(keyPrefix); this.prefGroups = Preconditions.checkNotNull(prefGroups); } public static SharedPreferencesBackupHelperSimulator fromPreferenceGroups( String keyPrefix, Set prefGroups) { return new SharedPreferencesBackupHelperSimulator(keyPrefix, prefGroups); } public static SharedPreferencesBackupHelperSimulator fromHelper( String keyPrefix, SharedPreferencesBackupHelper helper) { return new SharedPreferencesBackupHelperSimulator( keyPrefix, extractPreferenceGroupsFromHelper(helper)); } @VisibleForTesting static Set extractPreferenceGroupsFromHelper(SharedPreferencesBackupHelper helper) { try { Field prefGroupsField = SharedPreferencesBackupHelper.class.getDeclaredField("mPrefGroups"); prefGroupsField.setAccessible(true); return ImmutableSet.copyOf((String[]) prefGroupsField.get(helper)); } catch (ReflectiveOperationException e) { throw new IllegalStateException( "Failed to construct SharedPreferencesBackupHelperSimulator", e); } } /** Collection of backed up shared preferences. */ public static class SharedPreferencesBackupData { /** Map from shared preferences file names to key-value preference maps. */ private final Map> preferences; public SharedPreferencesBackupData(Map> data) { this.preferences = Preconditions.checkNotNull(data); } @Override public boolean equals(Object obj) { return obj instanceof SharedPreferencesBackupData && preferences.equals(((SharedPreferencesBackupData) obj).preferences); } @Override public int hashCode() { return preferences.hashCode(); } public Map> getPreferences() { return preferences; } } @Override public Object backup(Context context) { ImmutableMap.Builder> dataToBackupBuilder = ImmutableMap.builder(); for (String prefGroup : prefGroups) { Map prefs = context.getSharedPreferences(prefGroup, MODE_PRIVATE).getAll(); if (prefs.isEmpty()) { Log.w(TAG, "Shared prefs \"" + prefGroup + "\" are empty. The helper \"" + keyPrefix + "\" assumes this is due to a missing (rather than empty) shared preferences file."); continue; } ImmutableMap.Builder prefsData = ImmutableMap.builder(); for (Map.Entry prefEntry : prefs.entrySet()) { String key = prefEntry.getKey(); Object value = prefEntry.getValue(); if (value instanceof Set) { value = ImmutableSet.copyOf((Set) value); } prefsData.put(key, value); } dataToBackupBuilder.put(prefGroup, prefsData.build()); } return new SharedPreferencesBackupData(dataToBackupBuilder.build()); } @Override public void restore(Context context, Object data) { if (!(data instanceof SharedPreferencesBackupData)) { throw new IllegalArgumentException("Invalid type of files to restore in helper \"" + keyPrefix + "\": " + data.getClass()); } Map> prefsToRestore = ((SharedPreferencesBackupData) data).getPreferences(); // Display a warning when missing/empty preferences are restored onto non-empty preferences. for (String prefGroup : prefGroups) { if (context.getSharedPreferences(prefGroup, MODE_PRIVATE).getAll().isEmpty()) { continue; } Map prefsData = prefsToRestore.get(prefGroup); if (prefsData == null) { Log.w(TAG, "Non-empty shared prefs \"" + prefGroup + "\" will NOT be cleared by helper \"" + keyPrefix + "\" because the corresponding file is missing in the restored data."); } else if (prefsData.isEmpty()) { Log.w(TAG, "Non-empty shared prefs \"" + prefGroup + "\" will be cleared by helper \"" + keyPrefix + "\" because the corresponding file is empty in the restored data."); } } for (Map.Entry> restoreEntry : prefsToRestore.entrySet()) { String prefGroup = restoreEntry.getKey(); if (!prefGroups.contains(prefGroup)) { Log.w(TAG, "Shared prefs \"" + prefGroup + "\" ignored by helper \"" + keyPrefix + "\"."); continue; } Map prefsData = restoreEntry.getValue(); Editor editor = context.getSharedPreferences(prefGroup, MODE_PRIVATE).edit().clear(); for (Map.Entry prefEntry : prefsData.entrySet()) { PersistentBackupAgentHelper.putSharedPreference( editor, prefEntry.getKey(), prefEntry.getValue()); } editor.apply(); } } }