Add CMSettingsProvider and CMDatabaseHelper

issue-id: CYNGNOS-828

Change-Id: I01c08c0e432d6a941950a565e5ab6664664e2a7f
This commit is contained in:
Yvonne Wong 2015-08-20 16:02:08 -07:00
parent e949433c08
commit 0eb2999091
17 changed files with 2217 additions and 2 deletions

View File

@ -163,7 +163,7 @@ LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:= build/tools/droiddoc/templates-sdk
LOCAL_DROIDDOC_OPTIONS:= \
-stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/cmsdk_stubs_current_intermediates/src \
-stubpackages cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.hardware:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.platform:org.cyanogenmod.platform \
-stubpackages cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.hardware:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.providers:cyanogenmod.platform:org.cyanogenmod.platform \
-api $(INTERNAL_CM_PLATFORM_API_FILE) \
-removedApi $(INTERNAL_CM_PLATFORM_REMOVED_API_FILE) \
-nodocs \
@ -192,7 +192,7 @@ LOCAL_MODULE := cm-system-api-stubs
LOCAL_DROIDDOC_OPTIONS:=\
-stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/cmsdk_system_stubs_current_intermediates/src \
-stubpackages cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.hardware:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.platform:org.cyanogenmod.platform \
-stubpackages cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.hardware:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.providers:cyanogenmod.platform:org.cyanogenmod.platform \
-showAnnotation android.annotation.SystemApi \
-api $(INTERNAL_CM_PLATFORM_SYSTEM_API_FILE) \
-removedApi $(INTERNAL_CM_PLATFORM_SYSTEM_REMOVED_API_FILE) \

View File

