1 /* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.preferences; 19 20 import com.google.common.annotations.VisibleForTesting; 21 import com.google.common.collect.Lists; 22 23 import android.app.backup.BackupManager; 24 import android.content.Context; 25 import android.content.SharedPreferences; 26 import android.content.SharedPreferences.Editor; 27 28 import com.android.mail.MailIntentService; 29 import com.android.mail.utils.LogTag; 30 import com.android.mail.utils.LogUtils; 31 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 36 /** 37 * A high-level API to store and retrieve preferences, that can be versioned in a similar manner as 38 * SQLite databases. You must not use the preference key 39 * {@value VersionedPrefs#PREFS_VERSION_NUMBER} 40 */ 41 public abstract class VersionedPrefs { 42 private final Context mContext; 43 private final String mSharedPreferencesName; 44 private final SharedPreferences mSharedPreferences; 45 private final Editor mEditor; 46 47 /** The key for the version number of the {@link SharedPreferences} file. */ 48 private static final String PREFS_VERSION_NUMBER = "prefs-version-number"; 49 50 /** 51 * The current version number for {@link SharedPreferences}. This is a constant for all 52 * applications based on UnifiedEmail. 53 */ 54 protected static final int CURRENT_VERSION_NUMBER = 3; 55 56 protected static final String LOG_TAG = LogTag.getLogTag(); 57 58 /** 59 * @param sharedPrefsName The name of the {@link SharedPreferences} file to use 60 */ 61 protected VersionedPrefs(final Context context, final String sharedPrefsName) { 62 mContext = context.getApplicationContext(); 63 mSharedPreferencesName = sharedPrefsName; 64 mSharedPreferences = context.getSharedPreferences(sharedPrefsName, Context.MODE_PRIVATE); 65 mEditor = mSharedPreferences.edit(); 66 67 final int oldVersion = getCurrentVersion(); 68 69 performUpgrade(oldVersion, CURRENT_VERSION_NUMBER); 70 setCurrentVersion(CURRENT_VERSION_NUMBER); 71 72 if (!hasMigrationCompleted()) { 73 final boolean migrationComplete = PreferenceMigratorHolder.createPreferenceMigrator() 74 .performMigration(context, oldVersion, CURRENT_VERSION_NUMBER); 75 76 if (migrationComplete) { 77 setMigrationComplete(); 78 } 79 } 80 } 81 82 protected Context getContext() { 83 return mContext; 84 } 85 86 public String getSharedPreferencesName() { 87 return mSharedPreferencesName; 88 } 89 90 protected SharedPreferences getSharedPreferences() { 91 return mSharedPreferences; 92 } 93 94 protected Editor getEditor() { 95 return mEditor; 96 } 97 98 /** 99 * Returns the current version of the {@link SharedPreferences} file. 100 */ 101 private int getCurrentVersion() { 102 return mSharedPreferences.getInt(PREFS_VERSION_NUMBER, 0); 103 } 104 105 private void setCurrentVersion(final int versionNumber) { 106 getEditor().putInt(PREFS_VERSION_NUMBER, versionNumber); 107 108 /* 109 * If the only preference we have is the version number, we do not want to commit it. 110 * Instead, we will wait for some other preference to be written. This prevents us from 111 * creating a file with only the version number. 112 */ 113 if (shouldBackUp()) { 114 getEditor().apply(); 115 } 116 } 117 118 protected boolean hasMigrationCompleted() { 119 return MailPrefs.get(mContext).hasMigrationCompleted(); 120 } 121 122 protected void setMigrationComplete() { 123 MailPrefs.get(mContext).setMigrationComplete(); 124 } 125 126 /** 127 * Commits all pending changes to the preferences. 128 */ 129 public void commit() { 130 getEditor().commit(); 131 } 132 133 /** 134 * Upgrades the {@link SharedPreferences} file. 135 * 136 * @param oldVersion The current version 137 * @param newVersion The new version 138 */ 139 protected abstract void performUpgrade(int oldVersion, int newVersion); 140 141 @VisibleForTesting 142 public void clearAllPreferences() { 143 getEditor().clear().commit(); 144 } 145 146 protected abstract boolean canBackup(String key); 147 148 /** 149 * Gets the value to backup for a given key-value pair. By default, returns the passed in value. 150 * 151 * @param key The key to backup 152 * @param value The locally stored value for the given key 153 * @return The value to backup 154 */ 155 protected Object getBackupValue(final String key, final Object value) { 156 return value; 157 } 158 159 /** 160 * Gets the value to restore for a given key-value pair. By default, returns the passed in 161 * value. 162 * 163 * @param key The key to restore 164 * @param value The backed up value for the given key 165 * @return The value to restore 166 */ 167 protected Object getRestoreValue(final String key, final Object value) { 168 return value; 169 } 170 171 /** 172 * Return a list of shared preferences that should be backed up. 173 */ 174 public List<BackupSharedPreference> getBackupPreferences() { 175 final List<BackupSharedPreference> backupPreferences = Lists.newArrayList(); 176 final SharedPreferences sharedPreferences = getSharedPreferences(); 177 final Map<String, ?> preferences = sharedPreferences.getAll(); 178 179 for (final Map.Entry<String, ?> entry : preferences.entrySet()) { 180 final String key = entry.getKey(); 181 182 if (!canBackup(key)) { 183 continue; 184 } 185 186 final Object value = entry.getValue(); 187 final Object backupValue = getBackupValue(key, value); 188 189 if (backupValue != null) { 190 backupPreferences.add(new SimpleBackupSharedPreference(key, backupValue)); 191 } 192 } 193 194 return backupPreferences; 195 } 196 197 /** 198 * Restores preferences from a backup. 199 */ 200 public void restorePreferences(final List<BackupSharedPreference> preferences) { 201 for (final BackupSharedPreference preference : preferences) { 202 final String key = preference.getKey(); 203 final Object value = preference.getValue(); 204 205 if (!canBackup(key) || value == null) { 206 continue; 207 } 208 209 final Object restoreValue = getRestoreValue(key, value); 210 211 if (restoreValue instanceof Boolean) { 212 getEditor().putBoolean(key, (Boolean) restoreValue); 213 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 214 } else if (restoreValue instanceof Float) { 215 getEditor().putFloat(key, (Float) restoreValue); 216 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 217 } else if (restoreValue instanceof Integer) { 218 getEditor().putInt(key, (Integer) restoreValue); 219 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 220 } else if (restoreValue instanceof Long) { 221 getEditor().putLong(key, (Long) restoreValue); 222 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 223 } else if (restoreValue instanceof String) { 224 getEditor().putString(key, (String) restoreValue); 225 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 226 } else if (restoreValue instanceof Set) { 227 getEditor().putStringSet(key, (Set<String>) restoreValue); 228 } else { 229 LogUtils.e(LOG_TAG, "Unknown MailPrefs preference data type: %s", value.getClass()); 230 } 231 } 232 233 getEditor().apply(); 234 } 235 236 /** 237 * <p> 238 * Checks if any of the preferences eligible for backup have been modified from their default 239 * values, and therefore should be backed up. 240 * </p> 241 * 242 * @return <code>true</code> if anything has been modified, <code>false</code> otherwise 243 */ 244 public boolean shouldBackUp() { 245 final Map<String, ?> allPrefs = getSharedPreferences().getAll(); 246 247 for (final String key : allPrefs.keySet()) { 248 if (canBackup(key)) { 249 return true; 250 } 251 } 252 253 return false; 254 } 255 256 /** 257 * Notifies {@link BackupManager} that we have new data to back up. 258 */ 259 protected void notifyBackupPreferenceChanged() { 260 MailIntentService.broadcastBackupDataChanged(getContext()); 261 } 262 } 263