summaryrefslogtreecommitdiffstats
path: root/packages/SettingsProvider/test
diff options
context:
space:
mode:
authorSvetoslav <svetoslavganov@google.com>2015-01-15 14:22:26 -0800
committerSvetoslav <svetoslavganov@google.com>2015-02-11 17:58:22 -0800
commit683914bfb13908bf380a25258cd45bcf43f13dc9 (patch)
tree8dbece2e42872875b0b3c4972bdcbf0ef24fbdc7 /packages/SettingsProvider/test
parent6e08723f65ee3393115c3288bc475e7362cb9117 (diff)
downloadframeworks_base-683914bfb13908bf380a25258cd45bcf43f13dc9.zip
frameworks_base-683914bfb13908bf380a25258cd45bcf43f13dc9.tar.gz
frameworks_base-683914bfb13908bf380a25258cd45bcf43f13dc9.tar.bz2
Rewrite of the settings provider.
This change modifies how global, secure, and system settings are managed. In particular, we are moving away from the database to an in-memory model where the settings are persisted asynchronously to XML. This simplifies evolution and improves performance, for example, changing a setting is down from around 400 ms to 10 ms as we do not hit the disk. The trade off is that we may lose data if the system dies before persisting the change. In practice this is not a problem because 1) this is very rare; 2) apps changing a setting use the setting itself to know if it changed, so next time the app runs (after a reboot that lost data) the app will be oblivious that data was lost. When persisting the settings we delay the write a bit to batch multiple changes. If a change occurs we reschedule the write but when a maximal delay occurs after the first non-persisted change we write to disk no matter what. This prevents a malicious app poking the settings all the time to prevent them being persisted. The settings are persisted in separate XML files for each type of setting per user. Specifically, they are in the user's system directory and the files are named: settings_type_of_settings.xml. Data migration is performed after the data base is upgraded to its last version after which the global, system, and secure tables are dropped. The global, secure, and system settings now have the same version and are upgraded as a whole per user to allow migration of settings between these them. The upgrade steps should be added to the SettingsProvider.UpgradeController and not in the DatabaseHelper. Setting states are mapped to an integer key derived from the user id and the setting type. Therefore, all setting states are in a lookup table which makes all opertions very fast. The code is a complete rewrite aiming for improved clarity and increased maintainability as opposed to using minor optimizations. Now setting and getting the changed setting takes around 10 ms. We can optimize later if needed. Now the code path through the call API and the one through the content provider APIs end up being the same which fixes bugs where some enterprise cases were not implemented in the content provider code path. Note that we are keeping the call code path as it is a bit faster than the provider APIs with about 2 ms for setting and getting a setting. The front-end settings APIs use the call method. Further, we are restricting apps writing to the system settings. If the app is targeting API higher than Lollipop MR1 we do not let them have their settings in the system ones. Otherwise, we warn that this will become an error. System apps like GMS core can change anything like the system or shell or root. Since old apps can add their settings, this can increase the system memory footprint with no limit. Therefore, we limit the amount of settings data an app can write to the system settings before starting to reject new data. Another problem with the system settings was that an app with a permission to write there can put invalid values for the settings. We now have validators for these settings that ensure only valid values are accepted. Since apps can put their settings in the system table, when the app is uninstalled this data is stale in the sytem table without ever being used. Now we keep the package that last changed the setting and when the package is removed all settings it touched that are not in the ones defined in the APIs are dropped. Keeping in memory settings means that we cannot handle arbitrary SQL operations, rather the supported operations are on a single setting by name and all settings (querying). This should not be a problem in practice but we have to verify it. For that reason, we log unsupported SQL operations to the event log to do some crunching and see what if any cases we should additionally support. There are also tests for the settings provider in this change. Change-Id: I941dc6e567588d9812905b147dbe1a3191c8dd68
Diffstat (limited to 'packages/SettingsProvider/test')
-rw-r--r--packages/SettingsProvider/test/Android.mk13
-rw-r--r--packages/SettingsProvider/test/AndroidManifest.xml35
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java185
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java121
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java407
5 files changed, 761 insertions, 0 deletions
diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk
new file mode 100644
index 0000000..01c6ccf
--- /dev/null
+++ b/packages/SettingsProvider/test/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SettingsProviderTest
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE) \ No newline at end of file
diff --git a/packages/SettingsProvider/test/AndroidManifest.xml b/packages/SettingsProvider/test/AndroidManifest.xml
new file mode 100644
index 0000000..7a86b5f
--- /dev/null
+++ b/packages/SettingsProvider/test/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.setting.test">
+
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
+
+ <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <uses-permission android:name="android.permission.MANAGE_USERS"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.providers.setting.test"
+ android:label="Settings Provider Tests" />
+</manifest>
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
new file mode 100644
index 0000000..f713c33
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 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 com.android.providers.settings;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+/**
+ * Base class for the SettingContentProvider tests.
+ */
+abstract class BaseSettingsProviderTest extends AndroidTestCase {
+ protected static final int SETTING_TYPE_GLOBAL = 1;
+ protected static final int SETTING_TYPE_SECURE = 2;
+ protected static final int SETTING_TYPE_SYSTEM = 3;
+
+ protected static final String FAKE_SETTING_NAME = "fake_setting_name";
+ protected static final String FAKE_SETTING_NAME_1 = "fake_setting_name1";
+ protected static final String FAKE_SETTING_VALUE = "fake_setting_value";
+ protected static final String FAKE_SETTING_VALUE_1 = "fake_setting_value_1";
+
+ private static final String[] NAME_VALUE_COLUMNS = new String[] {
+ Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE
+ };
+
+ protected int mSecondaryUserId = UserHandle.USER_OWNER;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ List<UserInfo> users = userManager.getUsers();
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ UserInfo user = users.get(i);
+ if (!user.isPrimary() && !user.isManagedProfile()) {
+ mSecondaryUserId = user.id;
+ break;
+ }
+ }
+ }
+
+ protected void setStringViaFrontEndApiSetting(int type, String name, String value, int userId) {
+ ContentResolver contentResolver = getContext().getContentResolver();
+
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ Settings.Global.putStringForUser(contentResolver, name, value, userId);
+ } break;
+
+ case SETTING_TYPE_SECURE: {
+ Settings.Secure.putStringForUser(contentResolver, name, value, userId);
+ } break;
+
+ case SETTING_TYPE_SYSTEM: {
+ Settings.System.putStringForUser(contentResolver, name, value, userId);
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected String getStringViaFrontEndApiSetting(int type, String name, int userId) {
+ ContentResolver contentResolver = getContext().getContentResolver();
+
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ return Settings.Global.getStringForUser(contentResolver, name, userId);
+ }
+
+ case SETTING_TYPE_SECURE: {
+ return Settings.Secure.getStringForUser(contentResolver, name, userId);
+ }
+
+ case SETTING_TYPE_SYSTEM: {
+ return Settings.System.getStringForUser(contentResolver, name, userId);
+ }
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected Uri insertStringViaProviderApi(int type, String name, String value,
+ boolean withTableRowUri) {
+ Uri uri = getBaseUriForType(type);
+ if (withTableRowUri) {
+ uri = Uri.withAppendedPath(uri, name);
+ }
+ ContentValues values = new ContentValues();
+ values.put(Settings.NameValueTable.NAME, name);
+ values.put(Settings.NameValueTable.VALUE, value);
+
+ return getContext().getContentResolver().insert(uri, values);
+ }
+
+ protected int deleteStringViaProviderApi(int type, String name) {
+ Uri uri = getBaseUriForType(type);
+ return getContext().getContentResolver().delete(uri, "name=?", new String[]{name});
+ }
+
+ protected int updateStringViaProviderApiSetting(int type, String name, String value) {
+ Uri uri = getBaseUriForType(type);
+ ContentValues values = new ContentValues();
+ values.put(Settings.NameValueTable.NAME, name);
+ values.put(Settings.NameValueTable.VALUE, value);
+ return getContext().getContentResolver().update(uri, values, "name=?",
+ new String[]{name});
+ }
+
+ protected String queryStringViaProviderApi(int type, String name) {
+ return queryStringViaProviderApi(type, name, false);
+ }
+
+ protected String queryStringViaProviderApi(int type, String name, boolean queryStringInQuotes) {
+ Uri uri = getBaseUriForType(type);
+
+ String queryString = queryStringInQuotes ? "(name=?)" : "name=?";
+
+ Cursor cursor = getContext().getContentResolver().query(uri, NAME_VALUE_COLUMNS,
+ queryString, new String[]{name}, null);
+
+ if (cursor == null) {
+ return null;
+ }
+
+ try {
+ if (cursor.moveToFirst()) {
+ final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+ return cursor.getString(valueColumnIdx);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ protected static Uri getBaseUriForType(int type) {
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ return Settings.Global.CONTENT_URI;
+ }
+
+ case SETTING_TYPE_SECURE: {
+ return Settings.Secure.CONTENT_URI;
+ }
+
+ case SETTING_TYPE_SYSTEM: {
+ return Settings.System.CONTENT_URI;
+ }
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java
new file mode 100644
index 0000000..d581f3b
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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 com.android.providers.settings;
+
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+* Performance tests for the SettingContentProvider.
+*/
+public class SettingsProviderPerformanceTest extends BaseSettingsProviderTest {
+ private static final String LOG_TAG = "SettingsProviderPerformanceTest";
+
+ private static final int ITERATION_COUNT = 100;
+
+ private static final int MICRO_SECONDS_IN_MILLISECOND = 1000;
+
+ private static final long MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS = 20;
+
+ public void testSetAndGetPerformanceForGlobalViaFrontEndApi() throws Exception {
+ // Start with a clean slate.
+ insertStringViaProviderApi(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+
+ final long startTimeMicro = SystemClock.currentTimeMicro();
+
+ try {
+ for (int i = 0; i < ITERATION_COUNT; i++) {
+ // Set the setting to its first value.
+ updateStringViaProviderApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE);
+
+ // Make sure the setting changed.
+ String firstValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE, firstValue);
+
+ // Set the setting to its second value.
+ updateStringViaProviderApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE_1);
+
+ // Make sure the setting changed.
+ String secondValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE_1, secondValue);
+ }
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+ }
+
+ final long elapsedTimeMicro = SystemClock.currentTimeMicro() - startTimeMicro;
+
+ final long averageTimePerIterationMillis = (long) ((((float) elapsedTimeMicro)
+ / ITERATION_COUNT) / MICRO_SECONDS_IN_MILLISECOND);
+
+ Log.i(LOG_TAG, "Average time to set and get setting via provider APIs: "
+ + averageTimePerIterationMillis + " ms");
+
+ assertTrue("Setting and getting a settings takes too long.", averageTimePerIterationMillis
+ < MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS);
+ }
+
+ public void testSetAndGetPerformanceForGlobalViaProviderApi() throws Exception {
+ // Start with a clean slate.
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+
+ final long startTimeMicro = SystemClock.currentTimeMicro();
+
+ try {
+ for (int i = 0; i < ITERATION_COUNT; i++) {
+ // Set the setting to its first value.
+ setStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, UserHandle.USER_OWNER);
+
+ // Make sure the setting changed.
+ String firstValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE, firstValue);
+
+ // Set the setting to its second value.
+ setStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE_1, UserHandle.USER_OWNER);
+
+ // Make sure the setting changed.
+ String secondValue = getStringViaFrontEndApiSetting(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, UserHandle.USER_OWNER);
+ assertEquals("Setting value didn't change", FAKE_SETTING_VALUE_1, secondValue);
+ }
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+ }
+
+ final long elapsedTimeMicro = SystemClock.currentTimeMicro() - startTimeMicro;
+
+ final long averageTimePerIterationMillis = (long) ((((float) elapsedTimeMicro)
+ / ITERATION_COUNT) / MICRO_SECONDS_IN_MILLISECOND);
+
+ Log.i(LOG_TAG, "Average time to set and get setting via front-eng APIs: "
+ + averageTimePerIterationMillis + " ms");
+
+ assertTrue("Setting and getting a settings takes too long.", averageTimePerIterationMillis
+ < MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS);
+ }
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
new file mode 100644
index 0000000..cbfcbf5
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2015 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 com.android.providers.settings;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tests for the SettingContentProvider.
+ *
+ * Before you run this test you must add a secondary user.
+ */
+public class SettingsProviderTest extends BaseSettingsProviderTest {
+ private static final String LOG_TAG = "SettingsProviderTest";
+
+ private static final long WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+
+ private static final String[] NAME_VALUE_COLUMNS = new String[]{
+ Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE
+ };
+
+ private final Object mLock = new Object();
+
+ public void testSetAndGetGlobalViaFrontEndApiForOwnerUser() throws Exception {
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, UserHandle.USER_OWNER);
+ }
+
+ public void testSetAndGetGlobalViaFrontEndApiForNonOwnerUser() throws Exception {
+ if (mSecondaryUserId == UserHandle.USER_OWNER) {
+ Log.w(LOG_TAG, "No secondary user. Skipping "
+ + "testSetAndGetGlobalViaFrontEndApiForNonOwnerUser");
+ return;
+ }
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, mSecondaryUserId);
+ }
+
+ public void testSetAndGetSecureViaFrontEndApiForOwnerUser() throws Exception {
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, UserHandle.USER_OWNER);
+ }
+
+ public void testSetAndGetSecureViaFrontEndApiForNonOwnerUser() throws Exception {
+ if (mSecondaryUserId == UserHandle.USER_OWNER) {
+ Log.w(LOG_TAG, "No secondary user. Skipping "
+ + "testSetAndGetSecureViaFrontEndApiForNonOwnerUser");
+ return;
+ }
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, mSecondaryUserId);
+ }
+
+ public void testSetAndGetSystemViaFrontEndApiForOwnerUser() throws Exception {
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, UserHandle.USER_OWNER);
+ }
+
+ public void testSetAndGetSystemViaFrontEndApiForNonOwnerUser() throws Exception {
+ if (mSecondaryUserId == UserHandle.USER_OWNER) {
+ Log.w(LOG_TAG, "No secondary user. Skipping "
+ + "testSetAndGetSystemViaFrontEndApiForNonOwnerUser");
+ return;
+ }
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, mSecondaryUserId);
+ }
+
+ public void testSetAndGetGlobalViaProviderApi() throws Exception {
+ performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testSetAndGetSecureViaProviderApi() throws Exception {
+ performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SECURE);
+ }
+
+ public void testSetAndGetSystemViaProviderApi() throws Exception {
+ performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SYSTEM);
+ }
+
+ public void testSelectAllGlobalViaProviderApi() throws Exception {
+ setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+ try {
+ queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_GLOBAL,
+ FAKE_SETTING_NAME);
+ } finally {
+ deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
+ }
+ }
+
+ public void testSelectAllSecureViaProviderApi() throws Exception {
+ setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SECURE,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+ try {
+ queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_SECURE,
+ FAKE_SETTING_NAME);
+ } finally {
+ deleteStringViaProviderApi(SETTING_TYPE_SECURE, FAKE_SETTING_NAME);
+ }
+ }
+
+ public void testSelectAllSystemViaProviderApi() throws Exception {
+ setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SYSTEM,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, true);
+ try {
+ queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_SYSTEM,
+ FAKE_SETTING_NAME);
+ } finally {
+ deleteStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME);
+ }
+ }
+
+ public void testQueryUpdateDeleteGlobalViaProviderApi() throws Exception {
+ doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testQueryUpdateDeleteSecureViaProviderApi() throws Exception {
+ doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SECURE);
+ }
+
+ public void testQueryUpdateDeleteSystemViaProviderApi() throws Exception {
+ doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SYSTEM);
+ }
+
+ public void testBulkInsertGlobalViaProviderApi() throws Exception {
+ toTestBulkInsertViaProviderApiForType(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testBulkInsertSystemViaProviderApi() throws Exception {
+ toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SYSTEM);
+ }
+
+ public void testBulkInsertSecureViaProviderApi() throws Exception {
+ toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SECURE);
+ }
+
+ public void testAppCannotRunsSystemOutOfMemoryWritingSystemSettings() throws Exception {
+ int insertedCount = 0;
+ try {
+ for (; insertedCount < 1200; insertedCount++) {
+ Log.w(LOG_TAG, "Adding app specific setting: " + insertedCount);
+ insertStringViaProviderApi(SETTING_TYPE_SYSTEM,
+ String.valueOf(insertedCount), FAKE_SETTING_VALUE, false);
+ }
+ fail("Adding app specific settings must be bound.");
+ } catch (Exception e) {
+ for (; insertedCount >= 0; insertedCount--) {
+ Log.w(LOG_TAG, "Removing app specific setting: " + insertedCount);
+ deleteStringViaProviderApi(SETTING_TYPE_SYSTEM,
+ String.valueOf(insertedCount));
+ }
+ }
+ }
+
+ public void testQueryStringInBracketsGlobalViaProviderApiForType() throws Exception {
+ doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_GLOBAL);
+ }
+
+ public void testQueryStringInBracketsSecureViaProviderApiForType() throws Exception {
+ doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SECURE);
+ }
+
+ public void testQueryStringInBracketsSystemViaProviderApiForType() throws Exception {
+ doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SYSTEM);
+ }
+
+ private void doTestQueryStringInBracketsViaProviderApiForType(int type) {
+ // Make sure we have a clean slate.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+
+ try {
+ // Insert the setting.
+ final Uri uri = insertStringViaProviderApi(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, false);
+ Uri expectUri = Uri.withAppendedPath(getBaseUriForType(type), FAKE_SETTING_NAME);
+ assertEquals("Did not get expected Uri.", expectUri, uri);
+
+ // Make sure the first setting is there.
+ String firstValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME, true);
+ assertEquals("Setting must be present", FAKE_SETTING_VALUE, firstValue);
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+ }
+ }
+
+ private void toTestBulkInsertViaProviderApiForType(int type) {
+ // Make sure we have a clean slate.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
+
+ try {
+ Uri uri = getBaseUriForType(type);
+ ContentValues[] allValues = new ContentValues[2];
+
+ // Insert the first setting.
+ ContentValues firstValues = new ContentValues();
+ firstValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME);
+ firstValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE);
+ allValues[0] = firstValues;
+
+ // Insert the first setting.
+ ContentValues secondValues = new ContentValues();
+ secondValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME_1);
+ secondValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE_1);
+ allValues[1] = secondValues;
+
+ // Verify insertion count.
+ final int insertCount = getContext().getContentResolver().bulkInsert(uri, allValues);
+ assertSame("Couldn't insert both values", 2, insertCount);
+
+ // Make sure the first setting is there.
+ String firstValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertEquals("First setting must be present", FAKE_SETTING_VALUE, firstValue);
+
+ // Make sure the second setting is there.
+ String secondValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME_1);
+ assertEquals("Second setting must be present", FAKE_SETTING_VALUE_1, secondValue);
+ } finally {
+ // Clean up.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
+ }
+ }
+
+ private void doTestQueryUpdateDeleteGlobalViaProviderApiForType(int type) throws Exception {
+ // Make sure it is not there.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
+
+ // Now selection should return nothing.
+ String value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertNull("Setting should not be present.", value);
+
+ // Insert the setting.
+ Uri uri = insertStringViaProviderApi(type,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
+ Uri expectUri = Uri.withAppendedPath(getBaseUriForType(type), FAKE_SETTING_NAME);
+ assertEquals("Did not get expected Uri.", expectUri, uri);
+
+ // Now selection should return the setting.
+ value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertEquals("Setting should be present.", FAKE_SETTING_VALUE, value);
+
+ // Update the setting.
+ final int changeCount = updateStringViaProviderApiSetting(type,
+ FAKE_SETTING_NAME, FAKE_SETTING_VALUE_1);
+ assertEquals("Did not get expected change count.", 1, changeCount);
+
+ // Now selection should return the new setting.
+ value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertEquals("Setting should be present.", FAKE_SETTING_VALUE_1, value);
+
+ // Delete the setting.
+ final int deletedCount = deleteStringViaProviderApi(type,
+ FAKE_SETTING_NAME);
+ assertEquals("Did not get expected deleted count", 1, deletedCount);
+
+ // Now selection should return nothing.
+ value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
+ assertNull("Setting should not be present.", value);
+ }
+
+ private void performSetAndGetSettingTestViaFrontEndApi(int type, int userId)
+ throws Exception {
+ try {
+ // Change the setting and assert a successful change.
+ setSettingViaFrontEndApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, userId);
+ } finally {
+ // Remove the setting.
+ setStringViaFrontEndApiSetting(type, FAKE_SETTING_NAME, null, userId);
+ }
+ }
+
+ private void performSetAndGetSettingTestViaProviderApi(int type)
+ throws Exception {
+ try {
+ // Change the setting and assert a successful change.
+ setSettingViaProviderApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, true);
+ } finally {
+ // Remove the setting.
+ setSettingViaProviderApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME, null,
+ true);
+ }
+ }
+
+ private void setSettingViaFrontEndApiAndAssertSuccessfulChange(final int type,
+ final String name, final String value, final int userId) throws Exception {
+ setSettingAndAssertSuccessfulChange(new Runnable() {
+ @Override
+ public void run() {
+ setStringViaFrontEndApiSetting(type, name, value, userId);
+ }
+ }, type, name, value, userId);
+ }
+
+ private void setSettingViaProviderApiAndAssertSuccessfulChange(final int type,
+ final String name, final String value, final boolean withTableRowUri)
+ throws Exception {
+ setSettingAndAssertSuccessfulChange(new Runnable() {
+ @Override
+ public void run() {
+ insertStringViaProviderApi(type, name, value, withTableRowUri);
+ }
+ }, type, name, value, UserHandle.USER_OWNER);
+ }
+
+ private void setSettingAndAssertSuccessfulChange(Runnable setCommand, final int type,
+ final String name, final String value, final int userId) throws Exception {
+ ContentResolver contentResolver = getContext().getContentResolver();
+
+ final Uri settingUri = getBaseUriForType(type);
+
+ final AtomicBoolean success = new AtomicBoolean();
+
+ ContentObserver contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ public void onChange(boolean selfChange, Uri changeUri, int changeId) {
+ Log.i(LOG_TAG, "onChange(" + selfChange + ", " + changeUri + ", " + changeId + ")");
+ assertEquals("Wrong change Uri", changeUri, settingUri);
+ assertEquals("Wrong user id", userId, changeId);
+ String changeValue = getStringViaFrontEndApiSetting(type, name, userId);
+ assertEquals("Wrong setting value", value, changeValue);
+
+ success.set(true);
+
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ }
+ };
+
+ contentResolver.registerContentObserver(settingUri, false, contentObserver, userId);
+
+ try {
+ setCommand.run();
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ if (success.get()) {
+ return;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ if (elapsedTimeMillis > WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS) {
+ fail("Could not change setting for "
+ + WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS + " ms");
+ }
+ final long remainingTimeMillis = WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS
+ - elapsedTimeMillis;
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ contentResolver.unregisterContentObserver(contentObserver);
+ }
+ }
+
+ private void queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(int type,
+ String name) {
+ Uri uri = getBaseUriForType(type);
+
+ Cursor cursor = getContext().getContentResolver().query(uri, NAME_VALUE_COLUMNS,
+ null, null, null);
+
+ if (cursor == null || !cursor.moveToFirst()) {
+ fail("Nothing selected");
+ }
+
+ try {
+ final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+
+ while (cursor.moveToNext()) {
+ String currentName = cursor.getString(nameColumnIdx);
+ if (name.equals(currentName)) {
+ return;
+ }
+ }
+
+ fail("Not found setting: " + name);
+ } finally {
+ cursor.close();
+ }
+ }
+}