summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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);
+ }
+ }
+ }
+}