diff options
Diffstat (limited to 'src/org/cyanogenmod/themes/provider/ThemesProvider.java')
-rw-r--r-- | src/org/cyanogenmod/themes/provider/ThemesProvider.java | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/themes/provider/ThemesProvider.java b/src/org/cyanogenmod/themes/provider/ThemesProvider.java new file mode 100644 index 0000000..6c70709 --- /dev/null +++ b/src/org/cyanogenmod/themes/provider/ThemesProvider.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2014 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.themes.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.UriMatcher; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.ThemeManager; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.provider.ThemesContract; +import android.provider.ThemesContract.MixnMatchColumns; +import android.provider.ThemesContract.ThemesColumns; +import android.util.Log; + +import org.cyanogenmod.themes.provider.AppReceiver; +import org.cyanogenmod.themes.provider.ThemesOpenHelper.MixnMatchTable; +import org.cyanogenmod.themes.provider.ThemesOpenHelper.ThemesTable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ThemesProvider extends ContentProvider { + private static final String TAG = ThemesProvider.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final int MIXNMATCH = 1; + private static final int MIXNMATCH_KEY = 2; + private static final int THEMES = 3; + private static final int THEMES_ID = 4; + + private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + + private final Handler mHandler = new Handler(); + private ThemesOpenHelper mDatabase; + + static { + sUriMatcher.addURI(ThemesContract.AUTHORITY, "mixnmatch/", MIXNMATCH); + sUriMatcher.addURI(ThemesContract.AUTHORITY, "mixnmatch/*", MIXNMATCH_KEY); + sUriMatcher.addURI(ThemesContract.AUTHORITY, "themes/", THEMES); + sUriMatcher.addURI(ThemesContract.AUTHORITY, "themes/#", THEMES_ID); + } + + public static void setActiveTheme(Context context, String pkgName) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + Editor edit = prefs.edit(); + edit.putString("SelectedThemePkgName", pkgName); + edit.commit(); + } + + public static String getActiveTheme(Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + return prefs.getString("SelectedThemePkgName", "default"); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int match = sUriMatcher.match(uri); + switch (match) { + case THEMES: + SQLiteDatabase sqlDB = mDatabase.getWritableDatabase(); + int rowsDeleted = sqlDB.delete(ThemesTable.TABLE_NAME, selection, selectionArgs); + getContext().getContentResolver().notifyChange(uri, null); + return rowsDeleted; + case MIXNMATCH: + throw new UnsupportedOperationException("Cannot delete rows in MixNMatch table"); + } + return 0; + } + + @Override + public String getType(Uri uri) { + int match = sUriMatcher.match(uri); + switch (match) { + case THEMES: + return "vnd.android.cursor.dir/themes"; + case THEMES_ID: + return "vnd.android.cursor.item/themes"; + case MIXNMATCH: + return "vnd.android.cursor.dir/mixnmatch"; + case MIXNMATCH_KEY: + return "vnd.android.cursor.item/mixnmatch"; + default: + return null; + } + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + int uriType = sUriMatcher.match(uri); + SQLiteDatabase sqlDB = mDatabase.getWritableDatabase(); + long id = 0; + switch (uriType) { + case THEMES: + id = sqlDB.insert(ThemesOpenHelper.ThemesTable.TABLE_NAME, null, values); + Intent intent = new Intent(getContext(), CopyImageService.class); + intent.putExtra(CopyImageService.EXTRA_PKG_NAME, + values.getAsString(ThemesColumns.PKG_NAME)); + getContext().startService(intent); + break; + case MIXNMATCH: + throw new UnsupportedOperationException("Cannot insert rows into MixNMatch table"); + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return Uri.parse(MixnMatchColumns.CONTENT_URI + "/" + id); + } + + @Override + public boolean onCreate() { + mDatabase = new ThemesOpenHelper(getContext()); + + /** + * Sync database with package manager + */ + mHandler.post(new Runnable() { + public void run() { + new VerifyInstalledThemesThread().start(); + } + }); + + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + SQLiteDatabase db = mDatabase.getReadableDatabase(); + + /* + * Choose the table to query and a sort order based on the code returned for the incoming + * URI. Here, too, only the statements for table 3 are shown. + */ + switch (sUriMatcher.match(uri)) { + case THEMES: + queryBuilder.setTables(ThemesOpenHelper.ThemesTable.TABLE_NAME); + break; + case THEMES_ID: + queryBuilder.setTables(ThemesOpenHelper.ThemesTable.TABLE_NAME); + queryBuilder.appendWhere(ThemesColumns._ID + "=" + uri.getLastPathSegment()); + break; + case MIXNMATCH: + queryBuilder.setTables(THEMES_MIXNMATCH_INNER_JOIN); + break; + case MIXNMATCH_KEY: + queryBuilder.setTables(THEMES_MIXNMATCH_INNER_JOIN); + queryBuilder.appendWhere(MixnMatchColumns.COL_KEY + "=" + uri.getLastPathSegment()); + break; + default: + return null; + } + + Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, + sortOrder); + if (cursor != null) { + cursor.setNotificationUri(getContext().getContentResolver(), uri); + } + + return cursor; + } + + private static final String THEMES_MIXNMATCH_INNER_JOIN = MixnMatchTable.TABLE_NAME + + " INNER JOIN " + ThemesTable.TABLE_NAME + " ON (" + MixnMatchColumns.COL_VALUE + + " = " + ThemesColumns.PKG_NAME + ")"; + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + + int rowsUpdated = 0; + SQLiteDatabase sqlDB = mDatabase.getWritableDatabase(); + + switch (sUriMatcher.match(uri)) { + case THEMES: + rowsUpdated = sqlDB.update(ThemesTable.TABLE_NAME, values, selection, selectionArgs); + if (updateNotTriggeredByContentProvider(values)) { + Intent intent = new Intent(getContext(), CopyImageService.class); + intent.putExtra(CopyImageService.EXTRA_PKG_NAME, + values.getAsString(ThemesColumns.PKG_NAME)); + getContext().startService(intent); + getContext().getContentResolver().notifyChange(uri, null); + } + return rowsUpdated; + case THEMES_ID: + rowsUpdated = sqlDB.update(ThemesTable.TABLE_NAME, values, selection, selectionArgs); + if (updateNotTriggeredByContentProvider(values)) { + Intent intent = new Intent(getContext(), CopyImageService.class); + intent.putExtra(CopyImageService.EXTRA_PKG_NAME, + values.getAsString(ThemesColumns.PKG_NAME)); + getContext().startService(intent); + getContext().getContentResolver().notifyChange(uri, null); + } + getContext().getContentResolver().notifyChange(uri, null); + return rowsUpdated; + case MIXNMATCH: + rowsUpdated = sqlDB.update(MixnMatchTable.TABLE_NAME, values, selection, selectionArgs); + getContext().getContentResolver().notifyChange(uri, null); + case MIXNMATCH_KEY: + // Don't support right now. Any need? + } + return rowsUpdated; + } + + /** + * 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 + * content resolver, we need to break the loop so that we don't kick off the service again. + */ + private boolean updateNotTriggeredByContentProvider(ContentValues values) { + if (values == null) return true; + return !(values.containsKey(ThemesColumns.HOMESCREEN_URI) + || values.containsKey(ThemesColumns.LOCKSCREEN_URI) || values + .containsKey(ThemesColumns.STYLE_URI)); + } + + /** + * This class has been modified from its original source. Original Source: ThemesProvider.java + * See https://github.com/tmobile/themes-platform-vendor-tmobile-providers-ThemeManager + * Copyright (C) 2010, T-Mobile USA, Inc. http://www.apache.org/licenses/LICENSE-2.0 + */ + private class VerifyInstalledThemesThread extends Thread { + private final SQLiteDatabase mDb; + + public VerifyInstalledThemesThread() { + mDb = mDatabase.getWritableDatabase(); + } + + public void run() { + android.os.Process.setThreadPriority(Thread.MIN_PRIORITY); + + long start; + + if (DEBUG) { + start = System.currentTimeMillis(); + } + + SQLiteDatabase db = mDb; + db.beginTransaction(); + try { + verifyPackages(); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + + if (DEBUG) { + Log.d(TAG, "VerifyInstalledThemesThread took " + + (System.currentTimeMillis() - start) + " ms."); + } + } + } + + private void verifyPackages() { + /* List all currently installed theme packages according to PM */ + List<PackageInfo> packages = getContext().getPackageManager().getInstalledPackages(0); + List<PackageInfo> themePackages = new ArrayList<PackageInfo>(); + Map<String, PackageInfo> pmThemes = new HashMap<String, PackageInfo>(); + for (PackageInfo info : packages) { + if (info.isThemeApk || info.isLegacyThemeApk) { + themePackages.add(info); + pmThemes.put(info.packageName, info); + } + } + + /* + * Get all the known themes according to the provider. Then discover which themes have + * been deleted, need updating, or need to be inserted into the db + */ + Cursor current = mDb.query(ThemesTable.TABLE_NAME, null, null, null, null, null, null); + List<String> deleteList = new LinkedList<String>(); + List<PackageInfo> updateList = new LinkedList<PackageInfo>(); + while (current.moveToNext()) { + int updateTimeIdx = current.getColumnIndex( + ThemesContract.ThemesColumns.LAST_UPDATE_TIME); + int pkgNameIdx = current.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + long updateTime = current.getLong(updateTimeIdx); + String pkgName = current.getString(pkgNameIdx); + + // Ignore default theme + if (pkgName.equals("default")) { + continue; + } + + // Packages which are not in PM should be deleted from db + PackageInfo info = pmThemes.get(pkgName); + if (info == null) { + deleteList.add(pkgName); + continue; + } + + // Updated packages in PM should be + // updated in the db + long pmUpdateTime = (info.lastUpdateTime == 0) ? info.firstInstallTime + : info.lastUpdateTime; + if (pmUpdateTime != updateTime) { + updateList.add(info); + } + + // The remaining packages in pmThemes + // will be the ones to insert into the provider + pmThemes.remove(pkgName); + } + + // Check currently applied components (fonts, wallpapers etc) and verify the theme is + // still installed. If it is not installed, set the component back to the default theme + List<String> moveToDefault = new LinkedList<String>(); + Cursor mixnmatch = mDb.query(MixnMatchTable.TABLE_NAME, null, null, null, null, null, + null); + while (mixnmatch.moveToNext()) { + String mixnmatchKey = mixnmatch.getString(mixnmatch + .getColumnIndex(MixnMatchColumns.COL_KEY)); + String component = ThemesContract.MixnMatchColumns + .mixNMatchKeyToComponent(mixnmatchKey); + + String pkg = mixnmatch.getString(mixnmatch + .getColumnIndex(MixnMatchColumns.COL_VALUE)); + if (deleteList.contains(pkg)) { + moveToDefault.add(component); + } + } + ThemeManager mService = (ThemeManager) getContext().getSystemService( + Context.THEME_SERVICE); + mService.requestThemeChange("default", moveToDefault); + + // Update the database after we revert to default + deleteThemes(deleteList); + insertThemes(pmThemes.values()); + updateThemes(updateList); + } + + private void deleteThemes(List<String> themesToDelete) { + int rows = 0; + String where = ThemesColumns.PKG_NAME + "=?"; + for (String pkgName : themesToDelete) { + String[] whereArgs = { pkgName }; + rows += mDb.delete(ThemesTable.TABLE_NAME, where, whereArgs); + } + Log.d(TAG, "Deleted " + rows); + } + + private void insertThemes(Collection<PackageInfo> themesToInsert) { + for (PackageInfo themeInfo : themesToInsert) { + try { + ThemePackageHelper.insertPackage(getContext(), themeInfo.packageName); + } catch (NameNotFoundException e) { + Log.e(TAG, "Unable to insert theme " + themeInfo.packageName, e); + } + } + } + + private void updateThemes(List<PackageInfo> themesToUpdate) { + for (PackageInfo themeInfo : themesToUpdate) { + try { + ThemePackageHelper.updatePackage(getContext(), themeInfo.packageName); + } catch (NameNotFoundException e) { + Log.e(TAG, "Unable to update theme " + themeInfo.packageName, e); + } + } + } + } + +} |