summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/cyanogenmod/themes/provider/ThemePackageHelper.java')
-rw-r--r--src/org/cyanogenmod/themes/provider/ThemePackageHelper.java273
1 files changed, 273 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java b/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java
new file mode 100644
index 0000000..73900a4
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java
@@ -0,0 +1,273 @@
+/*
+ * 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.ContentValues;
+import android.content.Context;
+import android.content.pm.LegacyThemeInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ThemeInfo;
+import android.content.res.AssetManager;
+import android.content.res.ThemeManager;
+import android.database.Cursor;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.MixnMatchColumns;
+import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class to populate the provider with info from the theme.
+ */
+public class ThemePackageHelper {
+ public final static String TAG = ThemePackageHelper.class.getName();
+
+ // Maps the theme component to its folder name in assets.
+ public static HashMap<String, String> sComponentToFolderName = new HashMap<String, String>();
+ static {
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_OVERLAYS, "overlays");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_BOOT_ANIM, "bootanimation");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_FONTS, "fonts");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_ICONS, "icons");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_LAUNCHER, "wallpapers");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_LOCKSCREEN, "lockscreen");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_ALARMS, "alarms");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_NOTIFICATIONS, "notifications");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_RINGTONES, "ringtones");
+ }
+
+ public static boolean insertPackage(Context context, String pkgName)
+ throws NameNotFoundException {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0);
+ if (pi == null)
+ return false;
+
+ Map<String, Boolean> capabilities = getCapabilities(context, pkgName);
+ if (pi.themeInfos != null && pi.themeInfos.length > 0) {
+ insertPackageInternal(context, pi, capabilities);
+ } else if (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0) {
+ insertLegacyPackageInternal(context, pi, capabilities);
+ }
+ return true;
+ }
+
+ private static void insertPackageInternal(Context context, PackageInfo pi,
+ Map<String, Boolean> capabilities) {
+ ThemeInfo info = pi.themeInfos[0];
+ boolean isPresentableTheme = ThemePackageHelper.isPresentableTheme(capabilities);
+
+ ContentValues values = new ContentValues();
+ values.put(ThemesColumns.PKG_NAME, pi.packageName);
+ values.put(ThemesColumns.TITLE, info.name);
+ values.put(ThemesColumns.AUTHOR, info.author);
+ values.put(ThemesColumns.DATE_CREATED, System.currentTimeMillis());
+ values.put(ThemesColumns.PRESENT_AS_THEME, isPresentableTheme);
+ values.put(ThemesColumns.IS_LEGACY_THEME, pi.isLegacyThemeApk);
+ values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+
+ // Insert theme capabilities
+ for (Map.Entry<String, Boolean> entry : capabilities.entrySet()) {
+ String component = entry.getKey();
+ Boolean isImplemented = entry.getValue();
+ values.put(component, isImplemented);
+ }
+
+ context.getContentResolver().insert(ThemesColumns.CONTENT_URI, values);
+ }
+
+ private static void insertLegacyPackageInternal(Context context, PackageInfo pi,
+ Map<String, Boolean> capabilities) {
+ LegacyThemeInfo info = pi.legacyThemeInfos[0];
+ ContentValues values = new ContentValues();
+ values.put(ThemesColumns.PKG_NAME, pi.packageName);
+ values.put(ThemesColumns.TITLE, info.name);
+ values.put(ThemesColumns.AUTHOR, info.author);
+ values.put(ThemesColumns.DATE_CREATED, System.currentTimeMillis());
+ values.put(ThemesColumns.PRESENT_AS_THEME, 1);
+ values.put(ThemesColumns.IS_LEGACY_THEME, pi.isLegacyThemeApk);
+ values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+
+ // Insert theme capabilities
+ for (Map.Entry<String, Boolean> entry : capabilities.entrySet()) {
+ String component = entry.getKey();
+ Boolean isImplemented = ThemesColumns.MODIFIES_OVERLAYS.equals(component) ? Boolean.TRUE
+ : entry.getValue();
+ values.put(component, isImplemented);
+ }
+
+ context.getContentResolver().insert(ThemesColumns.CONTENT_URI, values);
+ }
+
+ public static void updatePackage(Context context, String pkgName) throws NameNotFoundException {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0);
+ Map<String, Boolean> capabilities = getCapabilities(context, pkgName);
+ if (pi.themeInfos != null && pi.themeInfos.length > 0) {
+ updatePackageInternal(context, pi, capabilities);
+ } else if (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0) {
+ updateLegacyPackageInternal(context, pi, capabilities);
+ }
+ }
+
+ private static void updatePackageInternal(Context context, PackageInfo pi,
+ Map<String, Boolean> capabilities) {
+ ThemeInfo info = pi.themeInfos[0];
+ boolean isPresentableTheme = ThemePackageHelper.isPresentableTheme(capabilities);
+ ContentValues values = new ContentValues();
+ values.put(ThemesColumns.PKG_NAME, pi.packageName);
+ values.put(ThemesColumns.TITLE, info.name);
+ values.put(ThemesColumns.AUTHOR, info.author);
+ values.put(ThemesColumns.DATE_CREATED, System.currentTimeMillis());
+ values.put(ThemesColumns.PRESENT_AS_THEME, isPresentableTheme);
+ values.put(ThemesColumns.IS_LEGACY_THEME, pi.isLegacyThemeApk);
+ values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+
+ String where = ThemesColumns.PKG_NAME + "=?";
+ String[] args = { pi.packageName };
+ context.getContentResolver().update(ThemesColumns.CONTENT_URI, values, where, args);
+ }
+
+ private static void updateLegacyPackageInternal(Context context, PackageInfo pi,
+ Map<String, Boolean> capabilities) {
+ LegacyThemeInfo info = pi.legacyThemeInfos[0];
+ ContentValues values = new ContentValues();
+ values.put(ThemesColumns.PKG_NAME, pi.packageName);
+ values.put(ThemesColumns.TITLE, info.name);
+ values.put(ThemesColumns.AUTHOR, info.author);
+ values.put(ThemesColumns.DATE_CREATED, System.currentTimeMillis());
+ values.put(ThemesColumns.PRESENT_AS_THEME, 1);
+ values.put(ThemesColumns.IS_LEGACY_THEME, pi.isLegacyThemeApk);
+ values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+
+ String where = ThemesColumns.PKG_NAME + "=?";
+ String[] args = { pi.packageName };
+ context.getContentResolver().update(ThemesColumns.CONTENT_URI, values, where, args);
+ }
+
+ public static void removePackage(Context context, String pkgToRemove) {
+ // Check currently applied components (fonts, wallpapers etc) and verify the theme is still
+ // installed if it is not installed, we need to set the component back to the default theme
+ List<String> moveToDefault = new LinkedList<String>(); // components to move back to default
+ Cursor mixnmatch = context.getContentResolver().query(MixnMatchColumns.CONTENT_URI, 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 (pkgToRemove.equals(pkg)) {
+ moveToDefault.add(component);
+ }
+ }
+ ThemeManager manager = (ThemeManager) context.getSystemService(Context.THEME_SERVICE);
+ manager.requestThemeChange("default", moveToDefault);
+
+ // Delete the theme from the db
+ String selection = ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = { pkgToRemove };
+ context.getContentResolver().delete(ThemesColumns.CONTENT_URI, selection, selectionArgs);
+ }
+
+ /**
+ * Returns a map of components with value of true if the APK themes that component or false
+ * otherwise. Example of a theme that handles fonts but not ringtones: (MODIFIES_FONTS -> true,
+ * MODIFIES_RINGTONES -> false)
+ */
+ public static Map<String, Boolean> getCapabilities(Context context, String pkgName) {
+ PackageInfo pi = null;
+ try {
+ pi = context.getPackageManager().getPackageInfo(pkgName, 0);
+ } catch (Exception e) {
+ Log.e(TAG, "Error getting pi during insert", e);
+ return Collections.emptyMap();
+ }
+
+ // Determine what this theme is capable of
+ Context themeContext = null;
+ try {
+ themeContext = context.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ // Determine what components the theme implements.
+ // TODO: Some sort of verification for valid elements (ex font should have valid ttf files)
+ HashMap<String, Boolean> implementMap = new HashMap<String, Boolean>();
+ for (Map.Entry<String, String> entry : sComponentToFolderName.entrySet()) {
+ String component = entry.getKey();
+ String folderName = entry.getValue();
+ boolean hasComponent = pi.isLegacyThemeApk ? hasThemeComponentLegacy(pi, component)
+ : hasThemeComponent(themeContext, folderName);
+ implementMap.put(component, hasComponent);
+ }
+ return implementMap;
+ }
+
+ private static boolean hasThemeComponentLegacy(PackageInfo pi, String component) {
+ if (ThemesColumns.MODIFIES_OVERLAYS.equals(component)) {
+ return true;
+ } else if (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) {
+ if (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0
+ && pi.legacyThemeInfos[0].wallpaperResourceId != 0) {
+ return true;
+ }
+ } else if (ThemesColumns.MODIFIES_RINGTONES.equals(component)) {
+ if (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0
+ && !TextUtils.isEmpty(pi.legacyThemeInfos[0].ringtoneFileName)) {
+ return true;
+ }
+ } else if (ThemesColumns.MODIFIES_NOTIFICATIONS.equals(component)) {
+ if (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0
+ && !TextUtils.isEmpty(pi.legacyThemeInfos[0].notificationFileName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasThemeComponent(Context themeContext, String component) {
+ boolean found = false;
+ AssetManager assetManager = themeContext.getAssets();
+ try {
+ String[] assetList = assetManager.list(component);
+ if (assetList != null && assetList.length > 0) {
+ found = true;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "There was an error checking for asset " + component, e);
+ }
+ return found;
+ }
+
+ // Presently we are defining a "presentable" theme as any theme
+ // which implements 2+ components. Such themes can be shown to the user
+ // under the "themes" category.
+ public static boolean isPresentableTheme(Map<String, Boolean> implementMap) {
+ int count = 0;
+ for (Boolean isImplemented : implementMap.values()) {
+ count += isImplemented ? 1 : 0;
+ }
+ return count >= 2;
+ }
+}