Add way to migrate CM specific settings to CMSettingsProvider

issue-id: CYNGNOS-829

Change-Id: I08743ebf9ffd3846ae501ed41e396b1556dc41cf
This commit is contained in:
Yvonne Wong 2015-08-27 12:19:54 -07:00 committed by Gerrit Code Review
parent 8fc6affd38
commit 05d0129478
11 changed files with 1342 additions and 171 deletions

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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.
@ -356,6 +722,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
@ -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);
}
}

View File

@ -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");
}
}
}

View File

@ -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"

View File

@ -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 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);
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() {
Log.d(TAG, "Starting bulk insert test");
ContentValues[] contentValues = new ContentValues[mMap.size()];
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);
Cursor queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
final String placeholderSymbol = "?";
String[] placeholders = new String[contentValues.length];
for (int i = 0; i < placeholders.length; i++) {
placeholders[i] = placeholderSymbol;
}
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);
contentValue.put(Settings.NameValueTable.NAME, key);
contentValue.put(Settings.NameValueTable.VALUE, value1);
mContentResolver.insert(uri, contentValue);
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());
// check insert
Cursor queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
assertEquals(1, queryCursor.getCount());
assertExpectedKeyValuePair(queryCursor, key, value1);
queryCursor.moveToNext();
assertEquals(PROJECTIONS.length, queryCursor.getColumnCount());
// check insert with returned uri
queryCursor = mContentResolver.query(returnUri, PROJECTIONS, null, null, null);
assertEquals(1, queryCursor.getCount());
String actualKey = queryCursor.getString(0);
assertEquals(key1, actualKey);
assertEquals(value1, queryCursor.getString(1));
assertExpectedKeyValuePair(queryCursor, key, value1);
// test update
contentValue.clear();
contentValue.put(PROJECTIONS[1], value2);
// test update
contentValue.clear();
contentValue.put(Settings.NameValueTable.VALUE, value2);
int rowsAffected = mContentResolver.update(uri, contentValue, PROJECTIONS[0] + " = ?",
new String[]{key1});
assertEquals(1, rowsAffected);
int rowsAffected = mContentResolver.update(uri, contentValue,
Settings.NameValueTable.NAME + " = ?", new String[]{ key });
assertEquals(1, rowsAffected);
// check update
queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
assertEquals(1, queryCursor.getCount());
// check update
queryCursor = mContentResolver.query(uri, PROJECTIONS, Settings.NameValueTable.NAME +
" = ?", new String[]{ key }, null);
assertEquals(1, queryCursor.getCount());
queryCursor.moveToNext();
assertEquals(PROJECTIONS.length, queryCursor.getColumnCount());
assertExpectedKeyValuePair(queryCursor, key, value2);
actualKey = queryCursor.getString(0);
assertEquals(key1, actualKey);
assertEquals(value2, queryCursor.getString(1));
// test delete
rowsAffected = mContentResolver.delete(uri, Settings.NameValueTable.NAME + " = ?",
new String[]{ key });
assertEquals(1, rowsAffected);
// test delete
rowsAffected = mContentResolver.delete(uri, PROJECTIONS[0] + " = ?", new String[]{key1});
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();
}
}
}
// check delete
queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
assertEquals(0, queryCursor.getCount());
private void assertExpectedKeyValuePair(Cursor cursor, String expectedKey,
String expectedValue) {
cursor.moveToNext();
assertEquals(PROJECTIONS.length, cursor.getColumnCount());
String actualKey = cursor.getString(0);
assertEquals(expectedKey, actualKey);
assertEquals(expectedValue, cursor.getString(1));
}
}

View File

@ -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,10 +267,22 @@ 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
@ -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,10 +1038,22 @@ 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
@ -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
*/

View File

@ -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" />

View File

@ -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