summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod/themes/provider/util
diff options
context:
space:
mode:
authorClark Scheff <clark@cyngn.com>2014-06-13 17:50:28 -0700
committerAndy Mast <andy@cyngn.com>2015-01-15 09:47:45 -0800
commit3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf (patch)
tree5e9253dd240c4c81d501b0a5a7240732ebd61bbc /src/org/cyanogenmod/themes/provider/util
parent7ccb6305880d89f7ab10356dbe1a9f4ba34796ce (diff)
downloadandroid_packages_providers_ThemesProvider-3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf.tar.gz
android_packages_providers_ThemesProvider-3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf.tar.bz2
android_packages_providers_ThemesProvider-3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf.zip
Themes: Port to CM12 [4/6]
Auto-generate previews. Id: Ia8f12bc852fb708e52ccc98641eea278756b198f Add MODIFIES_STATUS_BAR and MODIFIES_NAVIGATION_BAR Id: If1f81b122cfb5075835c70dff0bcfa6dea5d8de8 Use image with largest size for boot ani preview. Id: I3f2c16aa6ceb18387925e2cf2812d8c3ddf0d3d7 Use same components used in the original chooser. Icon previews will be generated using the same login used in the theme chooser v1. Id: Iafcd9f3d78fc86a66fe3635d27ee9fb7d47ae531 Remove (Default) from Holo's title. Holo may not be the default theme and therefore we should not add this to the title. The clients can handle determining which theme is the default on a device and act accordingly. Id: I162b3af97b05205c3ddf1e6ce130d05d9e0fe81f Add query for retrieving previews of currently applied components. Id: Ic3cffb4b762a047af40c72846e6e6bcf9a273619 Delete previews from provider when theme removed Id: Ica221885b929f7671f048d367648f7c6f82e4529 Alias status bar background as navbar_background Id: I3a32ea59e8fa94689d20fe92fbbc544b9da6e32b Fix provider not updating previews due to missing action. Id: Icd9f38ab499cfc617862be7638f06a9273dbdc22 Retrieve correct wallpaper size and store as jpeg Id: I346516a5860819f15a6b689e6da1a6bdf4f2540f Add style preview. What was originally called STYLE_PREVIEW is now STYLE_THUMBNAIL and STYLE_PREVIEW is now a larger sized image of various controls. Id: Iecf9785e992efa98360c84af2054d3a2a113f094 Use ThemeUtils methods for getting wallpaper paths. Id: I7fb269abe38560d638a2bad74fcfb7ed6a9a4c43 Add newly added navbar background in ThemesContract. [1/2] Id: I6fc92388a58b69c7a52053103cf85bb348f6144a Keep track of when a theme was installed Id: I9cdf637d63a4ed3d14b978a7d6c019aed0afe8e0 Update provider with ComposedIconInfo changes in f/b Id: I6ad0db5f55ed0dfb42c30cb5946f5c10af5b5a0f Use getThemedResourcesForApplication for icons Since we no longer pass bitmaps around in ComposedIconInfo we need to make sure the icons are actually attached to the resources in order to retrieve the drawables for the composed icons. Id: I7c92ee9d4114399024743c01d0df7fcc0284c150 Themes: Let ThemeService handle all theme processing [3/3] Id: Ied877eb5f1e96774d2e415970034d0b892765134 Add theme to db when installed and defer processing This allows the theme to show up in the theme provider even though the theme resources still need to be compiled. Once they are compiled the themes provider will update the database with the previews. Id: I2505cb875ee36975745a46469b2e55afe4fd1e68 Catch excpetions that occur when generating icon previews Id: I22e270911af12b6bd3cd69c84205d8455f74cf79 REF: THEMES-387 Catch all exceptions for styles and systemui previews Id: I4e3ee24e049d6dedba331175e21a143872de1f8e Presentable themes should contian icons, wallpaper and overlays The thems provider considered any theme with 2 or more components as a presentable theme which may not be represented well in the Theme Chooser. This patch makes it mandatory that a theme have icons, wallpaper and overlays in order to be considered a presentable theme. Id: If9918bd50125a4cd74b7926c5617c882eb89d5c2 REF: CHOOSER-2 Get working on CM12 Id: If585b1c9ee10b049f473ba2ca4ee313e09b6e715 Add column for target api This column will allow us to easily track what version of CM a theme was built for in case of incompatibility issues. The system theme will have a value of 0 indicating that it works on whatever the current api level is on the device. This prevents us from updating the row for the system font whenever the api level increases. Id: Ic43b63f2f801243914124043c4f4113a6b75b914 CM11 -> CM12 Upgrade [2/3] Renamed the "holo" theme to "system" Id: I82f2cde2e26e0b13c95ebb0fc61253c805c0a16e Fix String.format in upgradeToVersion11() Id: I13917d0c7ab34ff25a6326aebc0d707780bd6769 Change-Id: Ic98ec257e56a82d7faef37c0d2a2cfe360d6e35c
Diffstat (limited to 'src/org/cyanogenmod/themes/provider/util')
-rw-r--r--src/org/cyanogenmod/themes/provider/util/BitmapUtils.java222
-rw-r--r--src/org/cyanogenmod/themes/provider/util/BootAnimationPreviewGenerator.java93
-rw-r--r--src/org/cyanogenmod/themes/provider/util/IconPreviewGenerator.java101
-rw-r--r--src/org/cyanogenmod/themes/provider/util/IconPreviewHelper.java190
-rw-r--r--src/org/cyanogenmod/themes/provider/util/LayoutRenderUtils.java49
-rw-r--r--src/org/cyanogenmod/themes/provider/util/StylePreviewGenerator.java49
-rw-r--r--src/org/cyanogenmod/themes/provider/util/SystemUiPreviewGenerator.java197
-rw-r--r--src/org/cyanogenmod/themes/provider/util/WallpaperPreviewGenerator.java88
8 files changed, 989 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/themes/provider/util/BitmapUtils.java b/src/org/cyanogenmod/themes/provider/util/BitmapUtils.java
new file mode 100644
index 0000000..002970b
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/BitmapUtils.java
@@ -0,0 +1,222 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.io.Closeable;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class BitmapUtils {
+ private static final String TAG = "BitmapUtils";
+
+ /**
+ * Returns the bitmap from the given uri loaded using the given options.
+ * Returns null on failure.
+ */
+ public static Bitmap loadBitmap(Context context, InputStream is, BitmapFactory.Options o) {
+ try {
+ return BitmapFactory.decodeStream(is, null, o);
+ } finally {
+ closeSilently(is);
+ }
+ }
+
+ public static Bitmap decodeFile(String path, int reqWidth, int reqHeight) {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(path);
+ return decodeStream(fis, reqWidth, reqHeight);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to open resource in path" + path, e);
+ } finally {
+ closeSilently(fis);
+ }
+ return null;
+ }
+
+ public static Bitmap decodeStream(InputStream is, int reqWidth, int reqHeight) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+
+ // Determine insample size
+ opts.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, opts);
+ opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
+
+ // Decode the bitmap, regionally if necessary
+ Bitmap bitmap = null;
+ opts.inJustDecodeBounds = false;
+ Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight);
+ try {
+ if (rect != null) {
+ BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+ bitmap = decoder.decodeRegion(rect, opts);
+ } else {
+ bitmap = BitmapFactory.decodeStream(is, null, opts);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to decode bitmap from stream", e);
+ }
+ return bitmap;
+ }
+
+ public static Bitmap decodeResource(Resources res, int resId, int reqWidth, int reqHeight) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+
+ // Determine insample size
+ opts.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, opts);
+ opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
+
+ // Decode the bitmap, regionally if necessary
+ Bitmap bitmap = null;
+ opts.inJustDecodeBounds = false;
+ Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight);
+
+ InputStream stream = null;
+ try {
+ if (rect != null) {
+ stream = res.openRawResource(resId, new TypedValue());
+ if (stream == null) return null;
+ BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(stream, false);
+ bitmap = decoder.decodeRegion(rect, opts);
+ } else {
+ bitmap = BitmapFactory.decodeResource(res, resId, opts);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to open resource " + resId, e);
+ } finally {
+ closeSilently(stream);
+ }
+ return bitmap;
+ }
+
+
+ public static Bitmap decodeByteArray(byte[] buffer, int reqWidth, int reqHeight) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+
+ // Determine insample size
+ opts.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(buffer, 0, buffer.length, opts);
+ opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
+
+ // Decode the bitmap, regionally if necessary
+ Bitmap bitmap = null;
+ opts.inJustDecodeBounds = false;
+ Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight);
+ try {
+ if (rect != null) {
+ BitmapRegionDecoder decoder =
+ BitmapRegionDecoder.newInstance(buffer, 0, buffer.length, false);
+ bitmap = decoder.decodeRegion(rect, opts);
+ } else {
+ bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, opts);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to decode bitmap from stream", e);
+ }
+ return bitmap;
+ }
+
+ public static Bitmap getBitmapFromAsset(Context ctx, String path, int reqWidth, int reqHeight) {
+ if (ctx == null || path == null)
+ return null;
+
+ Bitmap bitmap = null;
+ try {
+ AssetManager assets = ctx.getAssets();
+ InputStream is = assets.open(path);
+ bitmap = decodeStream(is, reqWidth, reqHeight);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return bitmap;
+ }
+
+
+ /**
+ * For excessively large images with an awkward ratio we
+ * will want to crop them
+ * @return
+ */
+ public static Rect getCropRectIfNecessary(
+ BitmapFactory.Options options,int reqWidth, int reqHeight) {
+ final int width = options.outWidth;
+ final int height = options.outHeight;
+ Rect rect = new Rect(0, 0, width, height);
+ // Determine downsampled size
+ int targetWidth = reqWidth * options.inSampleSize;
+ int targetHeight = reqHeight * options.inSampleSize;
+
+ if (targetHeight < height) {
+ rect.top = (height - targetHeight) / 2;
+ rect.bottom = rect.top + targetHeight;
+ }
+ if (targetWidth < width) {
+ rect.left = (width - targetWidth) / 2;
+ rect.right = rect.left + targetWidth;
+ }
+ return rect;
+ }
+
+ public static int calculateInSampleSize(
+ BitmapFactory.Options options, int reqWidth, int reqHeight) {
+ return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
+ }
+
+ // Modified from original source:
+ // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
+ public static int calculateInSampleSize(
+ int decodeWidth, int decodeHeight, int reqWidth, int reqHeight) {
+ // Raw height and width of image
+ int inSampleSize = 1;
+
+ if (decodeHeight > reqHeight || decodeWidth > reqWidth) {
+ final int halfHeight = decodeHeight / 2;
+ final int halfWidth = decodeWidth / 2;
+
+ // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+ // height and width larger than the requested height and width.
+ while ((halfHeight / inSampleSize) > reqHeight &&
+ (halfWidth / inSampleSize) > reqWidth) {
+ inSampleSize *= 2;
+ }
+ }
+
+ return inSampleSize;
+ }
+
+ public static void closeSilently(Closeable c) {
+ if (c == null) return;
+ try {
+ c.close();
+ } catch (IOException t) {
+ Log.w(TAG, "close fail ", t);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/util/BootAnimationPreviewGenerator.java b/src/org/cyanogenmod/themes/provider/util/BootAnimationPreviewGenerator.java
new file mode 100644
index 0000000..61771a7
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/BootAnimationPreviewGenerator.java
@@ -0,0 +1,93 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.ThemeConfig;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.view.View;
+import android.widget.ImageView;
+import org.cyanogenmod.themes.provider.R;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class BootAnimationPreviewGenerator {
+ public static final String SYSTEM_BOOT_ANI_PATH = "/system/media/bootanimation.zip";
+ public static final String THEME_BOOT_ANI_PATH = "bootanimation/bootanimation.zip";
+
+ private Context mContext;
+
+ public BootAnimationPreviewGenerator(Context context) {
+ mContext = context;
+ }
+
+ public Bitmap generateBootAnimationPreview(String pkgName)
+ throws IOException, PackageManager.NameNotFoundException {
+ ZipInputStream zis;
+ String previewName;
+ if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) {
+ previewName = getPreviewFrameEntryName(new FileInputStream(SYSTEM_BOOT_ANI_PATH));
+ zis = new ZipInputStream(new FileInputStream(SYSTEM_BOOT_ANI_PATH));
+ } else {
+ final Context themeCtx = mContext.createPackageContext(pkgName, 0);
+ previewName = getPreviewFrameEntryName(themeCtx.getAssets().open(THEME_BOOT_ANI_PATH));
+ zis = new ZipInputStream(themeCtx.getAssets().open(THEME_BOOT_ANI_PATH));
+ }
+ ZipEntry ze;
+ Bitmap bmp = null;
+ while ((ze = zis.getNextEntry()) != null) {
+ final String entryName = ze.getName();
+ if (entryName.equals(previewName)) {
+ bmp = BitmapFactory.decodeStream(zis);
+ break;
+ }
+ }
+ zis.close();
+
+ View v = View.inflate(mContext, R.layout.bootanimation_preview, null);
+ ImageView iv = (ImageView) v.findViewById(R.id.preview);
+ iv.setImageBitmap(bmp);
+ return LayoutRenderUtils.renderViewToBitmap(v);
+ }
+
+ private String getPreviewFrameEntryName(InputStream is) throws IOException {
+ ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is
+ : new ZipInputStream(new BufferedInputStream(is));
+ ZipEntry ze;
+ String previewName = null;
+ long max = 0;
+ while ((ze = zis.getNextEntry()) != null) {
+ final String entryName = ze.getName();
+ if (entryName.contains("/")
+ && (entryName.endsWith(".png") || entryName.endsWith(".jpg"))) {
+ if(ze.getSize() > max) {
+ previewName = entryName;
+ max = ze.getSize();
+ }
+ }
+ }
+ zis.close();
+
+ return previewName;
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/util/IconPreviewGenerator.java b/src/org/cyanogenmod/themes/provider/util/IconPreviewGenerator.java
new file mode 100644
index 0000000..b1857d8
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/IconPreviewGenerator.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+
+public class IconPreviewGenerator {
+ private static final ComponentName COMPONENT_DIALER =
+ new ComponentName("com.android.dialer", "com.android.dialer.DialtactsActivity");
+ private static final ComponentName COMPONENT_MESSAGING =
+ new ComponentName("com.android.mms", "com.android.mms.ui.ConversationList");
+ private static final ComponentName COMPONENT_CAMERANEXT =
+ new ComponentName("com.cyngn.cameranext", "com.android.camera.CameraLauncher");
+ private static final ComponentName COMPONENT_CAMERA =
+ new ComponentName("com.android.camera2", "com.android.camera.CameraLauncher");
+ private static final ComponentName COMPONENT_BROWSER =
+ new ComponentName("com.android.browser", "com.android.browser.BrowserActivity");
+ private static final ComponentName COMPONENT_SETTINGS =
+ new ComponentName("com.android.settings", "com.android.settings.Settings");
+ private static final ComponentName COMPONENT_CALENDAR =
+ new ComponentName("com.android.calendar", "com.android.calendar.AllInOneActivity");
+ private static final ComponentName COMPONENT_GALERY =
+ new ComponentName("com.android.gallery3d", "com.android.gallery3d.app.GalleryActivity");
+ private static final String CAMERA_NEXT_PACKAGE = "com.cyngn.cameranext";
+
+ private ComponentName[] mIconComponents;
+
+ private Context mContext;
+
+ public IconPreviewGenerator(Context context) {
+ mContext = context;
+ }
+
+ public IconItems generateIconItems(String pkgName) {
+ IconItems items = new IconItems();
+ IconPreviewHelper helper = new IconPreviewHelper(mContext, pkgName);
+
+ final ComponentName[] components = getIconComponents(mContext);
+ BitmapDrawable drawable;
+ drawable = (BitmapDrawable) helper.getIcon(components[0]);
+ items.icon1 = drawable.getBitmap();
+ drawable = (BitmapDrawable) helper.getIcon(components[1]);
+ items.icon2 = drawable.getBitmap();
+ drawable = (BitmapDrawable) helper.getIcon(components[2]);
+ items.icon3 = drawable.getBitmap();
+ return items;
+ }
+
+ private ComponentName[] getIconComponents(Context context) {
+ if (mIconComponents == null || mIconComponents.length == 0) {
+ mIconComponents = new ComponentName[]{COMPONENT_DIALER, COMPONENT_MESSAGING,
+ COMPONENT_CAMERA, COMPONENT_BROWSER};
+
+ PackageManager pm = context.getPackageManager();
+
+ // if device does not have telephony replace dialer and mms
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mIconComponents[0] = COMPONENT_CALENDAR;
+ mIconComponents[1] = COMPONENT_GALERY;
+ }
+
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+ mIconComponents[2] = COMPONENT_SETTINGS;
+ } else {
+ // decide on which camera icon to use
+ try {
+ if (pm.getPackageInfo(CAMERA_NEXT_PACKAGE, 0) != null) {
+ mIconComponents[2] = COMPONENT_CAMERANEXT;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // default to COMPONENT_CAMERA
+ }
+ }
+
+ }
+
+ return mIconComponents;
+ }
+ public class IconItems {
+ public Bitmap icon1;
+ public Bitmap icon2;
+ public Bitmap icon3;
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/util/IconPreviewHelper.java b/src/org/cyanogenmod/themes/provider/util/IconPreviewHelper.java
new file mode 100644
index 0000000..484136c
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/IconPreviewHelper.java
@@ -0,0 +1,190 @@
+/*
+ * 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.util;
+
+import android.app.ActivityManager;
+import android.app.ComposedIconInfo;
+import android.app.IconPackHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+
+/**
+ * This class handles all the logic to build a preview icon
+ * If the system currently has a theme applied we do NOT
+ * want this code to be impacted by it. So code in this
+ * class creates special "no theme attached" resource objects
+ * to retrieve objects from.
+ */
+public class IconPreviewHelper {
+ private static final String TAG = IconPreviewHelper.class.getSimpleName();
+ private final static float ICON_SCALE_FACTOR = 1.3f; //Arbitrary. Looks good
+
+ private Context mContext;
+ private DisplayMetrics mDisplayMetrics;
+ private Configuration mConfiguration;
+ private int mIconDpi = 0;
+ private String mThemePkgName;
+ private IconPackHelper mIconPackHelper;
+ private int mIconSize;
+
+ /**
+ * @param themePkgName - The package name of the theme we wish to preview
+ */
+ public IconPreviewHelper(Context context, String themePkgName) {
+ mContext = context;
+ mDisplayMetrics = context.getResources().getDisplayMetrics();
+ mConfiguration = context.getResources().getConfiguration();
+ ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mIconDpi = (int) (am.getLauncherLargeIconDensity() * ICON_SCALE_FACTOR);
+ mThemePkgName = themePkgName;
+ mIconPackHelper = new IconPackHelper(mContext);
+ try {
+ mIconPackHelper.loadIconPack(mThemePkgName);
+ } catch (NameNotFoundException e) {}
+ mIconSize = (int) (am.getLauncherLargeIconSize() * ICON_SCALE_FACTOR);
+ }
+
+ /**
+ * Returns the actual label name for a given component
+ * If the activity does not have a label it will return app's label
+ * If neither has a label returns empty string
+ */
+ public String getLabel(ComponentName component) {
+ String label = "";
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo appInfo = pm.getApplicationInfo(component.getPackageName(), 0);
+ ActivityInfo activityInfo = pm.getActivityInfo(component, 0);
+
+ AssetManager assets = new AssetManager();
+ assets.addAssetPath(appInfo.publicSourceDir);
+ Resources res = new Resources(assets, mDisplayMetrics, mConfiguration);
+
+ if (activityInfo.labelRes != 0) {
+ label = res.getString(activityInfo.labelRes);
+ } else if (appInfo.labelRes != 0) {
+ label = res.getString(appInfo.labelRes);
+ }
+ } catch(NameNotFoundException exception) {
+ Log.e(TAG, "unable to find pkg for " + component.toString());
+ }
+ return label;
+ }
+
+ /**
+ * Returns the icon for the given component regardless of the system's
+ * currently applied theme. If the preview theme does not support the icon, then
+ * return the system default icon.
+ */
+ public Drawable getIcon(ComponentName component) {
+ String packageName = component.getPackageName();
+ String activityName = component.getClassName();
+ Drawable icon = getThemedIcon(packageName, activityName);
+ if (icon == null) {
+ icon = getDefaultIcon(packageName, activityName);
+ }
+ if (icon != null) {
+ icon.setBounds(0, 0, mIconSize, mIconSize);
+ }
+ return icon;
+ }
+
+ private Drawable getThemedIcon(String pkgName, String activityName) {
+ Drawable drawable = null;
+ ActivityInfo info = new ActivityInfo();
+ info.packageName = pkgName;
+ info.name = activityName;
+ drawable = mIconPackHelper.getDrawableForActivityWithDensity(info, mIconDpi);
+
+ return drawable;
+ }
+
+ /**
+ * Returns the default icon. This can be the normal icon associated with the app or a composed
+ * icon if the icon pack supports background, mask, and/or foreground.
+ * @param pkgName
+ * @param activityName
+ * @return
+ */
+ private Drawable getDefaultIcon(String pkgName, String activityName) {
+ Drawable drawable = null;
+ ComponentName component = new ComponentName(pkgName, activityName);
+ PackageManager pm = mContext.getPackageManager();
+ Resources res = null;
+ try {
+ ActivityInfo info = pm.getActivityInfo(component, 0);
+ ApplicationInfo appInfo = pm.getApplicationInfo(pkgName, 0);
+
+ res = pm.getThemedResourcesForApplication(pkgName, mThemePkgName);
+
+ final int iconId = info.icon != 0 ? info.icon : appInfo.icon;
+ info.themedIcon = 0;
+ setupComposedIcon(res, info, iconId);
+ drawable = getFullResIcon(res, iconId);
+ } catch (NameNotFoundException e2) {
+ Log.w(TAG, "Unable to get the icon for " + pkgName + " using default");
+ }
+ drawable = (drawable != null) ?
+ getComposedIcon(res, drawable) : getFullResDefaultActivityIcon();
+ return drawable;
+ }
+
+ private Drawable getComposedIcon(Resources res, Drawable baseIcon) {
+ ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo();
+ if (res != null && iconInfo != null && (iconInfo.iconBacks != null ||
+ iconInfo.iconMask != 0 || iconInfo.iconUpon != 0)) {
+ return IconPackHelper.IconCustomizer.getComposedIconDrawable(baseIcon, res, iconInfo);
+ }
+ return baseIcon;
+ }
+
+ private void setupComposedIcon(Resources res, ActivityInfo info, int iconId) {
+ ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo();
+
+ res.setComposedIconInfo(iconInfo);
+
+ SparseArray<PackageItemInfo> icons = new SparseArray<PackageItemInfo>(1);
+ info.themedIcon = 0;
+ icons.put(iconId, info);
+ res.setIconResources(icons);
+ }
+
+ private Drawable getFullResIcon(Resources resources, int iconId) {
+ Drawable d;
+ try {
+ d = resources.getDrawableForDensity(iconId, mIconDpi, null, false);
+ } catch (Resources.NotFoundException e) {
+ d = null;
+ }
+ return (d != null) ? d : getFullResDefaultActivityIcon();
+ }
+
+ private Drawable getFullResDefaultActivityIcon() {
+ return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon);
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/util/LayoutRenderUtils.java b/src/org/cyanogenmod/themes/provider/util/LayoutRenderUtils.java
new file mode 100644
index 0000000..c174d8e
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/LayoutRenderUtils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class LayoutRenderUtils {
+
+ public static Bitmap renderViewToBitmap(View view) {
+ // Provide it with a layout params. It should necessarily be wrapping the
+ // content as we not really going to have a parent for it.
+ view.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT));
+
+ // Pre-measure the view so that height and width don't remain null.
+ view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+
+ // Assign a size and position to the view and all of its descendants
+ view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
+
+ // Create the bitmap
+ Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(),
+ view.getMeasuredHeight(),
+ Bitmap.Config.ARGB_8888);
+ // Create a canvas with the specified bitmap to draw into
+ Canvas c = new Canvas(bitmap);
+
+ // Render this view to the given Canvas
+ view.draw(c);
+ return bitmap;
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/util/StylePreviewGenerator.java b/src/org/cyanogenmod/themes/provider/util/StylePreviewGenerator.java
new file mode 100644
index 0000000..eebcb36
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/StylePreviewGenerator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.os.UserHandle;
+import android.view.View;
+
+import org.cyanogenmod.themes.provider.R;
+
+public class StylePreviewGenerator {
+ private Context mContext;
+
+ public StylePreviewGenerator(Context context) {
+ mContext = context;
+ }
+
+ public StyleItems generateStylePreviews(String pkgName) throws NameNotFoundException {
+ StyleItems items = new StyleItems();
+ Context themeContext = mContext.createPackageContextAsUser("android", pkgName, 0,
+ UserHandle.CURRENT);
+ View v = View.inflate(themeContext, R.layout.controls_thumbnail, null);
+ items.thumbnail = LayoutRenderUtils.renderViewToBitmap(v);
+
+ v = View.inflate(themeContext, R.layout.controls_preview, null);
+ items.preview = LayoutRenderUtils.renderViewToBitmap(v);
+ return items;
+ }
+
+ public class StyleItems {
+ public Bitmap thumbnail;
+ public Bitmap preview;
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/util/SystemUiPreviewGenerator.java b/src/org/cyanogenmod/themes/provider/util/SystemUiPreviewGenerator.java
new file mode 100644
index 0000000..d53ad2f
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/SystemUiPreviewGenerator.java
@@ -0,0 +1,197 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
+import android.widget.FrameLayout;
+import org.cyanogenmod.themes.provider.view.BatteryMeterView;
+
+import org.cyanogenmod.themes.provider.R;
+
+public class SystemUiPreviewGenerator {
+ public static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+ private static final String WIFI_DRAWABLE = "stat_sys_wifi_signal_3_fully";
+ private static final String SIGNAL_DRAWABLE = "stat_sys_signal_3_fully";
+ private static final String BLUETOOTH_DRAWABLE = "stat_sys_data_bluetooth_connected";
+ private static final String STATUS_BAR_BATTERY_WIDTH = "status_bar_battery_width";
+ private static final String STATUS_BAR_BATTERY_HEIGHT = "status_bar_battery_height";
+ private static final String STATUS_BAR_CLOCK_COLOR = "status_bar_clock_color";
+ private static final String SYSTEM_BAR_BACKGROUND = "system_bar_background";
+ private static final String STATUS_BAR_BACKGROUND_OPAQUE = "status_bar_background_opaque";
+ private static final String NAVIGATION_BAR_BACKGROUND_OPAQUE
+ = "navigation_bar_background_opaque";
+ private static final String IC_SYSBAR_BACK = "ic_sysbar_back";
+ private static final String IC_SYSBAR_HOME = "ic_sysbar_home";
+ private static final String IC_SYSBAR_RECENT = "ic_sysbar_recent";
+ private static final String STATUS_BAR_ICON_SIZE = "status_bar_icon_size";
+
+ private Context mContext;
+
+ public SystemUiPreviewGenerator(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Loads the necessary resources for the given theme.
+ * @param pkgName Package name for the theme to use
+ * @return
+ * @throws NameNotFoundException
+ */
+ public SystemUiItems generateSystemUiItems(String pkgName) throws NameNotFoundException {
+ PackageManager pm = mContext.getPackageManager();
+ Resources res = pm.getThemedResourcesForApplication(SYSTEMUI_PACKAGE, pkgName);
+ int iconSize = res.getDimensionPixelSize(res.getIdentifier(STATUS_BAR_ICON_SIZE, "dimen",
+ SYSTEMUI_PACKAGE));
+
+ SystemUiItems items = new SystemUiItems();
+ Drawable d = res.getDrawable(res.getIdentifier(BLUETOOTH_DRAWABLE, "drawable",
+ SYSTEMUI_PACKAGE));
+ items.bluetoothIcon = renderDrawableToBitmap(d, iconSize);
+
+ d = res.getDrawable(res.getIdentifier(WIFI_DRAWABLE, "drawable", SYSTEMUI_PACKAGE));
+ items.wifiIcon = renderDrawableToBitmap(d, iconSize);
+
+ d = res.getDrawable(res.getIdentifier(SIGNAL_DRAWABLE, "drawable", SYSTEMUI_PACKAGE));
+ items.signalIcon = renderDrawableToBitmap(d, iconSize);
+
+ items.clockColor = res.getColor(res.getIdentifier(STATUS_BAR_CLOCK_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+ // wifi margin no longer used in systemui
+ items.wifiMarginEnd = 0;
+ generateBatteryPreviews(res, items);
+ generateBackgroundPreviews(res, items);
+
+ items.navbarBack = BitmapFactory.decodeResource(res, res.getIdentifier(IC_SYSBAR_BACK,
+ "drawable", SYSTEMUI_PACKAGE));
+ items.navbarHome = BitmapFactory.decodeResource(res, res.getIdentifier(IC_SYSBAR_HOME,
+ "drawable", SYSTEMUI_PACKAGE));
+ items.navbarRecent = BitmapFactory.decodeResource(res, res.getIdentifier(IC_SYSBAR_RECENT,
+ "drawable", SYSTEMUI_PACKAGE));
+
+ return items;
+ }
+
+ private Bitmap renderDrawableToBitmap(Drawable d, int iconSize) {
+ if (d instanceof VectorDrawable) {
+ return renderVectorDrawableToBitmap((VectorDrawable) d, iconSize);
+ } else if (d instanceof BitmapDrawable) {
+ return ((BitmapDrawable) d).getBitmap();
+ }
+
+ return null;
+ }
+
+ private Bitmap renderVectorDrawableToBitmap(VectorDrawable d, int iconSize) {
+ Bitmap bmp = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bmp);
+ d.setBounds(0, 0, iconSize, iconSize);
+ d.draw(canvas);
+
+ return bmp;
+ }
+
+ /**
+ * Generates the various battery types using the provided resources.
+ * @param res
+ * @param items
+ */
+ private void generateBatteryPreviews(Resources res, SystemUiItems items) {
+ FrameLayout view = new FrameLayout(mContext);
+ BatteryMeterView battery = new BatteryMeterView(mContext, res);
+ view.addView(battery,
+ new FrameLayout.LayoutParams(mContext.getResources().getDimensionPixelSize(
+ R.dimen.status_bar_battery_width),
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.status_bar_battery_height)));
+ battery.setMode(BatteryMeterView.BatteryMeterMode.BATTERY_METER_ICON_PORTRAIT);
+ items.batteryPortrait = LayoutRenderUtils.renderViewToBitmap(view);
+ battery.setMode(BatteryMeterView.BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE);
+ items.batteryLandscape = LayoutRenderUtils.renderViewToBitmap(view);
+ battery.setMode(BatteryMeterView.BatteryMeterMode.BATTERY_METER_CIRCLE);
+ items.batteryCircle = LayoutRenderUtils.renderViewToBitmap(view);
+ }
+
+ private void generateBackgroundPreviews(Resources res, SystemUiItems items) {
+ int width = Math.max(res.getDisplayMetrics().widthPixels,
+ res.getDisplayMetrics().heightPixels);
+ int height = mContext.getResources().getDimensionPixelSize(
+ R.dimen.status_bar_battery_height);
+ Drawable defaultBackground = res.getDrawable(
+ res.getIdentifier(SYSTEM_BAR_BACKGROUND, "drawable", SYSTEMUI_PACKAGE));
+ defaultBackground.setBounds(0, 0, width, height);
+
+ Drawable statusbarBackground = null;
+ int resId = res.getIdentifier(STATUS_BAR_BACKGROUND_OPAQUE, "color", SYSTEMUI_PACKAGE);
+ if (resId != 0) {
+ statusbarBackground = new ColorDrawable(res.getColor(resId));
+ statusbarBackground.setBounds(0, 0, width, height);
+ }
+ items.statusbarBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(items.statusbarBackground);
+ if (statusbarBackground != null) {
+ statusbarBackground.draw(canvas);
+ } else {
+ defaultBackground.draw(canvas);
+ }
+
+ Drawable navbarBackground = null;
+ resId = res.getIdentifier(NAVIGATION_BAR_BACKGROUND_OPAQUE, "color", SYSTEMUI_PACKAGE);
+ if (resId != 0) {
+ navbarBackground = new ColorDrawable(res.getColor(resId));
+ navbarBackground.setBounds(0, 0, width, height);
+ }
+ items.navbarBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ canvas.setBitmap(items.navbarBackground);
+ if (navbarBackground != null) {
+ navbarBackground.draw(canvas);
+ } else {
+ defaultBackground.draw(canvas);
+ }
+ }
+
+ public class SystemUiItems {
+ /**
+ * Status bar items
+ */
+ public Bitmap bluetoothIcon;
+ public Bitmap wifiIcon;
+ public Bitmap signalIcon;
+ public Bitmap batteryPortrait;
+ public Bitmap batteryLandscape;
+ public Bitmap batteryCircle;
+ public Bitmap statusbarBackground;
+ public int clockColor;
+ public int wifiMarginEnd;
+
+ /**
+ * Navigation bar items
+ */
+ public Bitmap navbarBackground;
+ public Bitmap navbarBack;
+ public Bitmap navbarHome;
+ public Bitmap navbarRecent;
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/util/WallpaperPreviewGenerator.java b/src/org/cyanogenmod/themes/provider/util/WallpaperPreviewGenerator.java
new file mode 100644
index 0000000..01b888a
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/util/WallpaperPreviewGenerator.java
@@ -0,0 +1,88 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.ThemeConfig;
+import android.graphics.Bitmap;
+
+import org.cyanogenmod.themes.provider.R;
+
+import java.io.File;
+import java.io.IOException;
+
+public class WallpaperPreviewGenerator {
+ private static final String WALLPAPER_ASSET_PATH = "wallpapers";
+ private static final String LOCKSCREEN_ASSET_PATH = "lockscreen";
+ private Context mContext;
+ private int mPreviewSize;
+ private int mThumbnailSize;
+
+ public WallpaperPreviewGenerator(Context context) {
+ mContext = context;
+ final Resources res = context.getResources();
+ mPreviewSize = res.getDimensionPixelSize(R.dimen.wallpaper_preview_size);
+ mThumbnailSize = res.getDimensionPixelSize(R.dimen.wallpaper_thumbnail_size);
+ }
+
+ public WallpaperItems generateWallpaperPreviews(PackageInfo themeInfo)
+ throws NameNotFoundException, IOException {
+ WallpaperItems items = new WallpaperItems();
+ if (themeInfo == null) {
+ Resources res = mContext.getPackageManager().getThemedResourcesForApplication("android",
+ ThemeConfig.SYSTEM_DEFAULT);
+ items.wpPreview = items.lsPreview = BitmapUtils.decodeResource(res,
+ com.android.internal.R.drawable.default_wallpaper, mPreviewSize, mPreviewSize);
+ } else {
+ final Context themeContext = mContext.createPackageContext(themeInfo.packageName, 0);
+ final AssetManager assets = themeContext.getAssets();
+ String path = ThemeUtils.getWallpaperPath(assets);
+ if (path != null) {
+ items.wpPreview = BitmapUtils.getBitmapFromAsset(themeContext, path,
+ mPreviewSize, mPreviewSize);
+ }
+ path = ThemeUtils.getLockscreenWallpaperPath(assets);
+ if (path != null) {
+ items.lsPreview = BitmapUtils.getBitmapFromAsset(themeContext, path,
+ mPreviewSize, mPreviewSize);
+ }
+ }
+ if (items.wpPreview != null) {
+ items.wpThumbnail = Bitmap.createScaledBitmap(items.wpPreview, mThumbnailSize,
+ mThumbnailSize, true);
+ }
+ if (items.lsPreview != null) {
+ items.lsThumbnail = Bitmap.createScaledBitmap(items.lsPreview, mThumbnailSize,
+ mThumbnailSize, true);
+ }
+ return items;
+ }
+
+ public class WallpaperItems {
+ // Wallpaper items
+ public Bitmap wpThumbnail;
+ public Bitmap wpPreview;
+
+ // Lockscreen wallpaper items
+ public Bitmap lsThumbnail;
+ public Bitmap lsPreview;
+ }
+}