@ -442,6 +442,8 @@ package cyanogenmod.platform {
field public static final java.lang.String MODIFY_SOUND_SETTINGS = "cyanogenmod.permission.MODIFY_SOUND_SETTINGS";
field public static final java.lang.String PUBLISH_CUSTOM_TILE = "cyanogenmod.permission.PUBLISH_CUSTOM_TILE";
field public static final java.lang.String READ_MSIM_PHONE_STATE = "cyanogenmod.permission.READ_MSIM_PHONE_STATE";
field public static final java.lang.String WRITE_SECURE_SETTINGS = "cyanogenmod.permission.WRITE_SECURE_SETTINGS";
field public static final java.lang.String WRITE_SETTINGS = "cyanogenmod.permission.WRITE_SETTINGS";
}
public final class R {
@ -566,3 +568,68 @@ package cyanogenmod.profiles {
}
package cyanogenmod.providers {
public final class CMSettings {
ctor public CMSettings();
field public static final java.lang.String AUTHORITY = "cmsettings";
}
public static class CMSettings.CMSettingNotFoundException extends android.util.AndroidException {
ctor public CMSettings.CMSettingNotFoundException(java.lang.String);
}
public static final class CMSettings.Global extends android.provider.Settings.NameValueTable {
ctor public CMSettings.Global();
method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
method public static float getFloat(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static int getInt(android.content.ContentResolver, java.lang.String, int);
method public static int getInt(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static long getLong(android.content.ContentResolver, java.lang.String, long);
method public static long getLong(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static java.lang.String getString(android.content.ContentResolver, java.lang.String);
method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float);
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_global_version";
}
public static final class CMSettings.Secure extends android.provider.Settings.NameValueTable {
ctor public CMSettings.Secure();
method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
method public static float getFloat(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static int getInt(android.content.ContentResolver, java.lang.String, int);
method public static int getInt(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static long getLong(android.content.ContentResolver, java.lang.String, long);
method public static long getLong(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static java.lang.String getString(android.content.ContentResolver, java.lang.String);
method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float);
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String NAME_THEME_CONFIG = "name_theme_config";
field public static final java.lang.String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_secure_version";
}
public static final class CMSettings.System extends android.provider.Settings.NameValueTable {
ctor public CMSettings.System();
method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
method public static float getFloat(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static int getInt(android.content.ContentResolver, java.lang.String, int);
method public static int getInt(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static long getLong(android.content.ContentResolver, java.lang.String, long);
method public static long getLong(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static java.lang.String getString(android.content.ContentResolver, java.lang.String);
method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float);
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_system_version";
}
}

View File

@ -69,6 +69,19 @@
android:description="@string/permdesc_useHardwareFramework"
android:protectionLevel="system|signature" />
<!-- Allows an application to write to CM system settings -->
<permission android:name="cyanogenmod.permission.WRITE_SETTINGS"
android:label="@string/permlab_writeSettings"
android:description="@string/permdesc_writeSettings"
android:protectionLevel="normal" />
<!-- Allows an application to write to secure CM system settings.
<p>Not for use by third-party applications. -->
<permission android:name="cyanogenmod.permission.WRITE_SECURE_SETTINGS"
android:label="@string/permlab_writeSecureSettings"
android:description="@string/permdesc_writeSecureSettings"
android:protectionLevel="signature|system|development" />
<application android:process="system"
android:persistent="true"
android:hasCode="false"

View File

@ -42,6 +42,13 @@
<string name="permlab_useHardwareFramework">use hardware framework</string>
<string name="permdesc_useHardwareFramework">Allows an app access to the CM hardware framework.</string>
<!-- Labels for the WRITE_SETTINGS permission -->
<string name="permlab_writeSettings">modify CM system settings</string>
<string name="permdesc_writeSettings">Allows an app to modify CM system settings.</string>
<!-- Labels for the WRITE_SECURE_SETTINGS permission -->
<string name="permlab_writeSecureSettings">modify CM secure system settings</string>
<string name="permdesc_writeSecureSettings">Allows an app to modify CM secure system settings. Not for use by normal apps.</string>
<!-- Label to show for a service that is running because it is observing the user's custom tiles. -->
<string name="custom_tile_listener_binding_label">Custom tile listener</string>

View File

@ -0,0 +1,36 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
src_dir := src
res_dir := res
LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dir))
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
LOCAL_PACKAGE_NAME := CMSettingsProvider
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
LOCAL_JAVA_LIBRARIES := \
org.cyanogenmod.platform.sdk
include $(BUILD_PACKAGE)
########################
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.cyanogenmod.cmsettings"
coreApp="true"
android:sharedUserId="android.uid.system">
<!-- It is necessary to be a system app in order to update table versions in SystemProperties for
CMSettings to know whether or not the client side cache is up to date. It is also necessary
to run in the system process in order to start the content provider prior to running migration
for CM settings on user starting -->
<uses-permission android:name="android.permission.MANAGE_USERS" />
<application android:icon="@drawable/icon"
android:label="@string/app_name"
android:process="system"
android:killAfterRestore="false"
android:allowClearUserData="false"
android:enabled="true">
<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" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

View File

@ -0,0 +1,19 @@
<?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>
<string name="app_name">CMSettingsProvider</string>
</resources>

View File

@ -0,0 +1,126 @@
/**
* 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.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
import android.os.UserHandle;
import android.util.Log;
import java.io.File;
/**
* The CMDatabaseHelper allows creation of a database to store CM specific settings for a user
* in System, Secure, and Global tables.
*/
public class CMDatabaseHelper extends SQLiteOpenHelper{
private static final String TAG = "CMDatabaseHelper";
private static final boolean LOCAL_LOGV = false;
private static final String DATABASE_NAME = "cmsettings.db";
private static final int DATABASE_VERSION = 1;
static class CMTableNames {
static final String TABLE_SYSTEM = "system";
static final String TABLE_SECURE = "secure";
static final String TABLE_GLOBAL = "global";
}
private static final String CREATE_TABLE_SQL_FORMAT = "CREATE TABLE %s (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
"name TEXT UNIQUE ON CONFLICT REPLACE," +
"value TEXT" +
");)";
private static final String CREATE_INDEX_SQL_FORMAT = "CREATE INDEX %sIndex%d ON %s (name);";
private int mUserHandle;
/**
* Gets the appropriate database path for a specific user
* @param userId The database path for this user
* @return The database path string
*/
static String dbNameForUser(final int userId) {
// The owner gets the unadorned db name;
if (userId == UserHandle.USER_OWNER) {
return DATABASE_NAME;
} else {
// Place the database in the user-specific data tree so that it's
// cleaned up automatically when the user is deleted.
File databaseFile = new File(
Environment.getUserSystemDirectory(userId), DATABASE_NAME);
return databaseFile.getPath();
}
}
/**
* Creates an instance of {@link CMDatabaseHelper}
* @param context
* @param userId
*/
public CMDatabaseHelper(Context context, int userId) {
super(context, dbNameForUser(userId), null, DATABASE_VERSION);
mUserHandle = userId;
}
/**
* Creates System, Secure, and Global tables in the specified {@link SQLiteDatabase}
* @param db The database.
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.beginTransaction();
try {
createDbTable(db, CMTableNames.TABLE_SYSTEM);
createDbTable(db, CMTableNames.TABLE_SECURE);
if (mUserHandle == UserHandle.USER_OWNER) {
createDbTable(db, CMTableNames.TABLE_GLOBAL);
}
db.setTransactionSuccessful();
if (LOCAL_LOGV) Log.v(TAG, "Successfully created tables for cm settings db");
} finally {
db.endTransaction();
}
}
/**
* Creates a table and index for the specified database and table name
* @param db
* @param tableName
*/
private void createDbTable(SQLiteDatabase db, String tableName) {
if (LOCAL_LOGV) Log.v(TAG, "Creating table and index for: " + tableName);
String createTableSql = String.format(CREATE_TABLE_SQL_FORMAT, tableName);
db.execSQL(createTableSql);
String createIndexSql = String.format(CREATE_INDEX_SQL_FORMAT, tableName, 1, tableName);
db.execSQL(createIndexSql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}

View File

@ -0,0 +1,451 @@
/**
* 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.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.pm.PackageManager;
import android.database.AbstractCursor;
import android.database.Cursor;
import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import cyanogenmod.providers.CMSettings;
/**
* The CMSettingsProvider serves as a {@link ContentProvider} for CM specific settings
*/
public class CMSettingsProvider extends ContentProvider {
private static final String TAG = "CMSettingsProvider";
private static final boolean LOCAL_LOGV = false;
private static final boolean USER_CHECK_THROWS = true;
// Each defined user has their own settings
protected final SparseArray<CMDatabaseHelper> mDbHelpers = new SparseArray<CMDatabaseHelper>();
private static final int SYSTEM = 1;
private static final int SECURE = 2;
private static final int GLOBAL = 3;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_SYSTEM,
SYSTEM);
sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_SECURE,
SECURE);
sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_GLOBAL,
GLOBAL);
// TODO add other paths for getting specific items
}
private UserManager mUserManager;
private Uri.Builder mUriBuilder;
@Override
public boolean onCreate() {
if (LOCAL_LOGV) Log.d(TAG, "Creating CMSettingsProvider");
mUserManager = UserManager.get(getContext());
establishDbTracking(UserHandle.USER_OWNER);
mUriBuilder = new Uri.Builder();
mUriBuilder.scheme(ContentResolver.SCHEME_CONTENT);
mUriBuilder.authority(CMSettings.AUTHORITY);
// TODO Add migration for cm settings
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
String tableName = getTableNameFromUri(uri);
checkWritePermissions(tableName);
int callingUserId = UserHandle.getCallingUserId();
CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
callingUserId));
SQLiteDatabase db = dbHelper.getReadableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(tableName);
Cursor 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);
} catch (ClassCastException e) {
// details of the concrete Cursor implementation have changed and this code has
// not been updated to match -- complain and fail hard.
Log.wtf(TAG, "Incompatible cursor derivation");
throw e;
}
return returnCursor;
}
@Override
public String getType(Uri uri) {
// TODO: Implement
return null;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
return bulkInsertForUser(UserHandle.getCallingUserId(), uri, values);
}
/**
* Performs a bulk insert for a specific user.
* @param userId The user id to perform the bulk insert for.
* @param uri The content:// URI of the insertion request.
* @param values An array of sets of column_name/value pairs to add to the database.
* This must not be {@code null}.
* @return Number of rows inserted.
*/
int bulkInsertForUser(int userId, Uri uri, ContentValues[] values) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
if (values == null) {
throw new IllegalArgumentException("ContentValues cannot be null");
}
int numRowsAffected = 0;
String tableName = getTableNameFromUri(uri);
checkWritePermissions(tableName);
CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName, userId));
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {
for (ContentValues value : values) {
if (value == null) {
continue;
}
long rowId = db.insert(tableName, null, value);
if (rowId >= 0) {
numRowsAffected++;
if (LOCAL_LOGV) Log.d(TAG, tableName + " <- " + values);
} else {
return 0;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
if (numRowsAffected > 0) {
getContext().getContentResolver().notifyChange(uri, null);
notifyChange(uri, tableName, userId);
if (LOCAL_LOGV) Log.d(TAG, tableName + ": " + numRowsAffected + " row(s) inserted");
}
return numRowsAffected;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
if (values == null) {
throw new IllegalArgumentException("ContentValues cannot be null");
}
String tableName = getTableNameFromUri(uri);
checkWritePermissions(tableName);
int callingUserId = UserHandle.getCallingUserId();
CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
callingUserId));
long rowId = -1;
SQLiteDatabase db = dbHelper.getWritableDatabase();
try {
rowId = db.insert(tableName, null, values);
} finally {
db.close();
}
Uri returnUri = null;
if (rowId != -1) {
returnUri = ContentUris.withAppendedId(uri, rowId);
notifyChange(returnUri, tableName, callingUserId);
if (LOCAL_LOGV) Log.d(TAG, "Inserted row id: " + rowId + " into tableName: " +
tableName);
}
return returnUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
int numRowsAffected = 0;
// Allow only selection by key; a null/empty selection string will cause all rows in the
// table to be deleted
if (!TextUtils.isEmpty(selection) && selectionArgs.length > 0) {
String tableName = getTableNameFromUri(uri);
checkWritePermissions(tableName);
int callingUserId = UserHandle.getCallingUserId();
CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
callingUserId));
SQLiteDatabase db = dbHelper.getWritableDatabase();
try {
numRowsAffected = db.delete(tableName, selection, selectionArgs);
} finally {
db.close();
}
if (numRowsAffected > 0) {
notifyChange(uri, tableName, callingUserId);
if (LOCAL_LOGV) Log.d(TAG, tableName + ": " + numRowsAffected + " row(s) deleted");
}
}
return numRowsAffected;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
if (values == null) {
throw new IllegalArgumentException("ContentValues cannot be null");
}
String tableName = getTableNameFromUri(uri);
checkWritePermissions(tableName);
int callingUserId = UserHandle.getCallingUserId();
CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
callingUserId));
int numRowsAffected = 0;
SQLiteDatabase db = dbHelper.getWritableDatabase();
try {
numRowsAffected = db.update(tableName, values, selection, selectionArgs);
} finally {
db.close();
}
if (numRowsAffected > 0) {
getContext().getContentResolver().notifyChange(uri, null);
if (LOCAL_LOGV) Log.d(TAG, tableName + ": " + numRowsAffected + " row(s) updated");
}
return numRowsAffected;
}
/**
* 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.
* @param callingUser
* @return
*/
private CMDatabaseHelper getOrEstablishDatabase(int callingUser) {
if (callingUser >= android.os.Process.SYSTEM_UID) {
if (USER_CHECK_THROWS) {
throw new IllegalArgumentException("Uid rather than user handle: " + callingUser);
} else {
Log.wtf(TAG, "Establish db for uid rather than user: " + callingUser);
}
}
long oldId = Binder.clearCallingIdentity();
try {
CMDatabaseHelper dbHelper;
synchronized (this) {
dbHelper = mDbHelpers.get(callingUser);
}
if (null == dbHelper) {
establishDbTracking(callingUser);
synchronized (this) {
dbHelper = mDbHelpers.get(callingUser);
}
}
return dbHelper;
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
/**
* Check if a {@link CMDatabaseHelper} exists for a user and if it doesn't, a new helper is
* created and added to the list of tracked database helpers
* @param userId
*/
private void establishDbTracking(int userId) {
CMDatabaseHelper dbHelper;
synchronized (this) {
dbHelper = mDbHelpers.get(userId);
if (LOCAL_LOGV) {
Log.i(TAG, "Checking cm settings db helper for user " + userId);
}
if (dbHelper == null) {
if (LOCAL_LOGV) {
Log.i(TAG, "Installing new cm settings db helper for user " + userId);
}
dbHelper = new CMDatabaseHelper(getContext(), userId);
mDbHelpers.append(userId, dbHelper);
}
}
// Initialization of the db *outside* the locks. It's possible that racing
// threads might wind up here, the second having read the cache entries
// written by the first, but that's benign: the SQLite helper implementation
// manages concurrency itself, and it's important that we not run the db
// initialization with any of our own locks held, so we're fine.
SQLiteDatabase db = null;
try {
db = dbHelper.getWritableDatabase();
} catch (SQLiteCantOpenDatabaseException ex){
Log.e(TAG, "Unable to open writable database for user: " + userId, ex);
} finally {
db.close();
}
}
/**
* Makes sure the caller has permission to write this data.
* @param tableName supplied by the caller
* @throws SecurityException if the caller is forbidden to write.
*/
private void checkWritePermissions(String tableName) {
if ((CMDatabaseHelper.CMTableNames.TABLE_SECURE.equals(tableName) ||
CMDatabaseHelper.CMTableNames.TABLE_GLOBAL.equals(tableName)) &&
getContext().checkCallingOrSelfPermission(
cyanogenmod.platform.Manifest.permission.WRITE_SECURE_SETTINGS) !=
PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
String.format("Permission denial: writing to cm secure settings requires %1$s",
cyanogenmod.platform.Manifest.permission.WRITE_SECURE_SETTINGS));
}
}
/**
* Utilizes an {@link UriMatcher} to check for a valid combination of scheme, authority, and
* path and returns the corresponding table name
* @param uri
* @return Table name
*/
private String getTableNameFromUri(Uri uri) {
int code = sUriMatcher.match(uri);
switch (code) {
case SYSTEM:
return CMDatabaseHelper.CMTableNames.TABLE_SYSTEM;
case SECURE:
return CMDatabaseHelper.CMTableNames.TABLE_SECURE;
case GLOBAL:
return CMDatabaseHelper.CMTableNames.TABLE_GLOBAL;
default:
throw new IllegalArgumentException("Invalid uri: " + uri);
}
}
/**
* If the table is Global, the owner's user id is returned. Otherwise, the original user id
* is returned.
* @param tableName
* @param userId
* @return User id
*/
private int getUserIdForTable(String tableName, int userId) {
return CMDatabaseHelper.CMTableNames.TABLE_GLOBAL.equals(tableName) ?
UserHandle.USER_OWNER : userId;
}
/**
* Modify setting version for an updated table before notifying of change. The
* {@link CMSettings} class uses these to provide client-side caches.
* @param uri to send notifications for
* @param userId
*/
private void notifyChange(Uri uri, String tableName, int userId) {
String property = null;
final boolean isGlobal = tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_GLOBAL);
if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_SYSTEM)) {
property = CMSettings.System.SYS_PROP_CM_SETTING_VERSION;
} else if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_SECURE)) {
property = CMSettings.Secure.SYS_PROP_CM_SETTING_VERSION;
} else if (isGlobal) {
property = CMSettings.Global.SYS_PROP_CM_SETTING_VERSION;
}
if (property != null) {
long version = SystemProperties.getLong(property, 0) + 1;
if (LOCAL_LOGV) Log.v(TAG, "property: " + property + "=" + version);
SystemProperties.set(property, Long.toString(version));
}
final int notifyTarget = isGlobal ? UserHandle.USER_ALL : userId;
final long oldId = Binder.clearCallingIdentity();
try {
getContext().getContentResolver().notifyChange(uri, null, true, notifyTarget);
} finally {
Binder.restoreCallingIdentity(oldId);
}
if (LOCAL_LOGV) Log.v(TAG, "notifying for " + notifyTarget + ": " + uri);
}
// TODO Add caching
}

