1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content.cts; 18 19 import android.app.QueuedWork; 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.os.StrictMode; 25 import android.preference.PreferenceManager; 26 import android.test.AndroidTestCase; 27 import android.util.Log; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.util.HashMap; 33 import java.util.Map; 34 import java.util.Random; 35 36 /** 37 * Test {@link SharedPreferences}. 38 */ 39 public class SharedPreferencesTest extends AndroidTestCase { 40 private static final String TAG = "SharedPreferencesTest"; 41 42 private Context mContext; 43 44 private File mPrefsFile; 45 46 @Override 47 protected void setUp() throws Exception { 48 super.setUp(); 49 mContext = getContext(); 50 51 SharedPreferences prefs = getPrefs(); 52 prefs.edit().clear().commit(); 53 try { 54 ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo( 55 mContext.getPackageName(), 0); 56 mPrefsFile = new File(applicationInfo.dataDir, 57 "shared_prefs/android.content.cts_preferences.xml"); 58 } catch (PackageManager.NameNotFoundException e) { 59 throw new IllegalStateException(e); 60 } 61 mPrefsFile.delete(); 62 } 63 64 private SharedPreferences getPrefs() { 65 return PreferenceManager.getDefaultSharedPreferences(mContext); 66 } 67 68 public void testNoFileInitially() { 69 assertFalse(mPrefsFile.exists()); 70 } 71 72 public void testCommitCreatesFiles() { 73 SharedPreferences prefs = getPrefs(); 74 assertFalse(mPrefsFile.exists()); 75 prefs.edit().putString("foo", "bar").commit(); 76 assertTrue(mPrefsFile.exists()); 77 } 78 79 public void testDefaults() { 80 SharedPreferences prefs = getPrefs(); 81 String key = "not-set"; 82 assertFalse(prefs.contains(key)); 83 assertEquals(0, prefs.getAll().size()); 84 assertTrue(prefs.getAll().isEmpty()); 85 assertEquals(false, prefs.getBoolean(key, false)); 86 assertEquals(true, prefs.getBoolean(key, true)); 87 assertEquals(0.5f, prefs.getFloat(key, 0.5f)); 88 assertEquals(123, prefs.getInt(key, 123)); 89 assertEquals(999L, prefs.getLong(key, 999L)); 90 assertEquals("default", prefs.getString(key, "default")); 91 } 92 93 public void testPutNullRemovesKey() { 94 SharedPreferences prefs = getPrefs(); 95 prefs.edit().putString("test-key", "test-value").commit(); 96 assertEquals("test-value", prefs.getString("test-key", null)); 97 98 SharedPreferences.Editor editor = prefs.edit().putString("test-key", null); 99 assertEquals("test-value", prefs.getString("test-key", null)); 100 editor.commit(); 101 102 assertNull(prefs.getString("test-key", null)); 103 assertFalse(prefs.contains("test-key")); 104 } 105 106 private abstract class RedundantWriteTest { 107 // Do some initial operation on editor. No commit needed. 108 public abstract void setUp(SharedPreferences.Editor editor); 109 110 // Do some later operation on editor (e.g. a redundant edit). 111 // No commit needed. 112 public abstract void subsequentEdit(SharedPreferences.Editor editor); 113 114 public boolean expectingMutation() { 115 return false; 116 } 117 118 // Tests that a redundant edit after an initital setup doesn't 119 // result in a duplicate write-out to disk. 120 public final void test() throws Exception { 121 SharedPreferences prefs = getPrefs(); 122 SharedPreferences.Editor editor; 123 124 assertFalse(mPrefsFile.exists()); 125 prefs.edit().commit(); 126 assertTrue(mPrefsFile.exists()); 127 128 editor = prefs.edit(); 129 setUp(editor); 130 editor.commit(); 131 long modtimeMillis1 = mPrefsFile.lastModified(); 132 133 // Wait a second and modify the preferences in a dummy, 134 // redundant way. Wish I could inject a clock or disk mock 135 // here, but can't. Instead relying on checking modtime of 136 // file on disk. 137 Thread.sleep(1000); // ms 138 139 editor = prefs.edit(); 140 subsequentEdit(editor); 141 editor.commit(); 142 143 long modtimeMillis2 = mPrefsFile.lastModified(); 144 assertEquals(expectingMutation(), modtimeMillis1 != modtimeMillis2); 145 } 146 }; 147 148 public void testRedundantBoolean() throws Exception { 149 new RedundantWriteTest() { 150 public void setUp(SharedPreferences.Editor editor) { 151 editor.putBoolean("foo", true); 152 } 153 public void subsequentEdit(SharedPreferences.Editor editor) { 154 editor.putBoolean("foo", true); 155 } 156 }.test(); 157 } 158 159 public void testRedundantString() throws Exception { 160 new RedundantWriteTest() { 161 public void setUp(SharedPreferences.Editor editor) { 162 editor.putString("foo", "bar"); 163 } 164 public void subsequentEdit(SharedPreferences.Editor editor) { 165 editor.putString("foo", "bar"); 166 } 167 }.test(); 168 } 169 170 public void testNonRedundantString() throws Exception { 171 new RedundantWriteTest() { 172 public void setUp(SharedPreferences.Editor editor) { 173 editor.putString("foo", "bar"); 174 } 175 public void subsequentEdit(SharedPreferences.Editor editor) { 176 editor.putString("foo", "baz"); 177 } 178 public boolean expectingMutation() { 179 return true; 180 } 181 }.test(); 182 } 183 184 public void testRedundantClear() throws Exception { 185 new RedundantWriteTest() { 186 public void setUp(SharedPreferences.Editor editor) { 187 editor.clear(); 188 } 189 public void subsequentEdit(SharedPreferences.Editor editor) { 190 editor.clear(); 191 } 192 }.test(); 193 } 194 195 public void testNonRedundantClear() throws Exception { 196 new RedundantWriteTest() { 197 public void setUp(SharedPreferences.Editor editor) { 198 editor.putString("foo", "bar"); 199 } 200 public void subsequentEdit(SharedPreferences.Editor editor) { 201 editor.clear(); 202 } 203 public boolean expectingMutation() { 204 return true; 205 } 206 }.test(); 207 } 208 209 public void testRedundantRemove() throws Exception { 210 new RedundantWriteTest() { 211 public void setUp(SharedPreferences.Editor editor) { 212 editor.putString("foo", "bar"); 213 } 214 public void subsequentEdit(SharedPreferences.Editor editor) { 215 editor.remove("not-exist-key"); 216 } 217 }.test(); 218 } 219 220 public void testRedundantCommitWritesFileIfNotAlreadyExisting() { 221 SharedPreferences prefs = getPrefs(); 222 assertFalse(mPrefsFile.exists()); 223 prefs.edit().putString("foo", "bar").commit(); 224 assertTrue(mPrefsFile.exists()); 225 226 // Delete the file out from under it. (not sure why this 227 // would happen in practice, but perhaps the app did it for 228 // some reason...) 229 mPrefsFile.delete(); 230 231 // And verify that a redundant edit (which would otherwise not 232 // write do disk), still does write to disk if the file isn't 233 // there. 234 prefs.edit().putString("foo", "bar").commit(); 235 assertTrue(mPrefsFile.exists()); 236 } 237 238 public void testTorture() { 239 Map<String, String> expectedMap = new HashMap<String, String>(); 240 Random rand = new Random(); 241 242 SharedPreferences prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE); 243 prefs.edit().clear().commit(); 244 245 for (int i = 0; i < 100; i++) { 246 prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE); 247 assertEquals(expectedMap, prefs.getAll()); 248 249 String key = new Integer(rand.nextInt(25)).toString(); 250 String value = new Integer(i).toString(); 251 SharedPreferences.Editor editor = prefs.edit(); 252 253 if (rand.nextInt(100) < 85) { 254 Log.d(TAG, "Setting " + key + "=" + value); 255 editor.putString(key, value); 256 expectedMap.put(key, value); 257 } else { 258 Log.d(TAG, "Removing " + key); 259 editor.remove(key); 260 expectedMap.remove(key); 261 } 262 263 // Use apply on most, but commit some too. 264 if (rand.nextInt(100) < 85) { 265 Log.d(TAG, "apply."); 266 editor.apply(); 267 } else { 268 Log.d(TAG, "commit."); 269 editor.commit(); 270 } 271 272 assertEquals(expectedMap, prefs.getAll()); 273 } 274 } 275 276 // Checks that an in-memory commit doesn't mutate a data structure 277 // still being used while writing out to disk. 278 public void testTorture2() { 279 Random rand = new Random(); 280 281 for (int fi = 0; fi < 100; fi++) { 282 String prefsName = "torture_" + fi; 283 SharedPreferences prefs = mContext.getSharedPreferences(prefsName, Context.MODE_PRIVATE); 284 prefs.edit().clear().commit(); 285 Map<String, String> expectedMap = new HashMap<String, String>(); 286 287 for (int applies = 0; applies < 3; applies++) { 288 SharedPreferences.Editor editor = prefs.edit(); 289 for (int n = 0; n < 1000; n++) { 290 String key = new Integer(rand.nextInt(25)).toString(); 291 String value = new Integer(n).toString(); 292 editor.putString(key, value); 293 expectedMap.put(key, value); 294 } 295 editor.apply(); 296 } 297 QueuedWork.waitToFinish(); 298 299 String clonePrefsName = prefsName + "_clone"; 300 File prefsFile = mContext.getSharedPrefsFile(prefsName); 301 File prefsFileClone = mContext.getSharedPrefsFile(clonePrefsName); 302 prefsFileClone.delete(); 303 try { 304 FileOutputStream fos = new FileOutputStream(prefsFileClone); 305 FileInputStream fis = new FileInputStream(prefsFile); 306 byte buf[] = new byte[1024]; 307 int n; 308 while ((n = fis.read(buf)) > 0) { 309 fos.write(buf, 0, n); 310 } 311 fis.close(); 312 fos.close(); 313 } catch (IOException e) { 314 } 315 316 SharedPreferences clonePrefs = mContext.getSharedPreferences( 317 clonePrefsName, Context.MODE_PRIVATE); 318 assertEquals(expectedMap, clonePrefs.getAll()); 319 320 prefsFile.delete(); 321 prefsFileClone.delete(); 322 } 323 } 324 325 public void testModeMultiProcess() { 326 // Pre-load it. 327 mContext.getSharedPreferences("multiprocessTest", 0); 328 329 final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 330 try { 331 StrictMode.ThreadPolicy diskReadDeath = 332 new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyDeath().build(); 333 StrictMode.setThreadPolicy(diskReadDeath); 334 335 // This shouldn't hit disk. (it was already pre-loaded above) 336 mContext.getSharedPreferences("multiprocessTest", 0); 337 338 boolean didRead = false; 339 // This SHOULD hit disk. (multi-process flag is set) 340 try { 341 mContext.getSharedPreferences("multiprocessTest", Context.MODE_MULTI_PROCESS); 342 fail(); // we shouldn't get here. 343 } catch (StrictMode.StrictModeViolation e) { 344 didRead = true; 345 } 346 assertTrue(didRead); 347 } finally { 348 StrictMode.setThreadPolicy(oldPolicy); 349 } 350 } 351 } 352