summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/CMSettingsProvider/Android.mk2
-rw-r--r--packages/CMSettingsProvider/AndroidManifest.xml12
-rw-r--r--packages/CMSettingsProvider/res/values/defaults.xml57
-rw-r--r--packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java173
-rw-r--r--packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java444
-rw-r--r--packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java56
-rw-r--r--packages/CMSettingsProvider/tests/AndroidManifest.xml4
-rw-r--r--packages/CMSettingsProvider/tests/src/org/cyanogenmod/cmsettings/tests/CMSettingsProviderTest.java240
-rw-r--r--src/java/cyanogenmod/providers/CMSettings.java472
-rw-r--r--tests/AndroidManifest.xml1
-rw-r--r--tests/src/org/cyanogenmod/tests/providers/CMSettingsTest.java70
11 files changed, 1351 insertions, 180 deletions
diff --git a/packages/CMSettingsProvider/Android.mk b/packages/CMSettingsProvider/Android.mk
index 1d60321..7994ea0 100644
--- a/packages/CMSettingsProvider/Android.mk
+++ b/packages/CMSettingsProvider/Android.mk
@@ -27,7 +27,7 @@ LOCAL_PACKAGE_NAME := CMSettingsProvider
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_JAVA_LIBRARIES := \
+LOCAL_STATIC_JAVA_LIBRARIES := \
org.cyanogenmod.platform.sdk
include $(BUILD_PACKAGE)
diff --git a/packages/CMSettingsProvider/AndroidManifest.xml b/packages/CMSettingsProvider/AndroidManifest.xml
index b46fefc..201a6c9 100644
--- a/packages/CMSettingsProvider/AndroidManifest.xml
+++ b/packages/CMSettingsProvider/AndroidManifest.xml
@@ -31,12 +31,22 @@
android:allowClearUserData="false"
android:enabled="true">
- <provider android:name="CMSettingsProvider" android:authorities="cmsettings"
+ <provider android:name="CMSettingsProvider"
+ android:authorities="cmsettings"
android:multiprocess="false"
android:exported="true"
android:writePermission="cyanogenmod.permission.WRITE_SETTINGS"
android:singleUser="true"
android:initOrder="100" />
+ <receiver android:name="PreBootReceiver" android:enabled="true">
+ <!-- This broadcast is sent after the core system has finished
+ booting, before the home app is launched or BOOT_COMPLETED
+ is sent. -->
+ <intent-filter>
+ <action android:name="android.intent.action.PRE_BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+
</application>
</manifest>
diff --git a/packages/CMSettingsProvider/res/values/defaults.xml b/packages/CMSettingsProvider/res/values/defaults.xml
new file mode 100644
index 0000000..3b2d741
--- /dev/null
+++ b/packages/CMSettingsProvider/res/values/defaults.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014-2015 The CyanogenMod 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.
+-->
+<resources>
+ <!-- Defaults for System -->
+
+ <!-- Default for CMSettings.System.QS_QUICK_PULLDOWN
+ 0. Off
+ 1. Right
+ 2. Left -->
+ <integer name="def_qs_quick_pulldown">0</integer>
+
+ <!-- Defaults for Secure -->
+
+ <!-- Default for CMSettings.Secure.ADVANCED_MODE -->
+ <bool name="def_advanced_mode">true</bool>
+
+ <!-- Default for CMSettings.Secure.DEFAULT_THEME_COMPONENTS -->
+ <string name="def_theme_components"></string>
+
+ <!-- Default for CMSettings.Secure.DEFAULT_THEME_PACKAGE -->
+ <string name="def_theme_package"></string>
+
+ <!-- Defaults for CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR -->
+ <integer name="def_force_show_navbar">0</integer>
+
+ <!-- Default for CMSettings.Secure.QS_TILES
+ Comma-delimited, quick settings tiles. See QSConstants.java for a list of all available tiles -->
+ <string name="def_qs_tiles">wifi,bt,cell,airplane,rotation,flashlight,location,cast,visualizer,hotspot,live_display</string>
+
+ <!-- Default for CMSettings.Secure.STATS_COLLECTION -->
+ <bool name="def_stats_collection">false</bool>
+
+ <!-- Defaults for Global -->
+
+ <!-- Default for CMSettings.Global.DEVICE_NAME
+ $1=MODEL -->
+ <string name="def_device_name">%1$s</string>
+
+ <!-- Default for CMSettings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
+ 1==on -->
+ <integer name="def_heads_up_enabled">1</integer>
+
+</resources> \ No newline at end of file
diff --git a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java
index 85fbaa9..2016378 100644
--- a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java
+++ b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java
@@ -16,12 +16,16 @@
package org.cyanogenmod.cmsettings;
+import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Build;
import android.os.Environment;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
+import cyanogenmod.providers.CMSettings;
import java.io.File;
@@ -34,7 +38,7 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
private static final boolean LOCAL_LOGV = false;
private static final String DATABASE_NAME = "cmsettings.db";
- private static final int DATABASE_VERSION = 1;
+ private static final int DATABASE_VERSION = 2;
static class CMTableNames {
static final String TABLE_SYSTEM = "system";
@@ -50,6 +54,11 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
private static final String CREATE_INDEX_SQL_FORMAT = "CREATE INDEX %sIndex%d ON %s (name);";
+ private static final String DROP_TABLE_SQL_FORMAT = "DROP TABLE IF EXISTS %s;";
+
+ private static final String DROP_INDEX_SQL_FORMAT = "DROP INDEX IF EXISTS %sIndex%d;";
+
+ private Context mContext;
private int mUserHandle;
/**
@@ -77,11 +86,13 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
*/
public CMDatabaseHelper(Context context, int userId) {
super(context, dbNameForUser(userId), null, DATABASE_VERSION);
+ mContext = context;
mUserHandle = userId;
}
/**
- * Creates System, Secure, and Global tables in the specified {@link SQLiteDatabase}
+ * Creates System, Secure, and Global tables in the specified {@link SQLiteDatabase} and loads
+ * default values into the created tables.
* @param db The database.
*/
@Override
@@ -96,9 +107,11 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
createDbTable(db, CMTableNames.TABLE_GLOBAL);
}
+ loadSettings(db);
+
db.setTransactionSuccessful();
- if (LOCAL_LOGV) Log.v(TAG, "Successfully created tables for cm settings db");
+ if (LOCAL_LOGV) Log.d(TAG, "Successfully created tables for cm settings db");
} finally {
db.endTransaction();
}
@@ -106,11 +119,11 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
/**
* Creates a table and index for the specified database and table name
- * @param db
- * @param tableName
+ * @param db The {@link SQLiteDatabase} to create the table and index in.
+ * @param tableName The name of the database table to create.
*/
private void createDbTable(SQLiteDatabase db, String tableName) {
- if (LOCAL_LOGV) Log.v(TAG, "Creating table and index for: " + tableName);
+ if (LOCAL_LOGV) Log.d(TAG, "Creating table and index for: " + tableName);
String createTableSql = String.format(CREATE_TABLE_SQL_FORMAT, tableName);
db.execSQL(createTableSql);
@@ -121,6 +134,154 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ int upgradeVersion = oldVersion;
+
+ if (upgradeVersion < 2) {
+ db.beginTransaction();
+ try {
+ loadSettings(db);
+
+ db.setTransactionSuccessful();
+
+ upgradeVersion = 2;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ // *** Remember to update DATABASE_VERSION above!
+
+ if (upgradeVersion < newVersion) {
+ Log.w(TAG, "Got stuck trying to upgrade db. Old version: " + oldVersion
+ + ", version stuck at: " + upgradeVersion + ", new version: "
+ + newVersion + ". Must wipe the cm settings provider.");
+
+ dropDbTable(db, CMTableNames.TABLE_SYSTEM);
+ dropDbTable(db, CMTableNames.TABLE_SECURE);
+
+ if (mUserHandle == UserHandle.USER_OWNER) {
+ dropDbTable(db, CMTableNames.TABLE_GLOBAL);
+ }
+
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Drops the table and index for the specified database and table name
+ * @param db The {@link SQLiteDatabase} to drop the table and index in.
+ * @param tableName The name of the database table to drop.
+ */
+ private void dropDbTable(SQLiteDatabase db, String tableName) {
+ if (LOCAL_LOGV) Log.d(TAG, "Dropping table and index for: " + tableName);
+
+ String dropTableSql = String.format(DROP_TABLE_SQL_FORMAT, tableName);
+ db.execSQL(dropTableSql);
+
+ String dropIndexSql = String.format(DROP_INDEX_SQL_FORMAT, tableName, 1);
+ db.execSQL(dropIndexSql);
+ }
+
+ /**
+ * Loads default values for specific settings into the database.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ */
+ private void loadSettings(SQLiteDatabase db) {
+ // System
+ loadIntegerSetting(db, CMTableNames.TABLE_SYSTEM, CMSettings.System.QS_QUICK_PULLDOWN,
+ R.integer.def_qs_quick_pulldown);
+
+ // Secure
+ loadBooleanSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.ADVANCED_MODE,
+ R.bool.def_advanced_mode);
+
+ loadStringSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.DEFAULT_THEME_COMPONENTS,
+ R.string.def_theme_components);
+ loadStringSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.DEFAULT_THEME_PACKAGE,
+ R.string.def_theme_package);
+
+ loadIntegerSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR,
+ R.integer.def_force_show_navbar);
+
+ loadStringSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.QS_TILES,
+ R.string.def_qs_tiles);
+
+ loadBooleanSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.STATS_COLLECTION,
+ R.bool.def_stats_collection);
+
+ // Global
+ if (mUserHandle == UserHandle.USER_OWNER) {
+ loadSettingsForTable(db, CMTableNames.TABLE_GLOBAL, CMSettings.Global.DEVICE_NAME,
+ getDefaultDeviceName());
+
+ loadIntegerSetting(db, CMTableNames.TABLE_GLOBAL,
+ CMSettings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ R.integer.def_heads_up_enabled);
+ }
+ }
+
+ /**
+ * Loads a string resource into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param resId The name of the string resource.
+ */
+ private void loadStringSetting(SQLiteDatabase db, String tableName, String name, int resId) {
+ loadSettingsForTable(db, tableName, name, mContext.getResources().getString(resId));
+ }
+
+ /**
+ * Loads a boolean resource into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param resId The name of the boolean resource.
+ */
+ private void loadBooleanSetting(SQLiteDatabase db, String tableName, String name, int resId) {
+ loadSettingsForTable(db, tableName, name,
+ mContext.getResources().getBoolean(resId) ? "1" : "0");
+ }
+
+ /**
+ * Loads an integer resource into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param resId The name of the integer resource.
+ */
+ private void loadIntegerSetting(SQLiteDatabase db, String tableName, String name, int resId) {
+ loadSettingsForTable(db, tableName, name,
+ Integer.toString(mContext.getResources().getInteger(resId)));
+ }
+
+ /**
+ * Loads a name/value pair into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param value The value to insert into the table.
+ */
+ private void loadSettingsForTable(SQLiteDatabase db, String tableName, String name,
+ String value) {
+ if (LOCAL_LOGV) Log.d(TAG, "Loading key: " + name + ", value: " + value);
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Settings.NameValueTable.NAME, name);
+ contentValues.put(Settings.NameValueTable.VALUE, value);
+
+ db.insertWithOnConflict(tableName, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE);
+ }
+
+ /**
+ * @return Gets the default device name
+ */
+ private String getDefaultDeviceName() {
+ return mContext.getResources().getString(R.string.def_device_name, Build.MODEL);
}
}
diff --git a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java
index a9a96fb..5b1453c 100644
--- a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java
+++ b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java
@@ -16,26 +16,41 @@
package org.cyanogenmod.cmsettings;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.database.AbstractCursor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import cyanogenmod.providers.CMSettings;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
/**
* The CMSettingsProvider serves as a {@link ContentProvider} for CM specific settings
*/
@@ -45,6 +60,10 @@ public class CMSettingsProvider extends ContentProvider {
private static final boolean USER_CHECK_THROWS = true;
+ private static final String PREF_HAS_MIGRATED_CM_SETTINGS = "has_migrated_cm_settings";
+
+ private static final Bundle NULL_SETTING = Bundle.forPair("value", null);
+
// Each defined user has their own settings
protected final SparseArray<CMDatabaseHelper> mDbHelpers = new SparseArray<CMDatabaseHelper>();
@@ -52,6 +71,13 @@ public class CMSettingsProvider extends ContentProvider {
private static final int SECURE = 2;
private static final int GLOBAL = 3;
+ private static final int SYSTEM_ITEM_NAME = 4;
+ private static final int SECURE_ITEM_NAME = 5;
+ private static final int GLOBAL_ITEM_NAME = 6;
+
+ private static final String ITEM_MATCHER = "/*";
+ private static final String NAME_SELECTION = Settings.NameValueTable.NAME + " = ?";
+
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
@@ -61,11 +87,17 @@ public class CMSettingsProvider extends ContentProvider {
SECURE);
sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_GLOBAL,
GLOBAL);
- // TODO add other paths for getting specific items
+ sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_SYSTEM +
+ ITEM_MATCHER, SYSTEM_ITEM_NAME);
+ sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_SECURE +
+ ITEM_MATCHER, SECURE_ITEM_NAME);
+ sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_GLOBAL +
+ ITEM_MATCHER, GLOBAL_ITEM_NAME);
}
private UserManager mUserManager;
private Uri.Builder mUriBuilder;
+ private SharedPreferences mSharedPrefs;
@Override
public boolean onCreate() {
@@ -79,36 +111,349 @@ public class CMSettingsProvider extends ContentProvider {
mUriBuilder.scheme(ContentResolver.SCHEME_CONTENT);
mUriBuilder.authority(CMSettings.AUTHORITY);
- // TODO Add migration for cm settings
+ mSharedPrefs = getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
+
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ getContext().registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_OWNER);
+ String action = intent.getAction();
+
+ if (LOCAL_LOGV) Log.d(TAG, "Received intent: " + action + " for user: " + userId);
+
+ if (action.equals(Intent.ACTION_USER_REMOVED)) {
+ onUserRemoved(userId);
+ }
+ }
+ }, userFilter);
return true;
}
+ // region Migration Methods
+
+ /**
+ * Migrates CM settings for all existing users if this has not been run before.
+ */
+ private void migrateCMSettingsForExistingUsersIfNeeded() {
+ boolean hasMigratedCMSettings = mSharedPrefs.getBoolean(PREF_HAS_MIGRATED_CM_SETTINGS,
+ false);
+
+ if (!hasMigratedCMSettings) {
+ long startTime = System.currentTimeMillis();
+
+ for (UserInfo user : mUserManager.getUsers()) {
+ migrateCMSettingsForUser(user.id);
+ }
+
+ mSharedPrefs.edit().putBoolean(PREF_HAS_MIGRATED_CM_SETTINGS, true).commit();
+
+ // TODO: Add this as part of a boot message to the UI
+ long timeDiffMillis = System.currentTimeMillis() - startTime;
+ if (LOCAL_LOGV) Log.d(TAG, "Migration finished in " + timeDiffMillis + " milliseconds");
+ }
+ }
+
+ /**
+ * Migrates CM settings for a specific user.
+ * @param userId The id of the user to run CM settings migration for.
+ */
+ private void migrateCMSettingsForUser(int userId) {
+ synchronized (this) {
+ if (LOCAL_LOGV) Log.d(TAG, "CM settings will be migrated for user id: " + userId);
+
+ // Migrate system settings
+ HashMap<String, String> systemToCmSettingsMap = new HashMap<String, String>();
+ systemToCmSettingsMap.put(Settings.System.QS_QUICK_PULLDOWN,
+ CMSettings.System.QS_QUICK_PULLDOWN);
+
+ int rowsMigrated = migrateCMSettingsForTable(userId,
+ CMDatabaseHelper.CMTableNames.TABLE_SYSTEM, systemToCmSettingsMap);
+ if (LOCAL_LOGV) Log.d(TAG, "Migrated " + rowsMigrated + " to CM system table");
+
+ // Migrate secure settings
+ HashMap<String, String> secureToCmSettingsMap = new HashMap<String, String>();
+ secureToCmSettingsMap.put(Settings.Secure.ADVANCED_MODE,
+ CMSettings.Secure.ADVANCED_MODE);
+ secureToCmSettingsMap.put(Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT,
+ CMSettings.Secure.BUTTON_BACKLIGHT_TIMEOUT);
+ secureToCmSettingsMap.put(Settings.Secure.BUTTON_BRIGHTNESS,
+ CMSettings.Secure.BUTTON_BRIGHTNESS);
+ secureToCmSettingsMap.put(Settings.Secure.DEFAULT_THEME_COMPONENTS,
+ CMSettings.Secure.DEFAULT_THEME_COMPONENTS);
+ secureToCmSettingsMap.put(Settings.Secure.DEFAULT_THEME_PACKAGE,
+ CMSettings.Secure.DEFAULT_THEME_PACKAGE);
+ secureToCmSettingsMap.put(Settings.Secure.DEV_FORCE_SHOW_NAVBAR,
+ CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR);
+ secureToCmSettingsMap.put(
+ Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY,
+ CMSettings.Secure.NAME_THEME_CONFIG);
+ secureToCmSettingsMap.put(Settings.Secure.KEYBOARD_BRIGHTNESS,
+ CMSettings.Secure.KEYBOARD_BRIGHTNESS);
+ secureToCmSettingsMap.put(Settings.Secure.POWER_MENU_ACTIONS,
+ CMSettings.Secure.POWER_MENU_ACTIONS);
+ secureToCmSettingsMap.put(Settings.Secure.STATS_COLLECTION,
+ CMSettings.Secure.STATS_COLLECTION);
+ secureToCmSettingsMap.put(Settings.Secure.QS_SHOW_BRIGHTNESS_SLIDER,
+ CMSettings.Secure.QS_SHOW_BRIGHTNESS_SLIDER);
+ secureToCmSettingsMap.put(Settings.Secure.QS_TILES,
+ CMSettings.Secure.QS_TILES);
+ secureToCmSettingsMap.put(Settings.Secure.QS_USE_MAIN_TILES,
+ CMSettings.Secure.QS_USE_MAIN_TILES);
+ secureToCmSettingsMap.put(Settings.Secure.VOLUME_LINK_NOTIFICATION,
+ CMSettings.Secure.VOLUME_LINK_NOTIFICATION);
+
+ int navRingTargetsLength = Settings.Secure.NAVIGATION_RING_TARGETS.length;
+ int cmNavRingTargetsLength = CMSettings.Secure.NAVIGATION_RING_TARGETS.length;
+ int minNavRingTargetsLength = navRingTargetsLength <= cmNavRingTargetsLength ?
+ navRingTargetsLength : cmNavRingTargetsLength;
+
+ for (int i = 0; i < minNavRingTargetsLength; i++) {
+ systemToCmSettingsMap.put(Settings.Secure.NAVIGATION_RING_TARGETS[i],
+ CMSettings.Secure.NAVIGATION_RING_TARGETS[i]);
+ }
+
+ rowsMigrated = migrateCMSettingsForTable(userId,
+ CMDatabaseHelper.CMTableNames.TABLE_SECURE, secureToCmSettingsMap);
+ if (LOCAL_LOGV) Log.d(TAG, "Migrated " + rowsMigrated + " to CM secure table");
+
+ // Migrate global settings
+ if (userId == UserHandle.USER_OWNER) {
+ HashMap<String, String> globalToCmSettingsMap = new HashMap<String, String>();
+ globalToCmSettingsMap.put(Settings.Global.DEVICE_NAME,
+ CMSettings.Global.DEVICE_NAME);
+ globalToCmSettingsMap.put(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ CMSettings.Global.HEADS_UP_NOTIFICATIONS_ENABLED);
+
+ rowsMigrated = migrateCMSettingsForTable(userId,
+ CMDatabaseHelper.CMTableNames.TABLE_GLOBAL, globalToCmSettingsMap);
+ if (LOCAL_LOGV) Log.d(TAG, "Migrated " + rowsMigrated + " to CM global table");
+ }
+ }
+ }
+
+ /**
+ * Migrates CM settings for a specific table and user id.
+ * @param userId The id of the user to run CM settings migration for.
+ * @param tableName The name of the table to run CM settings migration on.
+ * @param settingsMap A mapping between key names in {@link Settings} and {@link CMSettings}
+ * @return Number of rows migrated.
+ */
+ private int migrateCMSettingsForTable(int userId, String tableName, HashMap<String,
+ String> settingsMap) {
+ ContentResolver contentResolver = getContext().getContentResolver();
+ Set<Map.Entry<String, String>> entrySet = settingsMap.entrySet();
+ ContentValues[] contentValues = new ContentValues[settingsMap.size()];
+
+ int migrateSettingsCount = 0;
+ for (Map.Entry<String, String> keyPair : entrySet) {
+ String settingsKey = keyPair.getKey();
+ String cmSettingsKey = keyPair.getValue();
+ String settingsValue = null;
+
+ if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_SYSTEM)) {
+ settingsValue = Settings.System.getStringForUser(contentResolver, settingsKey,
+ userId);
+ }
+ else if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_SECURE)) {
+ settingsValue = Settings.Secure.getStringForUser(contentResolver, settingsKey,
+ userId);
+ }
+ else if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_GLOBAL)) {
+ settingsValue = Settings.Global.getStringForUser(contentResolver, settingsKey,
+ userId);
+ }
+
+ if (LOCAL_LOGV) Log.d(TAG, "Table: " + tableName + ", Key: " + settingsKey + ", Value: "
+ + settingsValue);
+
+ ContentValues contentValue = new ContentValues();
+ contentValue.put(Settings.NameValueTable.NAME, cmSettingsKey);
+ contentValue.put(Settings.NameValueTable.VALUE, settingsValue);
+ contentValues[migrateSettingsCount++] = contentValue;
+ }
+
+ int rowsInserted = 0;
+ if (contentValues.length > 0) {
+ Uri uri = mUriBuilder.build();
+ uri = uri.buildUpon().appendPath(tableName).build();
+ rowsInserted = bulkInsertForUser(userId, uri, contentValues);
+ }
+
+ return rowsInserted;
+ }
+
+ /**
+ * Performs cleanup for the removed user.
+ * @param userId The id of the user that is removed.
+ */
+ private void onUserRemoved(int userId) {
+ synchronized (this) {
+ // the db file itself will be deleted automatically, but we need to tear down
+ // our helpers and other internal bookkeeping.
+
+ mDbHelpers.delete(userId);
+
+ if (LOCAL_LOGV) Log.d(TAG, "User " + userId + " is removed");
+ }
+ }
+
+ // endregion Migration Methods
+
+ // region Content Provider Methods
+
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ if (LOCAL_LOGV) Log.d(TAG, "Call method: " + method);
+
+ int callingUserId = UserHandle.getCallingUserId();
+ if (args != null) {
+ int reqUser = args.getInt(CMSettings.CALL_METHOD_USER_KEY, callingUserId);
+ if (reqUser != callingUserId) {
+ callingUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), reqUser, false, true,
+ "get/set setting for user", null);
+ if (LOCAL_LOGV) Log.v(TAG, " access setting for user " + callingUserId);
+ }
+ }
+
+ // Migrate methods
+ if (CMSettings.CALL_METHOD_MIGRATE_SETTINGS.equals(method)) {
+ migrateCMSettingsForExistingUsersIfNeeded();
+
+ return null;
+ } else if (CMSettings.CALL_METHOD_MIGRATE_SETTINGS_FOR_USER.equals(method)) {
+ migrateCMSettingsForUser(callingUserId);
+
+ return null;
+ }
+
+ // Get methods
+ if (CMSettings.CALL_METHOD_GET_SYSTEM.equals(method)) {
+ return lookupSingleValue(callingUserId, CMSettings.System.CONTENT_URI, request);
+ }
+ else if (CMSettings.CALL_METHOD_GET_SECURE.equals(method)) {
+ return lookupSingleValue(callingUserId, CMSettings.Secure.CONTENT_URI, request);
+ }
+ else if (CMSettings.CALL_METHOD_GET_GLOBAL.equals(method)) {
+ return lookupSingleValue(callingUserId, CMSettings.Global.CONTENT_URI, request);
+ }
+
+ // Put methods - new value is in the args bundle under the key named by
+ // the Settings.NameValueTable.VALUE static.
+ final String newValue = (args == null)
+ ? null : args.getString(Settings.NameValueTable.VALUE);
+
+ // Framework can't do automatic permission checking for calls, so we need
+ // to do it here.
+ if (getContext().checkCallingOrSelfPermission(
+ cyanogenmod.platform.Manifest.permission.WRITE_SETTINGS) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ String.format("Permission denial: writing to settings requires %1$s",
+ cyanogenmod.platform.Manifest.permission.WRITE_SETTINGS));
+ }
+
+ // Put methods
+ final ContentValues values = new ContentValues();
+ values.put(Settings.NameValueTable.NAME, request);
+ values.put(Settings.NameValueTable.VALUE, newValue);
+
+ if (CMSettings.CALL_METHOD_PUT_SYSTEM.equals(method)) {
+ insertForUser(callingUserId, CMSettings.System.CONTENT_URI, values);
+ }
+ else if (CMSettings.CALL_METHOD_PUT_SECURE.equals(method)) {
+ insertForUser(callingUserId, CMSettings.Secure.CONTENT_URI, values);
+ }
+ else if (CMSettings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
+ insertForUser(callingUserId, CMSettings.Global.CONTENT_URI, values);
+ }
+
+ return null;
+ }
+
+ /**
+ * Looks up a single value for a specific user, uri, and key.
+ * @param userId The id of the user to perform the lookup for.
+ * @param uri The uri for which table to perform the lookup in.
+ * @param key The key to perform the lookup with.
+ * @return A single value stored in a {@link Bundle}.
+ */
+ private Bundle lookupSingleValue(int userId, Uri uri, String key) {
+ Cursor cursor = null;
+ try {
+ cursor = queryForUser(userId, uri, new String[]{ Settings.NameValueTable.VALUE },
+ Settings.NameValueTable.NAME + " = ?", new String[]{ key }, null);
+
+ if (cursor != null && cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ String value = cursor.getString(0);
+ return value == null ? NULL_SETTING : Bundle.forPair(Settings.NameValueTable.VALUE,
+ value);
+ }
+ } catch (SQLiteException e) {
+ Log.w(TAG, "settings lookup error", e);
+ return null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return NULL_SETTING;
+ }
+
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ return queryForUser(UserHandle.getCallingUserId(), uri, projection, selection,
+ selectionArgs, sortOrder);
+ }
+
+ /**
+ * Performs a query for a specific user.
+ * @param userId The id of the user to perform the query for.
+ * @param uri The uri for which table to perform the query on. Optionally, the uri can end in
+ * the name of a specific element to query for.
+ * @param projection The columns that are returned in the {@link Cursor}.
+ * @param selection The column names that the selection criteria applies to.
+ * @param selectionArgs The column values that the selection criteria applies to.
+ * @param sortOrder The ordering of how the values should be returned in the {@link Cursor}.
+ * @return {@link Cursor} of the results from the query.
+ */
+ private Cursor queryForUser(int userId, Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
- String tableName = getTableNameFromUri(uri);
+ int code = sUriMatcher.match(uri);
+ String tableName = getTableNameFromUriMatchCode(code);
checkWritePermissions(tableName);
- int callingUserId = UserHandle.getCallingUserId();
- CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
- callingUserId));
+ CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName, userId));
SQLiteDatabase db = dbHelper.getReadableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(tableName);
- Cursor returnCursor = queryBuilder.query(db, projection, selection, selectionArgs, null,
- null, sortOrder);
+ Cursor returnCursor;
+ if (isItemUri(code)) {
+ // The uri is looking for an element with a specific name
+ returnCursor = queryBuilder.query(db, projection, NAME_SELECTION,
+ new String[] { uri.getLastPathSegment() }, null, null, sortOrder);
+ } else {
+ returnCursor = queryBuilder.query(db, projection, selection, selectionArgs, null,
+ null, sortOrder);
+ }
+
// the default Cursor interface does not support per-user observation
try {
AbstractCursor abstractCursor = (AbstractCursor) returnCursor;
- abstractCursor.setNotificationUri(getContext().getContentResolver(), uri,
- callingUserId);
+ abstractCursor.setNotificationUri(getContext().getContentResolver(), uri, userId);
} catch (ClassCastException e) {
// details of the concrete Cursor implementation have changed and this code has
// not been updated to match -- complain and fail hard.
@@ -121,8 +466,14 @@ public class CMSettingsProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
- // TODO: Implement
- return null;
+ int code = sUriMatcher.match(uri);
+ String tableName = getTableNameFromUriMatchCode(code);
+
+ if (isItemUri(code)) {
+ return "vnd.android.cursor.item/" + tableName;
+ } else {
+ return "vnd.android.cursor.dir/" + tableName;
+ }
}
@Override
@@ -166,8 +517,6 @@ public class CMSettingsProvider extends ContentProvider {
if (rowId >= 0) {
numRowsAffected++;
-
- if (LOCAL_LOGV) Log.d(TAG, tableName + " <- " + values);
} else {
return 0;
}
@@ -179,7 +528,6 @@ public class CMSettingsProvider extends ContentProvider {
}
if (numRowsAffected > 0) {
- getContext().getContentResolver().notifyChange(uri, null);
notifyChange(uri, tableName, userId);
if (LOCAL_LOGV) Log.d(TAG, tableName + ": " + numRowsAffected + " row(s) inserted");
}
@@ -189,6 +537,18 @@ public class CMSettingsProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
+ return insertForUser(UserHandle.getCallingUserId(), uri, values);
+ }
+
+ /**
+ * Performs insert for a specific user.
+ * @param userId The user id to perform the insert for.
+ * @param uri The content:// URI of the insertion request.
+ * @param values A sets of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
+ * @return
+ */
+ private Uri insertForUser(int userId, Uri uri, ContentValues values) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
@@ -200,17 +560,16 @@ public class CMSettingsProvider extends ContentProvider {
String tableName = getTableNameFromUri(uri);
checkWritePermissions(tableName);
- int callingUserId = UserHandle.getCallingUserId();
- CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
- callingUserId));
+ CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName, userId));
SQLiteDatabase db = dbHelper.getWritableDatabase();
long rowId = db.insert(tableName, null, values);
Uri returnUri = null;
if (rowId > -1) {
- returnUri = ContentUris.withAppendedId(uri, rowId);
- notifyChange(returnUri, tableName, callingUserId);
+ String name = values.getAsString(Settings.NameValueTable.NAME);
+ returnUri = Uri.withAppendedPath(uri, name);
+ notifyChange(returnUri, tableName, userId);
if (LOCAL_LOGV) Log.d(TAG, "Inserted row id: " + rowId + " into tableName: " +
tableName);
}
@@ -235,8 +594,8 @@ public class CMSettingsProvider extends ContentProvider {
int callingUserId = UserHandle.getCallingUserId();
CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
callingUserId));
- SQLiteDatabase db = dbHelper.getWritableDatabase();
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
numRowsAffected = db.delete(tableName, selection, selectionArgs);
if (numRowsAffected > 0) {
@@ -250,6 +609,11 @@ public class CMSettingsProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ // NOTE: update() is never called by the front-end CMSettings API, and updates that
+ // wind up affecting rows in Secure that are globally shared will not have the
+ // intended effect (the update will be invisible to the rest of the system).
+ // This should have no practical effect, since writes to the Secure db can only
+ // be done by system code, and that code should be using the correct API up front.
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
@@ -269,13 +633,15 @@ public class CMSettingsProvider extends ContentProvider {
int numRowsAffected = db.update(tableName, values, selection, selectionArgs);
if (numRowsAffected > 0) {
- getContext().getContentResolver().notifyChange(uri, null);
+ notifyChange(uri, tableName, callingUserId);
if (LOCAL_LOGV) Log.d(TAG, tableName + ": " + numRowsAffected + " row(s) updated");
}
return numRowsAffected;
}
+ // endregion Content Provider Methods
+
/**
* Tries to get a {@link CMDatabaseHelper} for the specified user and if it does not exist, a
* new instance of {@link CMDatabaseHelper} is created for the specified user and returned.
@@ -357,6 +723,26 @@ public class CMSettingsProvider extends ContentProvider {
}
/**
+ * Returns whether the matched uri code refers to an item in a table
+ * @param code
+ * @return
+ */
+ private boolean isItemUri(int code) {
+ switch (code) {
+ case SYSTEM:
+ case SECURE:
+ case GLOBAL:
+ return false;
+ case SYSTEM_ITEM_NAME:
+ case SECURE_ITEM_NAME:
+ case GLOBAL_ITEM_NAME:
+ return true;
+ default:
+ throw new IllegalArgumentException("Invalid uri match code: " + code);
+ }
+ }
+
+ /**
* Utilizes an {@link UriMatcher} to check for a valid combination of scheme, authority, and
* path and returns the corresponding table name
* @param uri
@@ -365,15 +751,27 @@ public class CMSettingsProvider extends ContentProvider {
private String getTableNameFromUri(Uri uri) {
int code = sUriMatcher.match(uri);
+ return getTableNameFromUriMatchCode(code);
+ }
+
+ /**
+ * Returns the corresponding table name for the matched uri code
+ * @param code
+ * @return
+ */
+ private String getTableNameFromUriMatchCode(int code) {
switch (code) {
case SYSTEM:
+ case SYSTEM_ITEM_NAME:
return CMDatabaseHelper.CMTableNames.TABLE_SYSTEM;
case SECURE:
+ case SECURE_ITEM_NAME:
return CMDatabaseHelper.CMTableNames.TABLE_SECURE;
case GLOBAL:
+ case GLOBAL_ITEM_NAME:
return CMDatabaseHelper.CMTableNames.TABLE_GLOBAL;
default:
- throw new IllegalArgumentException("Invalid uri: " + uri);
+ throw new IllegalArgumentException("Invalid uri match code: " + code);
}
}
diff --git a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java
new file mode 100644
index 0000000..ef0a6f0
--- /dev/null
+++ b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 org.cyanogenmod.cmsettings;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.util.Log;
+import cyanogenmod.providers.CMSettings;
+
+public class PreBootReceiver extends BroadcastReceiver{
+ private static final String TAG = "CMSettingsReceiver";
+ private static final boolean LOCAL_LOGV = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (LOCAL_LOGV) {
+ Log.d(TAG, "Received pre boot intent. Attempting to migrate CM settings.");
+ }
+
+ ContentResolver contentResolver = context.getContentResolver();
+ IContentProvider contentProvider = contentResolver.acquireProvider(
+ CMSettings.AUTHORITY);
+
+ try{
+ contentProvider.call(contentResolver.getPackageName(),
+ CMSettings.CALL_METHOD_MIGRATE_SETTINGS, null, null);
+
+ context.getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, getClass()),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to trigger settings migration due to RemoteException");
+ }
+ }
+}
diff --git a/packages/CMSettingsProvider/tests/AndroidManifest.xml b/packages/CMSettingsProvider/tests/AndroidManifest.xml
index e82a7d8..c564317 100644
--- a/packages/CMSettingsProvider/tests/AndroidManifest.xml
+++ b/packages/CMSettingsProvider/tests/AndroidManifest.xml
@@ -18,6 +18,10 @@
<uses-permission android:name="cyanogenmod.permission.WRITE_SETTINGS"/>
<uses-permission android:name="cyanogenmod.permission.WRITE_SECURE_SETTINGS"/>
+ <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" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
diff --git a/packages/CMSettingsProvider/tests/src/org/cyanogenmod/cmsettings/tests/CMSettingsProviderTest.java b/packages/CMSettingsProvider/tests/src/org/cyanogenmod/cmsettings/tests/CMSettingsProviderTest.java
index 7ec446c..01070d0 100644
--- a/packages/CMSettingsProvider/tests/src/org/cyanogenmod/cmsettings/tests/CMSettingsProviderTest.java
+++ b/packages/CMSettingsProvider/tests/src/org/cyanogenmod/cmsettings/tests/CMSettingsProviderTest.java
@@ -18,11 +18,19 @@ package org.cyanogenmod.cmsettings.tests;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.UserInfo;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
-import android.util.Log;
+import android.text.TextUtils;
import cyanogenmod.providers.CMSettings;
import java.util.LinkedHashMap;
@@ -31,129 +39,219 @@ import java.util.Map;
public class CMSettingsProviderTest extends AndroidTestCase {
private static final String TAG = "CMSettingsProviderTest";
- private static final LinkedHashMap<String, String> mMap = new LinkedHashMap<String, String>();
+ private static final LinkedHashMap<String, String> sMap = new LinkedHashMap<String, String>();
static {
- mMap.put("testKey1", "value1");
- mMap.put("testKey2", "value2");
- mMap.put("testKey3", "value3");
+ sMap.put("testKey1", "value1");
+ sMap.put("testKey2", "value2");
+ sMap.put("testKey3", "value3");
}
- private static final String[] PROJECTIONS = new String[] { "name", "value" };
+ private static final String[] PROJECTIONS = new String[] { Settings.NameValueTable.NAME,
+ Settings.NameValueTable.VALUE };
private ContentResolver mContentResolver;
+ private UserManager mUserManager;
+ private UserInfo mGuest;
@Override
public void setUp() {
mContentResolver = mContext.getContentResolver();
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ @Override
+ public void tearDown() {
+ if (mGuest != null) {
+ mUserManager.removeUser(mGuest.id);
+ }
}
@MediumTest
- public void testBulkInsertSuccess() {
- Log.d(TAG, "Starting bulk insert test");
+ public void testMigrateCMSettingsForOtherUser() {
+ // Make sure there's an owner
+ assertTrue(findUser(mUserManager, UserHandle.USER_OWNER));
+
+ mGuest = mUserManager.createGuest(mContext, "GuestUser1");
+ assertNotNull(mGuest);
+
+ testMigrateSettingsForUser(mGuest.id);
+ }
+
+ private void testMigrateSettingsForUser(int userId) {
+ // Setup values in Settings
+ final String expectedPullDownValue = "testQuickPullDownValue";
+ Settings.System.putStringForUser(mContentResolver, Settings.System.QS_QUICK_PULLDOWN,
+ expectedPullDownValue, userId);
+
+ final int expectedKeyboardBrightness = 4;
+ Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.KEYBOARD_BRIGHTNESS,
+ expectedKeyboardBrightness, userId);
+
+ Bundle arg = new Bundle();
+ arg.putInt(CMSettings.CALL_METHOD_USER_KEY, userId);
+ IContentProvider contentProvider = mContentResolver.acquireProvider(
+ CMSettings.AUTHORITY);
+
+ try{
+ // Trigger migrate settings for guest
+ contentProvider.call(mContentResolver.getPackageName(),
+ CMSettings.CALL_METHOD_MIGRATE_SETTINGS_FOR_USER, null, arg);
+ } catch (RemoteException ex) {
+ fail("Failed to trigger settings migration due to RemoteException");
+ }
+
+ // Check values
+ final String actualPullDownValue = CMSettings.System.getStringForUser(mContentResolver,
+ CMSettings.System.QS_QUICK_PULLDOWN, userId);
+ assertEquals(expectedPullDownValue, actualPullDownValue);
- ContentValues[] contentValues = new ContentValues[mMap.size()];
+ final int actualKeyboardBrightness = CMSettings.Secure.getIntForUser(mContentResolver,
+ CMSettings.Secure.KEYBOARD_BRIGHTNESS, -1, userId);
+ assertEquals(expectedKeyboardBrightness, actualKeyboardBrightness);
+ }
+
+ private boolean findUser(UserManager userManager, int userHandle) {
+ for (UserInfo user : userManager.getUsers()) {
+ if (user.id == userHandle) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @MediumTest
+ public void testBulkInsertSuccess() {
+ ContentValues[] contentValues = new ContentValues[sMap.size()];
+ String[] keyValues = new String[sMap.size()];
int count = 0;
- for (Map.Entry<String, String> kVPair : mMap.entrySet()) {
+ for (Map.Entry<String, String> kVPair : sMap.entrySet()) {
ContentValues contentValue = new ContentValues();
- contentValue.put(PROJECTIONS[0], kVPair.getKey());
- contentValue.put(PROJECTIONS[1], kVPair.getValue());
+
+ final String key = kVPair.getKey();
+ contentValue.put(Settings.NameValueTable.NAME, key);
+ keyValues[count] = key;
+
+ contentValue.put(Settings.NameValueTable.VALUE, kVPair.getValue());
contentValues[count++] = contentValue;
}
- testBulkInsertForUri(CMSettings.System.CONTENT_URI, contentValues);
- testBulkInsertForUri(CMSettings.Secure.CONTENT_URI, contentValues);
- testBulkInsertForUri(CMSettings.Global.CONTENT_URI, contentValues);
-
- Log.d(TAG, "Finished bulk insert test");
+ testBulkInsertForUri(CMSettings.System.CONTENT_URI, contentValues, keyValues);
+ testBulkInsertForUri(CMSettings.Secure.CONTENT_URI, contentValues, keyValues);
+ testBulkInsertForUri(CMSettings.Global.CONTENT_URI, contentValues, keyValues);
}
- private void testBulkInsertForUri(Uri uri, ContentValues[] contentValues) {
+ private void testBulkInsertForUri(Uri uri, ContentValues[] contentValues, String[] keyValues) {
int rowsInserted = mContentResolver.bulkInsert(uri, contentValues);
- assertEquals(mMap.size(), rowsInserted);
+ assertEquals(sMap.size(), rowsInserted);
+
+ final String placeholderSymbol = "?";
+ String[] placeholders = new String[contentValues.length];
+ for (int i = 0; i < placeholders.length; i++) {
+ placeholders[i] = placeholderSymbol;
+ }
- Cursor queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
+ final String placeholdersString = TextUtils.join(",", placeholders);
+
+ Cursor queryCursor = mContentResolver.query(uri, PROJECTIONS,
+ Settings.NameValueTable.NAME + " IN (" + placeholdersString + ")", keyValues,
+ null);
+ assertEquals(contentValues.length, queryCursor.getCount());
try {
while (queryCursor.moveToNext()) {
assertEquals(PROJECTIONS.length, queryCursor.getColumnCount());
String actualKey = queryCursor.getString(0);
- assertTrue(mMap.containsKey(actualKey));
+ assertTrue(sMap.containsKey(actualKey));
- assertEquals(mMap.get(actualKey), queryCursor.getString(1));
+ assertEquals(sMap.get(actualKey), queryCursor.getString(1));
}
-
- Log.d(TAG, "Test successful");
}
finally {
queryCursor.close();
}
// TODO: Find a better way to cleanup database/use ProviderTestCase2 without process crash
- for (String key : mMap.keySet()) {
- mContentResolver.delete(uri, PROJECTIONS[0] + " = ?", new String[]{ key });
+ for (String key : sMap.keySet()) {
+ mContentResolver.delete(uri, Settings.NameValueTable.NAME + " = ?",
+ new String[]{ key });
}
}
@MediumTest
public void testInsertUpdateDeleteSuccess() {
- Log.d(TAG, "Starting insert/update/delete test");
-
testInsertUpdateDeleteForUri(CMSettings.System.CONTENT_URI);
testInsertUpdateDeleteForUri(CMSettings.Secure.CONTENT_URI);
testInsertUpdateDeleteForUri(CMSettings.Global.CONTENT_URI);
-
- Log.d(TAG, "Finished insert/update/delete test");
}
private void testInsertUpdateDeleteForUri(Uri uri) {
- String key1 = "testKey1";
+ String key = "key";
String value1 = "value1";
String value2 = "value2";
// test insert
ContentValues contentValue = new ContentValues();
- contentValue.put(PROJECTIONS[0], key1);
- contentValue.put(PROJECTIONS[1], value1);
-
- mContentResolver.insert(uri, contentValue);
-
- // check insert
- Cursor queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
- assertEquals(1, queryCursor.getCount());
-
- queryCursor.moveToNext();
- assertEquals(PROJECTIONS.length, queryCursor.getColumnCount());
-
- String actualKey = queryCursor.getString(0);
- assertEquals(key1, actualKey);
- assertEquals(value1, queryCursor.getString(1));
-
- // test update
- contentValue.clear();
- contentValue.put(PROJECTIONS[1], value2);
-
- int rowsAffected = mContentResolver.update(uri, contentValue, PROJECTIONS[0] + " = ?",
- new String[]{key1});
- assertEquals(1, rowsAffected);
-
- // check update
- queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
- assertEquals(1, queryCursor.getCount());
-
- queryCursor.moveToNext();
- assertEquals(PROJECTIONS.length, queryCursor.getColumnCount());
-
- actualKey = queryCursor.getString(0);
- assertEquals(key1, actualKey);
- assertEquals(value2, queryCursor.getString(1));
+ contentValue.put(Settings.NameValueTable.NAME, key);
+ contentValue.put(Settings.NameValueTable.VALUE, value1);
+
+ Uri expectedUri = uri.withAppendedPath(uri, key);
+ Uri returnUri = mContentResolver.insert(uri, contentValue);
+ assertEquals(expectedUri, returnUri);
+
+ Cursor queryCursor = null;
+ try {
+ // check insert
+ queryCursor = mContentResolver.query(uri, PROJECTIONS, Settings.NameValueTable.NAME +
+ " = ?", new String[]{ key }, null);
+ assertEquals(1, queryCursor.getCount());
+
+ assertExpectedKeyValuePair(queryCursor, key, value1);
+
+ // check insert with returned uri
+ queryCursor = mContentResolver.query(returnUri, PROJECTIONS, null, null, null);
+ assertEquals(1, queryCursor.getCount());
+
+ assertExpectedKeyValuePair(queryCursor, key, value1);
+
+ // test update
+ contentValue.clear();
+ contentValue.put(Settings.NameValueTable.VALUE, value2);
+
+ int rowsAffected = mContentResolver.update(uri, contentValue,
+ Settings.NameValueTable.NAME + " = ?", new String[]{ key });
+ assertEquals(1, rowsAffected);
+
+ // check update
+ queryCursor = mContentResolver.query(uri, PROJECTIONS, Settings.NameValueTable.NAME +
+ " = ?", new String[]{ key }, null);
+ assertEquals(1, queryCursor.getCount());
+
+ assertExpectedKeyValuePair(queryCursor, key, value2);
+
+ // test delete
+ rowsAffected = mContentResolver.delete(uri, Settings.NameValueTable.NAME + " = ?",
+ new String[]{ key });
+ assertEquals(1, rowsAffected);
+
+ // check delete
+ queryCursor = mContentResolver.query(uri, PROJECTIONS, Settings.NameValueTable.NAME +
+ " = ?", new String[]{ key }, null);
+ assertEquals(0, queryCursor.getCount());
+ } finally {
+ if (queryCursor != null) {
+ queryCursor.close();
+ }
+ }
+ }
- // test delete
- rowsAffected = mContentResolver.delete(uri, PROJECTIONS[0] + " = ?", new String[]{key1});
- assertEquals(1, rowsAffected);
+ private void assertExpectedKeyValuePair(Cursor cursor, String expectedKey,
+ String expectedValue) {
+ cursor.moveToNext();
+ assertEquals(PROJECTIONS.length, cursor.getColumnCount());
- // check delete
- queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
- assertEquals(0, queryCursor.getCount());
+ String actualKey = cursor.getString(0);
+ assertEquals(expectedKey, actualKey);
+ assertEquals(expectedValue, cursor.getString(1));
}
}
diff --git a/src/java/cyanogenmod/providers/CMSettings.java b/src/java/cyanogenmod/providers/CMSettings.java
index afeacc8..3178378 100644
--- a/src/java/cyanogenmod/providers/CMSettings.java
+++ b/src/java/cyanogenmod/providers/CMSettings.java
@@ -20,6 +20,7 @@ import android.content.ContentResolver;
import android.content.IContentProvider;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -44,10 +45,57 @@ public final class CMSettings {
}
}
+ // region Call Methods
+
+ /**
+ * @hide - User handle argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_USER_KEY = "_user";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'system' table.
+ */
+ public static final String CALL_METHOD_GET_SYSTEM = "GET_system";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'secure' table.
+ */
+ public static final String CALL_METHOD_GET_SECURE = "GET_secure";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'global' table.
+ */
+ public static final String CALL_METHOD_GET_GLOBAL = "GET_global";
+
+ /**
+ * @hide - Private call() method to write to 'system' table
+ */
+ public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
+
+ /**
+ * @hide - Private call() method to write to 'secure' table
+ */
+ public static final String CALL_METHOD_PUT_SECURE = "PUT_secure";
+
+ /**
+ * @hide - Private call() method to write to 'global' table
+ */
+ public static final String CALL_METHOD_PUT_GLOBAL= "PUT_global";
+
+ /**
+ * @hide - Private call() method on CMSettingsProvider to migrate CM settings
+ */
+ public static final String CALL_METHOD_MIGRATE_SETTINGS = "migrate_settings";
+
+ /**
+ * @hide - Private call() method on CMSettingsProvider to migrate CM settings for a user
+ */
+ public static final String CALL_METHOD_MIGRATE_SETTINGS_FOR_USER = "migrate_settings_for_user";
+
+ // endregion
+
// Thread-safe.
private static class NameValueCache {
- // TODO Add call options for fast path at insert and get
-
private final String mVersionSystemProperty;
private final Uri mUri;
@@ -62,9 +110,17 @@ public final class CMSettings {
// Initially null; set lazily and held forever. Synchronized on 'this'.
private IContentProvider mContentProvider = null;
- public NameValueCache(String versionSystemProperty, Uri uri) {
+ // The method we'll call (or null, to not use) on the provider
+ // for the fast path of retrieving settings.
+ private final String mCallGetCommand;
+ private final String mCallSetCommand;
+
+ public NameValueCache(String versionSystemProperty, Uri uri,
+ String getCommand, String setCommand) {
mVersionSystemProperty = versionSystemProperty;
mUri = uri;
+ mCallGetCommand = getCommand;
+ mCallSetCommand = setCommand;
}
private IContentProvider lazyGetProvider(ContentResolver cr) {
@@ -79,7 +135,30 @@ public final class CMSettings {
}
/**
- * Gets a a string value with the specified name from the name/value cache if possible. If
+ * Puts a string name/value pair into the content provider for the specified user.
+ * @param cr The content resolver to use.
+ * @param name The name of the key to put into the content provider.
+ * @param value The value to put into the content provider.
+ * @param userId The user id to use for the content provider.
+ * @return Whether the put was successful.
+ */
+ public boolean putStringForUser(ContentResolver cr, String name, String value,
+ final int userId) {
+ try {
+ Bundle arg = new Bundle();
+ arg.putString(Settings.NameValueTable.VALUE, value);
+ arg.putInt(CALL_METHOD_USER_KEY, userId);
+ IContentProvider cp = lazyGetProvider(cr);
+ cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets a string value with the specified name from the name/value cache if possible. If
* not, it will use the content resolver and perform a query.
* @param cr Content resolver to use if name/value cache does not contain the name or if
* the cache version is older than the current version.
@@ -90,6 +169,7 @@ public final class CMSettings {
public String getStringForUser(ContentResolver cr, String name, final int userId) {
final boolean isSelf = (userId == UserHandle.myUserId());
if (isSelf) {
+ if (LOCAL_LOGV) Log.d(TAG, "get setting for self");
long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
// Our own user's settings data uses a client-side cache
@@ -115,6 +195,40 @@ public final class CMSettings {
IContentProvider cp = lazyGetProvider(cr);
+ // Try the fast path first, not using query(). If this
+ // fails (alternate Settings provider that doesn't support
+ // this interface?) then we fall back to the query/table
+ // interface.
+ if (mCallGetCommand != null) {
+ try {
+ Bundle args = null;
+ if (!isSelf) {
+ args = new Bundle();
+ args.putInt(CALL_METHOD_USER_KEY, userId);
+ }
+ Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+ if (b != null) {
+ String value = b.getPairValue();
+ // Don't update our cache for reads of other users' data
+ if (isSelf) {
+ synchronized (this) {
+ mValues.put(name, value);
+ }
+ } else {
+ if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userId
+ + " by " + UserHandle.myUserId()
+ + " so not updating cache");
+ }
+ return value;
+ }
+ // If the response Bundle is null, we fall through
+ // to the query interface below.
+ } catch (RemoteException e) {
+ // Not supported by the remote side? Fall through
+ // to query().
+ }
+ }
+
Cursor c = null;
try {
c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
@@ -143,9 +257,8 @@ public final class CMSettings {
}
/**
- * System settings, containing miscellaneous CM system preferences. This
- * table holds simple name/value pairs. There are convenience
- * functions for accessing individual settings entries.
+ * System settings, containing miscellaneous CM system preferences. This table holds simple
+ * name/value pairs. There are convenience functions for accessing individual settings entries.
*/
public static final class System extends Settings.NameValueTable {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/system");
@@ -154,11 +267,23 @@ public final class CMSettings {
private static final NameValueCache sNameValueCache = new NameValueCache(
SYS_PROP_CM_SETTING_VERSION,
- CONTENT_URI);
+ CONTENT_URI,
+ CALL_METHOD_GET_SYSTEM,
+ CALL_METHOD_PUT_SYSTEM);
// region Methods
/**
+ * Construct the content URI for a particular name/value pair, useful for monitoring changes
+ * with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI
+ */
+ public static Uri getUriFor(String name) {
+ return Settings.NameValueTable.getUriFor(CONTENT_URI, name);
+ }
+
+ /**
* Look up a name in the database.
* @param resolver to access the database with
* @param name to look up in the table
@@ -170,8 +295,8 @@ public final class CMSettings {
/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
- int userHandle) {
- return sNameValueCache.getStringForUser(resolver, name, userHandle);
+ int userId) {
+ return sNameValueCache.getStringForUser(resolver, name, userId);
}
/**
@@ -182,11 +307,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putString(ContentResolver resolver, String name, String value) {
- return putString(resolver, CONTENT_URI, name, value);
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userId) {
+ return sNameValueCache.putStringForUser(resolver, name, value, userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as an integer. Note that internally setting values are always
* stored as strings; this function converts the string to an integer
* for you. The default value will be returned if the setting is
@@ -200,7 +331,12 @@ public final class CMSettings {
* or not a valid integer.
*/
public static int getInt(ContentResolver cr, String name, int def) {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userId) {
+ String v = getStringForUser(cr, name, userId);
try {
return v != null ? Integer.parseInt(v) : def;
} catch (NumberFormatException e) {
@@ -209,7 +345,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as an integer. Note that internally setting values are always
* stored as strings; this function converts the string to an integer
* for you.
@@ -228,7 +364,13 @@ public final class CMSettings {
*/
public static int getInt(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
try {
return Integer.parseInt(v);
} catch (NumberFormatException e) {
@@ -250,11 +392,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putInt(ContentResolver cr, String name, int value) {
- return putString(cr, name, Integer.toString(value));
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userId) {
+ return putStringForUser(cr, name, Integer.toString(value), userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a {@code long}. Note that internally setting values are always
* stored as strings; this function converts the string to a {@code long}
* for you. The default value will be returned if the setting is
@@ -268,7 +416,13 @@ public final class CMSettings {
* or not a valid {@code long}.
*/
public static long getLong(ContentResolver cr, String name, long def) {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userId) {
+ String valString = getStringForUser(cr, name, userId);
long value;
try {
value = valString != null ? Long.parseLong(valString) : def;
@@ -279,7 +433,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a {@code long}. Note that internally setting values are always
* stored as strings; this function converts the string to a {@code long}
* for you.
@@ -297,7 +451,13 @@ public final class CMSettings {
*/
public static long getLong(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String valString = getStringForUser(cr, name, userId);
try {
return Long.parseLong(valString);
} catch (NumberFormatException e) {
@@ -306,7 +466,7 @@ public final class CMSettings {
}
/**
- * Convenience function for updating a secure settings value as a long
+ * Convenience function for updating a single settings value as a long
* integer. This will either create a new entry in the table if the
* given name does not exist, or modify the value of the existing row
* with that name. Note that internally setting values are always
@@ -319,11 +479,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putLong(ContentResolver cr, String name, long value) {
- return putString(cr, name, Long.toString(value));
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userId) {
+ return putStringForUser(cr, name, Long.toString(value), userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a floating point number. Note that internally setting values are
* always stored as strings; this function converts the string to an
* float for you. The default value will be returned if the setting
@@ -337,7 +503,13 @@ public final class CMSettings {
* or not a valid float.
*/
public static float getFloat(ContentResolver cr, String name, float def) {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userId) {
+ String v = getStringForUser(cr, name, userId);
try {
return v != null ? Float.parseFloat(v) : def;
} catch (NumberFormatException e) {
@@ -346,7 +518,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single system settings value
* as a float. Note that internally setting values are always
* stored as strings; this function converts the string to a float
* for you.
@@ -365,7 +537,13 @@ public final class CMSettings {
*/
public static float getFloat(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
if (v == null) {
throw new CMSettingNotFoundException(name);
}
@@ -390,7 +568,13 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putFloat(ContentResolver cr, String name, float value) {
- return putString(cr, name, Float.toString(value));
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userId) {
+ return putStringForUser(cr, name, Float.toString(value), userId);
}
// endregion
@@ -408,8 +592,8 @@ public final class CMSettings {
}
/**
- * Secure settings, containing miscellaneous CM secure preferences. This
- * table holds simple name/value pairs. There are convenience
+ * Secure settings, containing miscellaneous CM secure preferences. This
+ * table holds simple name/value pairs. There are convenience
* functions for accessing individual settings entries.
*/
public static final class Secure extends Settings.NameValueTable {
@@ -419,7 +603,21 @@ public final class CMSettings {
private static final NameValueCache sNameValueCache = new NameValueCache(
SYS_PROP_CM_SETTING_VERSION,
- CONTENT_URI);
+ CONTENT_URI,
+ CALL_METHOD_GET_SECURE,
+ CALL_METHOD_PUT_SECURE);
+
+ // region Methods
+
+ /**
+ * Construct the content URI for a particular name/value pair, useful for monitoring changes
+ * with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI
+ */
+ public static Uri getUriFor(String name) {
+ return Settings.NameValueTable.getUriFor(CONTENT_URI, name);
+ }
/**
* Look up a name in the database.
@@ -433,8 +631,8 @@ public final class CMSettings {
/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
- int userHandle) {
- return sNameValueCache.getStringForUser(resolver, name, userHandle);
+ int userId) {
+ return sNameValueCache.getStringForUser(resolver, name, userId);
}
/**
@@ -445,11 +643,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putString(ContentResolver resolver, String name, String value) {
- return putString(resolver, CONTENT_URI, name, value);
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userId) {
+ return sNameValueCache.putStringForUser(resolver, name, value, userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as an integer. Note that internally setting values are always
* stored as strings; this function converts the string to an integer
* for you. The default value will be returned if the setting is
@@ -463,7 +667,12 @@ public final class CMSettings {
* or not a valid integer.
*/
public static int getInt(ContentResolver cr, String name, int def) {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userId) {
+ String v = getStringForUser(cr, name, userId);
try {
return v != null ? Integer.parseInt(v) : def;
} catch (NumberFormatException e) {
@@ -472,7 +681,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as an integer. Note that internally setting values are always
* stored as strings; this function converts the string to an integer
* for you.
@@ -491,7 +700,13 @@ public final class CMSettings {
*/
public static int getInt(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
try {
return Integer.parseInt(v);
} catch (NumberFormatException e) {
@@ -513,11 +728,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putInt(ContentResolver cr, String name, int value) {
- return putString(cr, name, Integer.toString(value));
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userId) {
+ return putStringForUser(cr, name, Integer.toString(value), userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a {@code long}. Note that internally setting values are always
* stored as strings; this function converts the string to a {@code long}
* for you. The default value will be returned if the setting is
@@ -531,7 +752,13 @@ public final class CMSettings {
* or not a valid {@code long}.
*/
public static long getLong(ContentResolver cr, String name, long def) {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userId) {
+ String valString = getStringForUser(cr, name, userId);
long value;
try {
value = valString != null ? Long.parseLong(valString) : def;
@@ -542,7 +769,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a {@code long}. Note that internally setting values are always
* stored as strings; this function converts the string to a {@code long}
* for you.
@@ -560,7 +787,13 @@ public final class CMSettings {
*/
public static long getLong(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String valString = getStringForUser(cr, name, userId);
try {
return Long.parseLong(valString);
} catch (NumberFormatException e) {
@@ -569,7 +802,7 @@ public final class CMSettings {
}
/**
- * Convenience function for updating a secure settings value as a long
+ * Convenience function for updating a single settings value as a long
* integer. This will either create a new entry in the table if the
* given name does not exist, or modify the value of the existing row
* with that name. Note that internally setting values are always
@@ -582,11 +815,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putLong(ContentResolver cr, String name, long value) {
- return putString(cr, name, Long.toString(value));
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userId) {
+ return putStringForUser(cr, name, Long.toString(value), userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a floating point number. Note that internally setting values are
* always stored as strings; this function converts the string to an
* float for you. The default value will be returned if the setting
@@ -600,7 +839,13 @@ public final class CMSettings {
* or not a valid float.
*/
public static float getFloat(ContentResolver cr, String name, float def) {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userId) {
+ String v = getStringForUser(cr, name, userId);
try {
return v != null ? Float.parseFloat(v) : def;
} catch (NumberFormatException e) {
@@ -609,7 +854,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single system settings value
* as a float. Note that internally setting values are always
* stored as strings; this function converts the string to a float
* for you.
@@ -628,7 +873,13 @@ public final class CMSettings {
*/
public static float getFloat(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
if (v == null) {
throw new CMSettingNotFoundException(name);
}
@@ -653,7 +904,13 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putFloat(ContentResolver cr, String name, float value) {
- return putString(cr, name, Float.toString(value));
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userId) {
+ return putStringForUser(cr, name, Float.toString(value), userId);
}
// endregion
@@ -770,8 +1027,8 @@ public final class CMSettings {
}
/**
- * Global settings, containing miscellaneous CM global preferences. This
- * table holds simple name/value pairs. There are convenience
+ * Global settings, containing miscellaneous CM global preferences. This
+ * table holds simple name/value pairs. There are convenience
* functions for accessing individual settings entries.
*/
public static final class Global extends Settings.NameValueTable {
@@ -781,11 +1038,23 @@ public final class CMSettings {
private static final NameValueCache sNameValueCache = new NameValueCache(
SYS_PROP_CM_SETTING_VERSION,
- CONTENT_URI);
+ CONTENT_URI,
+ CALL_METHOD_GET_GLOBAL,
+ CALL_METHOD_PUT_GLOBAL);
// region Methods
/**
+ * Construct the content URI for a particular name/value pair, useful for monitoring changes
+ * with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI
+ */
+ public static Uri getUriFor(String name) {
+ return Settings.NameValueTable.getUriFor(CONTENT_URI, name);
+ }
+
+ /**
* Look up a name in the database.
* @param resolver to access the database with
* @param name to look up in the table
@@ -797,8 +1066,8 @@ public final class CMSettings {
/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
- int userHandle) {
- return sNameValueCache.getStringForUser(resolver, name, userHandle);
+ int userId) {
+ return sNameValueCache.getStringForUser(resolver, name, userId);
}
/**
@@ -809,11 +1078,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putString(ContentResolver resolver, String name, String value) {
- return putString(resolver, CONTENT_URI, name, value);
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userId) {
+ return sNameValueCache.putStringForUser(resolver, name, value, userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as an integer. Note that internally setting values are always
* stored as strings; this function converts the string to an integer
* for you. The default value will be returned if the setting is
@@ -827,7 +1102,12 @@ public final class CMSettings {
* or not a valid integer.
*/
public static int getInt(ContentResolver cr, String name, int def) {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userId) {
+ String v = getStringForUser(cr, name, userId);
try {
return v != null ? Integer.parseInt(v) : def;
} catch (NumberFormatException e) {
@@ -836,7 +1116,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as an integer. Note that internally setting values are always
* stored as strings; this function converts the string to an integer
* for you.
@@ -855,7 +1135,13 @@ public final class CMSettings {
*/
public static int getInt(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
try {
return Integer.parseInt(v);
} catch (NumberFormatException e) {
@@ -877,11 +1163,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putInt(ContentResolver cr, String name, int value) {
- return putString(cr, name, Integer.toString(value));
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userId) {
+ return putStringForUser(cr, name, Integer.toString(value), userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a {@code long}. Note that internally setting values are always
* stored as strings; this function converts the string to a {@code long}
* for you. The default value will be returned if the setting is
@@ -895,7 +1187,13 @@ public final class CMSettings {
* or not a valid {@code long}.
*/
public static long getLong(ContentResolver cr, String name, long def) {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userId) {
+ String valString = getStringForUser(cr, name, userId);
long value;
try {
value = valString != null ? Long.parseLong(valString) : def;
@@ -906,7 +1204,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a {@code long}. Note that internally setting values are always
* stored as strings; this function converts the string to a {@code long}
* for you.
@@ -924,7 +1222,13 @@ public final class CMSettings {
*/
public static long getLong(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String valString = getStringForUser(cr, name, userId);
try {
return Long.parseLong(valString);
} catch (NumberFormatException e) {
@@ -933,7 +1237,7 @@ public final class CMSettings {
}
/**
- * Convenience function for updating a secure settings value as a long
+ * Convenience function for updating a single settings value as a long
* integer. This will either create a new entry in the table if the
* given name does not exist, or modify the value of the existing row
* with that name. Note that internally setting values are always
@@ -946,11 +1250,17 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putLong(ContentResolver cr, String name, long value) {
- return putString(cr, name, Long.toString(value));
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userId) {
+ return putStringForUser(cr, name, Long.toString(value), userId);
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single settings value
* as a floating point number. Note that internally setting values are
* always stored as strings; this function converts the string to an
* float for you. The default value will be returned if the setting
@@ -964,7 +1274,13 @@ public final class CMSettings {
* or not a valid float.
*/
public static float getFloat(ContentResolver cr, String name, float def) {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userId) {
+ String v = getStringForUser(cr, name, userId);
try {
return v != null ? Float.parseFloat(v) : def;
} catch (NumberFormatException e) {
@@ -973,7 +1289,7 @@ public final class CMSettings {
}
/**
- * Convenience function for retrieving a single secure settings value
+ * Convenience function for retrieving a single system settings value
* as a float. Note that internally setting values are always
* stored as strings; this function converts the string to a float
* for you.
@@ -992,7 +1308,13 @@ public final class CMSettings {
*/
public static float getFloat(ContentResolver cr, String name)
throws CMSettingNotFoundException {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
if (v == null) {
throw new CMSettingNotFoundException(name);
}
@@ -1017,7 +1339,13 @@ public final class CMSettings {
* @return true if the value was set, false on database errors
*/
public static boolean putFloat(ContentResolver cr, String name, float value) {
- return putString(cr, name, Float.toString(value));
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userId) {
+ return putStringForUser(cr, name, Float.toString(value), userId);
}
// endregion
@@ -1032,7 +1360,7 @@ public final class CMSettings {
public static final String DEVICE_NAME = "device_name";
/**
- * Defines global heads up toggle. One of HEADS_UP_OFF, HEADS_UP_ON.
+ * Defines global heads up toggle. One of HEADS_UP_OFF, HEADS_UP_ON.
*
* @hide
*/
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 5657441..c345576 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -10,6 +10,7 @@
<uses-permission android:name="cyanogenmod.permission.PUBLISH_CUSTOM_TILE" />
<uses-permission android:name="cyanogenmod.permission.WRITE_SETTINGS"/>
<uses-permission android:name="cyanogenmod.permission.WRITE_SECURE_SETTINGS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="cyanogenmod.permission.MODIFY_NETWORK_SETTINGS" />
<uses-permission android:name="cyanogenmod.permission.MODIFY_SOUND_SETTINGS" />
<uses-permission android:name="cyanogenmod.permission.MANAGE_ALARMS" />
diff --git a/tests/src/org/cyanogenmod/tests/providers/CMSettingsTest.java b/tests/src/org/cyanogenmod/tests/providers/CMSettingsTest.java
index d179af8..77299db 100644
--- a/tests/src/org/cyanogenmod/tests/providers/CMSettingsTest.java
+++ b/tests/src/org/cyanogenmod/tests/providers/CMSettingsTest.java
@@ -17,6 +17,10 @@
package org.cyanogenmod.tests.providers;
import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
@@ -24,10 +28,20 @@ import cyanogenmod.providers.CMSettings;
public class CMSettingsTest extends AndroidTestCase{
private ContentResolver mContentResolver;
+ private CMSettingsTestObserver mTestObserver;
+
+ private static boolean sIsOnChangedCalled = false;
+ private static Uri sExpectedUriChange = null;
@Override
public void setUp() {
mContentResolver = getContext().getContentResolver();
+ mTestObserver = new CMSettingsTestObserver(null);
+ }
+
+ @Override
+ public void tearDown() {
+ mContentResolver.unregisterContentObserver(mTestObserver);
}
@MediumTest
@@ -43,6 +57,12 @@ public class CMSettingsTest extends AndroidTestCase{
String actualValue = CMSettings.System.getString(mContentResolver, key);
assertEquals(expectedValue, actualValue);
+ // setup observer
+ sIsOnChangedCalled = false;
+ sExpectedUriChange = CMSettings.System.getUriFor(key);
+ mContentResolver.registerContentObserver(sExpectedUriChange, false, mTestObserver,
+ UserHandle.USER_ALL);
+
// replace
final String expectedReplaceValue = "systemTestValue2";
isPutSuccessful = CMSettings.System.putString(mContentResolver, key, expectedReplaceValue);
@@ -53,9 +73,13 @@ public class CMSettingsTest extends AndroidTestCase{
assertEquals(expectedReplaceValue, actualValue);
// delete to clean up
- int rowsAffected = mContentResolver.delete(CMSettings.System.CONTENT_URI, Settings.NameValueTable.NAME + " = ?",
- new String[]{ key });
+ int rowsAffected = mContentResolver.delete(CMSettings.System.CONTENT_URI,
+ Settings.NameValueTable.NAME + " = ?", new String[]{ key });
assertEquals(1, rowsAffected);
+
+ if (!sIsOnChangedCalled) {
+ fail("On change was never called or was called with the wrong uri");
+ }
}
@MediumTest
@@ -71,6 +95,12 @@ public class CMSettingsTest extends AndroidTestCase{
String actualValue = CMSettings.Secure.getString(mContentResolver, key);
assertEquals(expectedValue, actualValue);
+ // setup observer
+ sIsOnChangedCalled = false;
+ sExpectedUriChange = CMSettings.Secure.getUriFor(key);
+ mContentResolver.registerContentObserver(sExpectedUriChange, false, mTestObserver,
+ UserHandle.USER_ALL);
+
// replace
final String expectedReplaceValue = "secureTestValue2";
isPutSuccessful = CMSettings.Secure.putString(mContentResolver, key, expectedReplaceValue);
@@ -81,9 +111,13 @@ public class CMSettingsTest extends AndroidTestCase{
assertEquals(expectedReplaceValue, actualValue);
// delete to clean up
- int rowsAffected = mContentResolver.delete(CMSettings.Secure.CONTENT_URI, Settings.NameValueTable.NAME + " = ?",
- new String[]{ key });
+ int rowsAffected = mContentResolver.delete(CMSettings.Secure.CONTENT_URI,
+ Settings.NameValueTable.NAME + " = ?", new String[]{ key });
assertEquals(1, rowsAffected);
+
+ if (!sIsOnChangedCalled) {
+ fail("On change was never called or was called with the wrong uri");
+ }
}
@MediumTest
@@ -99,6 +133,12 @@ public class CMSettingsTest extends AndroidTestCase{
String actualValue = CMSettings.Global.getString(mContentResolver, key);
assertEquals(expectedValue, actualValue);
+ // setup observer
+ sIsOnChangedCalled = false;
+ sExpectedUriChange = CMSettings.Global.getUriFor(key);
+ mContentResolver.registerContentObserver(sExpectedUriChange, false, mTestObserver,
+ UserHandle.USER_OWNER);
+
// replace
final String expectedReplaceValue = "globalTestValue2";
isPutSuccessful = CMSettings.Global.putString(mContentResolver, key, expectedReplaceValue);
@@ -109,9 +149,27 @@ public class CMSettingsTest extends AndroidTestCase{
assertEquals(expectedReplaceValue, actualValue);
// delete to clean up
- int rowsAffected = mContentResolver.delete(CMSettings.Global.CONTENT_URI, Settings.NameValueTable.NAME + " = ?",
- new String[]{ key });
+ int rowsAffected = mContentResolver.delete(CMSettings.Global.CONTENT_URI,
+ Settings.NameValueTable.NAME + " = ?", new String[]{ key });
assertEquals(1, rowsAffected);
+
+ if (!sIsOnChangedCalled) {
+ fail("On change was never called or was called with the wrong uri");
+ }
+ }
+
+ private class CMSettingsTestObserver extends ContentObserver {
+
+ public CMSettingsTestObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (sExpectedUriChange.equals(uri)) {
+ sIsOnChangedCalled = true;
+ }
+ }
}
// TODO Add tests for other users