aboutsummaryrefslogtreecommitdiffstats
path: root/packages/CMSettingsProvider/src/org
diff options
context:
space:
mode:
authorYvonne Wong <ywong@cyngn.com>2015-08-27 12:19:54 -0700
committerGerrit Code Review <gerrit@cyanogenmod.org>2015-09-25 13:17:19 -0700
commit05d01294782115b652a0ef28e5cb35ab3a7ad642 (patch)
tree3c7abc8666e1fd1c17ca68df1c364a81b2e9f651 /packages/CMSettingsProvider/src/org
parent8fc6affd388f70784e0850ebe4bf8d774389d79c (diff)
downloadvendor_cmsdk-05d01294782115b652a0ef28e5cb35ab3a7ad642.zip
vendor_cmsdk-05d01294782115b652a0ef28e5cb35ab3a7ad642.tar.gz
vendor_cmsdk-05d01294782115b652a0ef28e5cb35ab3a7ad642.tar.bz2
Add way to migrate CM specific settings to CMSettingsProvider
issue-id: CYNGNOS-829 Change-Id: I08743ebf9ffd3846ae501ed41e396b1556dc41cf
Diffstat (limited to 'packages/CMSettingsProvider/src/org')
-rw-r--r--packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java173
-rw-r--r--packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java444
-rw-r--r--packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java56
3 files changed, 644 insertions, 29 deletions
diff --git a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java
index 85fbaa9..2016378 100644
--- a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java
+++ b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMDatabaseHelper.java
@@ -16,12 +16,16 @@
package org.cyanogenmod.cmsettings;
+import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Build;
import android.os.Environment;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
+import cyanogenmod.providers.CMSettings;
import java.io.File;
@@ -34,7 +38,7 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
private static final boolean LOCAL_LOGV = false;
private static final String DATABASE_NAME = "cmsettings.db";
- private static final int DATABASE_VERSION = 1;
+ private static final int DATABASE_VERSION = 2;
static class CMTableNames {
static final String TABLE_SYSTEM = "system";
@@ -50,6 +54,11 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
private static final String CREATE_INDEX_SQL_FORMAT = "CREATE INDEX %sIndex%d ON %s (name);";
+ private static final String DROP_TABLE_SQL_FORMAT = "DROP TABLE IF EXISTS %s;";
+
+ private static final String DROP_INDEX_SQL_FORMAT = "DROP INDEX IF EXISTS %sIndex%d;";
+
+ private Context mContext;
private int mUserHandle;
/**
@@ -77,11 +86,13 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
*/
public CMDatabaseHelper(Context context, int userId) {
super(context, dbNameForUser(userId), null, DATABASE_VERSION);
+ mContext = context;
mUserHandle = userId;
}
/**
- * Creates System, Secure, and Global tables in the specified {@link SQLiteDatabase}
+ * Creates System, Secure, and Global tables in the specified {@link SQLiteDatabase} and loads
+ * default values into the created tables.
* @param db The database.
*/
@Override
@@ -96,9 +107,11 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
createDbTable(db, CMTableNames.TABLE_GLOBAL);
}
+ loadSettings(db);
+
db.setTransactionSuccessful();
- if (LOCAL_LOGV) Log.v(TAG, "Successfully created tables for cm settings db");
+ if (LOCAL_LOGV) Log.d(TAG, "Successfully created tables for cm settings db");
} finally {
db.endTransaction();
}
@@ -106,11 +119,11 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
/**
* Creates a table and index for the specified database and table name
- * @param db
- * @param tableName
+ * @param db The {@link SQLiteDatabase} to create the table and index in.
+ * @param tableName The name of the database table to create.
*/
private void createDbTable(SQLiteDatabase db, String tableName) {
- if (LOCAL_LOGV) Log.v(TAG, "Creating table and index for: " + tableName);
+ if (LOCAL_LOGV) Log.d(TAG, "Creating table and index for: " + tableName);
String createTableSql = String.format(CREATE_TABLE_SQL_FORMAT, tableName);
db.execSQL(createTableSql);
@@ -121,6 +134,154 @@ public class CMDatabaseHelper extends SQLiteOpenHelper{
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ int upgradeVersion = oldVersion;
+
+ if (upgradeVersion < 2) {
+ db.beginTransaction();
+ try {
+ loadSettings(db);
+
+ db.setTransactionSuccessful();
+
+ upgradeVersion = 2;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ // *** Remember to update DATABASE_VERSION above!
+
+ if (upgradeVersion < newVersion) {
+ Log.w(TAG, "Got stuck trying to upgrade db. Old version: " + oldVersion
+ + ", version stuck at: " + upgradeVersion + ", new version: "
+ + newVersion + ". Must wipe the cm settings provider.");
+
+ dropDbTable(db, CMTableNames.TABLE_SYSTEM);
+ dropDbTable(db, CMTableNames.TABLE_SECURE);
+
+ if (mUserHandle == UserHandle.USER_OWNER) {
+ dropDbTable(db, CMTableNames.TABLE_GLOBAL);
+ }
+
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Drops the table and index for the specified database and table name
+ * @param db The {@link SQLiteDatabase} to drop the table and index in.
+ * @param tableName The name of the database table to drop.
+ */
+ private void dropDbTable(SQLiteDatabase db, String tableName) {
+ if (LOCAL_LOGV) Log.d(TAG, "Dropping table and index for: " + tableName);
+
+ String dropTableSql = String.format(DROP_TABLE_SQL_FORMAT, tableName);
+ db.execSQL(dropTableSql);
+
+ String dropIndexSql = String.format(DROP_INDEX_SQL_FORMAT, tableName, 1);
+ db.execSQL(dropIndexSql);
+ }
+
+ /**
+ * Loads default values for specific settings into the database.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ */
+ private void loadSettings(SQLiteDatabase db) {
+ // System
+ loadIntegerSetting(db, CMTableNames.TABLE_SYSTEM, CMSettings.System.QS_QUICK_PULLDOWN,
+ R.integer.def_qs_quick_pulldown);
+
+ // Secure
+ loadBooleanSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.ADVANCED_MODE,
+ R.bool.def_advanced_mode);
+
+ loadStringSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.DEFAULT_THEME_COMPONENTS,
+ R.string.def_theme_components);
+ loadStringSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.DEFAULT_THEME_PACKAGE,
+ R.string.def_theme_package);
+
+ loadIntegerSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR,
+ R.integer.def_force_show_navbar);
+
+ loadStringSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.QS_TILES,
+ R.string.def_qs_tiles);
+
+ loadBooleanSetting(db, CMTableNames.TABLE_SECURE, CMSettings.Secure.STATS_COLLECTION,
+ R.bool.def_stats_collection);
+
+ // Global
+ if (mUserHandle == UserHandle.USER_OWNER) {
+ loadSettingsForTable(db, CMTableNames.TABLE_GLOBAL, CMSettings.Global.DEVICE_NAME,
+ getDefaultDeviceName());
+
+ loadIntegerSetting(db, CMTableNames.TABLE_GLOBAL,
+ CMSettings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ R.integer.def_heads_up_enabled);
+ }
+ }
+
+ /**
+ * Loads a string resource into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param resId The name of the string resource.
+ */
+ private void loadStringSetting(SQLiteDatabase db, String tableName, String name, int resId) {
+ loadSettingsForTable(db, tableName, name, mContext.getResources().getString(resId));
+ }
+
+ /**
+ * Loads a boolean resource into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param resId The name of the boolean resource.
+ */
+ private void loadBooleanSetting(SQLiteDatabase db, String tableName, String name, int resId) {
+ loadSettingsForTable(db, tableName, name,
+ mContext.getResources().getBoolean(resId) ? "1" : "0");
+ }
+
+ /**
+ * Loads an integer resource into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param resId The name of the integer resource.
+ */
+ private void loadIntegerSetting(SQLiteDatabase db, String tableName, String name, int resId) {
+ loadSettingsForTable(db, tableName, name,
+ Integer.toString(mContext.getResources().getInteger(resId)));
+ }
+
+ /**
+ * Loads a name/value pair into a database table. If a conflict occurs, that value is not
+ * inserted into the database table.
+ * @param db The {@link SQLiteDatabase} to insert into.
+ * @param tableName The name of the table to insert into.
+ * @param name The name of the value to insert into the table.
+ * @param value The value to insert into the table.
+ */
+ private void loadSettingsForTable(SQLiteDatabase db, String tableName, String name,
+ String value) {
+ if (LOCAL_LOGV) Log.d(TAG, "Loading key: " + name + ", value: " + value);
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Settings.NameValueTable.NAME, name);
+ contentValues.put(Settings.NameValueTable.VALUE, value);
+
+ db.insertWithOnConflict(tableName, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE);
+ }
+
+ /**
+ * @return Gets the default device name
+ */
+ private String getDefaultDeviceName() {
+ return mContext.getResources().getString(R.string.def_device_name, Build.MODEL);
}
}
diff --git a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java
index a9a96fb..5b1453c 100644
--- a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java
+++ b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/CMSettingsProvider.java
@@ -16,26 +16,41 @@
package org.cyanogenmod.cmsettings;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.database.AbstractCursor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import cyanogenmod.providers.CMSettings;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
/**
* The CMSettingsProvider serves as a {@link ContentProvider} for CM specific settings
*/
@@ -45,6 +60,10 @@ public class CMSettingsProvider extends ContentProvider {
private static final boolean USER_CHECK_THROWS = true;
+ private static final String PREF_HAS_MIGRATED_CM_SETTINGS = "has_migrated_cm_settings";
+
+ private static final Bundle NULL_SETTING = Bundle.forPair("value", null);
+
// Each defined user has their own settings
protected final SparseArray<CMDatabaseHelper> mDbHelpers = new SparseArray<CMDatabaseHelper>();
@@ -52,6 +71,13 @@ public class CMSettingsProvider extends ContentProvider {
private static final int SECURE = 2;
private static final int GLOBAL = 3;
+ private static final int SYSTEM_ITEM_NAME = 4;
+ private static final int SECURE_ITEM_NAME = 5;
+ private static final int GLOBAL_ITEM_NAME = 6;
+
+ private static final String ITEM_MATCHER = "/*";
+ private static final String NAME_SELECTION = Settings.NameValueTable.NAME + " = ?";
+
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
@@ -61,11 +87,17 @@ public class CMSettingsProvider extends ContentProvider {
SECURE);
sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_GLOBAL,
GLOBAL);
- // TODO add other paths for getting specific items
+ sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_SYSTEM +
+ ITEM_MATCHER, SYSTEM_ITEM_NAME);
+ sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_SECURE +
+ ITEM_MATCHER, SECURE_ITEM_NAME);
+ sUriMatcher.addURI(CMSettings.AUTHORITY, CMDatabaseHelper.CMTableNames.TABLE_GLOBAL +
+ ITEM_MATCHER, GLOBAL_ITEM_NAME);
}
private UserManager mUserManager;
private Uri.Builder mUriBuilder;
+ private SharedPreferences mSharedPrefs;
@Override
public boolean onCreate() {
@@ -79,36 +111,349 @@ public class CMSettingsProvider extends ContentProvider {
mUriBuilder.scheme(ContentResolver.SCHEME_CONTENT);
mUriBuilder.authority(CMSettings.AUTHORITY);
- // TODO Add migration for cm settings
+ mSharedPrefs = getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
+
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ getContext().registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_OWNER);
+ String action = intent.getAction();
+
+ if (LOCAL_LOGV) Log.d(TAG, "Received intent: " + action + " for user: " + userId);
+
+ if (action.equals(Intent.ACTION_USER_REMOVED)) {
+ onUserRemoved(userId);
+ }
+ }
+ }, userFilter);
return true;
}
+ // region Migration Methods
+
+ /**
+ * Migrates CM settings for all existing users if this has not been run before.
+ */
+ private void migrateCMSettingsForExistingUsersIfNeeded() {
+ boolean hasMigratedCMSettings = mSharedPrefs.getBoolean(PREF_HAS_MIGRATED_CM_SETTINGS,
+ false);
+
+ if (!hasMigratedCMSettings) {
+ long startTime = System.currentTimeMillis();
+
+ for (UserInfo user : mUserManager.getUsers()) {
+ migrateCMSettingsForUser(user.id);
+ }
+
+ mSharedPrefs.edit().putBoolean(PREF_HAS_MIGRATED_CM_SETTINGS, true).commit();
+
+ // TODO: Add this as part of a boot message to the UI
+ long timeDiffMillis = System.currentTimeMillis() - startTime;
+ if (LOCAL_LOGV) Log.d(TAG, "Migration finished in " + timeDiffMillis + " milliseconds");
+ }
+ }
+
+ /**
+ * Migrates CM settings for a specific user.
+ * @param userId The id of the user to run CM settings migration for.
+ */
+ private void migrateCMSettingsForUser(int userId) {
+ synchronized (this) {
+ if (LOCAL_LOGV) Log.d(TAG, "CM settings will be migrated for user id: " + userId);
+
+ // Migrate system settings
+ HashMap<String, String> systemToCmSettingsMap = new HashMap<String, String>();
+ systemToCmSettingsMap.put(Settings.System.QS_QUICK_PULLDOWN,
+ CMSettings.System.QS_QUICK_PULLDOWN);
+
+ int rowsMigrated = migrateCMSettingsForTable(userId,
+ CMDatabaseHelper.CMTableNames.TABLE_SYSTEM, systemToCmSettingsMap);
+ if (LOCAL_LOGV) Log.d(TAG, "Migrated " + rowsMigrated + " to CM system table");
+
+ // Migrate secure settings
+ HashMap<String, String> secureToCmSettingsMap = new HashMap<String, String>();
+ secureToCmSettingsMap.put(Settings.Secure.ADVANCED_MODE,
+ CMSettings.Secure.ADVANCED_MODE);
+ secureToCmSettingsMap.put(Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT,
+ CMSettings.Secure.BUTTON_BACKLIGHT_TIMEOUT);
+ secureToCmSettingsMap.put(Settings.Secure.BUTTON_BRIGHTNESS,
+ CMSettings.Secure.BUTTON_BRIGHTNESS);
+ secureToCmSettingsMap.put(Settings.Secure.DEFAULT_THEME_COMPONENTS,
+ CMSettings.Secure.DEFAULT_THEME_COMPONENTS);
+ secureToCmSettingsMap.put(Settings.Secure.DEFAULT_THEME_PACKAGE,
+ CMSettings.Secure.DEFAULT_THEME_PACKAGE);
+ secureToCmSettingsMap.put(Settings.Secure.DEV_FORCE_SHOW_NAVBAR,
+ CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR);
+ secureToCmSettingsMap.put(
+ Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY,
+ CMSettings.Secure.NAME_THEME_CONFIG);
+ secureToCmSettingsMap.put(Settings.Secure.KEYBOARD_BRIGHTNESS,
+ CMSettings.Secure.KEYBOARD_BRIGHTNESS);
+ secureToCmSettingsMap.put(Settings.Secure.POWER_MENU_ACTIONS,
+ CMSettings.Secure.POWER_MENU_ACTIONS);
+ secureToCmSettingsMap.put(Settings.Secure.STATS_COLLECTION,
+ CMSettings.Secure.STATS_COLLECTION);
+ secureToCmSettingsMap.put(Settings.Secure.QS_SHOW_BRIGHTNESS_SLIDER,
+ CMSettings.Secure.QS_SHOW_BRIGHTNESS_SLIDER);
+ secureToCmSettingsMap.put(Settings.Secure.QS_TILES,
+ CMSettings.Secure.QS_TILES);
+ secureToCmSettingsMap.put(Settings.Secure.QS_USE_MAIN_TILES,
+ CMSettings.Secure.QS_USE_MAIN_TILES);
+ secureToCmSettingsMap.put(Settings.Secure.VOLUME_LINK_NOTIFICATION,
+ CMSettings.Secure.VOLUME_LINK_NOTIFICATION);
+
+ int navRingTargetsLength = Settings.Secure.NAVIGATION_RING_TARGETS.length;
+ int cmNavRingTargetsLength = CMSettings.Secure.NAVIGATION_RING_TARGETS.length;
+ int minNavRingTargetsLength = navRingTargetsLength <= cmNavRingTargetsLength ?
+ navRingTargetsLength : cmNavRingTargetsLength;
+
+ for (int i = 0; i < minNavRingTargetsLength; i++) {
+ systemToCmSettingsMap.put(Settings.Secure.NAVIGATION_RING_TARGETS[i],
+ CMSettings.Secure.NAVIGATION_RING_TARGETS[i]);
+ }
+
+ rowsMigrated = migrateCMSettingsForTable(userId,
+ CMDatabaseHelper.CMTableNames.TABLE_SECURE, secureToCmSettingsMap);
+ if (LOCAL_LOGV) Log.d(TAG, "Migrated " + rowsMigrated + " to CM secure table");
+
+ // Migrate global settings
+ if (userId == UserHandle.USER_OWNER) {
+ HashMap<String, String> globalToCmSettingsMap = new HashMap<String, String>();
+ globalToCmSettingsMap.put(Settings.Global.DEVICE_NAME,
+ CMSettings.Global.DEVICE_NAME);
+ globalToCmSettingsMap.put(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ CMSettings.Global.HEADS_UP_NOTIFICATIONS_ENABLED);
+
+ rowsMigrated = migrateCMSettingsForTable(userId,
+ CMDatabaseHelper.CMTableNames.TABLE_GLOBAL, globalToCmSettingsMap);
+ if (LOCAL_LOGV) Log.d(TAG, "Migrated " + rowsMigrated + " to CM global table");
+ }
+ }
+ }
+
+ /**
+ * Migrates CM settings for a specific table and user id.
+ * @param userId The id of the user to run CM settings migration for.
+ * @param tableName The name of the table to run CM settings migration on.
+ * @param settingsMap A mapping between key names in {@link Settings} and {@link CMSettings}
+ * @return Number of rows migrated.
+ */
+ private int migrateCMSettingsForTable(int userId, String tableName, HashMap<String,
+ String> settingsMap) {
+ ContentResolver contentResolver = getContext().getContentResolver();
+ Set<Map.Entry<String, String>> entrySet = settingsMap.entrySet();
+ ContentValues[] contentValues = new ContentValues[settingsMap.size()];
+
+ int migrateSettingsCount = 0;
+ for (Map.Entry<String, String> keyPair : entrySet) {
+ String settingsKey = keyPair.getKey();
+ String cmSettingsKey = keyPair.getValue();
+ String settingsValue = null;
+
+ if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_SYSTEM)) {
+ settingsValue = Settings.System.getStringForUser(contentResolver, settingsKey,
+ userId);
+ }
+ else if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_SECURE)) {
+ settingsValue = Settings.Secure.getStringForUser(contentResolver, settingsKey,
+ userId);
+ }
+ else if (tableName.equals(CMDatabaseHelper.CMTableNames.TABLE_GLOBAL)) {
+ settingsValue = Settings.Global.getStringForUser(contentResolver, settingsKey,
+ userId);
+ }
+
+ if (LOCAL_LOGV) Log.d(TAG, "Table: " + tableName + ", Key: " + settingsKey + ", Value: "
+ + settingsValue);
+
+ ContentValues contentValue = new ContentValues();
+ contentValue.put(Settings.NameValueTable.NAME, cmSettingsKey);
+ contentValue.put(Settings.NameValueTable.VALUE, settingsValue);
+ contentValues[migrateSettingsCount++] = contentValue;
+ }
+
+ int rowsInserted = 0;
+ if (contentValues.length > 0) {
+ Uri uri = mUriBuilder.build();
+ uri = uri.buildUpon().appendPath(tableName).build();
+ rowsInserted = bulkInsertForUser(userId, uri, contentValues);
+ }
+
+ return rowsInserted;
+ }
+
+ /**
+ * Performs cleanup for the removed user.
+ * @param userId The id of the user that is removed.
+ */
+ private void onUserRemoved(int userId) {
+ synchronized (this) {
+ // the db file itself will be deleted automatically, but we need to tear down
+ // our helpers and other internal bookkeeping.
+
+ mDbHelpers.delete(userId);
+
+ if (LOCAL_LOGV) Log.d(TAG, "User " + userId + " is removed");
+ }
+ }
+
+ // endregion Migration Methods
+
+ // region Content Provider Methods
+
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ if (LOCAL_LOGV) Log.d(TAG, "Call method: " + method);
+
+ int callingUserId = UserHandle.getCallingUserId();
+ if (args != null) {
+ int reqUser = args.getInt(CMSettings.CALL_METHOD_USER_KEY, callingUserId);
+ if (reqUser != callingUserId) {
+ callingUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), reqUser, false, true,
+ "get/set setting for user", null);
+ if (LOCAL_LOGV) Log.v(TAG, " access setting for user " + callingUserId);
+ }
+ }
+
+ // Migrate methods
+ if (CMSettings.CALL_METHOD_MIGRATE_SETTINGS.equals(method)) {
+ migrateCMSettingsForExistingUsersIfNeeded();
+
+ return null;
+ } else if (CMSettings.CALL_METHOD_MIGRATE_SETTINGS_FOR_USER.equals(method)) {
+ migrateCMSettingsForUser(callingUserId);
+
+ return null;
+ }
+
+ // Get methods
+ if (CMSettings.CALL_METHOD_GET_SYSTEM.equals(method)) {
+ return lookupSingleValue(callingUserId, CMSettings.System.CONTENT_URI, request);
+ }
+ else if (CMSettings.CALL_METHOD_GET_SECURE.equals(method)) {
+ return lookupSingleValue(callingUserId, CMSettings.Secure.CONTENT_URI, request);
+ }
+ else if (CMSettings.CALL_METHOD_GET_GLOBAL.equals(method)) {
+ return lookupSingleValue(callingUserId, CMSettings.Global.CONTENT_URI, request);
+ }
+
+ // Put methods - new value is in the args bundle under the key named by
+ // the Settings.NameValueTable.VALUE static.
+ final String newValue = (args == null)
+ ? null : args.getString(Settings.NameValueTable.VALUE);
+
+ // Framework can't do automatic permission checking for calls, so we need
+ // to do it here.
+ if (getContext().checkCallingOrSelfPermission(
+ cyanogenmod.platform.Manifest.permission.WRITE_SETTINGS) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ String.format("Permission denial: writing to settings requires %1$s",
+ cyanogenmod.platform.Manifest.permission.WRITE_SETTINGS));
+ }
+
+ // Put methods
+ final ContentValues values = new ContentValues();
+ values.put(Settings.NameValueTable.NAME, request);
+ values.put(Settings.NameValueTable.VALUE, newValue);
+
+ if (CMSettings.CALL_METHOD_PUT_SYSTEM.equals(method)) {
+ insertForUser(callingUserId, CMSettings.System.CONTENT_URI, values);
+ }
+ else if (CMSettings.CALL_METHOD_PUT_SECURE.equals(method)) {
+ insertForUser(callingUserId, CMSettings.Secure.CONTENT_URI, values);
+ }
+ else if (CMSettings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
+ insertForUser(callingUserId, CMSettings.Global.CONTENT_URI, values);
+ }
+
+ return null;
+ }
+
+ /**
+ * Looks up a single value for a specific user, uri, and key.
+ * @param userId The id of the user to perform the lookup for.
+ * @param uri The uri for which table to perform the lookup in.
+ * @param key The key to perform the lookup with.
+ * @return A single value stored in a {@link Bundle}.
+ */
+ private Bundle lookupSingleValue(int userId, Uri uri, String key) {
+ Cursor cursor = null;
+ try {
+ cursor = queryForUser(userId, uri, new String[]{ Settings.NameValueTable.VALUE },
+ Settings.NameValueTable.NAME + " = ?", new String[]{ key }, null);
+
+ if (cursor != null && cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ String value = cursor.getString(0);
+ return value == null ? NULL_SETTING : Bundle.forPair(Settings.NameValueTable.VALUE,
+ value);
+ }
+ } catch (SQLiteException e) {
+ Log.w(TAG, "settings lookup error", e);
+ return null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return NULL_SETTING;
+ }
+
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ return queryForUser(UserHandle.getCallingUserId(), uri, projection, selection,
+ selectionArgs, sortOrder);
+ }
+
+ /**
+ * Performs a query for a specific user.
+ * @param userId The id of the user to perform the query for.
+ * @param uri The uri for which table to perform the query on. Optionally, the uri can end in
+ * the name of a specific element to query for.
+ * @param projection The columns that are returned in the {@link Cursor}.
+ * @param selection The column names that the selection criteria applies to.
+ * @param selectionArgs The column values that the selection criteria applies to.
+ * @param sortOrder The ordering of how the values should be returned in the {@link Cursor}.
+ * @return {@link Cursor} of the results from the query.
+ */
+ private Cursor queryForUser(int userId, Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
- String tableName = getTableNameFromUri(uri);
+ int code = sUriMatcher.match(uri);
+ String tableName = getTableNameFromUriMatchCode(code);
checkWritePermissions(tableName);
- int callingUserId = UserHandle.getCallingUserId();
- CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
- callingUserId));
+ CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName, userId));
SQLiteDatabase db = dbHelper.getReadableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(tableName);
- Cursor returnCursor = queryBuilder.query(db, projection, selection, selectionArgs, null,
- null, sortOrder);
+ Cursor returnCursor;
+ if (isItemUri(code)) {
+ // The uri is looking for an element with a specific name
+ returnCursor = queryBuilder.query(db, projection, NAME_SELECTION,
+ new String[] { uri.getLastPathSegment() }, null, null, sortOrder);
+ } else {
+ returnCursor = queryBuilder.query(db, projection, selection, selectionArgs, null,
+ null, sortOrder);
+ }
+
// the default Cursor interface does not support per-user observation
try {
AbstractCursor abstractCursor = (AbstractCursor) returnCursor;
- abstractCursor.setNotificationUri(getContext().getContentResolver(), uri,
- callingUserId);
+ abstractCursor.setNotificationUri(getContext().getContentResolver(), uri, userId);
} catch (ClassCastException e) {
// details of the concrete Cursor implementation have changed and this code has
// not been updated to match -- complain and fail hard.
@@ -121,8 +466,14 @@ public class CMSettingsProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
- // TODO: Implement
- return null;
+ int code = sUriMatcher.match(uri);
+ String tableName = getTableNameFromUriMatchCode(code);
+
+ if (isItemUri(code)) {
+ return "vnd.android.cursor.item/" + tableName;
+ } else {
+ return "vnd.android.cursor.dir/" + tableName;
+ }
}
@Override
@@ -166,8 +517,6 @@ public class CMSettingsProvider extends ContentProvider {
if (rowId >= 0) {
numRowsAffected++;
-
- if (LOCAL_LOGV) Log.d(TAG, tableName + " <- " + values);
} else {
return 0;
}
@@ -179,7 +528,6 @@ public class CMSettingsProvider extends ContentProvider {
}
if (numRowsAffected > 0) {
- getContext().getContentResolver().notifyChange(uri, null);
notifyChange(uri, tableName, userId);
if (LOCAL_LOGV) Log.d(TAG, tableName + ": " + numRowsAffected + " row(s) inserted");
}
@@ -189,6 +537,18 @@ public class CMSettingsProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
+ return insertForUser(UserHandle.getCallingUserId(), uri, values);
+ }
+
+ /**
+ * Performs insert for a specific user.
+ * @param userId The user id to perform the insert for.
+ * @param uri The content:// URI of the insertion request.
+ * @param values A sets of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
+ * @return
+ */
+ private Uri insertForUser(int userId, Uri uri, ContentValues values) {
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
@@ -200,17 +560,16 @@ public class CMSettingsProvider extends ContentProvider {
String tableName = getTableNameFromUri(uri);
checkWritePermissions(tableName);
- int callingUserId = UserHandle.getCallingUserId();
- CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
- callingUserId));
+ CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName, userId));
SQLiteDatabase db = dbHelper.getWritableDatabase();
long rowId = db.insert(tableName, null, values);
Uri returnUri = null;
if (rowId > -1) {
- returnUri = ContentUris.withAppendedId(uri, rowId);
- notifyChange(returnUri, tableName, callingUserId);
+ String name = values.getAsString(Settings.NameValueTable.NAME);
+ returnUri = Uri.withAppendedPath(uri, name);
+ notifyChange(returnUri, tableName, userId);
if (LOCAL_LOGV) Log.d(TAG, "Inserted row id: " + rowId + " into tableName: " +
tableName);
}
@@ -235,8 +594,8 @@ public class CMSettingsProvider extends ContentProvider {
int callingUserId = UserHandle.getCallingUserId();
CMDatabaseHelper dbHelper = getOrEstablishDatabase(getUserIdForTable(tableName,
callingUserId));
- SQLiteDatabase db = dbHelper.getWritableDatabase();
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
numRowsAffected = db.delete(tableName, selection, selectionArgs);
if (numRowsAffected > 0) {
@@ -250,6 +609,11 @@ public class CMSettingsProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ // NOTE: update() is never called by the front-end CMSettings API, and updates that
+ // wind up affecting rows in Secure that are globally shared will not have the
+ // intended effect (the update will be invisible to the rest of the system).
+ // This should have no practical effect, since writes to the Secure db can only
+ // be done by system code, and that code should be using the correct API up front.
if (uri == null) {
throw new IllegalArgumentException("Uri cannot be null");
}
@@ -269,13 +633,15 @@ public class CMSettingsProvider extends ContentProvider {
int numRowsAffected = db.update(tableName, values, selection, selectionArgs);
if (numRowsAffected > 0) {
- getContext().getContentResolver().notifyChange(uri, null);
+ notifyChange(uri, tableName, callingUserId);
if (LOCAL_LOGV) Log.d(TAG, tableName + ": " + numRowsAffected + " row(s) updated");
}
return numRowsAffected;
}
+ // endregion Content Provider Methods
+
/**
* Tries to get a {@link CMDatabaseHelper} for the specified user and if it does not exist, a
* new instance of {@link CMDatabaseHelper} is created for the specified user and returned.
@@ -357,6 +723,26 @@ public class CMSettingsProvider extends ContentProvider {
}
/**
+ * Returns whether the matched uri code refers to an item in a table
+ * @param code
+ * @return
+ */
+ private boolean isItemUri(int code) {
+ switch (code) {
+ case SYSTEM:
+ case SECURE:
+ case GLOBAL:
+ return false;
+ case SYSTEM_ITEM_NAME:
+ case SECURE_ITEM_NAME:
+ case GLOBAL_ITEM_NAME:
+ return true;
+ default:
+ throw new IllegalArgumentException("Invalid uri match code: " + code);
+ }
+ }
+
+ /**
* Utilizes an {@link UriMatcher} to check for a valid combination of scheme, authority, and
* path and returns the corresponding table name
* @param uri
@@ -365,15 +751,27 @@ public class CMSettingsProvider extends ContentProvider {
private String getTableNameFromUri(Uri uri) {
int code = sUriMatcher.match(uri);
+ return getTableNameFromUriMatchCode(code);
+ }
+
+ /**
+ * Returns the corresponding table name for the matched uri code
+ * @param code
+ * @return
+ */
+ private String getTableNameFromUriMatchCode(int code) {
switch (code) {
case SYSTEM:
+ case SYSTEM_ITEM_NAME:
return CMDatabaseHelper.CMTableNames.TABLE_SYSTEM;
case SECURE:
+ case SECURE_ITEM_NAME:
return CMDatabaseHelper.CMTableNames.TABLE_SECURE;
case GLOBAL:
+ case GLOBAL_ITEM_NAME:
return CMDatabaseHelper.CMTableNames.TABLE_GLOBAL;
default:
- throw new IllegalArgumentException("Invalid uri: " + uri);
+ throw new IllegalArgumentException("Invalid uri match code: " + code);
}
}
diff --git a/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java
new file mode 100644
index 0000000..ef0a6f0
--- /dev/null
+++ b/packages/CMSettingsProvider/src/org/cyanogenmod/cmsettings/PreBootReceiver.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.cmsettings;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.util.Log;
+import cyanogenmod.providers.CMSettings;
+
+public class PreBootReceiver extends BroadcastReceiver{
+ private static final String TAG = "CMSettingsReceiver";
+ private static final boolean LOCAL_LOGV = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (LOCAL_LOGV) {
+ Log.d(TAG, "Received pre boot intent. Attempting to migrate CM settings.");
+ }
+
+ ContentResolver contentResolver = context.getContentResolver();
+ IContentProvider contentProvider = contentResolver.acquireProvider(
+ CMSettings.AUTHORITY);
+
+ try{
+ contentProvider.call(contentResolver.getPackageName(),
+ CMSettings.CALL_METHOD_MIGRATE_SETTINGS, null, null);
+
+ context.getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, getClass()),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to trigger settings migration due to RemoteException");
+ }
+ }
+}