From 421334fb237459f8f902320c454370d4f1772190 Mon Sep 17 00:00:00 2001 From: d34d Date: Tue, 23 Feb 2016 08:55:12 -0800 Subject: Implement theme mixes from ThemesContract Made some changes in the database schema.Changed the is_installed state in case of a reinstallation of a theme.Implemented the delete and update methods for the Theme_Mixes. Made changes according to the first code review. RM-290 Change-Id: I9fa1266c51ff69b95713dc1e2aff9448cc4242f2 TICKET: CYNGNOS-2121 --- .../themes/provider/ThemesOpenHelper.java | 45 +++- .../themes/provider/ThemesProvider.java | 250 ++++++++++++++++++++- 2 files changed, 291 insertions(+), 4 deletions(-) diff --git a/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java b/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java index d70ad50..5456daf 100644 --- a/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java +++ b/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java @@ -32,13 +32,15 @@ import cyanogenmod.providers.ThemesContract; import cyanogenmod.providers.ThemesContract.ThemesColumns; import cyanogenmod.providers.ThemesContract.MixnMatchColumns; import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemeMixColumns; +import cyanogenmod.providers.ThemesContract.ThemeMixEntryColumns; import org.cyanogenmod.internal.util.ThemeUtils; public class ThemesOpenHelper extends SQLiteOpenHelper { private static final String TAG = ThemesOpenHelper.class.getName(); - private static final int DATABASE_VERSION = 20; + private static final int DATABASE_VERSION = 21; private static final String DATABASE_NAME = "themes.db"; private static final String SYSTEM_THEME_PKG_NAME = ThemeConfig.SYSTEM_DEFAULT; private static final String OLD_SYSTEM_THEME_PKG_NAME = "holo"; @@ -55,6 +57,8 @@ public class ThemesOpenHelper extends SQLiteOpenHelper { db.execSQL(ThemesTable.THEMES_TABLE_CREATE); db.execSQL(MixnMatchTable.MIXNMATCH_TABLE_CREATE); db.execSQL(PreviewsTable.PREVIEWS_TABLE_CREATE); + db.execSQL(ThemeMixesTable.THEME_MIXES_TABLE_CREATE); + db.execSQL(ThemeMixEntriesTable.THEME_MIX_ENTRIES_TABLE_CREATE); ThemesTable.insertSystemDefaults(db, mContext); MixnMatchTable.insertDefaults(db); @@ -138,6 +142,10 @@ public class ThemesOpenHelper extends SQLiteOpenHelper { upgradeToVersion20(db); oldVersion = 20; } + if (oldVersion == 20) { + upgradeToVersion21(db); + oldVersion = 21; + } if (oldVersion != DATABASE_VERSION) { Log.e(TAG, "Recreating db because unknown database version: " + oldVersion); dropTables(db); @@ -497,10 +505,18 @@ public class ThemesOpenHelper extends SQLiteOpenHelper { SYSTEM_THEME_PKG_NAME)); } + private void upgradeToVersion21(SQLiteDatabase db) { + // create the theme mix tables + db.execSQL(ThemeMixesTable.THEME_MIXES_TABLE_CREATE); + db.execSQL(ThemeMixEntriesTable.THEME_MIX_ENTRIES_TABLE_CREATE); + } + private void dropTables(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + ThemesTable.TABLE_NAME); db.execSQL("DROP TABLE IF EXISTS " + MixnMatchTable.TABLE_NAME); db.execSQL("DROP TABLE IF EXISTS " + PreviewsTable.TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + ThemeMixesTable.TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + ThemeMixEntriesTable.TABLE_NAME); } public static class ThemesTable { @@ -648,6 +664,33 @@ public class ThemesOpenHelper extends SQLiteOpenHelper { } } + public static class ThemeMixesTable { + protected static final String TABLE_NAME = "theme_mixes"; + + private static final String THEME_MIXES_TABLE_CREATE = + "CREATE TABLE " + TABLE_NAME + " (" + + ThemeMixColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + ThemeMixColumns.TITLE + " TEXT NOT NULL " + + ")"; + } + + public static class ThemeMixEntriesTable { + protected static final String TABLE_NAME = "theme_mix_entries"; + + private static final String THEME_MIX_ENTRIES_TABLE_CREATE = + "CREATE TABLE " + TABLE_NAME + " (" + + ThemeMixEntryColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + ThemeMixEntryColumns.THEME_MIX_ID + " INTEGER NOT NULL, " + + ThemeMixEntryColumns.COMPONENT_TYPE + " TEXT NON NULL, " + + ThemeMixEntryColumns.COMPONENT_ID + " INTEGER DEFAULT 0," + + ThemeMixEntryColumns.PACKAGE_NAME + " TEXT NOT NULL, " + + ThemeMixEntryColumns.THEME_NAME + " TEXT NOT NULL, " + + ThemeMixEntryColumns.IS_INSTALLED + " INTEGER DEFAULT 1, " + + "FOREIGN KEY(" + ThemeMixEntryColumns.THEME_MIX_ID + ") REFERENCES " + + ThemeMixesTable.TABLE_NAME + "(" + ThemeMixColumns._ID + ")" + + ")"; + } + private static boolean isSystemDefault(Context context) { // == is okay since we are checking if what is returned is the same constant string value return ThemeConfig.SYSTEM_DEFAULT == ThemeUtils.getDefaultThemePackageName(context); diff --git a/src/org/cyanogenmod/themes/provider/ThemesProvider.java b/src/org/cyanogenmod/themes/provider/ThemesProvider.java index 61828fb..464a9a4 100644 --- a/src/org/cyanogenmod/themes/provider/ThemesProvider.java +++ b/src/org/cyanogenmod/themes/provider/ThemesProvider.java @@ -16,6 +16,7 @@ package org.cyanogenmod.themes.provider; +import android.annotation.NonNull; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; @@ -27,6 +28,7 @@ import android.content.UriMatcher; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; @@ -39,6 +41,8 @@ import cyanogenmod.providers.ThemesContract; import cyanogenmod.providers.ThemesContract.MixnMatchColumns; import cyanogenmod.providers.ThemesContract.PreviewColumns; import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.providers.ThemesContract.ThemeMixColumns; +import cyanogenmod.providers.ThemesContract.ThemeMixEntryColumns; import cyanogenmod.themes.ThemeManager; import cyanogenmod.themes.ThemeChangeRequest; import cyanogenmod.themes.ThemeChangeRequest.RequestType; @@ -47,6 +51,8 @@ import org.cyanogenmod.internal.util.ThemeUtils; import org.cyanogenmod.themes.provider.ThemesOpenHelper.MixnMatchTable; import org.cyanogenmod.themes.provider.ThemesOpenHelper.PreviewsTable; import org.cyanogenmod.themes.provider.ThemesOpenHelper.ThemesTable; +import org.cyanogenmod.themes.provider.ThemesOpenHelper.ThemeMixesTable; +import org.cyanogenmod.themes.provider.ThemesOpenHelper.ThemeMixEntriesTable; import org.cyanogenmod.themes.provider.util.PreviewUtils; import org.cyanogenmod.themes.provider.util.ProviderUtils; @@ -72,6 +78,10 @@ public class ThemesProvider extends ContentProvider { private static final int PREVIEWS_ID = 6; private static final int APPLIED_PREVIEWS = 7; private static final int COMPONENTS_PREVIEWS = 8; + private static final int THEME_MIXES = 9; + private static final int THEME_MIX_ENTRIES = 10; + private static final int THEME_MIX_ENTRIES_ID = 11; + private static final int THEME_MIX_PREVIEWS = 12; private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); @@ -89,6 +99,10 @@ public class ThemesProvider extends ContentProvider { sUriMatcher.addURI(ThemesContract.AUTHORITY, "previews/#", PREVIEWS_ID); sUriMatcher.addURI(ThemesContract.AUTHORITY, "applied_previews/", APPLIED_PREVIEWS); sUriMatcher.addURI(ThemesContract.AUTHORITY, "components_previews/", COMPONENTS_PREVIEWS); + sUriMatcher.addURI(ThemesContract.AUTHORITY, "theme_mixes/", THEME_MIXES); + sUriMatcher.addURI(ThemesContract.AUTHORITY, "theme_mix_entries/", THEME_MIX_ENTRIES); + sUriMatcher.addURI(ThemesContract.AUTHORITY, "theme_mix_entries/#", THEME_MIX_ENTRIES_ID); + sUriMatcher.addURI(ThemesContract.AUTHORITY, "theme_mix_previews/#", THEME_MIX_PREVIEWS); } public static void setActiveTheme(Context context, String pkgName) { @@ -134,6 +148,13 @@ public class ThemesProvider extends ContentProvider { String themePreviewsDir = filesDir + File.separator + PreviewUtils.PREVIEWS_DIR + File.separator + pkgName; PreviewGenerationService.clearThemePreviewsDir(themePreviewsDir); + + // mark theme mix entries for this package as uninstalled + final String where = ThemeMixEntryColumns.PACKAGE_NAME + "=?"; + final String[] whereArgs = { pkgName }; + final ContentValues values = new ContentValues(); + values.put(ThemeMixEntryColumns.IS_INSTALLED, 0); + sqlDB.update(ThemeMixEntriesTable.TABLE_NAME, values, where, whereArgs); } c.close(); @@ -163,6 +184,40 @@ public class ThemesProvider extends ContentProvider { return rowsDeleted; case MIXNMATCH: throw new UnsupportedOperationException("Cannot delete rows in MixNMatch table"); + case THEME_MIXES: + sqlDB = mDatabase.getWritableDatabase(); + // Get the theme's _id and delete preview images + columns = new String[] { ThemeMixColumns._ID }; + c = sqlDB.query(ThemeMixesTable.TABLE_NAME, columns, selection, + selectionArgs, null, null, null); + if (c == null) return 0; + + if (c.moveToFirst()) { + idx = c.getColumnIndex(ThemeMixColumns._ID); + int mixId = c.getInt(idx); + if (mixId != -1) { + // delete all mix entries associated with this mix + sqlDB.delete(ThemeMixEntriesTable.TABLE_NAME, + ThemeMixEntryColumns.THEME_MIX_ID + "=" + mixId, null); + } + rowsDeleted = sqlDB.delete(ThemeMixesTable.TABLE_NAME, selection, selectionArgs); + } + c.close(); + if (rowsDeleted > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return rowsDeleted; + case THEME_MIX_ENTRIES: + case THEME_MIX_ENTRIES_ID: + sqlDB = mDatabase.getWritableDatabase(); + // Get the theme's _id and delete preview images + rowsDeleted = sqlDB.delete(ThemeMixEntriesTable.TABLE_NAME, + ThemeMixEntryColumns.THEME_MIX_ID + "=" + c.getInt(idx), null); + + if (rowsDeleted > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return rowsDeleted; } return 0; } @@ -183,6 +238,12 @@ public class ThemesProvider extends ContentProvider { return "vnd.android.cursor.dir/previews"; case PREVIEWS_ID: return "vnd.android.cursor.item/previews"; + case THEME_MIXES: + return "vnd.android.cursor.dir/theme_mixes"; + case THEME_MIX_ENTRIES: + return "vnd.android.cursor.dir/theme_mix_entries"; + case THEME_MIX_ENTRIES_ID: + return "vnd.android.cursor.item/theme_mix_entries"; default: return null; } @@ -191,6 +252,7 @@ public class ThemesProvider extends ContentProvider { @Override public Uri insert(Uri uri, ContentValues values) { int uriType = sUriMatcher.match(uri); + String packageName = values.getAsString(ThemesColumns.PKG_NAME); SQLiteDatabase sqlDB = mDatabase.getWritableDatabase(); long id = 0; switch (uriType) { @@ -201,11 +263,18 @@ public class ThemesProvider extends ContentProvider { processPreviews = state == ThemesColumns.InstallState.INSTALLED; } id = sqlDB.insert(ThemesOpenHelper.ThemesTable.TABLE_NAME, null, values); + + // Update themes_mix_entries when a theme is re installed. + final String where = ThemeMixEntryColumns.PACKAGE_NAME + "=?"; + final String[] whereArgs = { packageName }; + final ContentValues values_new = new ContentValues(); + values_new.put(ThemeMixEntryColumns.IS_INSTALLED, 1); + sqlDB.update(ThemeMixEntriesTable.TABLE_NAME, values_new, where, whereArgs); + if (processPreviews) { Intent intent = new Intent(getContext(), PreviewGenerationService.class); intent.setAction(PreviewGenerationService.ACTION_INSERT); - intent.putExtra(PreviewGenerationService.EXTRA_PKG_NAME, - values.getAsString(ThemesColumns.PKG_NAME)); + intent.putExtra(PreviewGenerationService.EXTRA_PKG_NAME, packageName); getContext().startService(intent); } break; @@ -214,15 +283,60 @@ public class ThemesProvider extends ContentProvider { case PREVIEWS: id = sqlDB.insert(ThemesOpenHelper.PreviewsTable.TABLE_NAME, null, values); break; + case THEME_MIXES: + id = sqlDB.insert(ThemeMixesTable.TABLE_NAME, null, values); + break; + case THEME_MIX_ENTRIES: + id = sqlDB.insert(ThemeMixEntriesTable.TABLE_NAME, null, values); + break; default: } if (id >= 0) { - ContentUris.withAppendedId(uri, id); + uri = ContentUris.withAppendedId(uri, id); getContext().getContentResolver().notifyChange(uri, null); } return uri; } + @Override + public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { + int uriType = sUriMatcher.match(uri); + switch (uriType) { + case MIXNMATCH: + case MIXNMATCH_KEY: + case THEMES: + case THEMES_ID: + case PREVIEWS: + case PREVIEWS_ID: + case APPLIED_PREVIEWS: + case COMPONENTS_PREVIEWS: + case THEME_MIXES: + case THEME_MIX_PREVIEWS: + case THEME_MIX_ENTRIES_ID: + throw new UnsupportedOperationException("Bulk insert not supported"); + case THEME_MIX_ENTRIES: + int rowsInserted = 0; + SQLiteDatabase sqlDB = mDatabase.getWritableDatabase(); + sqlDB.beginTransaction(); + try { + for (ContentValues value : values) { + long id = sqlDB.insertOrThrow(ThemeMixEntriesTable.TABLE_NAME, null, value); + if (id <= 0) { + throw new SQLException("Failed to insert row into " + uri); + } + rowsInserted++; + } + sqlDB.setTransactionSuccessful(); + getContext().getContentResolver().notifyChange(uri, null); + } finally { + sqlDB.endTransaction(); + } + return rowsInserted; + } + + return 0; + } + @Override public boolean onCreate() { mDatabase = new ThemesOpenHelper(getContext()); @@ -286,6 +400,14 @@ public class ThemesProvider extends ContentProvider { break; case APPLIED_PREVIEWS: return getAppliedPreviews(db); + case THEME_MIXES: + queryBuilder.setTables(ThemeMixesTable.TABLE_NAME); + break; + case THEME_MIX_ENTRIES: + queryBuilder.setTables(ThemeMixEntriesTable.TABLE_NAME); + break; + case THEME_MIX_PREVIEWS: + return getThemeMixPreviews(db, ContentUris.parseId(uri)); default: return null; } @@ -353,6 +475,16 @@ public class ThemesProvider extends ContentProvider { rowsUpdated = sqlDB.update(PreviewsTable.TABLE_NAME, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); break; + case THEME_MIXES: + rowsUpdated = sqlDB.update + (ThemeMixesTable.TABLE_NAME, values, selection, selectionArgs); + getContext().getContentResolver().notifyChange(uri,null); + break; + case THEME_MIX_ENTRIES: + rowsUpdated = sqlDB.update + (ThemeMixEntriesTable.TABLE_NAME, values, selection, selectionArgs); + getContext().getContentResolver().notifyChange(uri,null); + break; } return rowsUpdated; } @@ -445,6 +577,118 @@ public class ThemesProvider extends ContentProvider { return null; } + /** + * Queries the currently applied components and creates a SQLite statement consisting + * of a series of (SELECT ...) statements + * @param db Readable database + * @return + */ + private Cursor getThemeMixPreviews(SQLiteDatabase db, long themeMixId) { + String[] projection = {ThemeMixEntryColumns.COMPONENT_TYPE, + ThemeMixEntryColumns.COMPONENT_ID, + ThemeMixEntryColumns.PACKAGE_NAME}; + String selection = ThemeMixEntryColumns.THEME_MIX_ID + "=? AND " + + ThemeMixEntryColumns.IS_INSTALLED + "=?"; + String[] selectionArgs = { Long.toString(themeMixId), "1" }; + Cursor c = db.query(ThemeMixEntriesTable.TABLE_NAME, projection, selection, selectionArgs, + null, null, null); + if (c != null) { + StringBuilder sb = new StringBuilder("SELECT * FROM "); + String delimeter = ""; + while (c.moveToNext()) { + String component = c.getString(0); + int componentId = c.getInt(1); + String pkgName = c.getString(2); + if (component != null && pkgName != null) { + // We need to get the theme's id using its package name + String[] columns = { ThemesColumns._ID }; + selection = ThemesColumns.PKG_NAME + "=? AND " + component + "=?"; + selectionArgs = new String[] {pkgName, "1"}; + Cursor current = db.query(ThemesTable.TABLE_NAME, columns, selection, + selectionArgs, null, null, null); + int id = -1; + if (current != null) { + if (current.moveToFirst()) id = current.getInt(0); + current.close(); + } + if (id >= 0) { + if (ThemesColumns.MODIFIES_STATUS_BAR.equals(component)) { + for (String previewKey : PreviewsTable.STATUS_BAR_PREVIEW_KEYS) { + sb.append(delimeter).append(String.format(Locale.US, + "(SELECT %s AS %s FROM previews WHERE %s=%d " + + "AND %s='%s' AND %s=%d)", + PreviewColumns.COL_VALUE, previewKey, + PreviewColumns.THEME_ID, id, PreviewColumns.COL_KEY, + previewKey, PreviewColumns.COMPONENT_ID, componentId)); + delimeter = ","; + } + } else if (ThemesColumns.MODIFIES_ICONS.equals(component)) { + for (String previewKey : PreviewsTable.ICON_PREVIEW_KEYS) { + sb.append(delimeter).append(String.format(Locale.US, + "(SELECT %s AS %s FROM previews WHERE %s=%d AND %s='%s' " + + "AND %s=%d)", + PreviewColumns.COL_VALUE, previewKey, + PreviewColumns.THEME_ID, id, PreviewColumns.COL_KEY, + previewKey, PreviewColumns.COMPONENT_ID, componentId)); + delimeter = ","; + } + } else if (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) { + String previewKey = PreviewColumns.WALLPAPER_PREVIEW; + sb.append(delimeter).append(String.format(Locale.US, + "(SELECT %s AS %s FROM previews WHERE %s=%d AND %s='%s' " + + "AND %s=%d)", + PreviewColumns.COL_VALUE, previewKey, PreviewColumns.THEME_ID, + id, PreviewColumns.COL_KEY, previewKey, + PreviewColumns.COMPONENT_ID, componentId)); + delimeter = ","; + } else if (ThemesColumns.MODIFIES_NAVIGATION_BAR.equals(component)) { + for (String previewKey : PreviewsTable.NAVIGATION_BAR_PREVIEW_KEYS) { + sb.append(delimeter).append(String.format(Locale.US, + "(SELECT %s AS %s FROM previews WHERE %s=%d AND %s='%s' " + + "AND %s=%d)", + PreviewColumns.COL_VALUE, previewKey, + PreviewColumns.THEME_ID, id, PreviewColumns.COL_KEY, + previewKey, PreviewColumns.COMPONENT_ID, componentId)); + delimeter = ","; + } + } else if (ThemesColumns.MODIFIES_OVERLAYS.equals(component)) { + String previewKey = PreviewColumns.STYLE_PREVIEW; + sb.append(delimeter).append(String.format(Locale.US, + "(SELECT %s AS %s FROM previews WHERE %s=%d AND %s='%s' " + + "AND %s=%d)", + PreviewColumns.COL_VALUE, previewKey, PreviewColumns.THEME_ID, + id, PreviewColumns.COL_KEY, previewKey, + PreviewColumns.COMPONENT_ID, componentId)); + delimeter = ","; + } else if (ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { + String previewKey = PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW; + sb.append(delimeter).append(String.format(Locale.US, + "(SELECT %s AS %s FROM previews WHERE %s=%d AND %s='%s' " + + "AND %s=%d)", + PreviewColumns.COL_VALUE, previewKey, PreviewColumns.THEME_ID, + id, PreviewColumns.COL_KEY, previewKey, + PreviewColumns.COMPONENT_ID, componentId)); + delimeter = ","; + } else if (ThemesColumns.MODIFIES_LOCKSCREEN.equals(component)) { + String previewKey = PreviewColumns.LOCK_WALLPAPER_PREVIEW; + sb.append(delimeter).append(String.format(Locale.US, + "(SELECT %s AS %s FROM previews WHERE %s=%d AND %s='%s' " + + "AND %s=%d)", + PreviewColumns.COL_VALUE, previewKey, PreviewColumns.THEME_ID, + id, PreviewColumns.COL_KEY, previewKey, + PreviewColumns.COMPONENT_ID, componentId)); + delimeter = ","; + } + } + } + } + c.close(); + sb.append(";"); + return db.rawQuery(sb.toString(), null); + } + return null; + } + /** * When there is an insert or update to a theme, an async service will kick off to update * several of the preview image columns. Since this service also calls a 2nd update on the -- cgit v1.2.3