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 = 4; 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 BasePreferenceMigrator preferenceMigrator = 74 PreferenceMigratorHolder.createPreferenceMigrator(); 75 final boolean migrationComplete; 76 if (preferenceMigrator != null) { 77 migrationComplete = preferenceMigrator 78 .performMigration(context, oldVersion, CURRENT_VERSION_NUMBER); 79 } else { 80 LogUtils.w(LogUtils.TAG, "No preference migrator found, not migrating preferences"); 81 migrationComplete = false; 82 } 83 84 if (migrationComplete) { 85 setMigrationComplete(); 86 } 87 } 88 } 89 90 protected Context getContext() { 91 return mContext; 92 } 93 94 public String getSharedPreferencesName() { 95 return mSharedPreferencesName; 96 } 97 98 protected SharedPreferences getSharedPreferences() { 99 return mSharedPreferences; 100 } 101 102 protected Editor getEditor() { 103 return mEditor; 104 } 105 106 public void registerOnSharedPreferenceChangeListener( 107 SharedPreferences.OnSharedPreferenceChangeListener listener) { 108 mSharedPreferences.registerOnSharedPreferenceChangeListener(listener); 109 } 110 111 public void unregisterOnSharedPreferenceChangeListener( 112 SharedPreferences.OnSharedPreferenceChangeListener listener) { 113 mSharedPreferences.unregisterOnSharedPreferenceChangeListener(listener); 114 } 115 116 /** 117 * Returns the current version of the {@link SharedPreferences} file. 118 */ 119 private int getCurrentVersion() { 120 return mSharedPreferences.getInt(PREFS_VERSION_NUMBER, 0); 121 } 122 123 private void setCurrentVersion(final int versionNumber) { 124 getEditor().putInt(PREFS_VERSION_NUMBER, versionNumber); 125 126 /* 127 * If the only preference we have is the version number, we do not want to commit it. 128 * Instead, we will wait for some other preference to be written. This prevents us from 129 * creating a file with only the version number. 130 */ 131 if (shouldBackUp()) { 132 getEditor().apply(); 133 } 134 } 135 136 protected boolean hasMigrationCompleted() { 137 return MailPrefs.get(mContext).hasMigrationCompleted(); 138 } 139 140 protected void setMigrationComplete() { 141 MailPrefs.get(mContext).setMigrationComplete(); 142 } 143 144 /** 145 * Commits all pending changes to the preferences. 146 */ 147 public void commit() { 148 getEditor().commit(); 149 } 150 151 /** 152 * Upgrades the {@link SharedPreferences} file. 153 * 154 * @param oldVersion The current version 155 * @param newVersion The new version 156 */ 157 protected abstract void performUpgrade(int oldVersion, int newVersion); 158 159 @VisibleForTesting 160 public void clearAllPreferences() { 161 getEditor().clear().commit(); 162 } 163 164 protected abstract boolean canBackup(String key); 165 166 /** 167 * Gets the value to backup for a given key-value pair. By default, returns the passed in value. 168 * 169 * @param key The key to backup 170 * @param value The locally stored value for the given key 171 * @return The value to backup 172 */ 173 protected Object getBackupValue(final String key, final Object value) { 174 return value; 175 } 176 177 /** 178 * Gets the value to restore for a given key-value pair. By default, returns the passed in 179 * value. 180 * 181 * @param key The key to restore 182 * @param value The backed up value for the given key 183 * @return The value to restore 184 */ 185 protected Object getRestoreValue(final String key, final Object value) { 186 return value; 187 } 188 189 /** 190 * Return a list of shared preferences that should be backed up. 191 */ 192 public List<BackupSharedPreference> getBackupPreferences() { 193 final List<BackupSharedPreference> backupPreferences = Lists.newArrayList(); 194 final SharedPreferences sharedPreferences = getSharedPreferences(); 195 final Map<String, ?> preferences = sharedPreferences.getAll(); 196 197 for (final Map.Entry<String, ?> entry : preferences.entrySet()) { 198 final String key = entry.getKey(); 199 200 if (!canBackup(key)) { 201 continue; 202 } 203 204 final Object value = entry.getValue(); 205 final Object backupValue = getBackupValue(key, value); 206 207 if (backupValue != null) { 208 backupPreferences.add(new SimpleBackupSharedPreference(key, backupValue)); 209 } 210 } 211 212 return backupPreferences; 213 } 214 215 /** 216 * Restores preferences from a backup. 217 */ 218 public void restorePreferences(final List<BackupSharedPreference> preferences) { 219 for (final BackupSharedPreference preference : preferences) { 220 final String key = preference.getKey(); 221 final Object value = preference.getValue(); 222 223 if (!canBackup(key) || value == null) { 224 continue; 225 } 226 227 final Object restoreValue = getRestoreValue(key, value); 228 229 if (restoreValue instanceof Boolean) { 230 getEditor().putBoolean(key, (Boolean) restoreValue); 231 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 232 } else if (restoreValue instanceof Float) { 233 getEditor().putFloat(key, (Float) restoreValue); 234 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 235 } else if (restoreValue instanceof Integer) { 236 getEditor().putInt(key, (Integer) restoreValue); 237 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 238 } else if (restoreValue instanceof Long) { 239 getEditor().putLong(key, (Long) restoreValue); 240 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 241 } else if (restoreValue instanceof String) { 242 getEditor().putString(key, (String) restoreValue); 243 LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference); 244 } else if (restoreValue instanceof Set) { 245 getEditor().putStringSet(key, (Set<String>) restoreValue); 246 } else { 247 LogUtils.e(LOG_TAG, "Unknown MailPrefs preference data type: %s", value.getClass()); 248 } 249 } 250 251 getEditor().apply(); 252 } 253 254 /** 255 * <p> 256 * Checks if any of the preferences eligible for backup have been modified from their default 257 * values, and therefore should be backed up. 258 * </p> 259 * 260 * @return <code>true</code> if anything has been modified, <code>false</code> otherwise 261 */ 262 public boolean shouldBackUp() { 263 final Map<String, ?> allPrefs = getSharedPreferences().getAll(); 264 265 for (final String key : allPrefs.keySet()) { 266 if (canBackup(key)) { 267 return true; 268 } 269 } 270 271 return false; 272 } 273 274 /** 275 * Notifies {@link BackupManager} that we have new data to back up. 276 */ 277 protected void notifyBackupPreferenceChanged() { 278 MailIntentService.broadcastBackupDataChanged(getContext()); 279 } 280 } 281