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