summaryrefslogtreecommitdiffstats
path: root/src/org
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
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')
-rw-r--r--src/org/cyanogenmod/themes/provider/AppReceiver.java30
-rw-r--r--src/org/cyanogenmod/themes/provider/BitmapUtils.java92
-rw-r--r--src/org/cyanogenmod/themes/provider/CopyImageService.java211
-rw-r--r--src/org/cyanogenmod/themes/provider/PreviewGenerationService.java231
-rw-r--r--src/org/cyanogenmod/themes/provider/ThemePackageHelper.java133
-rw-r--r--src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java338
-rw-r--r--src/org/cyanogenmod/themes/provider/ThemesProvider.java229
-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
-rwxr-xr-xsrc/org/cyanogenmod/themes/provider/view/BatteryMeterView.java733
16 files changed, 2518 insertions, 468 deletions
diff --git a/src/org/cyanogenmod/themes/provider/AppReceiver.java b/src/org/cyanogenmod/themes/provider/AppReceiver.java
index c856abd..af9688b 100644
--- a/src/org/cyanogenmod/themes/provider/AppReceiver.java
+++ b/src/org/cyanogenmod/themes/provider/AppReceiver.java
@@ -39,9 +39,10 @@ public class AppReceiver extends BroadcastReceiver {
final String action = intent.getAction();
try {
if (Intent.ACTION_PACKAGE_ADDED.equals(action) && !isReplacing) {
- if (!isThemeBeingProcessed(context, pkgName)) {
- ThemePackageHelper.insertPackage(context, pkgName);
- } else {
+ final boolean themeProcessing = isThemeBeingProcessed(context, pkgName);
+ ThemePackageHelper.insertPackage(context, pkgName, !themeProcessing);
+
+ if (themeProcessing) {
// store this package name so we know it's being processed and it can be
// added to the DB when ACTION_THEME_RESOURCES_CACHED is received
PreferenceUtils.addThemeBeingProcessed(context, pkgName);
@@ -49,17 +50,18 @@ public class AppReceiver extends BroadcastReceiver {
} else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
ThemePackageHelper.removePackage(context, pkgName);
} else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
- if (!isThemeBeingProcessed(context, pkgName)) {
- if (themeExistsInProvider(context, pkgName)) {
- ThemePackageHelper.updatePackage(context, pkgName);
- } else {
- // Edge case where app was not a theme in previous install
- ThemePackageHelper.insertPackage(context, pkgName);
- }
+ final boolean themeProcessing = isThemeBeingProcessed(context, pkgName);
+ if (themeExistsInProvider(context, pkgName)) {
+ ThemePackageHelper.updatePackage(context, pkgName);
} else {
- // store this package name so we know it's being processed and it can be
- // added to the DB when ACTION_THEME_RESOURCES_CACHED is received
- PreferenceUtils.addThemeBeingProcessed(context, pkgName);
+ // Edge case where app was not a theme in previous install
+ ThemePackageHelper.insertPackage(context, pkgName, !themeProcessing);
+
+ if (themeProcessing) {
+ // store this package name so we know it's being processed and it can be
+ // added or updated when ACTION_THEME_RESOURCES_CACHED is received
+ PreferenceUtils.addThemeBeingProcessed(context, pkgName);
+ }
}
} else if (Intent.ACTION_THEME_RESOURCES_CACHED.equals(action)) {
final String themePkgName = intent.getStringExtra(Intent.EXTRA_THEME_PACKAGE_NAME);
@@ -73,7 +75,7 @@ public class AppReceiver extends BroadcastReceiver {
ThemePackageHelper.updatePackage(context, themePkgName);
} else {
// Edge case where app was not a theme in previous install
- ThemePackageHelper.insertPackage(context, themePkgName);
+ ThemePackageHelper.insertPackage(context, themePkgName, true);
}
}
}
diff --git a/src/org/cyanogenmod/themes/provider/BitmapUtils.java b/src/org/cyanogenmod/themes/provider/BitmapUtils.java
deleted file mode 100644
index 4a49068..0000000
--- a/src/org/cyanogenmod/themes/provider/BitmapUtils.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.util.Log;
-
-import java.io.Closeable;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class BitmapUtils {
- private static final String TAG = "BitmapUtils";
- private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
-
- /**
- * 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);
- }
- }
-
- /**
- * Loads a bitmap that has been downsampled using sampleSize from a given url.
- */
- public static Bitmap loadDownsampledBitmap(Context context, InputStream is, int sampleSize) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inMutable = true;
- options.inSampleSize = sampleSize;
- return loadBitmap(context, is, options);
- }
-
- /**
- * Loads a bitmap that is downsampled by at least the input sample size. In
- * low-memory situations, the bitmap may be downsampled further.
- */
- public static Bitmap loadBitmapWithBackouts(Context context, InputStream is, int sampleSize) {
- boolean noBitmap = true;
- int num_tries = 0;
- if (sampleSize <= 0) {
- sampleSize = 1;
- }
- Bitmap bmap = null;
- while (noBitmap) {
- try {
- // Try to decode, downsample if low-memory.
- bmap = loadDownsampledBitmap(context, is, sampleSize);
- noBitmap = false;
- } catch (java.lang.OutOfMemoryError e) {
- // Try with more downsampling before failing for good.
- if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
- throw e;
- }
- bmap = null;
- System.gc();
- sampleSize *= 2;
- }
- }
- return bmap;
- }
-
- 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/CopyImageService.java b/src/org/cyanogenmod/themes/provider/CopyImageService.java
deleted file mode 100644
index cbf7be2..0000000
--- a/src/org/cyanogenmod/themes/provider/CopyImageService.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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.app.IntentService;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.AssetManager;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.ThemesContract.ThemesColumns;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/*
- * Copies images from the theme APK to the local provider's cache
- */
-public class CopyImageService extends IntentService {
- public static final String ACTION_INSERT = "org.cyanogenmod.themes.provider.action.insert";
- public static final String ACTION_DELETE = "org.cyanogenmod.themes.provider.action.delete";
- public static final String EXTRA_PKG_NAME = "extra_pkg_name";
-
- private static final String TAG = CopyImageService.class.getName();
- private static final String IMAGES_PATH =
- "/data/org.cyanogenmod.themes.provider/files/images/";
- private static final String WALLPAPER_PATH =
- "/data/org.cyanogenmod.themes.provider/files/wallpapers/";
-
- private static final String WALLPAPER_PREVIEW = "images/wallpaper_preview";
- private static final String LOCKSCREEN_PREVIEW = "images/lockscreen_preview";
- private static final String STYLES_PREVIEW = "images/styles_preview";
-
- private static final String EXT_JPG = ".jpg";
- private static final String EXT_PNG = ".png";
-
- public CopyImageService() {
- super(CopyImageService.class.getName());
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- if (intent.getExtras() == null || intent.getExtras().getString(EXTRA_PKG_NAME) == null) {
- Log.e(TAG, "No package name or extras provided");
- return;
- }
-
- String pkgName = intent.getExtras().getString(EXTRA_PKG_NAME);
- if (ACTION_INSERT.equals(intent.getAction())) {
- createPreviewImages(this, pkgName);
- insertPreviewValuesIntoDb(pkgName);
- } else if (ACTION_DELETE.equals(intent.getAction())) {
- deletePreviewImages(pkgName);
- }
- }
-
- public static void createPreviewImages(Context context, String pkgName) {
- // Presently this is just mocked up. IE We expect the theme APK to
- // provide the bitmap.
- Context themeContext = null;
- try {
- themeContext = context.createPackageContext(pkgName,
- Context.CONTEXT_IGNORE_SECURITY);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Error getting themeContext", e);
- return;
- }
-
- // This is for testing only. We copy some assets from APK and put into
- // internal storage
- AssetManager assetManager = themeContext.getAssets();
- try {
- InputStream homescreen = getPreviewAsset(assetManager, WALLPAPER_PREVIEW);
-
- InputStream lockscreen = getPreviewAsset(assetManager, LOCKSCREEN_PREVIEW);
-
- File dataDir = context.getFilesDir(); // Environment.getDataDirectory();
- File imgDir = new File(dataDir, "images");
- File wpDir = new File(dataDir, "wallpapers");
- imgDir.mkdir();
- wpDir.mkdir();
-
- File homescreenOut = new File(imgDir, pkgName + ".homescreen.jpg");
- File lockscreenOut = new File(imgDir, pkgName + ".lockscreen.jpg");
-
- FileOutputStream out = new FileOutputStream(homescreenOut);
- Bitmap bmp = BitmapUtils.loadBitmapWithBackouts(context, homescreen, 1);
- bmp.compress(Bitmap.CompressFormat.JPEG, 90, out);
- out.close();
-
- out = new FileOutputStream(lockscreenOut);
- bmp = BitmapUtils.loadBitmapWithBackouts(context, lockscreen, 1);
- bmp.compress(Bitmap.CompressFormat.JPEG, 90, out);
- out.close();
- } catch (IOException e) {
- Log.e(TAG, "ThemesOpenHelper could not copy preview image");
- }
-
- //Copy Style preview
- try {
- InputStream stylepreview = getPreviewAsset(assetManager, STYLES_PREVIEW);
- File dataDir = context.getFilesDir();
- File imgDir = new File(dataDir, "images");
- imgDir.mkdir();
-
- File styleOut = new File(imgDir, pkgName + ".stylepreview.jpg");
-
- FileOutputStream out = new FileOutputStream(styleOut);
- Bitmap bmp = BitmapUtils.loadBitmapWithBackouts(context, stylepreview, 1);
- bmp.compress(Bitmap.CompressFormat.JPEG, 90, out);
- out.close();
- } catch (IOException e) {
- Log.e(TAG, "ThemesOpenHelper could not copy style image data");
- }
- }
-
- private void insertPreviewValuesIntoDb(String pkgName) {
- String homescreen = getHomeScreenPreviewPath(pkgName);
- String lockscreen = getLockScreenPreviewPath(pkgName);
- String stylePreview = getStylesPreviewPath(pkgName);
- String wallpaper = getWallpaperPreviewPath(pkgName);
-
- Uri hsUri = Uri.parse(homescreen);
- Uri lsUri = Uri.parse(lockscreen);
- Uri wpUri = Uri.parse(wallpaper);
- Uri styleUri = Uri.parse(stylePreview);
-
- String where = ThemesColumns.PKG_NAME + "=?";
- String[] selectionArgs = { pkgName };
-
- ContentValues values = new ContentValues();
- values.put(ThemesColumns.HOMESCREEN_URI, hsUri.toString());
- values.put(ThemesColumns.LOCKSCREEN_URI, lsUri.toString());
- values.put(ThemesColumns.STYLE_URI, styleUri.toString());
- values.put(ThemesColumns.WALLPAPER_URI, "file:///android_asset/wallpapers/wallpaper1.jpg");
-
- getContentResolver().update(ThemesColumns.CONTENT_URI, values,
- where, selectionArgs);
- }
-
- private void deletePreviewImages(String pkgName) {
- File home = new File(getHomeScreenPreviewPath(pkgName));
- home.delete();
-
- File lockscreen = new File(getLockScreenPreviewPath(pkgName));
- lockscreen.delete();
-
- File style = new File(getStylesPreviewPath(pkgName));
- style.delete();
-
- File wallpaper = new File(getWallpaperPreviewPath(pkgName));
- wallpaper.delete();
- }
-
- private static String getHomeScreenPreviewPath(String pkgName) {
- return Environment.getDataDirectory().getPath()
- + IMAGES_PATH + pkgName
- + ".homescreen.jpg";
- }
-
- private static String getLockScreenPreviewPath(String pkgName) {
- return Environment.getDataDirectory().getPath()
- + IMAGES_PATH + pkgName
- + ".lockscreen.jpg";
- }
-
- private static String getStylesPreviewPath(String pkgName) {
- return Environment.getDataDirectory().getPath()
- + IMAGES_PATH + pkgName
- + ".stylepreview.jpg";
- }
-
- private static String getWallpaperPreviewPath(String pkgName) {
- return ContentResolver.SCHEME_FILE + "://"
- + Environment.getDataDirectory().getPath()
- + WALLPAPER_PATH + pkgName
- + ".wallpaper1.jpg";
- }
-
- private static InputStream getPreviewAsset(AssetManager am, String preview) throws IOException {
- InputStream is = null;
- try {
- is = am.open(preview + EXT_JPG);
- } catch (IOException e) {
- // we'll try and fallback to PNG
- }
- if (is == null) is = am.open(preview + EXT_PNG);
-
- return is;
- }
-}
diff --git a/src/org/cyanogenmod/themes/provider/PreviewGenerationService.java b/src/org/cyanogenmod/themes/provider/PreviewGenerationService.java
new file mode 100644
index 0000000..0db60f6
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/PreviewGenerationService.java
@@ -0,0 +1,231 @@
+/*
+ * 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.app.IntentService;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.ThemeConfig;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.os.Bundle;
+import android.provider.ThemesContract.ThemesColumns;
+import android.provider.ThemesContract.PreviewColumns;
+import android.util.Log;
+import org.cyanogenmod.themes.provider.util.BootAnimationPreviewGenerator;
+import org.cyanogenmod.themes.provider.util.IconPreviewGenerator;
+import org.cyanogenmod.themes.provider.util.IconPreviewGenerator.IconItems;
+import org.cyanogenmod.themes.provider.util.StylePreviewGenerator;
+import org.cyanogenmod.themes.provider.util.StylePreviewGenerator.StyleItems;
+import org.cyanogenmod.themes.provider.util.SystemUiPreviewGenerator;
+import org.cyanogenmod.themes.provider.util.SystemUiPreviewGenerator.SystemUiItems;
+import org.cyanogenmod.themes.provider.util.WallpaperPreviewGenerator;
+import org.cyanogenmod.themes.provider.util.WallpaperPreviewGenerator.WallpaperItems;
+
+import java.io.ByteArrayOutputStream;
+
+/*
+ * Copies images from the theme APK to the local provider's cache
+ */
+public class PreviewGenerationService extends IntentService {
+ public static final String ACTION_INSERT = "org.cyanogenmod.themes.provider.action.insert";
+ public static final String ACTION_UPDATE = "org.cyanogenmod.themes.provider.action.update";
+ public static final String EXTRA_PKG_NAME = "extra_pkg_name";
+ public static final String EXTRA_HAS_SYSTEMUI = "extra_has_system_ui";
+ public static final String EXTRA_HAS_ICONS = "extra_has_icons";
+ public static final String EXTRA_HAS_WALLPAPER = "extra_has_wallpaper";
+ public static final String EXTRA_HAS_STYLES = "extra_has_styles";
+ public static final String EXTRA_HAS_BOOTANIMATION = "extra_has_bootanimation";
+
+ private static final String TAG = PreviewGenerationService.class.getName();
+
+ public PreviewGenerationService() {
+ super(PreviewGenerationService.class.getName());
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (intent.getExtras() == null || intent.getExtras().getString(EXTRA_PKG_NAME) == null) {
+ Log.e(TAG, "No package name or extras provided");
+ return;
+ }
+
+ final Bundle extras = intent.getExtras();
+ String pkgName = extras.getString(EXTRA_PKG_NAME);
+ boolean hasSystemUi = extras.getBoolean(EXTRA_HAS_SYSTEMUI, false);
+ boolean hasIcons = extras.getBoolean(EXTRA_HAS_ICONS, false);
+ boolean hasWallpaper = extras.getBoolean(EXTRA_HAS_WALLPAPER, false);
+ boolean hasStyles = extras.getBoolean(EXTRA_HAS_STYLES, false);
+ boolean hasBootanimation = extras.getBoolean(EXTRA_HAS_BOOTANIMATION, false);
+ boolean isSystemTheme = ThemeConfig.SYSTEM_DEFAULT.equals(pkgName);
+ final String action = intent.getAction();
+ if (ACTION_INSERT.equals(action) || ACTION_UPDATE.equals(action)) {
+ PackageInfo info = null;
+ try {
+ if (!isSystemTheme ) {
+ info = getPackageManager().getPackageInfo(pkgName, 0);
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Unable to get package info for " + pkgName, e);
+ }
+ if (isSystemTheme || info != null) {
+ SystemUiItems items = null;
+ try {
+ items = !hasSystemUi ? null :
+ new SystemUiPreviewGenerator(this).generateSystemUiItems(pkgName);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create statusbar previews for " + pkgName, e);
+ }
+
+ IconItems iconItems = null;
+ if (hasIcons) {
+ try {
+ iconItems = new IconPreviewGenerator(this).generateIconItems(pkgName);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create icon previews for " + pkgName, e);
+ }
+ }
+
+ WallpaperItems wallpaperItems = null;
+ if (hasWallpaper) {
+ try {
+ wallpaperItems = new WallpaperPreviewGenerator(this)
+ .generateWallpaperPreviews(info);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create wallpaper previews for " + pkgName, e);
+ }
+ }
+
+ StyleItems styleItems = null;
+ if (hasStyles) {
+ try {
+ styleItems = new StylePreviewGenerator(this).generateStylePreviews(pkgName);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create style previews for " + pkgName, e);
+ }
+ }
+
+ Bitmap bootAnim = null;
+ if (hasBootanimation) {
+ try {
+ bootAnim = new BootAnimationPreviewGenerator(this)
+ .generateBootAnimationPreview(pkgName);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create boot animation preview for " + pkgName, e);
+ }
+ }
+ insertPreviewItemsIntoDb(pkgName, items, iconItems, wallpaperItems, styleItems,
+ bootAnim);
+ }
+ }
+ }
+
+ private void insertPreviewItemsIntoDb(String pkgName, SystemUiItems items, IconItems icons,
+ WallpaperItems wallpaperItems, StyleItems styleItems,
+ Bitmap bootAnim) {
+ String[] projection = {ThemesColumns._ID};
+ String selection = ThemesColumns.PKG_NAME + "=?";
+ String[] selectionArgs = { pkgName };
+
+ final ContentResolver resolver = getContentResolver();
+ Cursor cursor = resolver.query(ThemesColumns.CONTENT_URI, projection, selection,
+ selectionArgs, null);
+
+ if (cursor != null) {
+ cursor.moveToFirst();
+ int id = cursor.getInt(cursor.getColumnIndexOrThrow(ThemesColumns._ID));
+ cursor.close();
+
+ ContentValues values = new ContentValues();
+ values.put(PreviewColumns.THEME_ID, id);
+ if (items != null) {
+ values.put(PreviewColumns.STATUSBAR_BACKGROUND,
+ getBitmapBlobPng(items.statusbarBackground));
+ values.put(PreviewColumns.STATUSBAR_BLUETOOTH_ICON,
+ getBitmapBlobPng(items.bluetoothIcon));
+ values.put(PreviewColumns.STATUSBAR_WIFI_ICON,
+ getBitmapBlobPng(items.wifiIcon));
+ values.put(PreviewColumns.STATUSBAR_SIGNAL_ICON,
+ getBitmapBlobPng(items.signalIcon));
+ values.put(PreviewColumns.STATUSBAR_BATTERY_PORTRAIT,
+ getBitmapBlobPng(items.batteryPortrait));
+ values.put(PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE,
+ getBitmapBlobPng(items.batteryLandscape));
+ values.put(PreviewColumns.STATUSBAR_BATTERY_CIRCLE,
+ getBitmapBlobPng(items.batteryCircle));
+ values.put(PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, items.clockColor);
+ values.put(PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, items.wifiMarginEnd);
+ values.put(PreviewColumns.NAVBAR_BACKGROUND,
+ getBitmapBlobPng(items.navbarBackground));
+ values.put(PreviewColumns.NAVBAR_BACK_BUTTON,
+ getBitmapBlobPng(items.navbarBack));
+ values.put(PreviewColumns.NAVBAR_HOME_BUTTON,
+ getBitmapBlobPng(items.navbarHome));
+ values.put(PreviewColumns.NAVBAR_RECENT_BUTTON,
+ getBitmapBlobPng(items.navbarRecent));
+ }
+ if (icons != null) {
+ values.put(PreviewColumns.ICON_PREVIEW_1, getBitmapBlobPng(icons.icon1));
+ values.put(PreviewColumns.ICON_PREVIEW_2, getBitmapBlobPng(icons.icon2));
+ values.put(PreviewColumns.ICON_PREVIEW_3, getBitmapBlobPng(icons.icon3));
+ }
+ if (wallpaperItems != null) {
+ values.put(PreviewColumns.WALLPAPER_PREVIEW,
+ getBitmapBlobJpg(wallpaperItems.wpPreview));
+ values.put(PreviewColumns.WALLPAPER_THUMBNAIL,
+ getBitmapBlobPng(wallpaperItems.wpThumbnail));
+ values.put(PreviewColumns.LOCK_WALLPAPER_PREVIEW,
+ getBitmapBlobJpg(wallpaperItems.lsPreview));
+ values.put(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL,
+ getBitmapBlobPng(wallpaperItems.lsThumbnail));
+ }
+ if (styleItems != null) {
+ values.put(PreviewColumns.STYLE_THUMBNAIL, getBitmapBlobPng(styleItems.thumbnail));
+ values.put(PreviewColumns.STYLE_PREVIEW, getBitmapBlobPng(styleItems.preview));
+ }
+ if (bootAnim != null) {
+ values.put(PreviewColumns.BOOTANIMATION_THUMBNAIL, getBitmapBlobPng(bootAnim));
+ }
+
+ selection = PreviewColumns.THEME_ID + "=?";
+ selectionArgs = new String[] { String.valueOf(id) };
+ // Try an update first, if that returns 0 then we need to insert these values
+ if (resolver.update(
+ PreviewColumns.CONTENT_URI, values, selection, selectionArgs) == 0) {
+ resolver.insert(PreviewColumns.CONTENT_URI, values);
+ }
+ }
+ }
+
+ private static byte[] getBitmapBlobPng(Bitmap bmp) {
+ return getBitmapBlob(bmp, CompressFormat.PNG, 100);
+ }
+
+ private static byte[] getBitmapBlobJpg(Bitmap bmp) {
+ return getBitmapBlob(bmp, CompressFormat.JPEG, 80);
+ }
+
+ private static byte[] getBitmapBlob(Bitmap bmp, CompressFormat format, int quality) {
+ if (bmp == null) return null;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ bmp.compress(format, quality, out);
+ return out.toByteArray();
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java b/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java
index feb4647..f26e5e7 100644
--- a/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java
+++ b/src/org/cyanogenmod/themes/provider/ThemePackageHelper.java
@@ -17,7 +17,6 @@ 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;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -31,7 +30,6 @@ 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;
@@ -41,7 +39,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import static android.content.res.ThemeConfig.HOLO_DEFAULT;
+import static android.content.res.ThemeConfig.SYSTEM_DEFAULT;
/**
* Helper class to populate the provider with info from the theme.
@@ -61,31 +59,33 @@ public class ThemePackageHelper {
sComponentToFolderName.put(ThemesColumns.MODIFIES_ALARMS, "alarms");
sComponentToFolderName.put(ThemesColumns.MODIFIES_NOTIFICATIONS, "notifications");
sComponentToFolderName.put(ThemesColumns.MODIFIES_RINGTONES, "ringtones");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_STATUS_BAR,
+ "overlays/com.android.systemui");
+ sComponentToFolderName.put(ThemesColumns.MODIFIES_NAVIGATION_BAR,
+ "overlays/com.android.systemui");
}
- public static boolean insertPackage(Context context, String pkgName)
+ public static boolean insertPackage(Context context, String pkgName, boolean processPreviews)
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);
+ if (pi.themeInfo != null) {
+ insertPackageInternal(context, pi, capabilities, processPreviews);
} else if (pi.isLegacyIconPackApk){
// We must be here because it is a legacy icon pack
capabilities = new HashMap<String, Boolean>();
capabilities.put(ThemesColumns.MODIFIES_ICONS, true);
- insertLegacyIconPackInternal(context, pi, capabilities);
+ insertLegacyIconPackInternal(context, pi, capabilities,processPreviews);
}
return true;
}
private static void insertPackageInternal(Context context, PackageInfo pi,
- Map<String, Boolean> capabilities) {
- ThemeInfo info = pi.themeInfos[0];
+ Map<String, Boolean> capabilities, boolean processPreviews) {
+ ThemeInfo info = pi.themeInfo;
boolean isPresentableTheme = ThemePackageHelper.isPresentableTheme(capabilities);
ContentValues values = new ContentValues();
@@ -94,30 +94,13 @@ public class ThemePackageHelper {
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.IS_DEFAULT_THEME,
- ThemeUtils.getDefaultThemePackageName(context).equals(pi.packageName) ? 1 : 0);
- values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
-
- // Insert theme capabilities
- insertCapabilities(capabilities, values);
-
- 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.IS_LEGACY_THEME, false);
values.put(ThemesColumns.IS_DEFAULT_THEME,
ThemeUtils.getDefaultThemePackageName(context).equals(pi.packageName) ? 1 : 0);
values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+ values.put(ThemesColumns.INSTALL_TIME, pi.firstInstallTime);
+ values.put(ThemesProvider.KEY_PROCESS_PREVIEWS, processPreviews);
+ values.put(ThemesColumns.TARGET_API, pi.applicationInfo.targetSdkVersion);
// Insert theme capabilities
insertCapabilities(capabilities, values);
@@ -126,7 +109,7 @@ public class ThemePackageHelper {
}
private static void insertLegacyIconPackInternal(Context context, PackageInfo pi,
- Map<String, Boolean> capabilities) {
+ Map<String, Boolean> capabilities, boolean processPreviews) {
PackageManager pm = context.getPackageManager();
CharSequence labelName = pm.getApplicationLabel(pi.applicationInfo);
if (labelName == null) labelName = context.getString(R.string.unknown_app_name);
@@ -137,7 +120,10 @@ public class ThemePackageHelper {
values.put(ThemesColumns.AUTHOR, "");
values.put(ThemesColumns.DATE_CREATED, System.currentTimeMillis());
values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+ values.put(ThemesColumns.INSTALL_TIME, pi.firstInstallTime);
values.put(ThemesColumns.IS_LEGACY_ICONPACK, 1);
+ values.put(ThemesProvider.KEY_PROCESS_PREVIEWS, processPreviews);
+ values.put(ThemesColumns.TARGET_API, pi.applicationInfo.targetSdkVersion);
// Insert theme capabilities
insertCapabilities(capabilities, values);
@@ -146,15 +132,13 @@ public class ThemePackageHelper {
}
public static void updatePackage(Context context, String pkgName) throws NameNotFoundException {
- if (HOLO_DEFAULT.equals(pkgName)) {
- updateHoloPackageInternal(context);
+ if (SYSTEM_DEFAULT.equals(pkgName)) {
+ updateSystemPackageInternal(context);
} else {
PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0);
Map<String, Boolean> capabilities = getCapabilities(context, pkgName);
- if (pi.themeInfos != null && pi.themeInfos.length > 0) {
+ if (pi.themeInfo != null) {
updatePackageInternal(context, pi, capabilities);
- } else if (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0) {
- updateLegacyPackageInternal(context, pi, capabilities);
} else if (pi.isLegacyIconPackApk) {
updateLegacyIconPackInternal(context, pi, capabilities);
}
@@ -166,7 +150,7 @@ public class ThemePackageHelper {
private static void updatePackageInternal(Context context, PackageInfo pi,
Map<String, Boolean> capabilities) {
- ThemeInfo info = pi.themeInfos[0];
+ ThemeInfo info = pi.themeInfo;
boolean isPresentableTheme = ThemePackageHelper.isPresentableTheme(capabilities);
ContentValues values = new ContentValues();
values.put(ThemesColumns.PKG_NAME, pi.packageName);
@@ -174,44 +158,26 @@ public class ThemePackageHelper {
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.IS_LEGACY_THEME, false);
values.put(ThemesColumns.IS_DEFAULT_THEME,
ThemeUtils.getDefaultThemePackageName(context).equals(pi.packageName) ? 1 : 0);
values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+ values.put(ThemesColumns.INSTALL_TIME, pi.firstInstallTime);
- String where = ThemesColumns.PKG_NAME + "=?";
- String[] args = { pi.packageName };
- context.getContentResolver().update(ThemesColumns.CONTENT_URI, values, where, args);
- }
+ // Insert theme capabilities
+ insertCapabilities(capabilities, values);
- private static void updateHoloPackageInternal(Context context) {
- ContentValues values = new ContentValues();
- values.put(ThemesColumns.IS_DEFAULT_THEME,
- HOLO_DEFAULT == ThemeUtils.getDefaultThemePackageName(context) ? 1 : 0);
String where = ThemesColumns.PKG_NAME + "=?";
- String[] args = { HOLO_DEFAULT };
+ 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];
+ private static void updateSystemPackageInternal(Context context) {
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.IS_DEFAULT_THEME,
- ThemeUtils.getDefaultThemePackageName(context).equals(pi.packageName) ? 1 : 0);
- values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
-
- // Insert theme capabilities
- insertCapabilities(capabilities, values);
-
+ SYSTEM_DEFAULT == ThemeUtils.getDefaultThemePackageName(context) ? 1 : 0);
String where = ThemesColumns.PKG_NAME + "=?";
- String[] args = { pi.packageName };
+ String[] args = { SYSTEM_DEFAULT };
context.getContentResolver().update(ThemesColumns.CONTENT_URI, values, where, args);
}
@@ -227,6 +193,7 @@ public class ThemePackageHelper {
values.put(ThemesColumns.TITLE, labelName.toString());
values.put(ThemesColumns.DATE_CREATED, System.currentTimeMillis());
values.put(ThemesColumns.LAST_UPDATE_TIME, pi.lastUpdateTime);
+ values.put(ThemesColumns.INSTALL_TIME, pi.firstInstallTime);
String where = ThemesColumns.PKG_NAME + "=?";
String[] args = { pi.packageName };
@@ -288,8 +255,7 @@ public class ThemePackageHelper {
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);
+ boolean hasComponent = hasThemeComponent(themeContext, folderName);
implementMap.put(component, hasComponent);
}
return implementMap;
@@ -304,29 +270,7 @@ public class ThemePackageHelper {
}
}
- 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) {
+ public static boolean hasThemeComponent(Context themeContext, String component) {
boolean found = false;
AssetManager assetManager = themeContext.getAssets();
try {
@@ -344,11 +288,10 @@ public class ThemePackageHelper {
// 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;
+ return implementMap != null &&
+ implementMap.containsKey(ThemesColumns.MODIFIES_LAUNCHER) &&
+ implementMap.containsKey(ThemesColumns.MODIFIES_ICONS) &&
+ implementMap.containsKey(ThemesColumns.MODIFIES_OVERLAYS);
}
private static void reapplyInstalledComponentsForTheme(Context context, String pkgName) {
diff --git a/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java b/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java
index 458deae..33a8336 100644
--- a/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java
+++ b/src/org/cyanogenmod/themes/provider/ThemesOpenHelper.java
@@ -17,22 +17,27 @@ package org.cyanogenmod.themes.provider;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ThemeUtils;
import android.content.res.ThemeConfig;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.ThemesContract;
import android.provider.ThemesContract.ThemesColumns;
import android.provider.ThemesContract.MixnMatchColumns;
+import android.provider.ThemesContract.PreviewColumns;
import android.util.Log;
public class ThemesOpenHelper extends SQLiteOpenHelper {
private static final String TAG = ThemesOpenHelper.class.getName();
- private static final int DATABASE_VERSION = 5;
+ private static final int DATABASE_VERSION = 11;
private static final String DATABASE_NAME = "themes.db";
- private static final String DEFAULT_PKG_NAME = ThemeConfig.HOLO_DEFAULT;
+ private static final String DEFAULT_PKG_NAME = ThemeConfig.SYSTEM_DEFAULT;
private Context mContext;
@@ -45,9 +50,11 @@ public class ThemesOpenHelper extends SQLiteOpenHelper {
public void onCreate(SQLiteDatabase db) {
db.execSQL(ThemesTable.THEMES_TABLE_CREATE);
db.execSQL(MixnMatchTable.MIXNMATCH_TABLE_CREATE);
+ db.execSQL(PreviewsTable.PREVIEWS_TABLE_CREATE);
- ThemesTable.insertHoloDefaults(db, mContext);
+ ThemesTable.insertSystemDefaults(db, mContext);
MixnMatchTable.insertDefaults(db);
+ PreviewsTable.insertDefaults(mContext);
}
@Override
@@ -70,6 +77,30 @@ public class ThemesOpenHelper extends SQLiteOpenHelper {
upgradeToVersion5(db);
oldVersion = 5;
}
+ if (oldVersion == 5) {
+ upgradeToVersion6(db);
+ oldVersion = 6;
+ }
+ if (oldVersion == 6) {
+ upgradeToVersion7(db);
+ oldVersion = 7;
+ }
+ if (oldVersion == 7) {
+ upgradeToVersion8(db);
+ oldVersion = 8;
+ }
+ if (oldVersion == 8) {
+ upgradeToVersion9(db);
+ oldVersion = 9;
+ }
+ if (oldVersion == 9) {
+ upgradeToVersion10(db);
+ oldVersion = 10;
+ }
+ if (oldVersion == 10) {
+ upgradeToVersion11(db);
+ oldVersion = 11;
+ }
if (oldVersion != DATABASE_VERSION) {
Log.e(TAG, "Recreating db because unknown database version: " + oldVersion);
dropTables(db);
@@ -111,12 +142,12 @@ public class ThemesOpenHelper extends SQLiteOpenHelper {
db.execSQL(addIsDefault);
// change default package name to holo
- String changeDefaultToHolo = String.format("UPDATE %s SET %s='%s' WHERE" +
+ String changeDefaultToSystem = String.format("UPDATE %s SET %s='%s' WHERE" +
" %s='%s'", ThemesTable.TABLE_NAME, ThemesColumns.PKG_NAME,
DEFAULT_PKG_NAME, ThemesColumns.PKG_NAME, "default");
- db.execSQL(changeDefaultToHolo);
+ db.execSQL(changeDefaultToSystem);
- if (isHoloDefault(mContext)) {
+ if (isSystemDefault(mContext)) {
// flag holo as default if
String makeHoloDefault = String.format("UPDATE %s SET %s=%d WHERE" +
" %s='%s'", ThemesTable.TABLE_NAME, ThemesColumns.IS_DEFAULT_THEME, 1,
@@ -130,9 +161,204 @@ public class ThemesOpenHelper extends SQLiteOpenHelper {
MixnMatchColumns.COL_VALUE, "default"));
}
+ private void upgradeToVersion6(SQLiteDatabase db) {
+ db.execSQL(PreviewsTable.PREVIEWS_TABLE_CREATE);
+
+ // remove (Default) from Holo's title
+ db.execSQL(String.format("UPDATE %s SET %s='%s' WHERE %s='%s'", ThemesTable.TABLE_NAME,
+ ThemesColumns.TITLE, "Holo", ThemesColumns.PKG_NAME, "holo"));
+
+ // we need to update any existing themes
+ final String[] projection = { ThemesColumns.PKG_NAME, ThemesColumns.MODIFIES_STATUS_BAR,
+ ThemesColumns.MODIFIES_ICONS, ThemesColumns.MODIFIES_OVERLAYS,
+ ThemesColumns.MODIFIES_LAUNCHER, ThemesColumns.MODIFIES_BOOT_ANIM };
+ final String selection = ThemesColumns.MODIFIES_OVERLAYS + "=?";
+ final String[] selectionArgs = { "1" };
+ final Cursor c = db.query(ThemesTable.TABLE_NAME, projection, selection, selectionArgs,
+ null, null, null);
+ if (c != null) {
+ while(c.moveToNext()) {
+ Intent intent = new Intent(mContext, PreviewGenerationService.class);
+ intent.setAction(PreviewGenerationService.ACTION_INSERT);
+ intent.putExtra(PreviewGenerationService.EXTRA_PKG_NAME, c.getString(0));
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_SYSTEMUI, c.getInt(1) == 1);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_ICONS, c.getInt(2) == 1);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_STYLES, c.getInt(3) == 1);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_WALLPAPER, c.getInt(4) == 1);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_BOOTANIMATION, c.getInt(5) == 1);
+ mContext.startService(intent);
+ }
+ c.close();
+ }
+ }
+
+ private void upgradeToVersion7(SQLiteDatabase db) {
+ String addStatusBar = String.format("ALTER TABLE %s ADD COLUMN %s INTEGER",
+ ThemesTable.TABLE_NAME, ThemesColumns.MODIFIES_STATUS_BAR);
+ String addNavBar = String.format("ALTER TABLE %s ADD COLUMN %s INTEGER",
+ ThemesTable.TABLE_NAME, ThemesColumns.MODIFIES_NAVIGATION_BAR);
+ db.execSQL(addStatusBar);
+ db.execSQL(addNavBar);
+
+ // we need to update any existing themes
+ final String[] projection = { ThemesColumns.PKG_NAME, ThemesColumns.IS_LEGACY_THEME };
+ final String selection = ThemesColumns.MODIFIES_OVERLAYS + "=? OR " +
+ ThemesColumns.IS_LEGACY_THEME + "=?";
+ final String[] selectionArgs = { "1", "1" };
+ final Cursor c = db.query(ThemesTable.TABLE_NAME, projection, selection, selectionArgs,
+ null, null, null);
+ if (c != null) {
+ while(c.moveToNext()) {
+ final String pkgName = c.getString(0);
+ final boolean isLegacyTheme = c.getInt(1) == 1;
+ boolean hasSystemUi = false;
+ if (DEFAULT_PKG_NAME.equals(pkgName) || isLegacyTheme) {
+ hasSystemUi = true;
+ } else {
+ try {
+ Context themeContext = mContext.createPackageContext(pkgName, 0);
+ hasSystemUi = ThemePackageHelper.hasThemeComponent(themeContext,
+ ThemePackageHelper.sComponentToFolderName.get(
+ ThemesColumns.MODIFIES_STATUS_BAR));
+ } catch (PackageManager.NameNotFoundException e) {
+ // default to false
+ }
+ }
+ if (hasSystemUi) {
+ db.execSQL(String.format("UPDATE %S SET %s='1', %s='1' WHERE %s='%s'",
+ ThemesTable.TABLE_NAME, ThemesColumns.MODIFIES_STATUS_BAR,
+ ThemesColumns.MODIFIES_NAVIGATION_BAR, ThemesColumns.PKG_NAME,
+ pkgName));
+ Intent intent = new Intent(mContext, PreviewGenerationService.class);
+ intent.setAction(PreviewGenerationService.ACTION_INSERT);
+ intent.putExtra(PreviewGenerationService.EXTRA_PKG_NAME, pkgName);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_SYSTEMUI, true);
+ mContext.startService(intent);
+ }
+ }
+ c.close();
+ }
+ }
+
+ private void upgradeToVersion8(SQLiteDatabase db) {
+ String addNavBar = String.format("ALTER TABLE %s ADD COLUMN %s BLOB",
+ PreviewsTable.TABLE_NAME, PreviewColumns.NAVBAR_BACKGROUND);
+ db.execSQL(addNavBar);
+
+ // we need to update any existing themes with the new NAVBAR_BACKGROUND
+ final String[] projection = { ThemesColumns.PKG_NAME, ThemesColumns.IS_LEGACY_THEME };
+ final String selection = ThemesColumns.MODIFIES_OVERLAYS + "=? OR " +
+ ThemesColumns.IS_LEGACY_THEME + "=?";
+ final String[] selectionArgs = { "1", "1" };
+ final Cursor c = db.query(ThemesTable.TABLE_NAME, projection, selection, selectionArgs,
+ null, null, null);
+ if (c != null) {
+ while(c.moveToNext()) {
+ final String pkgName = c.getString(0);
+ final boolean isLegacyTheme = c.getInt(1) == 1;
+ boolean hasSystemUi = false;
+ if (DEFAULT_PKG_NAME.equals(pkgName) || isLegacyTheme) {
+ hasSystemUi = true;
+ } else {
+ try {
+ Context themeContext = mContext.createPackageContext(pkgName, 0);
+ hasSystemUi = ThemePackageHelper.hasThemeComponent(themeContext,
+ ThemePackageHelper.sComponentToFolderName.get(
+ ThemesColumns.MODIFIES_STATUS_BAR));
+ } catch (PackageManager.NameNotFoundException e) {
+ // default to false
+ }
+ }
+ if (hasSystemUi) {
+ Intent intent = new Intent(mContext, PreviewGenerationService.class);
+ intent.setAction(PreviewGenerationService.ACTION_INSERT);
+ intent.putExtra(PreviewGenerationService.EXTRA_PKG_NAME, pkgName);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_SYSTEMUI, true);
+ mContext.startService(intent);
+ }
+ }
+ c.close();
+ }
+ }
+
+ private void upgradeToVersion9(SQLiteDatabase db) {
+ String addNavBar = String.format("ALTER TABLE %s ADD COLUMN %s INTEGER DEFAULT 0",
+ ThemesTable.TABLE_NAME, ThemesColumns.INSTALL_TIME);
+ db.execSQL(addNavBar);
+
+ // we need to update any existing themes with their install time
+ final String[] projection = { ThemesColumns.PKG_NAME };
+ final Cursor c = db.query(ThemesTable.TABLE_NAME, projection, null, null, null, null, null);
+ if (c != null) {
+ while(c.moveToNext()) {
+ final String pkgName = c.getString(0);
+ try {
+ PackageInfo pi = mContext.getPackageManager().getPackageInfo(pkgName, 0);
+ db.execSQL(String.format("UPDATE %s SET %s='%d' WHERE %s='%s'",
+ ThemesTable.TABLE_NAME, ThemesColumns.INSTALL_TIME, pi.firstInstallTime,
+ ThemesColumns.PKG_NAME, pkgName));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to update install time for " + pkgName, e);
+ }
+ }
+ c.close();
+ }
+ }
+
+ private void upgradeToVersion10(SQLiteDatabase db) {
+ // add API entries
+ String sql = String.format("ALTER TABLE %s ADD COLUMN %s TEXT",
+ ThemesTable.TABLE_NAME, ThemesColumns.TARGET_API);
+ db.execSQL(sql);
+
+ // we need to update any existing themes with their install time
+ final String[] projection = { ThemesColumns.PKG_NAME };
+ final Cursor c = db.query(ThemesTable.TABLE_NAME, projection, null, null, null, null, null);
+ if (c != null) {
+ while(c.moveToNext()) {
+ final String pkgName = c.getString(0);
+ int targetSdk = -1;
+ if (DEFAULT_PKG_NAME.equals(pkgName)) {
+ // 0 is a special value used for the system theme, not to be confused with the
+ // default theme which may not be the same as the system theme.
+ targetSdk = 0;
+ } else {
+ try {
+ PackageInfo pi = mContext.getPackageManager().getPackageInfo(pkgName, 0);
+ targetSdk = pi.applicationInfo.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to update target sdk for " + pkgName, e);
+ }
+ }
+ if (targetSdk != -1) {
+ db.execSQL(String.format("UPDATE %s SET %s='%d' WHERE %s='%s'", ThemesTable
+ .TABLE_NAME, ThemesColumns.TARGET_API,
+ targetSdk, ThemesColumns.PKG_NAME, pkgName));
+ }
+ }
+ c.close();
+ }
+ }
+
+ private void upgradeToVersion11(SQLiteDatabase db) {
+ // Update holo theme to be called "system"
+ final String NEW_THEME_TITLE = "System";
+ final String PREV_SYSTEM_PKG_NAME = "holo";
+ String holoToSystem = String.format("UPDATE TABLE %s " +
+ "SET title=%s, pkg_name=%s " +
+ "WHERE %s='%s'",
+ ThemesTable.TABLE_NAME,
+ NEW_THEME_TITLE,
+ DEFAULT_PKG_NAME,
+ ThemesColumns.PKG_NAME, PREV_SYSTEM_PKG_NAME);
+ db.execSQL(holoToSystem);
+
+ }
+
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);
}
public static class ThemesTable {
@@ -164,26 +390,25 @@ public class ThemesOpenHelper extends SQLiteOpenHelper {
ThemesColumns.MODIFIES_NOTIFICATIONS + " INTEGER DEFAULT 0, " +
ThemesColumns.MODIFIES_ALARMS + " INTEGER DEFAULT 0, " +
ThemesColumns.MODIFIES_OVERLAYS + " INTEGER DEFAULT 0, " +
+ ThemesColumns.MODIFIES_STATUS_BAR + " INTEGER DEFAULT 0, " +
+ ThemesColumns.MODIFIES_NAVIGATION_BAR + " INTEGER DEFAULT 0, " +
ThemesColumns.PRESENT_AS_THEME + " INTEGER DEFAULT 0, " +
- ThemesColumns.IS_LEGACY_THEME + " INTEGER DEFAULT 0," +
- ThemesColumns.IS_DEFAULT_THEME + " INTEGER DEFAULT 0," +
- ThemesColumns.IS_LEGACY_ICONPACK + " INTEGER DEFAULT 0," +
- ThemesColumns.LAST_UPDATE_TIME + " INTEGER DEFAULT 0" +
+ ThemesColumns.IS_LEGACY_THEME + " INTEGER DEFAULT 0, " +
+ ThemesColumns.IS_DEFAULT_THEME + " INTEGER DEFAULT 0, " +
+ ThemesColumns.IS_LEGACY_ICONPACK + " INTEGER DEFAULT 0, " +
+ ThemesColumns.LAST_UPDATE_TIME + " INTEGER DEFAULT 0, " +
+ ThemesColumns.INSTALL_TIME + " INTEGER DEFAULT 0, " +
+ ThemesColumns.TARGET_API + " INTEGER DEFAULT 0" +
")";
- public static void insertHoloDefaults(SQLiteDatabase db, Context context) {
- int isDefault = isHoloDefault(context) ? 1 : 0;
+ public static void insertSystemDefaults(SQLiteDatabase db, Context context) {
+ int isDefault = isSystemDefault(context) ? 1 : 0;
ContentValues values = new ContentValues();
- values.put(ThemesColumns.TITLE, "Holo (Default)");
+ values.put(ThemesColumns.TITLE, "System");
values.put(ThemesColumns.PKG_NAME, DEFAULT_PKG_NAME);
values.put(ThemesColumns.PRIMARY_COLOR, 0xff33b5e5);
values.put(ThemesColumns.SECONDARY_COLOR, 0xff000000);
values.put(ThemesColumns.AUTHOR, "Android");
- values.put(ThemesColumns.BOOT_ANIM_URI, "file:///android_asset/default_holo_theme/holo_boot_anim.jpg");
- values.put(ThemesColumns.HOMESCREEN_URI, "file:///android_asset/default_holo_theme/holo_homescreen.png");
- values.put(ThemesColumns.LOCKSCREEN_URI, "file:///android_asset/default_holo_theme/holo_lockscreen.png");
- values.put(ThemesColumns.STYLE_URI, "file:///android_asset/default_holo_theme/style.jpg");
- values.put(ThemesColumns.WALLPAPER_URI, "file:///android_asset/default_holo_theme/blueice_modcircle.jpg");
values.put(ThemesColumns.MODIFIES_ALARMS, 1);
values.put(ThemesColumns.MODIFIES_BOOT_ANIM, 1);
values.put(ThemesColumns.MODIFIES_FONTS, 1);
@@ -192,6 +417,8 @@ public class ThemesOpenHelper extends SQLiteOpenHelper {
values.put(ThemesColumns.MODIFIES_LOCKSCREEN, 1);
values.put(ThemesColumns.MODIFIES_NOTIFICATIONS, 1);
values.put(ThemesColumns.MODIFIES_RINGTONES, 1);
+ values.put(ThemesColumns.MODIFIES_STATUS_BAR, 1);
+ values.put(ThemesColumns.MODIFIES_NAVIGATION_BAR, 1);
values.put(ThemesColumns.PRESENT_AS_THEME, 1);
values.put(ThemesColumns.IS_LEGACY_THEME, 0);
values.put(ThemesColumns.IS_DEFAULT_THEME, isDefault);
@@ -219,9 +446,80 @@ public class ThemesOpenHelper extends SQLiteOpenHelper {
}
}
- private static boolean isHoloDefault(Context context) {
+ public static class PreviewsTable {
+ protected static final String TABLE_NAME = "previews";
+ private static final String PREVIEWS_TABLE_CREATE =
+ "CREATE TABLE " + TABLE_NAME + " (" +
+ PreviewColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ PreviewColumns.THEME_ID + " INTEGER, " +
+ PreviewColumns.STATUSBAR_BACKGROUND + " BLOB, " +
+ PreviewColumns.STATUSBAR_BLUETOOTH_ICON + " BLOB, " +
+ PreviewColumns.STATUSBAR_WIFI_ICON + " BLOB, " +
+ PreviewColumns.STATUSBAR_SIGNAL_ICON + " BLOB, " +
+ PreviewColumns.STATUSBAR_BATTERY_PORTRAIT + " BLOB, " +
+ PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE + " BLOB, " +
+ PreviewColumns.STATUSBAR_BATTERY_CIRCLE + " BLOB, " +
+ PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR + " INTEGER, " +
+ PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END + " INTEGER, " +
+ PreviewColumns.NAVBAR_BACKGROUND + " BLOB, " +
+ PreviewColumns.NAVBAR_BACK_BUTTON + " BLOB, " +
+ PreviewColumns.NAVBAR_HOME_BUTTON + " BLOB, " +
+ PreviewColumns.NAVBAR_RECENT_BUTTON + " BLOB, " +
+ PreviewColumns.ICON_PREVIEW_1 + " BLOB, " +
+ PreviewColumns.ICON_PREVIEW_2 + " BLOB, " +
+ PreviewColumns.ICON_PREVIEW_3 + " BLOB, " +
+ PreviewColumns.ICON_PREVIEW_4 + " BLOB, " +
+ PreviewColumns.STYLE_PREVIEW + " BLOB, " +
+ PreviewColumns.STYLE_THUMBNAIL + " BLOB, " +
+ PreviewColumns.WALLPAPER_PREVIEW + " BLOB, " +
+ PreviewColumns.WALLPAPER_THUMBNAIL + " BLOB, " +
+ PreviewColumns.LOCK_WALLPAPER_PREVIEW + " BLOB, " +
+ PreviewColumns.LOCK_WALLPAPER_THUMBNAIL + " BLOB, " +
+ PreviewColumns.BOOTANIMATION_THUMBNAIL + " BLOB, " +
+ "FOREIGN KEY (" + PreviewColumns.THEME_ID + ") REFERENCES " +
+ ThemesTable.TABLE_NAME + "(" + ThemesColumns._ID + ")" +
+ ")";
+
+ public static final String[] STATUS_BAR_PREVIEW_COLUMNS = {
+ PreviewColumns.STATUSBAR_BACKGROUND,
+ PreviewColumns.STATUSBAR_BLUETOOTH_ICON,
+ PreviewColumns.STATUSBAR_WIFI_ICON,
+ PreviewColumns.STATUSBAR_SIGNAL_ICON,
+ PreviewColumns.STATUSBAR_BATTERY_PORTRAIT,
+ PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE,
+ PreviewColumns.STATUSBAR_BATTERY_CIRCLE,
+ PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END,
+ PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR
+ };
+ public static final String[] NAVIGATION_BAR_PREVIEW_COLUMNS = {
+ PreviewColumns.NAVBAR_BACK_BUTTON,
+ PreviewColumns.NAVBAR_HOME_BUTTON,
+ PreviewColumns.NAVBAR_RECENT_BUTTON,
+ PreviewColumns.NAVBAR_BACKGROUND
+ };
+ public static final String[] ICON_PREVIEW_COLUMNS = {
+ PreviewColumns.ICON_PREVIEW_1,
+ PreviewColumns.ICON_PREVIEW_2,
+ PreviewColumns.ICON_PREVIEW_3,
+ PreviewColumns.ICON_PREVIEW_4
+ };
+
+ public static void insertDefaults(Context context) {
+ Intent intent = new Intent(context, PreviewGenerationService.class);
+ intent.setAction(PreviewGenerationService.ACTION_INSERT);
+ intent.putExtra(PreviewGenerationService.EXTRA_PKG_NAME, DEFAULT_PKG_NAME);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_SYSTEMUI, true);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_ICONS, true);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_STYLES, true);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_WALLPAPER, true);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_BOOTANIMATION, true);
+ context.startService(intent);
+ }
+ }
+
+ private static boolean isSystemDefault(Context context) {
// == is okay since we are checking if what is returned is the same constant string value
- return ThemeConfig.HOLO_DEFAULT == ThemeUtils.getDefaultThemePackageName(context);
+ 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 24c79bf..b64eafe 100644
--- a/src/org/cyanogenmod/themes/provider/ThemesProvider.java
+++ b/src/org/cyanogenmod/themes/provider/ThemesProvider.java
@@ -35,10 +35,13 @@ import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.ThemesContract;
import android.provider.ThemesContract.MixnMatchColumns;
+import android.provider.ThemesContract.PreviewColumns;
import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
import android.util.Log;
import org.cyanogenmod.themes.provider.ThemesOpenHelper.MixnMatchTable;
+import org.cyanogenmod.themes.provider.ThemesOpenHelper.PreviewsTable;
import org.cyanogenmod.themes.provider.ThemesOpenHelper.ThemesTable;
import java.util.ArrayList;
@@ -48,7 +51,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import static android.content.res.ThemeConfig.HOLO_DEFAULT;
+import static android.content.res.ThemeConfig.SYSTEM_DEFAULT;
public class ThemesProvider extends ContentProvider {
private static final String TAG = ThemesProvider.class.getSimpleName();
@@ -57,9 +60,14 @@ public class ThemesProvider extends ContentProvider {
private static final int MIXNMATCH_KEY = 2;
private static final int THEMES = 3;
private static final int THEMES_ID = 4;
+ private static final int PREVIEWS = 5;
+ private static final int PREVIEWS_ID = 6;
+ private static final int APPLIED_PREVIEWS = 7;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ public static final String KEY_PROCESS_PREVIEWS = "process_previews";
+
private final Handler mHandler = new Handler();
private ThemesOpenHelper mDatabase;
@@ -68,6 +76,9 @@ public class ThemesProvider extends ContentProvider {
sUriMatcher.addURI(ThemesContract.AUTHORITY, "mixnmatch/*", MIXNMATCH_KEY);
sUriMatcher.addURI(ThemesContract.AUTHORITY, "themes/", THEMES);
sUriMatcher.addURI(ThemesContract.AUTHORITY, "themes/#", THEMES_ID);
+ sUriMatcher.addURI(ThemesContract.AUTHORITY, "previews/", PREVIEWS);
+ sUriMatcher.addURI(ThemesContract.AUTHORITY, "previews/#", PREVIEWS_ID);
+ sUriMatcher.addURI(ThemesContract.AUTHORITY, "applied_previews/", APPLIED_PREVIEWS);
}
public static void setActiveTheme(Context context, String pkgName) {
@@ -90,17 +101,14 @@ public class ThemesProvider extends ContentProvider {
case THEMES:
SQLiteDatabase sqlDB = mDatabase.getWritableDatabase();
- // Determine the pkg name and delete preview images
- String[] columns = new String[] {ThemesColumns.PKG_NAME};
+ // Get the theme's _id and delete preview images
+ String[] columns = new String[] { ThemesColumns._ID };
Cursor c = sqlDB.query(ThemesTable.TABLE_NAME, columns, selection,
selectionArgs, null, null, null);
if (c == null) return 0;
if (c.moveToFirst()) {
- String pkgName = c.getString(0);
- Intent intent = new Intent(getContext(), CopyImageService.class);
- intent.setAction(CopyImageService.ACTION_DELETE);
- intent.putExtra(CopyImageService.EXTRA_PKG_NAME, pkgName);
- getContext().startService(intent);
+ sqlDB.delete(PreviewsTable.TABLE_NAME,
+ PreviewColumns.THEME_ID + "=" + c.getInt(0), null);
}
c.close();
@@ -125,6 +133,10 @@ public class ThemesProvider extends ContentProvider {
return "vnd.android.cursor.dir/mixnmatch";
case MIXNMATCH_KEY:
return "vnd.android.cursor.item/mixnmatch";
+ case PREVIEWS:
+ return "vnd.android.cursor.dir/previews";
+ case PREVIEWS_ID:
+ return "vnd.android.cursor.item/previews";
default:
return null;
}
@@ -137,17 +149,41 @@ public class ThemesProvider extends ContentProvider {
long id = 0;
switch (uriType) {
case THEMES:
+ boolean processPreviews = true;
+ if (values.containsKey(KEY_PROCESS_PREVIEWS)) {
+ processPreviews = values.getAsBoolean(KEY_PROCESS_PREVIEWS);
+ values.remove(KEY_PROCESS_PREVIEWS);
+ }
id = sqlDB.insert(ThemesOpenHelper.ThemesTable.TABLE_NAME, null, values);
- Intent intent = new Intent(getContext(), CopyImageService.class);
- intent.setAction(CopyImageService.ACTION_INSERT);
- intent.putExtra(CopyImageService.EXTRA_PKG_NAME,
- values.getAsString(ThemesColumns.PKG_NAME));
- getContext().startService(intent);
+ 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));
+ Boolean hasSystemUi = values.getAsBoolean(ThemesColumns.MODIFIES_STATUS_BAR);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_SYSTEMUI,
+ hasSystemUi != null && hasSystemUi);
+ Boolean hasIcons = values.getAsBoolean(ThemesColumns.MODIFIES_ICONS);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_ICONS,
+ hasIcons != null && hasIcons);
+ Boolean hasStyles = values.getAsBoolean(ThemesColumns.MODIFIES_OVERLAYS);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_STYLES,
+ hasStyles != null && hasStyles);
+ Boolean hasWallpaper = values.getAsBoolean(ThemesColumns.MODIFIES_LAUNCHER);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_WALLPAPER,
+ hasWallpaper != null && hasWallpaper);
+ Boolean hasBootAni = values.getAsBoolean(ThemesColumns.MODIFIES_BOOT_ANIM);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_BOOTANIMATION,
+ hasBootAni != null && hasBootAni);
+ getContext().startService(intent);
+ }
break;
case MIXNMATCH:
throw new UnsupportedOperationException("Cannot insert rows into MixNMatch table");
+ case PREVIEWS:
+ id = sqlDB.insert(ThemesOpenHelper.PreviewsTable.TABLE_NAME, null, values);
+ break;
default:
- throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(MixnMatchColumns.CONTENT_URI + "/" + id);
@@ -195,6 +231,15 @@ public class ThemesProvider extends ContentProvider {
queryBuilder.setTables(THEMES_MIXNMATCH_INNER_JOIN);
queryBuilder.appendWhere(MixnMatchColumns.COL_KEY + "=" + uri.getLastPathSegment());
break;
+ case PREVIEWS:
+ queryBuilder.setTables(THEMES_PREVIEWS_INNER_JOIN);
+ break;
+ case PREVIEWS_ID:
+ queryBuilder.setTables(THEMES_PREVIEWS_INNER_JOIN);
+ queryBuilder.appendWhere(PreviewColumns._ID + "=" + uri.getLastPathSegment());
+ break;
+ case APPLIED_PREVIEWS:
+ return getAppliedPreviews(db);
default:
return null;
}
@@ -212,6 +257,10 @@ public class ThemesProvider extends ContentProvider {
+ " INNER JOIN " + ThemesTable.TABLE_NAME + " ON (" + MixnMatchColumns.COL_VALUE
+ " = " + ThemesColumns.PKG_NAME + ")";
+ private static final String THEMES_PREVIEWS_INNER_JOIN = PreviewsTable.TABLE_NAME
+ + " INNER JOIN " + ThemesTable.TABLE_NAME + " ON (" + PreviewColumns.THEME_ID
+ + " = " + ThemesTable.TABLE_NAME + "." + ThemesColumns._ID + ")";
+
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
@@ -220,36 +269,120 @@ public class ThemesProvider extends ContentProvider {
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:
+ String pkgName = values.getAsString(ThemesColumns.PKG_NAME);
+ final boolean updatePreviews = getShouldUpdatePreviews(sqlDB, pkgName);
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,
+ if (updateNotTriggeredByContentProvider(values) && updatePreviews) {
+ Intent intent = new Intent(getContext(), PreviewGenerationService.class);
+ intent.setAction(PreviewGenerationService.ACTION_UPDATE);
+ intent.putExtra(PreviewGenerationService.EXTRA_PKG_NAME,
values.getAsString(ThemesColumns.PKG_NAME));
+ Boolean hasSystemUi = values.getAsBoolean(ThemesColumns.MODIFIES_STATUS_BAR);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_SYSTEMUI,
+ hasSystemUi != null && hasSystemUi);
+ Boolean hasIcons = values.getAsBoolean(ThemesColumns.MODIFIES_ICONS);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_ICONS,
+ hasIcons != null && hasIcons);
+ Boolean hasStyles = values.getAsBoolean(ThemesColumns.MODIFIES_OVERLAYS);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_STYLES,
+ hasStyles != null && hasStyles);
+ Boolean hasWallpaper = values.getAsBoolean(ThemesColumns.MODIFIES_LAUNCHER);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_WALLPAPER,
+ hasWallpaper != null && hasWallpaper);
+ Boolean hasBootAni = values.getAsBoolean(ThemesColumns.MODIFIES_BOOT_ANIM);
+ intent.putExtra(PreviewGenerationService.EXTRA_HAS_BOOTANIMATION,
+ hasBootAni != null && hasBootAni);
getContext().startService(intent);
- getContext().getContentResolver().notifyChange(uri, null);
}
getContext().getContentResolver().notifyChange(uri, null);
- return rowsUpdated;
+ break;
case MIXNMATCH:
rowsUpdated = sqlDB.update(MixnMatchTable.TABLE_NAME, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
+ break;
case MIXNMATCH_KEY:
// Don't support right now. Any need?
+ break;
+ case PREVIEWS:
+ rowsUpdated = sqlDB.update(PreviewsTable.TABLE_NAME, values, selection, selectionArgs);
+ getContext().getContentResolver().notifyChange(uri, null);
+ break;
}
return rowsUpdated;
}
/**
+ * Queries the currently applied components and creates a SQLite statement consisting
+ * of a series of (SELECT ...) statements
+ * @param db Readable database
+ * @return
+ */
+ private Cursor getAppliedPreviews(SQLiteDatabase db) {
+ Cursor c = db.query(MixnMatchTable.TABLE_NAME, null, null, null, null, null, null);
+ if (c != null) {
+ StringBuilder sb = new StringBuilder("SELECT * FROM ");
+ String delimeter = "";
+ while (c.moveToNext()) {
+ String key = c.getString(0);
+ String pkgName = c.getString(1);
+ String component = key != null ? MixnMatchColumns.mixNMatchKeyToComponent(key) :
+ null;
+ if (component != null && pkgName != null) {
+ // We need to get the theme's id using its package name
+ String[] columns = { ThemesColumns._ID };
+ Cursor current = db.query(ThemesTable.TABLE_NAME, columns,
+ ThemesColumns.PKG_NAME + "='" + pkgName + "'", null, 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)) {
+ sb.append(delimeter).append("(SELECT ");
+ sb.append(TextUtils.join(",",
+ PreviewsTable.STATUS_BAR_PREVIEW_COLUMNS));
+ sb.append(String.format(" FROM previews WHERE %s=%d)",
+ PreviewColumns.THEME_ID, id));
+ delimeter = ",";
+ } else if (ThemesColumns.MODIFIES_ICONS.equals(component)) {
+ sb.append(delimeter).append("(SELECT ");
+ sb.append(TextUtils.join(",", PreviewsTable.ICON_PREVIEW_COLUMNS));
+ sb.append(String.format(" FROM previews WHERE %s=%d)",
+ PreviewColumns.THEME_ID, id));
+ delimeter = ",";
+ } else if (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) {
+ sb.append(delimeter).append("(SELECT ");
+ sb.append(String.format("%s", PreviewColumns.WALLPAPER_PREVIEW));
+ sb.append(String.format(" FROM previews WHERE %s=%d)",
+ PreviewColumns.THEME_ID, id));
+ delimeter = ",";
+ } else if (ThemesColumns.MODIFIES_NAVIGATION_BAR.equals(component)) {
+ sb.append(delimeter).append("(SELECT ");
+ sb.append(TextUtils.join(",",
+ PreviewsTable.NAVIGATION_BAR_PREVIEW_COLUMNS));
+ sb.append(String.format(" FROM previews WHERE %s=%d)",
+ PreviewColumns.THEME_ID, id));
+ delimeter = ",";
+ } else if (ThemesColumns.MODIFIES_OVERLAYS.equals(component)) {
+ sb.append(delimeter).append("(SELECT ");
+ sb.append(PreviewColumns.STYLE_PREVIEW);
+ sb.append(String.format(" FROM previews WHERE %s=%d)",
+ PreviewColumns.THEME_ID, id));
+ 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
* content resolver, we need to break the loop so that we don't kick off the service again.
@@ -261,6 +394,31 @@ public class ThemesProvider extends ContentProvider {
.containsKey(ThemesColumns.STYLE_URI));
}
+ private boolean getShouldUpdatePreviews(SQLiteDatabase db, String pkgName) {
+ if (pkgName != null) {
+ long lastUpdateTime = 0;
+ String[] columns = {ThemesColumns.LAST_UPDATE_TIME};
+ String selection = ThemesColumns.PKG_NAME + "=?";
+ String[] selectionArgs = {pkgName};
+ Cursor c =
+ db.query(ThemesTable.TABLE_NAME, columns, selection, selectionArgs, null, null,
+ null);
+ if (c != null) {
+ c.moveToFirst();
+ lastUpdateTime = c.getInt(0);
+ c.close();
+ }
+
+ try {
+ PackageInfo pi = getContext().getPackageManager().getPackageInfo(pkgName, 0);
+ return lastUpdateTime < pi.lastUpdateTime;
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to retrieve PackageInfo for " + pkgName, e);
+ }
+ }
+ return false;
+ }
+
/**
* This class has been modified from its original source. Original Source: ThemesProvider.java
* See https://github.com/tmobile/themes-platform-vendor-tmobile-providers-ThemeManager
@@ -303,7 +461,7 @@ public class ThemesProvider extends ContentProvider {
List<PackageInfo> themePackages = new ArrayList<PackageInfo>();
Map<String, PackageInfo> pmThemes = new HashMap<String, PackageInfo>();
for (PackageInfo info : packages) {
- if (info.isThemeApk || info.isLegacyThemeApk || info.isLegacyIconPackApk) {
+ if (info.isThemeApk || info.isLegacyIconPackApk) {
themePackages.add(info);
pmThemes.put(info.packageName, info);
}
@@ -326,10 +484,10 @@ public class ThemesProvider extends ContentProvider {
String pkgName = current.getString(pkgNameIdx);
boolean isDefault = current.getInt(isDefaultIdx) == 1;
- // Ignore holo theme
- if (pkgName.equals(HOLO_DEFAULT)) {
- if (defaultThemePkg.equals(HOLO_DEFAULT) != isDefault) {
- updateList.add(HOLO_DEFAULT);
+ // Ignore system theme
+ if (pkgName.equals(SYSTEM_DEFAULT)) {
+ if (defaultThemePkg.equals(SYSTEM_DEFAULT) != isDefault) {
+ updateList.add(SYSTEM_DEFAULT);
}
continue;
}
@@ -374,7 +532,7 @@ public class ThemesProvider extends ContentProvider {
}
ThemeManager mService = (ThemeManager) getContext().getSystemService(
Context.THEME_SERVICE);
- mService.requestThemeChange(HOLO_DEFAULT, moveToDefault);
+ mService.requestThemeChange(SYSTEM_DEFAULT, moveToDefault);
// Update the database after we revert to default
deleteThemes(deleteList);
@@ -395,7 +553,7 @@ public class ThemesProvider extends ContentProvider {
private void insertThemes(Collection<PackageInfo> themesToInsert) {
for (PackageInfo themeInfo : themesToInsert) {
try {
- ThemePackageHelper.insertPackage(getContext(), themeInfo.packageName);
+ ThemePackageHelper.insertPackage(getContext(), themeInfo.packageName, true);
} catch (NameNotFoundException e) {
Log.e(TAG, "Unable to insert theme " + themeInfo.packageName, e);
}
@@ -412,5 +570,4 @@ public class ThemesProvider extends ContentProvider {
}
}
}
-
}
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;
+ }
+}
diff --git a/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java b/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
new file mode 100755
index 0000000..4b772fa
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2013 The Android Open Source 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.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import static org.cyanogenmod.themes.provider.util.SystemUiPreviewGenerator.SYSTEMUI_PACKAGE;
+
+public class BatteryMeterView extends View {
+ private static final String TAG = BatteryMeterView.class.getSimpleName();
+ private static final boolean ENABLE_PERCENT = true;
+
+ private static final String BATTERYMETER_COLOR_LEVELS = "batterymeter_color_levels";
+ private static final String BATTERYMETER_COLOR_VALUES = "batterymeter_color_values";
+ private static final String BATTERYMETER_FRAME_COLOR = "batterymeter_frame_color";
+ private static final String BATTERYMETER_BOLT_COLOR = "batterymeter_bolt_color";
+ private static final String BATTERYMETER_BOLT_POINTS = "batterymeter_bolt_points";
+ private static final String STATUS_BAR_CLOCK_COLOR = "status_bar_clock_color";
+
+ public static enum BatteryMeterMode {
+ BATTERY_METER_GONE,
+ BATTERY_METER_ICON_PORTRAIT,
+ BATTERY_METER_ICON_LANDSCAPE,
+ BATTERY_METER_CIRCLE,
+ BATTERY_METER_TEXT
+ }
+
+ private final Runnable mInvalidate = new Runnable() {
+ public void run() {
+ invalidateIfVisible();
+ }
+ };
+
+ private final Handler mHandler;
+
+ protected BatteryMeterMode mMeterMode = null;
+ protected boolean mShowPercent = false;
+ protected boolean mAttached;
+
+ private int mHeight;
+ private int mWidth;
+
+ final int[] mColors;
+ final int[] mBoltPoints;
+ final int mFrameColor;
+ final int mBoltColor;
+ final int mTextColor;
+
+ private BatteryMeterDrawable mBatteryMeterDrawable;
+ private final Object mLock = new Object();
+
+ private Resources mResources;
+
+ public BatteryMeterView(Context context, Resources res) {
+ this(context, null, 0, res);
+ }
+
+ public BatteryMeterView(Context context, AttributeSet attrs, Resources res) {
+ this(context, attrs, 0, res);
+ }
+
+ public BatteryMeterView(Context context, AttributeSet attrs, int defStyle, Resources res) {
+ super(context, attrs, defStyle);
+ mHandler = new Handler();
+
+ TypedArray levels = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_COLOR_LEVELS,
+ "array", SYSTEMUI_PACKAGE));
+ TypedArray colors = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_COLOR_VALUES,
+ "array", SYSTEMUI_PACKAGE));
+ TypedArray boltPoints = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_BOLT_POINTS,
+ "array", SYSTEMUI_PACKAGE));
+
+ mResources = res;
+
+ int N = levels.length();
+ mColors = new int[2*N];
+ for (int i=0; i<N; i++) {
+ mColors[2*i] = levels.getInt(i, 0);
+ mColors[2*i+1] = colors.getColor(i, 0);
+ }
+ levels.recycle();
+ colors.recycle();
+
+ N = boltPoints.length();
+ mBoltPoints = new int[N];
+ for (int i = 0; i < N; i++) {
+ mBoltPoints[i] = boltPoints.getInt(i, 0);
+ }
+ boltPoints.recycle();
+
+ mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
+ context.getContentResolver(), "status_bar_show_battery_percent", 0);
+
+ mMeterMode = BatteryMeterMode.BATTERY_METER_CIRCLE;
+ mBatteryMeterDrawable = createBatteryMeterDrawable(mMeterMode);
+
+ mFrameColor = res.getColor(res.getIdentifier(BATTERYMETER_FRAME_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+ mBoltColor = res.getColor(res.getIdentifier(BATTERYMETER_BOLT_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+ mTextColor = res.getColor(res.getIdentifier(STATUS_BAR_CLOCK_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ protected BatteryMeterDrawable createBatteryMeterDrawable(BatteryMeterMode mode) {
+ Resources res = mResources;
+ switch (mode) {
+ case BATTERY_METER_CIRCLE:
+ return new CircleBatteryMeterDrawable(res);
+
+ case BATTERY_METER_TEXT:
+ return new TextBatteryMeterDrawable(res);
+
+ case BATTERY_METER_ICON_LANDSCAPE:
+ return new NormalBatteryMeterDrawable(res, true);
+
+ default:
+ return new NormalBatteryMeterDrawable(res, false);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (mMeterMode == BatteryMeterMode.BATTERY_METER_CIRCLE) {
+ height += (CircleBatteryMeterDrawable.STROKE_WITH / 3);
+ width = height;
+ } else if (mMeterMode == BatteryMeterMode.BATTERY_METER_TEXT) {
+ width = (int)((TextBatteryMeterDrawable) mBatteryMeterDrawable).calculateMeasureWidth();
+ onSizeChanged(width, height, 0, 0); // Force a size changed event
+ } else if (mMeterMode.compareTo(BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) == 0) {
+ width = (int)(height * 1.2f);
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mHeight = h;
+ mWidth = w;
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onSizeChanged(w, h, oldw, oldh);
+ }
+ }
+ }
+
+ protected void invalidateIfVisible() {
+ if (getVisibility() == View.VISIBLE && mAttached) {
+ if (mAttached) {
+ postInvalidate();
+ } else {
+ invalidate();
+ }
+ }
+ }
+
+ public int getColorForLevel(int percent) {
+ int thresh, color = 0;
+ for (int i=0; i<mColors.length; i+=2) {
+ thresh = mColors[i];
+ color = mColors[i+1];
+ if (percent <= thresh) return color;
+ }
+ return color;
+ }
+
+ public void setShowPercent(boolean show) {
+ if (ENABLE_PERCENT) {
+ mShowPercent = show;
+ invalidateIfVisible();
+ }
+ }
+
+ public void setMode(BatteryMeterMode mode) {
+ if (mMeterMode == mode) {
+ return;
+ }
+
+ mMeterMode = mode;
+ if (mode == BatteryMeterMode.BATTERY_METER_GONE) {
+ setVisibility(View.GONE);
+ synchronized (mLock) {
+ mBatteryMeterDrawable = null;
+ }
+ } else {
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onDispose();
+ }
+ mBatteryMeterDrawable = createBatteryMeterDrawable(mode);
+ }
+ if (mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_PORTRAIT ||
+ mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) {
+ ((NormalBatteryMeterDrawable)mBatteryMeterDrawable).loadBoltPoints(
+ mContext.getResources());
+ }
+ setVisibility(View.VISIBLE);
+ postInvalidate();
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onDraw(c);
+ }
+ }
+ }
+
+ protected interface BatteryMeterDrawable {
+ void onDraw(Canvas c);
+ void onSizeChanged(int w, int h, int oldw, int oldh);
+ void onDispose();
+ }
+
+ protected class NormalBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ public static final boolean SINGLE_DIGIT_PERCENT = false;
+
+ public static final int FULL = 96;
+ public static final int EMPTY = 4;
+
+ public static final float SUBPIXEL = 0.4f; // inset rects for softer edges
+
+ private boolean mDisposed;
+
+ protected final boolean mHorizontal;
+
+ private Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
+ private int mButtonHeight;
+ private float mTextHeight;
+
+ private final float[] mBoltPoints;
+ private final Path mBoltPath = new Path();
+
+ private final RectF mFrame = new RectF();
+ private final RectF mButtonFrame = new RectF();
+ private final RectF mClipFrame = new RectF();
+ private final RectF mBoltFrame = new RectF();
+
+ public NormalBatteryMeterDrawable(Resources res, boolean horizontal) {
+ super();
+ mHorizontal = horizontal;
+ mDisposed = false;
+
+ mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mFramePaint.setColor(mFrameColor);
+ mFramePaint.setDither(true);
+ mFramePaint.setStrokeWidth(0);
+ mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mFramePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
+
+ mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBatteryPaint.setDither(true);
+ mBatteryPaint.setStrokeWidth(0);
+ mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setColor(mBoltColor);
+ Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
+ mTextPaint.setTypeface(font);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+ mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mWarningTextPaint.setColor(mColors[1]);
+ font = Typeface.create("sans-serif", Typeface.BOLD);
+ mWarningTextPaint.setTypeface(font);
+ mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
+
+ mBoltPaint = new Paint();
+ mBoltPaint.setAntiAlias(true);
+ mBoltPaint.setColor(mFrameColor);
+ mBoltPoints = loadBoltPoints(res);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ final int level = 50;
+
+ float drawFrac = (float) level / 100f;
+ final int pt = getPaddingTop() + (mHorizontal ? (int)(mHeight * 0.20f) : 0);
+ final int pl = getPaddingLeft();
+ final int pr = getPaddingRight();
+ final int pb = getPaddingBottom();
+ int height = mHeight - pt - pb;
+ int width = mWidth - pl - pr;
+
+ mButtonHeight = (int) ((mHorizontal ? width : height) * 0.12f);
+
+ mFrame.set(0, 0, width, height);
+ mFrame.offset(pl, pt);
+
+ if (mHorizontal) {
+ mButtonFrame.set(
+ /*cover frame border of intersecting area*/
+ width - (mButtonHeight + 5) - mFrame.left,
+ mFrame.top + height * 0.25f,
+ mFrame.right,
+ mFrame.bottom - height * 0.25f);
+
+ mButtonFrame.top += SUBPIXEL;
+ mButtonFrame.bottom -= SUBPIXEL;
+ mButtonFrame.right -= SUBPIXEL;
+ } else {
+ mButtonFrame.set(
+ mFrame.left + width * 0.25f,
+ mFrame.top,
+ mFrame.right - width * 0.25f,
+ mFrame.top + mButtonHeight + 5 /*cover frame border of intersecting area*/);
+
+ mButtonFrame.top += SUBPIXEL;
+ mButtonFrame.left += SUBPIXEL;
+ mButtonFrame.right -= SUBPIXEL;
+ }
+
+ if (mHorizontal) {
+ mFrame.right -= mButtonHeight;
+ } else {
+ mFrame.top += mButtonHeight;
+ }
+ mFrame.left += SUBPIXEL;
+ mFrame.top += SUBPIXEL;
+ mFrame.right -= SUBPIXEL;
+ mFrame.bottom -= SUBPIXEL;
+
+ // first, draw the battery shape
+ c.drawRect(mFrame, mFramePaint);
+
+ // fill 'er up
+ final int color = getColorForLevel(level);
+ mBatteryPaint.setColor(color);
+
+ if (level >= FULL) {
+ drawFrac = 1f;
+ } else if (level <= EMPTY) {
+ drawFrac = 0f;
+ }
+
+ c.drawRect(mButtonFrame, drawFrac == 1f ? mBatteryPaint : mFramePaint);
+
+ mClipFrame.set(mFrame);
+ if (mHorizontal) {
+ mClipFrame.right -= (mFrame.width() * (1f - drawFrac));
+ } else {
+ mClipFrame.top += (mFrame.height() * (1f - drawFrac));
+ }
+
+ c.save(Canvas.CLIP_SAVE_FLAG);
+ c.clipRect(mClipFrame);
+ c.drawRect(mFrame, mBatteryPaint);
+ c.restore();
+
+ // draw the bolt
+ final float bl = (int)(mFrame.left + mFrame.width() / (mHorizontal ? 9f : 4.5f));
+ final float bt = (int)(mFrame.top + mFrame.height() / (mHorizontal ? 4.5f : 6f));
+ final float br = (int)(mFrame.right - mFrame.width() / (mHorizontal ? 6f : 7f));
+ final float bb = (int)(mFrame.bottom - mFrame.height() / (mHorizontal ? 7f : 10f));
+ if (mBoltFrame.left != bl || mBoltFrame.top != bt
+ || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
+ mBoltFrame.set(bl, bt, br, bb);
+ mBoltPath.reset();
+ mBoltPath.moveTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ for (int i = 2; i < mBoltPoints.length; i += 2) {
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
+ }
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ }
+ c.drawPath(mBoltPath, mBoltPaint);
+ if (mShowPercent) {
+ final float nofull = mHorizontal ? 0.75f : 0.6f;
+ final float single = mHorizontal ? 0.86f : 0.75f;
+ mTextPaint.setTextSize(height *
+ (SINGLE_DIGIT_PERCENT ? single
+ : nofull));
+ mTextHeight = -mTextPaint.getFontMetrics().ascent;
+
+ final String str = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
+ final float x = mWidth * 0.5f;
+ final float y = pt + (height + mTextHeight) * 0.47f;
+
+ c.drawText(str, x, y, mTextPaint);
+ }
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWarningTextPaint.setTextSize(h * 0.75f);
+ }
+
+ private float[] loadBoltPoints(Resources res) {
+ final int[] pts = BatteryMeterView.this.mBoltPoints;
+ int maxX = 0, maxY = 0;
+ for (int i = 0; i < pts.length; i += 2) {
+ maxX = Math.max(maxX, pts[i]);
+ maxY = Math.max(maxY, pts[i + 1]);
+ }
+ final float[] ptsF = new float[pts.length];
+ for (int i = 0; i < pts.length; i += 2) {
+ ptsF[i] = (float)pts[i] / maxX;
+ ptsF[i + 1] = (float)pts[i + 1] / maxY;
+ }
+ return ptsF;
+ }
+ }
+
+ protected class CircleBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ public static final float STROKE_WITH = 6.5f;
+
+ private boolean mDisposed;
+
+ private int mAnimOffset;
+ private boolean mIsAnimating; // stores charge-animation status to reliably
+ //remove callbacks
+
+ private int mCircleSize; // draw size of circle
+ private RectF mRectLeft; // contains the precalculated rect used in drawArc(),
+ // derived from mCircleSize
+ private float mTextX, mTextY; // precalculated position for drawText() to appear centered
+
+ private Paint mTextPaint;
+ private Paint mFrontPaint;
+ private Paint mBackPaint;
+ private Paint mBoltPaint;
+
+ private final RectF mBoltFrame = new RectF();
+ private final float[] mBoltPoints;
+ private final Path mBoltPath = new Path();
+
+ public CircleBatteryMeterDrawable(Resources res) {
+ super();
+ mDisposed = false;
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setColor(mTextColor);
+ Typeface font = Typeface.create("sans-serif-condensed", Typeface.NORMAL);
+ mTextPaint.setTypeface(font);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+ mFrontPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mFrontPaint.setStrokeCap(Paint.Cap.BUTT);
+ mFrontPaint.setDither(true);
+ mFrontPaint.setStrokeWidth(0);
+ mFrontPaint.setStyle(Paint.Style.STROKE);
+
+ mBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBackPaint.setColor(mFrameColor);
+ mBackPaint.setStrokeCap(Paint.Cap.BUTT);
+ mBackPaint.setDither(true);
+ mBackPaint.setStrokeWidth(0);
+ mBackPaint.setStyle(Paint.Style.STROKE);
+ mBackPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
+
+ mBoltPaint = new Paint();
+ mBoltPaint.setAntiAlias(true);
+ mBoltPaint.setColor(getColorForLevel(50));
+ mBoltPoints = loadBoltPoints(res);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ if (mRectLeft == null) {
+ initSizeBasedStuff();
+ }
+
+ drawCircle(c, mTextX, mRectLeft);
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ initSizeBasedStuff();
+ }
+
+ private float[] loadBoltPoints(Resources res) {
+ final int[] pts = BatteryMeterView.this.mBoltPoints;
+ int maxX = 0, maxY = 0;
+ for (int i = 0; i < pts.length; i += 2) {
+ maxX = Math.max(maxX, pts[i]);
+ maxY = Math.max(maxY, pts[i + 1]);
+ }
+ final float[] ptsF = new float[pts.length];
+ for (int i = 0; i < pts.length; i += 2) {
+ ptsF[i] = (float)pts[i] / maxX;
+ ptsF[i + 1] = (float)pts[i + 1] / maxY;
+ }
+ return ptsF;
+ }
+
+ private void drawCircle(Canvas canvas, float textX, RectF drawRect) {
+ int level = 50;
+ Paint paint;
+
+ paint = mFrontPaint;
+ paint.setColor(getColorForLevel(level));
+
+ // draw thin gray ring first
+ canvas.drawArc(drawRect, 270, 360, false, mBackPaint);
+ // draw colored arc representing charge level
+ canvas.drawArc(drawRect, 270 + mAnimOffset, 3.6f * level, false, paint);
+ // if chosen by options, draw percentage text in the middle
+ // always skip percentage when 100, so layout doesnt break
+
+ // draw the bolt
+ final float bl = (int)(drawRect.left + drawRect.width() / 3.2f);
+ final float bt = (int)(drawRect.top + drawRect.height() / 4f);
+ final float br = (int)(drawRect.right - drawRect.width() / 5.2f);
+ final float bb = (int)(drawRect.bottom - drawRect.height() / 8f);
+ if (mBoltFrame.left != bl || mBoltFrame.top != bt
+ || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
+ mBoltFrame.set(bl, bt, br, bb);
+ mBoltPath.reset();
+ mBoltPath.moveTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ for (int i = 2; i < mBoltPoints.length; i += 2) {
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
+ }
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ }
+ canvas.drawPath(mBoltPath, mBoltPaint);
+ }
+
+ /**
+ * initializes all size dependent variables
+ * sets stroke width and text size of all involved paints
+ * YES! i think the method name is appropriate
+ */
+ private void initSizeBasedStuff() {
+ mCircleSize = Math.min(getMeasuredWidth(), getMeasuredHeight());
+ mTextPaint.setTextSize(mCircleSize / 2f);
+
+ float strokeWidth = mCircleSize / STROKE_WITH;
+ mFrontPaint.setStrokeWidth(strokeWidth);
+ mBackPaint.setStrokeWidth(strokeWidth);
+
+ // calculate rectangle for drawArc calls
+ int pLeft = getPaddingLeft();
+ mRectLeft = new RectF(pLeft + strokeWidth / 2.0f, 0 + strokeWidth / 2.0f, mCircleSize
+ - strokeWidth / 2.0f + pLeft, mCircleSize - strokeWidth / 2.0f);
+
+ // calculate Y position for text
+ Rect bounds = new Rect();
+ mTextPaint.getTextBounds("99", 0, "99".length(), bounds);
+ mTextX = mCircleSize / 2.0f + getPaddingLeft();
+ // the +1dp at end of formula balances out rounding issues.works out on all resolutions
+ mTextY = mCircleSize / 2.0f + (bounds.bottom - bounds.top) / 2.0f
+ - strokeWidth / 2.0f + getResources().getDisplayMetrics().density;
+ }
+ }
+
+ protected class TextBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ private static final boolean DRAW_LEVEL = false;
+
+ public static final int FULL = 96;
+ public static final int EMPTY = 4;
+
+ private boolean mDisposed;
+
+ private float mTextX;
+ private float mTextY;
+
+ private boolean mOldPlugged = false;
+ private int mOldLevel = -1;
+
+ private boolean mIsAnimating;
+ private int mAnimOffset;
+
+ private Paint mBackPaint;
+ private Paint mFrontPaint;
+
+ public TextBatteryMeterDrawable(Resources res) {
+ super();
+ mDisposed = false;
+ mIsAnimating = false;
+
+ DisplayMetrics dm = res.getDisplayMetrics();
+
+ mBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBackPaint.setTextAlign(Paint.Align.RIGHT);
+ mBackPaint.setColor(mFrameColor);
+ mBackPaint.setTextSize(16.0f * dm.density);
+
+ mFrontPaint = new Paint(mBackPaint);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ int level = 50;
+ boolean plugged = true;
+
+ if (mOldLevel != level || mOldPlugged != plugged) {
+ mOldLevel = level;
+ mOldPlugged = plugged;
+
+ postInvalidate();
+ requestLayout();
+ return;
+ }
+
+ mFrontPaint.setColor(getColorForLevel(level));
+
+ // Is plugged? Then use the animation status
+ drawWithLevel(c, mAnimOffset, getLevel(level));
+ }
+
+ private void drawWithLevel(Canvas c, int level, String levelTxt) {
+ Rect bounds = getBounds(level);
+
+ // Draw the background
+ c.drawText(levelTxt, mTextX, mTextY, mBackPaint);
+
+ // Draw the foreground
+ c.save();
+ c.clipRect(0.0f, mTextY - ((level * bounds.height()) / 100.0f), mTextX, mTextY);
+ c.drawText(levelTxt, mTextX, mTextY, mFrontPaint);
+ c.restore();
+ }
+
+ private void drawWithoutLevel(Canvas c, String levelTxt) {
+ // We need to draw the overlay back paint to get the proper color
+ c.drawText(levelTxt, mTextX, mTextY, mBackPaint);
+ c.drawText(levelTxt, mTextX, mTextY, mFrontPaint);
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ Rect bounds = getBounds(50);
+ float onedp = mContext.getResources().getDisplayMetrics().density * 0.5f;
+ float height = h - getPaddingBottom() - getPaddingTop();
+
+ mTextX = w;
+ mTextY = h - getPaddingBottom() - (height / 2 - bounds.height() /2) + onedp;
+ }
+
+ protected float calculateMeasureWidth() {
+ Rect bounds = getBounds(50);
+ float onedp = mContext.getResources().getDisplayMetrics().density;
+ return bounds.width() + getPaddingStart() + getPaddingEnd() + onedp;
+ }
+
+ private Rect getBounds(int level) {
+ Rect bounds = new Rect();
+ String levelTxt = getLevel(level);
+ mBackPaint.getTextBounds(levelTxt, 0, levelTxt.length(), bounds);
+ return bounds;
+ }
+
+ private String getLevel(int level) {
+ if (level == -1) {
+ return String.format("?", level);
+ }
+ return String.format("%s%%", level);
+ }
+
+ private void resetChargeAnimation() {
+ if (mIsAnimating) {
+ mIsAnimating = false;
+ mAnimOffset = 0;
+ mHandler.removeCallbacks(mInvalidate);
+ }
+ }
+ }
+}