summaryrefslogtreecommitdiffstats
path: root/src/com/google/android/libraries/backup/shadow/SharedPreferencesBackupHelperSimulator.java
blob: 407f3f0cb4746d3c19624384a8913d97c85929c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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.
 *
 * <p>{@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<String> prefGroups;

  private SharedPreferencesBackupHelperSimulator(String keyPrefix, Set<String> prefGroups) {
    super(keyPrefix);
    this.prefGroups = Preconditions.checkNotNull(prefGroups);
  }

  public static SharedPreferencesBackupHelperSimulator fromPreferenceGroups(
      String keyPrefix, Set<String> prefGroups) {
    return new SharedPreferencesBackupHelperSimulator(keyPrefix, prefGroups);
  }

  public static SharedPreferencesBackupHelperSimulator fromHelper(
      String keyPrefix, SharedPreferencesBackupHelper helper) {
    return new SharedPreferencesBackupHelperSimulator(
        keyPrefix, extractPreferenceGroupsFromHelper(helper));
  }

  @VisibleForTesting
  static Set<String> 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<String, Map<String, ?>> preferences;

    public SharedPreferencesBackupData(Map<String, Map<String, ?>> 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<String, Map<String, ?>> getPreferences() {
      return preferences;
    }
  }

  @Override
  public Object backup(Context context) {
    ImmutableMap.Builder<String, Map<String, ?>> dataToBackupBuilder = ImmutableMap.builder();
    for (String prefGroup : prefGroups) {
      Map<String, ?> 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<String, Object> prefsData = ImmutableMap.builder();
      for (Map.Entry<String, ?> 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<String, Map<String, ?>> 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<String, ?> 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<String, Map<String, ?>> restoreEntry : prefsToRestore.entrySet()) {
      String prefGroup = restoreEntry.getKey();
      if (!prefGroups.contains(prefGroup)) {
        Log.w(TAG, "Shared prefs \"" + prefGroup + "\" ignored by helper \"" + keyPrefix + "\".");
        continue;
      }
      Map<String, ?> prefsData = restoreEntry.getValue();
      Editor editor = context.getSharedPreferences(prefGroup, MODE_PRIVATE).edit().clear();
      for (Map.Entry<String, ?> prefEntry : prefsData.entrySet()) {
        PersistentBackupAgentHelper.putSharedPreference(
            editor, prefEntry.getKey(), prefEntry.getValue());
      }
      editor.apply();
    }
  }
}