View File

@ -0,0 +1,34 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_PACKAGE_NAME := CMSettingsProviderTests
LOCAL_INSTRUMENTATION_FOR := CMSettingsProvider
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_CERTIFICATE := platform
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_STATIC_JAVA_LIBRARIES := \
org.cyanogenmod.platform.sdk
include $(BUILD_PACKAGE)

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.cyanogenmod.cmsettings.tests">
<uses-permission android:name="cyanogenmod.permission.WRITE_SETTINGS"/>
<uses-permission android:name="cyanogenmod.permission.WRITE_SECURE_SETTINGS"/>
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="org.cyanogenmod.cmsettings.tests"
android:label="CM Settings Provider Tests" />
<application>
<uses-library android:name="android.test.runner" />
</application>
</manifest>

View File

@ -0,0 +1,159 @@
/**
* 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.tests;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import cyanogenmod.providers.CMSettings;
import java.util.LinkedHashMap;
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>();
static {
mMap.put("testKey1", "value1");
mMap.put("testKey2", "value2");
mMap.put("testKey3", "value3");
}
private static final String[] PROJECTIONS = new String[] { "name", "value" };
private ContentResolver mContentResolver;
@Override
public void setUp() {
mContentResolver = mContext.getContentResolver();
}
@MediumTest
public void testBulkInsertSuccess() {
Log.d(TAG, "Starting bulk insert test");
ContentValues[] contentValues = new ContentValues[mMap.size()];
int count = 0;
for (Map.Entry<String, String> kVPair : mMap.entrySet()) {
ContentValues contentValue = new ContentValues();
contentValue.put(PROJECTIONS[0], kVPair.getKey());
contentValue.put(PROJECTIONS[1], 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");
}
private void testBulkInsertForUri(Uri uri, ContentValues[] contentValues) {
int rowsInserted = mContentResolver.bulkInsert(uri, contentValues);
assertEquals(mMap.size(), rowsInserted);
Cursor queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
try {
while (queryCursor.moveToNext()) {
assertEquals(PROJECTIONS.length, queryCursor.getColumnCount());
String actualKey = queryCursor.getString(0);
assertTrue(mMap.containsKey(actualKey));
assertEquals(mMap.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 });
}
}
@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 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));
// test delete
rowsAffected = mContentResolver.delete(uri, PROJECTIONS[0] + " = ?", new String[]{key1});
assertEquals(1, rowsAffected);
// check delete
queryCursor = mContentResolver.query(uri, PROJECTIONS, null, null, null);
assertEquals(0, queryCursor.getCount());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -442,6 +442,8 @@ package cyanogenmod.platform {
field public static final java.lang.String MODIFY_SOUND_SETTINGS = "cyanogenmod.permission.MODIFY_SOUND_SETTINGS";
field public static final java.lang.String PUBLISH_CUSTOM_TILE = "cyanogenmod.permission.PUBLISH_CUSTOM_TILE";
field public static final java.lang.String READ_MSIM_PHONE_STATE = "cyanogenmod.permission.READ_MSIM_PHONE_STATE";
field public static final java.lang.String WRITE_SECURE_SETTINGS = "cyanogenmod.permission.WRITE_SECURE_SETTINGS";
field public static final java.lang.String WRITE_SETTINGS = "cyanogenmod.permission.WRITE_SETTINGS";
}
public final class R {
@ -566,3 +568,68 @@ package cyanogenmod.profiles {
}
package cyanogenmod.providers {
public final class CMSettings {
ctor public CMSettings();
field public static final java.lang.String AUTHORITY = "cmsettings";
}
public static class CMSettings.CMSettingNotFoundException extends android.util.AndroidException {
ctor public CMSettings.CMSettingNotFoundException(java.lang.String);
}
public static final class CMSettings.Global extends android.provider.Settings.NameValueTable {
ctor public CMSettings.Global();
method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
method public static float getFloat(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static int getInt(android.content.ContentResolver, java.lang.String, int);
method public static int getInt(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static long getLong(android.content.ContentResolver, java.lang.String, long);
method public static long getLong(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static java.lang.String getString(android.content.ContentResolver, java.lang.String);
method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float);
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_global_version";
}
public static final class CMSettings.Secure extends android.provider.Settings.NameValueTable {
ctor public CMSettings.Secure();
method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
method public static float getFloat(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static int getInt(android.content.ContentResolver, java.lang.String, int);
method public static int getInt(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static long getLong(android.content.ContentResolver, java.lang.String, long);
method public static long getLong(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static java.lang.String getString(android.content.ContentResolver, java.lang.String);
method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float);
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String NAME_THEME_CONFIG = "name_theme_config";
field public static final java.lang.String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_secure_version";
}
public static final class CMSettings.System extends android.provider.Settings.NameValueTable {
ctor public CMSettings.System();
method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
method public static float getFloat(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static int getInt(android.content.ContentResolver, java.lang.String, int);
method public static int getInt(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static long getLong(android.content.ContentResolver, java.lang.String, long);
method public static long getLong(android.content.ContentResolver, java.lang.String) throws cyanogenmod.providers.CMSettings.CMSettingNotFoundException;
method public static java.lang.String getString(android.content.ContentResolver, java.lang.String);
method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float);
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_system_version";
}
}

View File

@ -6,6 +6,8 @@
<uses-permission android:name="cyanogenmod.permission.PUBLISH_CUSTOM_TILE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="cyanogenmod.permission.WRITE_SETTINGS"/>
<uses-permission android:name="cyanogenmod.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="cyanogenmod.permission.MODIFY_NETWORK_SETTINGS" />
<uses-permission android:name="cyanogenmod.permission.MODIFY_SOUND_SETTINGS" />
<uses-permission android:name="android.permission.REBOOT" />

View File

@ -0,0 +1,118 @@
/**
* 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.tests.providers;
import android.content.ContentResolver;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import cyanogenmod.providers.CMSettings;
public class CMSettingsTest extends AndroidTestCase{
private ContentResolver mContentResolver;
@Override
public void setUp() {
mContentResolver = getContext().getContentResolver();
}
@MediumTest
public void testPutAndGetSystemString() {
final String key = "key";
// put
final String expectedValue = "systemTestValue1";
boolean isPutSuccessful = CMSettings.System.putString(mContentResolver, key, expectedValue);
assertTrue(isPutSuccessful);
// get
String actualValue = CMSettings.System.getString(mContentResolver, key);
assertEquals(expectedValue, actualValue);
// replace
final String expectedReplaceValue = "systemTestValue2";
isPutSuccessful = CMSettings.System.putString(mContentResolver, key, expectedReplaceValue);
assertTrue(isPutSuccessful);
// get
actualValue = CMSettings.System.getString(mContentResolver, key);
assertEquals(expectedReplaceValue, actualValue);
// delete to clean up
int rowsAffected = mContentResolver.delete(CMSettings.System.CONTENT_URI, Settings.NameValueTable.NAME + " = ?",
new String[]{ key });
assertEquals(1, rowsAffected);
}
@MediumTest
public void testPutAndGetSecureString() {
final String key = "key";
// put
final String expectedValue = "secureTestValue1";
boolean isPutSuccessful = CMSettings.Secure.putString(mContentResolver, key, expectedValue);
assertTrue(isPutSuccessful);
// get
String actualValue = CMSettings.Secure.getString(mContentResolver, key);
assertEquals(expectedValue, actualValue);
// replace
final String expectedReplaceValue = "secureTestValue2";
isPutSuccessful = CMSettings.Secure.putString(mContentResolver, key, expectedReplaceValue);
assertTrue(isPutSuccessful);
// get
actualValue = CMSettings.Secure.getString(mContentResolver, key);
assertEquals(expectedReplaceValue, actualValue);
// delete to clean up
int rowsAffected = mContentResolver.delete(CMSettings.Secure.CONTENT_URI, Settings.NameValueTable.NAME + " = ?",
new String[]{ key });
assertEquals(1, rowsAffected);
}
@MediumTest
public void testPutAndGetGlobalString() {
final String key = "key";
// put
final String expectedValue = "globalTestValue1";
boolean isPutSuccessful = CMSettings.Global.putString(mContentResolver, key, expectedValue);
assertTrue(isPutSuccessful);
// get
String actualValue = CMSettings.Global.getString(mContentResolver, key);
assertEquals(expectedValue, actualValue);
// replace
final String expectedReplaceValue = "globalTestValue2";
isPutSuccessful = CMSettings.Global.putString(mContentResolver, key, expectedReplaceValue);
assertTrue(isPutSuccessful);
// get
actualValue = CMSettings.Global.getString(mContentResolver, key);
assertEquals(expectedReplaceValue, actualValue);
// delete to clean up
int rowsAffected = mContentResolver.delete(CMSettings.Global.CONTENT_URI, Settings.NameValueTable.NAME + " = ?",
new String[]{ key });
assertEquals(1, rowsAffected);
}
// TODO Add tests for other users
}