Home | History | Annotate | Download | only in shadow
      1 package com.google.android.libraries.backup.shadow;
      2 
      3 import static android.content.Context.MODE_PRIVATE;
      4 
      5 import android.app.backup.SharedPreferencesBackupHelper;
      6 import android.content.Context;
      7 import android.content.SharedPreferences.Editor;
      8 import android.util.Log;
      9 import com.google.android.libraries.backup.PersistentBackupAgentHelper;
     10 import com.google.common.annotations.VisibleForTesting;
     11 import com.google.common.base.Preconditions;
     12 import com.google.common.collect.ImmutableMap;
     13 import com.google.common.collect.ImmutableSet;
     14 import java.lang.reflect.Field;
     15 import java.util.Map;
     16 import java.util.Set;
     17 
     18 /**
     19  * Representation of {@link SharedPreferencesBackupHelper} configuration used for testing. This
     20  * class simulates backing up and restoring shared preferences by storing them in memory.
     21  *
     22  * <p>{@see BackupAgentHelperShadow}
     23  */
     24 public class SharedPreferencesBackupHelperSimulator extends BackupHelperSimulator {
     25   private static final String TAG = "SharedPreferencesBackup";
     26 
     27   /** Shared preferences file names which should be backed up/restored. */
     28   private final Set<String> prefGroups;
     29 
     30   private SharedPreferencesBackupHelperSimulator(String keyPrefix, Set<String> prefGroups) {
     31     super(keyPrefix);
     32     this.prefGroups = Preconditions.checkNotNull(prefGroups);
     33   }
     34 
     35   public static SharedPreferencesBackupHelperSimulator fromPreferenceGroups(
     36       String keyPrefix, Set<String> prefGroups) {
     37     return new SharedPreferencesBackupHelperSimulator(keyPrefix, prefGroups);
     38   }
     39 
     40   public static SharedPreferencesBackupHelperSimulator fromHelper(
     41       String keyPrefix, SharedPreferencesBackupHelper helper) {
     42     return new SharedPreferencesBackupHelperSimulator(
     43         keyPrefix, extractPreferenceGroupsFromHelper(helper));
     44   }
     45 
     46   @VisibleForTesting
     47   static Set<String> extractPreferenceGroupsFromHelper(SharedPreferencesBackupHelper helper) {
     48     try {
     49       Field prefGroupsField = SharedPreferencesBackupHelper.class.getDeclaredField("mPrefGroups");
     50       prefGroupsField.setAccessible(true);
     51       return ImmutableSet.copyOf((String[]) prefGroupsField.get(helper));
     52     } catch (ReflectiveOperationException e) {
     53       throw new IllegalStateException(
     54           "Failed to construct SharedPreferencesBackupHelperSimulator", e);
     55     }
     56   }
     57 
     58   /** Collection of backed up shared preferences. */
     59   public static class SharedPreferencesBackupData {
     60     /** Map from shared preferences file names to key-value preference maps. */
     61     private final Map<String, Map<String, ?>> preferences;
     62 
     63     public SharedPreferencesBackupData(Map<String, Map<String, ?>> data) {
     64       this.preferences = Preconditions.checkNotNull(data);
     65     }
     66 
     67     @Override
     68     public boolean equals(Object obj) {
     69       return obj instanceof SharedPreferencesBackupData
     70           && preferences.equals(((SharedPreferencesBackupData) obj).preferences);
     71     }
     72 
     73     @Override
     74     public int hashCode() {
     75       return preferences.hashCode();
     76     }
     77 
     78     public Map<String, Map<String, ?>> getPreferences() {
     79       return preferences;
     80     }
     81   }
     82 
     83   @Override
     84   public Object backup(Context context) {
     85     ImmutableMap.Builder<String, Map<String, ?>> dataToBackupBuilder = ImmutableMap.builder();
     86     for (String prefGroup : prefGroups) {
     87       Map<String, ?> prefs = context.getSharedPreferences(prefGroup, MODE_PRIVATE).getAll();
     88       if (prefs.isEmpty()) {
     89         Log.w(TAG, "Shared prefs \"" + prefGroup + "\" are empty. The helper \"" + keyPrefix
     90             + "\" assumes this is due to a missing (rather than empty) shared preferences file.");
     91         continue;
     92       }
     93       ImmutableMap.Builder<String, Object> prefsData = ImmutableMap.builder();
     94       for (Map.Entry<String, ?> prefEntry : prefs.entrySet()) {
     95         String key = prefEntry.getKey();
     96         Object value = prefEntry.getValue();
     97         if (value instanceof Set) {
     98           value = ImmutableSet.copyOf((Set<?>) value);
     99         }
    100         prefsData.put(key, value);
    101       }
    102       dataToBackupBuilder.put(prefGroup, prefsData.build());
    103     }
    104     return new SharedPreferencesBackupData(dataToBackupBuilder.build());
    105   }
    106 
    107   @Override
    108   public void restore(Context context, Object data) {
    109     if (!(data instanceof SharedPreferencesBackupData)) {
    110       throw new IllegalArgumentException("Invalid type of files to restore in helper \""
    111           + keyPrefix + "\": " + data.getClass());
    112     }
    113 
    114     Map<String, Map<String, ?>> prefsToRestore =
    115         ((SharedPreferencesBackupData) data).getPreferences();
    116 
    117     // Display a warning when missing/empty preferences are restored onto non-empty preferences.
    118     for (String prefGroup : prefGroups) {
    119       if (context.getSharedPreferences(prefGroup, MODE_PRIVATE).getAll().isEmpty()) {
    120         continue;
    121       }
    122       Map<String, ?> prefsData = prefsToRestore.get(prefGroup);
    123       if (prefsData == null) {
    124         Log.w(TAG, "Non-empty shared prefs \"" + prefGroup + "\" will NOT be cleared by helper \""
    125             + keyPrefix + "\" because the corresponding file is missing in the restored data.");
    126       } else if (prefsData.isEmpty()) {
    127         Log.w(TAG, "Non-empty shared prefs \"" + prefGroup + "\" will be cleared by helper \""
    128             + keyPrefix + "\" because the corresponding file is empty in the restored data.");
    129       }
    130     }
    131 
    132     for (Map.Entry<String, Map<String, ?>> restoreEntry : prefsToRestore.entrySet()) {
    133       String prefGroup = restoreEntry.getKey();
    134       if (!prefGroups.contains(prefGroup)) {
    135         Log.w(TAG, "Shared prefs \"" + prefGroup + "\" ignored by helper \"" + keyPrefix + "\".");
    136         continue;
    137       }
    138       Map<String, ?> prefsData = restoreEntry.getValue();
    139       Editor editor = context.getSharedPreferences(prefGroup, MODE_PRIVATE).edit().clear();
    140       for (Map.Entry<String, ?> prefEntry : prefsData.entrySet()) {
    141         PersistentBackupAgentHelper.putSharedPreference(
    142             editor, prefEntry.getKey(), prefEntry.getValue());
    143       }
    144       editor.apply();
    145     }
    146   }
    147 }
    148