blob: 45b9005474c1ffc59d1a245f1b6cf24d43fdff84 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.cts;
import android.app.QueuedWork;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.test.AndroidTestCase;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Test {@link SharedPreferences}.
*/
public class SharedPreferencesTest extends AndroidTestCase {
private static final String TAG = "SharedPreferencesTest";
private Context mContext;
private File mPrefsFile;
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getContext();
SharedPreferences prefs = getPrefs();
prefs.edit().clear().commit();
try {
ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
mContext.getPackageName(), PackageManager.ApplicationInfoFlags.of(0));
mPrefsFile = new File(applicationInfo.dataDir,
"shared_prefs/android.content.cts_preferences.xml");
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(e);
}
mPrefsFile.delete();
}
private SharedPreferences getPrefs() {
return PreferenceManager.getDefaultSharedPreferences(mContext);
}
public void testNoFileInitially() {
assertFalse(mPrefsFile.exists());
}
public void testCommitCreatesFiles() {
SharedPreferences prefs = getPrefs();
assertFalse(mPrefsFile.exists());
prefs.edit().putString("foo", "bar").commit();
assertTrue(mPrefsFile.exists());
}
public void testDefaults() {
SharedPreferences prefs = getPrefs();
String key = "not-set";
assertFalse(prefs.contains(key));
assertEquals(0, prefs.getAll().size());
assertTrue(prefs.getAll().isEmpty());
assertEquals(false, prefs.getBoolean(key, false));
assertEquals(true, prefs.getBoolean(key, true));
assertEquals(0.5f, prefs.getFloat(key, 0.5f));
assertEquals(123, prefs.getInt(key, 123));
assertEquals(999L, prefs.getLong(key, 999L));
assertEquals("default", prefs.getString(key, "default"));
}
public void testPutNullRemovesKey() {
SharedPreferences prefs = getPrefs();
prefs.edit().putString("test-key", "test-value").commit();
assertEquals("test-value", prefs.getString("test-key", null));
SharedPreferences.Editor editor = prefs.edit().putString("test-key", null);
assertEquals("test-value", prefs.getString("test-key", null));
editor.commit();
assertNull(prefs.getString("test-key", null));
assertFalse(prefs.contains("test-key"));
}
private abstract class RedundantWriteTest {
// Do some initial operation on editor. No commit needed.
public abstract void setUp(SharedPreferences.Editor editor);
// Do some later operation on editor (e.g. a redundant edit).
// No commit needed.
public abstract void subsequentEdit(SharedPreferences.Editor editor);
public boolean expectingMutation() {
return false;
}
// Tests that a redundant edit after an initital setup doesn't
// result in a duplicate write-out to disk.
public final void test() throws Exception {
SharedPreferences prefs = getPrefs();
SharedPreferences.Editor editor;
assertFalse(mPrefsFile.exists());
prefs.edit().commit();
assertTrue(mPrefsFile.exists());
editor = prefs.edit();
setUp(editor);
editor.commit();
long modtimeMillis1 = mPrefsFile.lastModified();
// Wait a second and modify the preferences in a dummy,
// redundant way. Wish I could inject a clock or disk mock
// here, but can't. Instead relying on checking modtime of
// file on disk.
Thread.sleep(1000); // ms
editor = prefs.edit();
subsequentEdit(editor);
editor.commit();
long modtimeMillis2 = mPrefsFile.lastModified();
assertEquals(expectingMutation(), modtimeMillis1 != modtimeMillis2);
}
};
public void testRedundantBoolean() throws Exception {
new RedundantWriteTest() {
public void setUp(SharedPreferences.Editor editor) {
editor.putBoolean("foo", true);
}
public void subsequentEdit(SharedPreferences.Editor editor) {
editor.putBoolean("foo", true);
}
}.test();
}
public void testRedundantString() throws Exception {
new RedundantWriteTest() {
public void setUp(SharedPreferences.Editor editor) {
editor.putString("foo", "bar");
}
public void subsequentEdit(SharedPreferences.Editor editor) {
editor.putString("foo", "bar");
}
}.test();
}
public void testNonRedundantString() throws Exception {
new RedundantWriteTest() {
public void setUp(SharedPreferences.Editor editor) {
editor.putString("foo", "bar");
}
public void subsequentEdit(SharedPreferences.Editor editor) {
editor.putString("foo", "baz");
}
public boolean expectingMutation() {
return true;
}
}.test();
}
public void testRedundantClear() throws Exception {
new RedundantWriteTest() {
public void setUp(SharedPreferences.Editor editor) {
editor.clear();
}
public void subsequentEdit(SharedPreferences.Editor editor) {
editor.clear();
}
}.test();
}
public void testNonRedundantClear() throws Exception {
new RedundantWriteTest() {
public void setUp(SharedPreferences.Editor editor) {
editor.putString("foo", "bar");
}
public void subsequentEdit(SharedPreferences.Editor editor) {
editor.clear();
}
public boolean expectingMutation() {
return true;
}
}.test();
}
public void testRedundantRemove() throws Exception {
new RedundantWriteTest() {
public void setUp(SharedPreferences.Editor editor) {
editor.putString("foo", "bar");
}
public void subsequentEdit(SharedPreferences.Editor editor) {
editor.remove("not-exist-key");
}
}.test();
}
public void testRedundantCommitWritesFileIfNotAlreadyExisting() {
SharedPreferences prefs = getPrefs();
assertFalse(mPrefsFile.exists());
prefs.edit().putString("foo", "bar").commit();
assertTrue(mPrefsFile.exists());
// Delete the file out from under it. (not sure why this
// would happen in practice, but perhaps the app did it for
// some reason...)
mPrefsFile.delete();
// And verify that a redundant edit (which would otherwise not
// write do disk), still does write to disk if the file isn't
// there.
prefs.edit().putString("foo", "bar").commit();
assertTrue(mPrefsFile.exists());
}
public void testTorture() {
Map<String, String> expectedMap = new HashMap<String, String>();
Random rand = new Random();
SharedPreferences prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE);
prefs.edit().clear().commit();
for (int i = 0; i < 100; i++) {
prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE);
assertEquals(expectedMap, prefs.getAll());
String key = new Integer(rand.nextInt(25)).toString();
String value = new Integer(i).toString();
SharedPreferences.Editor editor = prefs.edit();
if (rand.nextInt(100) < 85) {
Log.d(TAG, "Setting " + key + "=" + value);
editor.putString(key, value);
expectedMap.put(key, value);
} else {
Log.d(TAG, "Removing " + key);
editor.remove(key);
expectedMap.remove(key);
}
// Use apply on most, but commit some too.
if (rand.nextInt(100) < 85) {
Log.d(TAG, "apply.");
editor.apply();
} else {
Log.d(TAG, "commit.");
editor.commit();
}
assertEquals(expectedMap, prefs.getAll());
}
}
// Checks that an in-memory commit doesn't mutate a data structure
// still being used while writing out to disk.
public void testTorture2() {
Random rand = new Random();
for (int fi = 0; fi < 100; fi++) {
String prefsName = "torture_" + fi;
SharedPreferences prefs = mContext.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
prefs.edit().clear().commit();
Map<String, String> expectedMap = new HashMap<String, String>();
for (int applies = 0; applies < 3; applies++) {
SharedPreferences.Editor editor = prefs.edit();
for (int n = 0; n < 1000; n++) {
String key = new Integer(rand.nextInt(25)).toString();
String value = new Integer(n).toString();
editor.putString(key, value);
expectedMap.put(key, value);
}
editor.apply();
}
QueuedWork.waitToFinish();
String clonePrefsName = prefsName + "_clone";
File prefsFile = mContext.getSharedPrefsFile(prefsName);
File prefsFileClone = mContext.getSharedPrefsFile(clonePrefsName);
prefsFileClone.delete();
try {
FileOutputStream fos = new FileOutputStream(prefsFileClone);
FileInputStream fis = new FileInputStream(prefsFile);
byte buf[] = new byte[1024];
int n;
while ((n = fis.read(buf)) > 0) {
fos.write(buf, 0, n);
}
fis.close();
fos.close();
} catch (IOException e) {
}
SharedPreferences clonePrefs = mContext.getSharedPreferences(
clonePrefsName, Context.MODE_PRIVATE);
assertEquals(expectedMap, clonePrefs.getAll());
prefsFile.delete();
prefsFileClone.delete();
}
}
public void testModeMultiProcess() throws InterruptedException {
// Pre-load it.
mContext.getSharedPreferences("multiprocessTest", 0);
final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
StrictMode.ThreadPolicy diskReadDeath =
new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build();
StrictMode.setThreadPolicy(diskReadDeath);
final CountDownLatch latch = new CountDownLatch(1);
StrictMode.setViolationLogger(info -> latch.countDown());
// This shouldn't hit disk. (it was already pre-loaded above)
mContext.getSharedPreferences("multiprocessTest", 0);
boolean triggered = latch.await(1, TimeUnit.SECONDS);
assertFalse(triggered);
// This SHOULD hit disk. (multi-process flag is set)
mContext.getSharedPreferences("multiprocessTest", Context.MODE_MULTI_PROCESS);
triggered = latch.await(1, TimeUnit.SECONDS);
assertTrue(triggered);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
public void testSharedPrefsChangeListenerIsCalledOnCommit() throws InterruptedException {
// Setup on change listener
final CountDownLatch latch = new CountDownLatch(2);
final SharedPreferences.OnSharedPreferenceChangeListener listener =
(sharedPreferences, key) -> latch.countDown();
final SharedPreferences prefs = getPrefs();
try {
prefs.registerOnSharedPreferenceChangeListener(listener);
prefs.edit().putString("test-key", "test-value").commit(); // latch--
prefs.edit().remove("test-key").commit(); // latch--
assertTrue("OnSharedPreferenceChangeListener was not fired on #commit",
latch.await(10, TimeUnit.SECONDS));
} finally {
prefs.unregisterOnSharedPreferenceChangeListener(listener);
}
}
public void testSharedPrefsChangeListenerIsCalledOnApply() throws InterruptedException {
// Setup on change listener
final CountDownLatch latch = new CountDownLatch(2);
final SharedPreferences.OnSharedPreferenceChangeListener listener =
(sharedPreferences, key) -> latch.countDown();
final SharedPreferences prefs = getPrefs();
try {
prefs.registerOnSharedPreferenceChangeListener(listener);
prefs.edit().putString("test-key", "test-value").apply(); // latch--
prefs.edit().remove("test-key").apply(); // latch--
assertTrue("OnSharedPreferenceChangeListener was not fired on #apply",
latch.await(10, TimeUnit.SECONDS));
} finally {
prefs.unregisterOnSharedPreferenceChangeListener(listener);
}
}
public void testSharedPrefsChangeListenerIsCalledForClearOnCommit()
throws InterruptedException {
// Setup on change listener
final CountDownLatch latch = new CountDownLatch(1);
final SharedPreferences.OnSharedPreferenceChangeListener listener =
(sharedPreferences, key) -> {
if (key == null) {
latch.countDown();
}
};
final SharedPreferences prefs = getPrefs();
try {
prefs.registerOnSharedPreferenceChangeListener(listener);
prefs.edit().putString("test-key", "test-value").commit();
assertEquals("test-value", prefs.getString("test-key", null));
prefs.edit().clear().commit(); // latch--
assertTrue("OnSharedPreferenceChangeListener was not fired for clear() on #commit",
latch.await(10, TimeUnit.SECONDS));
} finally {
prefs.unregisterOnSharedPreferenceChangeListener(listener);
}
}
public void testSharedPrefsChangeListenerIsCalledForClearOnApply() throws InterruptedException {
// Setup on change listener
final CountDownLatch latch = new CountDownLatch(1);
final SharedPreferences.OnSharedPreferenceChangeListener listener =
(sharedPreferences, key) -> {
if (key == null) {
latch.countDown();
}
};
final SharedPreferences prefs = getPrefs();
try {
prefs.registerOnSharedPreferenceChangeListener(listener);
prefs.edit().putString("test-key", "test-value").commit();
assertEquals("test-value", prefs.getString("test-key", null));
prefs.edit().clear().apply(); // latch--
assertTrue("OnSharedPreferenceChangeListener was not fired for clear() on #apply",
latch.await(10, TimeUnit.SECONDS));
} finally {
prefs.unregisterOnSharedPreferenceChangeListener(listener);
}
}
}