summaryrefslogtreecommitdiffstats
path: root/core/java/android/content/res
diff options
context:
space:
mode:
authorAndy Mast <andy@cyngn.com>2014-02-02 12:52:58 -0800
committerd34d <clark@cyngn.com>2015-10-26 15:57:35 -0700
commit39f748480050ef6d555d03fc7c9315f3a0b2f30e (patch)
treea4af5b36022a6c91a12286997d1ac8544174f3ab /core/java/android/content/res
parent12aed4e65691e06656afa0d6c3ccbe05ccabf735 (diff)
downloadframeworks_base-39f748480050ef6d555d03fc7c9315f3a0b2f30e.zip
frameworks_base-39f748480050ef6d555d03fc7c9315f3a0b2f30e.tar.gz
frameworks_base-39f748480050ef6d555d03fc7c9315f3a0b2f30e.tar.bz2
Themes: Port to CM13 [1/3]
Themes: Aapt port Id: I106d447daf7935bada65e78911d8973ce0ca27ae Themes: Port our additions to idmap Id: I2e47cc23de4e7c0b884cccbd87c7d77079ac6824 Themes: remove legacy theme support from idmap Id: I17dfe35f9985d8cef790b26a8bcda738ea65917c Themes: Forward port changes to Installer.java Id: If64e856773e50f5ed74f2358e0c590abad724689 Themes: Add clean spec for aapt Id: I68f5f63f7e83b99230860dd2d8646e96da484b62 androidfw: Port CM overlay contributions Id: Id7b7f5f35011922c668fcea3a8aec5b42bd28653 androidfw: Port addOverlayPath and removeOverlayPath Id: I279db083af28fd8941f3227f2a7512ff094742c1 androidfw: Port addCommonOverlayPath Id: I12d2fe05a04f6a7e553c330505a475346374b507 androidfw: Port addIconPath Id: Ide3db28cde0c7f93edd9e7ad626ebace8d4105cc androidfw: Allow package ID to be overriden at runtime Id: Ieca3a0ae070a6c0ad0cf2b73b5944d83397d08b9 Aapt: Zip Parsing Test Cases w/ Refactoring Id: I28f9115700e186136432138d228111ebcfbd0480 Themes: Update tests for idmap Id: I3dae5bd376d122eab397863378599ae0ac7c6734 androidfw: Add test case for overriding package id Id: I2668c529e24a55cd6bc8437406fc284b853a75e7 androidfw: Add tests for bags The overlayOverridesStyleAttribute will currently fail since our changes to allow theming styles is not currently implemented. Id: Idfacc4382baf4152c839799a22b6cbe015ef2197 androidfw: Don't consider package ids 0x60 and 0x61 as dynamic Package IDs that are not 0x01 (system) or 0x7f (app) are treated as dynamic references. Overlays are assigned specific package ids that fall within the region of shared libraries. This patch treats them the same as system and app resource packages. Id: Ieecaa889bed50490796351302405a38f77c84f4e Theme Parsing & Info Id: I3583d7e8ca704402e3d8c6e1c7cea1645b91c06f Port ThemeUtils and its dependencies This is pretty much copy/paste from cm-11.0 Id: I406860a259136ccca107b981aca0369851df445e Themes: AndroidManifest and Intent port Id: Ib97e8539301d20d120fd8b49891fdaae8205fe42 ComposedIconInfo Port (w/ stubbed IconPackHelper) IconPackHelper is stubbed out so that PMService can reference it, we'll need to port the Resource stack before porting the implementation. Id: I59b680511de525e1d375a4f3be04347686b5e81b Port PackageManagerService and other dependencies Id: I11629d1e5eee21e01c060bc6c0393aae96034b69 Themes: Add in ThemeService See also: external/selinux. The policy must be added in order for the service to start without a security exception. Change-Id: Ic6f64796b264e430e9706a17a3fd2a35085fd1ca TODO: ThemeService / Keyguard interaction TODO: SystemServer - AppsLaunchFailure Add AppsLaunchFailure Id: I09a3826f89c62cb898866408e807f269616f48fc androidfw: Update bags tests The overlayOverridesStyleAttribute was updated such that it does not check the attribute of the theme before adding the overlay. The bag is cached on the first call and the next call, after the overlay is attached, returns that pre-computed bag so the test would always fail. We now simply check that the attribute matches the one in the overlay, and if so then the test passes. This patch also adds a new test to check that an overlay can reference and access resources that are unique to the overlay. Currently this test fails. Id: I3892df3f0d9443a73eaa11b3d5e97cfe86620a73 androidfw: Add test for referencing overlay styles as parents Id: I4fa3bd447c888e96176955924ebe7ee5c784ab55 androidfw: Allow referencing and retrieving overlay resources This patch allows a theme to reference it's own resources. Overlays get their own group which contains those resources that are not idmapped. Idmapped resources end up in the target's group. Id: Ibc119ddcdb35d44a8afec3c6152bcab2909cda18 androidfw: Fill in missing attributes from overlay style Id: I74051b379b73c728c6a2aa4bc62f3cd268a40b53 Protect windowNoTitle and windowActionBar attributes This patch creates a new method to define "protected" attributes. These are attributes like windowActionBar which should not be modified by a theme. Some apps (eg gmail) use the appcompat library which has its own Actionbar classes. When an app uses its own Actionbar it must not include the default actionbar which is achieved through the windowActionBar attribute. Some themes may try to change these attributes, which can will cause the app to crash. Id: Ie3bb7285eed09f3f13facf9d142ea9eb83796eec Themes: Use SYSTEM_DEFAULT in ThemeService Id: I52794dd98ca2f64aa50046ecdd7f79f27c21dd98 androidfw: Test missing parent attributes are merged in This test checks that an overlaid style contains any attributes that were in the original style but omitted in the overlaid style. Id: I6b496ef2eb0a7ef27b4fafdfda5bdf7ccffad989 androidfw: Add test case for protected attributes For this test to pass a protected attribute, such as windowNoTitle, must be equal to the original and not the value specified in the overlay. Id: Ic03f11214a1fc4139e3c48d7e72694a80f819023 Themes: Attach theme and icon resources from java Id: I9ffa0ce96a4af603b78b32d6b190f9698d3e4b4f Themes: Icons, icons, icons! Let there be icons. Legacy icons and composed icons are included in this patch. Id: I9fedafa270f1c4dc30c9c8ffd4cf619895e688e6 Themes: Retrieve explicitly themed context and resources Id: I4e41c251aee47361b183b60089bf5666540f653e Themes: Add themeChange config change to manifest Id: Ia84c0089a79637906e4f75fa38a56e8ff3b21a2b Themes: Register THEME_SERVICE in ContextImpl Id: I608a0b65c7e2ff0d69bae7bf343916f2b985f4a0 Themes: Remove legacy theme support Id: I25887843d31f705425aa40f9a23482fd2cafaef8 androidfw: correctly index paths in idmap Since we added the mtime values for the target and overlay, the indices are increased by two. Id: Ie0f5474d425945d58a12021cd2739240d2e98c0a androidfw: Fix opening assets from theme resources Id: Iedb51163a62b046cdf7fda1ad1b55cc1ee409047 Themes: Consider overlaid resource as "best fit" Id: Ife8342a49eb9502be52f085f88161b113332e9e6 Themes: Save and restore theme config Id: I3fcd445fb458aa6ed09397c05df6eb66d9be7235 Themes: Let ThemeService process additional themes Id: I45837f26948367d5cc6c520e8c53f9da60bd1fda AAPT: Don't applyVersionForCompatibility on android When compiling themes with aapt, we do not want aapt to call applyVersionForCompatibility as this causes the entries in the resource table to have an incorrect path. Id: Ie2c69533b3659c7b7458d6e4b7bdc84946d1be8e androidfw: Don't consider package id 0x5f as dynamic Package id 0x5f is reserved for common overlay resources and needs to be reserved so that it is not considered a dynamic. Id: Id27b8e0e2231ee8541365274d512e347afcfd05b AAPT: Include resources.arsc in apk Common resources needs the resources.arsc in the resources.apk so that it can be included when processing other overlays. Without it, common resources cannot be referenced. Id: I4aee29f660e4a0aa1909240dc0ca5680f0a2d135 Themes: Add keyguard wallpaper support to theme service Id: Ib8f8acd55ab4d2b6ef06ee0a630dc50c4f870beb Themes: Don't pre-process non .9.png images When creating a resources.apk we do not need to pre-process the normal .png images as those can be referenced directly from the theme's apk. Id: Iaf846a03ead9ecb1e68c040eac6e0ecbfc6e5875 Themes: Adjust offsets for idmap hashes Idmap now has a header so the indices to the hashes need to be incremented by one. Id: If1fb183cc116ef9e3ad6cb4e17b6e44763e9e72a Themes: Use single ThemeInfo instead of an array We only ever used the first index so there is no need to use an array of ThemeInfo(s) Id: I9e2af076bc17396a0c978be3c0d31c41277db3df New converter for Kitkat -> L fonts.xml L introduced a new fonts xml format. Its great, but our themes will keep using hte old format. This provides a converter and test cases. The parser was taken from the chooser and remains mostly unchanged with the exception of a getName() helper method. Id: Ia1d42c9e50eb7b52d2d98fe6dbeee530bef3adc2 Themes: Port theme bootanimation support to CM12 Id: Ie016884b0e3b77e08732308923ac44e0975e0116 Resources: Clear drawable cache Id: I04b5b78cce703194a2baeff9c51d2e4733b8ccc9 Font switching Id: Ia43060a7db624102cdcd9b0d9dc7148441401584 Zygote changes Id: Ie3681cf0d2b9929661cf1214e899cef9a5f37471 Recreate String Blocks Id: I4747ebd1a0908b76ae7214b0584948353d426fc5 add a getter for the x and y offsets of the wallpaper window Id: I35294bcac664e85cc5d344b50b5c4335a60d3f37 Themes: Don't spam logcat with CREATING STRING CACHE When processing resources with AAPT on the device, it spams the logcat with warning messages about CREATING STRING CACHE. Change ALOGW to ALOGV so it will only show when verbose logging is enabled. Id: I5b591c3336e176dd71cebe672d60721c29651b00 SystemUI: Audio Volume Panel Id: I78c471864af401b274597339b8451e65931fdb32 AmService uiContext port Id: Ida251d7f80797b0ec78b3d20cf60a795d6c4c1f0 Cleanly detach theme assets Types from an overlay are added to the target group's TypeList and need to be removed when the overlay assets are detached. Failing to remove these types results in resources not being retieved due to the erroneous types. Id: I4a9c624e30309e61fce905ced45c55acd3ac4845 Themes: GlobalAction Port Id: Ifd87e04f94a284e77f1c48bec9fd75d69c45c47e Themes: Do not store forward locked themes in ASEC containers If a theme is in a asec container and is applied, when the device is rebooted the device will get stuck in a nasty boot loop since the theme resources must be read and the asec container is not ready yet. Id: I1d93d8175d5c40b34c222974960c43352012a5ad Use systemui's applied theme for notifications. Notifications contain RemoteViews which are inflated using the application's context for which this notification belongs to. This can look out of place if SystemUI is using a different theme than the rest of the system. This patch will use SystemUIs theme when inflating the RemoteViews, giving us a more consistent look in the notification drawer. Id: I9514ce7fcc4858bad3d3c4190f55c1f5a1441d7c SysUI: Add theme support This ports over the changes needed to facilitate a theme change in SystemUI. Id: I673fb79db90994371a9c0627746a97414132f0ba Themes: Allow composing of VectorDrawable Base icons can be vector drawables. This patch allows them to be composed. Currently, VectorDrawables cannot have filters applied since they do not have a method to get the Paint object like BitmapDrawable and PaintDrawable. Id: I762c8e1f4d1c945b8ebc164bbd7944120324bd42 Themes: Add target api to ThemesContract This will allow the ThemesProvider to track the api a theme was built for. We may want to let the user know when a theme may not be designed for the version of CM installed on their device. Id: Idf0e6cef0ce9ac5e221ce5ff7e0b155ae0258d5f Access Themed ResTables from compiled theme apk [1/2] Before this patch the ResTable for a theme/app was created and accessed seperate from the compiled APK. Since the compiled APK has its own copy of the resources.arsc, we can just reuse the table in the APK instead. Id: I106a2434e74784bc04014831098f49fe128bc7e2 Themes: Port AppsLaunchFailureReceiver to CM12 Id: I5c3265e64aef1536ba5fceed0ec89082e786b686 Themes: Bump idmap hash version to 3 Due to changes in idmap, we need to force the recreation of resource cache when upgrading from CM11 to CM12. Id: I25c1e2c598bca889818e2d685651e3214c30ab3c Remove debug logs Id: Ia5cfa83ddf6da195e20526a94ba154864b8d0ecb Send target sdk version to aapt [1/2] If vector drawables are used in a theme we must have a minSdkVersion of 21 passed to aapt or else aapt will Segfault. Id: I687ee146f9f80543bbcdd06d93891cb3b23001c4 Add missing imports to ActivityThread Id: I09fe07807ed824ccb938e0e174b06653c613c403 Themes: Dynamically add/remove content from StatusBarWindow StatusBarWindowView has logic for resizing and fading content which doesn't always behave correctly if this view is not the root. Rather than create a container, this patch uses the existing StatusBarWindowView as the container and the inflated status bar is then added to this view. Id: Ia93d25a589419145f95d75b1b56eb3c2f300f935 Themes: don't use preloaded drawables when themed If we have themed assets we should try and load those rather than pulling from the preloaded drawables. This allows us to continue and preload drawables in ZygoteInit while maintaining the ability to theme those preloaded assets. Id: I68cfc099d328ece0791b6d0e5cf11d07097fd1fd CM11 -> CM12 Upgrade [1/3] - Introduce a new secure setting "THEME_PREV_BOOT_API_LEVEL". This field will always be set to the previous api level for themes. So if we upgrade from CM11 to CM12 this value will differ from the current API causing an upgrade to trigger - When moving from CM11 -> CM12, unapply incompatible overlays - Rename "holo" to "system" in secure settings themeConfig - Provide a testing downgrade script to put the secure settings db into a state similiar to CM11 (at least for themes) Id: I71be2c0ad83e60ffe8c574f913e5eaecb9700045 Themes: Add constant for system target API Id: I0a6caf65c9e8b0feeef1ae848ba4683235304e8c Change-Id: Ide6d4e1daf535a54efb1ec7cf39ef8b2fb8cf272
Diffstat (limited to 'core/java/android/content/res')
-rw-r--r--core/java/android/content/res/AssetManager.java217
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java16
-rw-r--r--core/java/android/content/res/Configuration.java84
-rw-r--r--core/java/android/content/res/IThemeChangeListener.aidl22
-rw-r--r--core/java/android/content/res/IThemeProcessingListener.aidl21
-rw-r--r--core/java/android/content/res/IThemeService.aidl40
-rw-r--r--core/java/android/content/res/Resources.java165
-rw-r--r--core/java/android/content/res/ResourcesKey.java7
-rw-r--r--core/java/android/content/res/ThemeConfig.java553
-rw-r--r--core/java/android/content/res/ThemeManager.java298
10 files changed, 1401 insertions, 22 deletions
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 8d96f5c..f663c50 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -24,6 +24,7 @@ import android.util.TypedValue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -77,6 +78,16 @@ public final class AssetManager implements AutoCloseable {
private boolean mOpen = true;
private HashMap<Long, RuntimeException> mRefStacks;
+ private String mAppName;
+
+ private boolean mThemeSupport;
+ private String mThemePackageName;
+ private String mIconPackageName;
+ private String mCommonResPackageName;
+ private ArrayList<Integer> mThemeCookies = new ArrayList<Integer>(2);
+ private int mIconPackCookie;
+ private int mCommonResCookie;
+
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
@@ -252,6 +263,12 @@ public final class AssetManager implements AutoCloseable {
}
}
+ /*package*/ final void recreateStringBlocks() {
+ synchronized (this) {
+ makeStringBlocks(sSystem.mStringBlocks);
+ }
+ }
+
/*package*/ final void makeStringBlocks(StringBlock[] seed) {
final int seedNum = (seed != null) ? seed.length : 0;
final int num = getStringBlockCount();
@@ -628,9 +645,11 @@ public final class AssetManager implements AutoCloseable {
* {@hide}
*/
- public final int addOverlayPath(String idmapPath) {
+ public final int addOverlayPath(String idmapPath, String resApkPath, String targetPkgPath,
+ String prefixPath) {
synchronized (this) {
- int res = addOverlayPathNative(idmapPath);
+ int res = addOverlayPathNative(idmapPath, resApkPath, targetPkgPath,
+ prefixPath);
makeStringBlocks(mStringBlocks);
return res;
}
@@ -641,7 +660,59 @@ public final class AssetManager implements AutoCloseable {
*
* {@hide}
*/
- public native final int addOverlayPathNative(String idmapPath);
+ private native final int addOverlayPathNative(String idmapPath,
+ String resApkPath, String targetPkgPath, String prefixPath);
+
+ /**
+ * Add a set of common assets.
+ *
+ * {@hide}
+ */
+ public final int addCommonOverlayPath(String idmapPath,
+ String resApkPath, String prefixPath) {
+ synchronized (this) {
+ return addCommonOverlayPathNative(idmapPath, resApkPath, prefixPath);
+ }
+ }
+
+ private native final int addCommonOverlayPathNative(String idmapPath,
+ String resApkPath, String prefixPath);
+
+ /**
+ * Add a set of assets as an icon pack. A pkgIdOverride value will change the package's id from
+ * what is in the resource table to a new value. Manage this carefully, if icon pack has more
+ * than one package then that next package's id will use pkgIdOverride+1.
+ *
+ * Icon packs are different from overlays as they have a different pkg id and
+ * do not use idmap so no targetPkg is required
+ *
+ * {@hide}
+ */
+ public final int addIconPath(String idmapPath, String resApkPath,
+ String prefixPath, int pkgIdOverride) {
+ synchronized (this) {
+ return addIconPathNative(idmapPath, resApkPath, prefixPath, pkgIdOverride);
+ }
+ }
+
+ private native final int addIconPathNative(String idmapPath,
+ String resApkPath, String prefixPath, int pkgIdOverride);
+
+ /**
+ * Delete a set of overlay assets from the asset manager. Not for use by
+ * applications. Returns true if succeeded or false on failure.
+ *
+ * Also works for icon packs
+ *
+ * {@hide}
+ */
+ public final boolean removeOverlayPath(String packageName, int cookie) {
+ synchronized (this) {
+ return removeOverlayPathNative(packageName, cookie);
+ }
+ }
+
+ private native final boolean removeOverlayPathNative(String packageName, int cookie);
/**
* Add multiple sets of assets to the asset manager at once. See
@@ -664,6 +735,126 @@ public final class AssetManager implements AutoCloseable {
}
/**
+ * Sets a flag indicating that this AssetManager should have themes
+ * attached, according to the initial request to create it by the
+ * ApplicationContext.
+ *
+ * {@hide}
+ */
+ public final void setThemeSupport(boolean themeSupport) {
+ mThemeSupport = themeSupport;
+ }
+
+ /**
+ * Should this AssetManager have themes attached, according to the initial
+ * request to create it by the ApplicationContext?
+ *
+ * {@hide}
+ */
+ public final boolean hasThemeSupport() {
+ return mThemeSupport;
+ }
+
+ /**
+ * Get package name of current icon pack (may return null).
+ * {@hide}
+ */
+ public String getIconPackageName() {
+ return mIconPackageName;
+ }
+
+ /**
+ * Sets icon package name
+ * {@hide}
+ */
+ public void setIconPackageName(String packageName) {
+ mIconPackageName = packageName;
+ }
+
+ /**
+ * Get package name of current common resources (may return null).
+ * {@hide}
+ */
+ public String getCommonResPackageName() {
+ return mCommonResPackageName;
+ }
+
+ /**
+ * Sets common resources package name
+ * {@hide}
+ */
+ public void setCommonResPackageName(String packageName) {
+ mCommonResPackageName = packageName;
+ }
+
+ /**
+ * Get package name of current theme (may return null).
+ * {@hide}
+ */
+ public String getThemePackageName() {
+ return mThemePackageName;
+ }
+
+ /**
+ * Sets package name and highest level style id for current theme (null, 0 is allowed).
+ * {@hide}
+ */
+ public void setThemePackageName(String packageName) {
+ mThemePackageName = packageName;
+ }
+
+ /**
+ * Get asset cookie for current theme (may return 0).
+ * {@hide}
+ */
+ public ArrayList<Integer> getThemeCookies() {
+ return mThemeCookies;
+ }
+
+ /** {@hide} */
+ public void setIconPackCookie(int cookie) {
+ mIconPackCookie = cookie;
+ }
+
+ /** {@hide} */
+ public int getIconPackCookie() {
+ return mIconPackCookie;
+ }
+
+ /** {@hide} */
+ public void setCommonResCookie(int cookie) {
+ mCommonResCookie = cookie;
+ }
+
+ /** {@hide} */
+ public int getCommonResCookie() {
+ return mCommonResCookie;
+ }
+
+ /**
+ * Sets asset cookie for current theme (0 if not a themed asset manager).
+ * {@hide}
+ */
+ public void addThemeCookie(int cookie) {
+ mThemeCookies.add(cookie);
+ }
+
+ /** {@hide} */
+ public String getAppName() {
+ return mAppName;
+ }
+
+ /** {@hide} */
+ public void setAppName(String pkgName) {
+ mAppName = pkgName;
+ }
+
+ /** {@hide} */
+ public boolean hasThemedAssets() {
+ return mThemeCookies.size() > 0;
+ }
+
+ /**
* Determine whether the state in this asset manager is up-to-date with
* the files on the filesystem. If false is returned, you need to
* instantiate a new AssetManager class to see the new data.
@@ -800,6 +991,26 @@ public final class AssetManager implements AutoCloseable {
/*package*/ native final int[] getStyleAttributes(int themeRes);
private native final void init(boolean isSystem);
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageCount();
+
+ /**
+ * {@hide}
+ */
+ public native final String getBasePackageName(int index);
+
+ /**
+ * {@hide}
+ */
+ public native final String getBaseResourcePackageName(int index);
+
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageId(int index);
+
private native final void destroy();
private final void incRefsLocked(long id) {
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index da35ee9..47d5d05 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -92,9 +92,15 @@ public class CompatibilityInfo implements Parcelable {
*/
public final float applicationInvertedScale;
+ /**
+ * Whether the application supports third-party theming.
+ */
+ public final boolean isThemeable;
+
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
boolean forceCompat) {
int compatFlags = 0;
+ isThemeable = appInfo.isThemeable;
if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
|| appInfo.largestWidthLimitDp != 0) {
@@ -242,17 +248,19 @@ public class CompatibilityInfo implements Parcelable {
}
private CompatibilityInfo(int compFlags,
- int dens, float scale, float invertedScale) {
+ int dens, float scale, float invertedScale, boolean isThemeable) {
mCompatibilityFlags = compFlags;
applicationDensity = dens;
applicationScale = scale;
applicationInvertedScale = invertedScale;
+ this.isThemeable = isThemeable;
}
private CompatibilityInfo() {
this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
1.0f,
- 1.0f);
+ 1.0f,
+ true);
}
/**
@@ -526,6 +534,7 @@ public class CompatibilityInfo implements Parcelable {
if (applicationDensity != oc.applicationDensity) return false;
if (applicationScale != oc.applicationScale) return false;
if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ if (isThemeable != oc.isThemeable) return false;
return true;
} catch (ClassCastException e) {
return false;
@@ -563,6 +572,7 @@ public class CompatibilityInfo implements Parcelable {
result = 31 * result + applicationDensity;
result = 31 * result + Float.floatToIntBits(applicationScale);
result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+ result = 31 * result + (isThemeable ? 1 : 0);
return result;
}
@@ -577,6 +587,7 @@ public class CompatibilityInfo implements Parcelable {
dest.writeInt(applicationDensity);
dest.writeFloat(applicationScale);
dest.writeFloat(applicationInvertedScale);
+ dest.writeInt(isThemeable ? 1 : 0);
}
public static final Parcelable.Creator<CompatibilityInfo> CREATOR
@@ -597,5 +608,6 @@ public class CompatibilityInfo implements Parcelable {
applicationDensity = source.readInt();
applicationScale = source.readFloat();
applicationInvertedScale = source.readFloat();
+ isThemeable = source.readInt() == 1 ? true : false;
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index fd60476..f077d4d 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2008 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,6 +83,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public Locale locale;
/**
+ * @hide
+ */
+ public ThemeConfig themeConfig;
+
+ /**
* Locale should persist on setting. This is hidden because it is really
* questionable whether this is the right way to expose the functionality.
* @hide
@@ -441,7 +447,47 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public static final int ORIENTATION_LANDSCAPE = 2;
/** @deprecated Not currently supported or used. */
@Deprecated public static final int ORIENTATION_SQUARE = 3;
-
+
+ /**
+ * @hide
+ * @deprecated
+ */
+ public static final String THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY
+ = "persist.sys.themePackageName";
+
+ /**
+ * @hide
+ * @deprecated
+ */
+ public static final String THEME_ICONPACK_PACKAGE_NAME_PERSISTENCE_PROPERTY
+ = "themeIconPackPkgName";
+
+ /**
+ * @hide
+ * @deprecated
+ */
+ public static final String THEME_FONT_PACKAGE_NAME_PERSISTENCE_PROPERTY
+ = "themeFontPackPkgName";
+
+ /**
+ * @hide
+ * Serialized json structure mapping app pkgnames to their set theme.
+ *
+ * {
+ * "default":{
+ *" stylePkgName":"com.jasonevil.theme.miuiv5dark",
+ * "iconPkgName":"com.cyngn.hexo",
+ * "fontPkgName":"com.cyngn.hexo"
+ * }
+ * }
+
+ * If an app does not have a specific theme set then it will use the 'default' theme+
+ * example: 'default' -> overlayPkgName: 'org.blue.theme'
+ * 'com.android.phone' -> 'com.red.theme'
+ * 'com.google.vending' -> 'com.white.theme'
+ */
+ public static final String THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY = "themeConfig";
+
/**
* Overall orientation of the screen. May be one of
* {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}.
@@ -673,8 +719,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatScreenHeightDp = o.compatScreenHeightDp;
compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
seq = o.seq;
+ if (o.themeConfig != null) {
+ themeConfig = (ThemeConfig) o.themeConfig.clone();
+ }
}
-
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("{");
@@ -809,6 +858,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
sb.append(" s.");
sb.append(seq);
}
+ sb.append(" themeResource=");
+ sb.append(themeConfig);
sb.append('}');
return sb.toString();
}
@@ -835,6 +886,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
densityDpi = DENSITY_DPI_UNDEFINED;
seq = 0;
+ themeConfig = null;
}
/** {@hide} */
@@ -977,7 +1029,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (delta.seq != 0) {
seq = delta.seq;
}
-
+
+ if (delta.themeConfig != null
+ && (themeConfig == null || !themeConfig.equals(delta.themeConfig))) {
+ changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ themeConfig = (ThemeConfig)delta.themeConfig.clone();
+ }
+
return changed;
}
@@ -1087,7 +1145,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& densityDpi != delta.densityDpi) {
changed |= ActivityInfo.CONFIG_DENSITY;
}
-
+ if (delta.themeConfig != null &&
+ (themeConfig == null || !themeConfig.equals(delta.themeConfig))) {
+ changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ }
return changed;
}
@@ -1103,7 +1164,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* @return Return true if the resource needs to be loaded, else false.
*/
public static boolean needNewResources(int configChanges, int interestingChanges) {
- return (configChanges & (interestingChanges|ActivityInfo.CONFIG_FONT_SCALE)) != 0;
+ return (configChanges & (interestingChanges |
+ ActivityInfo.CONFIG_FONT_SCALE |
+ ActivityInfo.CONFIG_THEME_RESOURCE)) != 0;
}
/**
@@ -1176,6 +1239,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(compatScreenHeightDp);
dest.writeInt(compatSmallestScreenWidthDp);
dest.writeInt(seq);
+ dest.writeParcelable(themeConfig, flags);
}
public void readFromParcel(Parcel source) {
@@ -1204,6 +1268,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatScreenHeightDp = source.readInt();
compatSmallestScreenWidthDp = source.readInt();
seq = source.readInt();
+ themeConfig = source.readParcelable(ThemeConfig.class.getClassLoader());
}
public static final Parcelable.Creator<Configuration> CREATOR
@@ -1271,7 +1336,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
n = this.smallestScreenWidthDp - that.smallestScreenWidthDp;
if (n != 0) return n;
n = this.densityDpi - that.densityDpi;
- //if (n != 0) return n;
+ if (n != 0) return n;
+ if (this.themeConfig == null) {
+ if (that.themeConfig != null) return 1;
+ } else {
+ n = this.themeConfig.compareTo(that.themeConfig);
+ }
return n;
}
@@ -1308,6 +1378,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
result = 31 * result + screenHeightDp;
result = 31 * result + smallestScreenWidthDp;
result = 31 * result + densityDpi;
+ result = 31 * result + (this.themeConfig != null ?
+ this.themeConfig.hashCode() : 0);
return result;
}
diff --git a/core/java/android/content/res/IThemeChangeListener.aidl b/core/java/android/content/res/IThemeChangeListener.aidl
new file mode 100644
index 0000000..a2e2abd
--- /dev/null
+++ b/core/java/android/content/res/IThemeChangeListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.content.res;
+
+/** {@hide} */
+oneway interface IThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+}
diff --git a/core/java/android/content/res/IThemeProcessingListener.aidl b/core/java/android/content/res/IThemeProcessingListener.aidl
new file mode 100644
index 0000000..2e1c16e
--- /dev/null
+++ b/core/java/android/content/res/IThemeProcessingListener.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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 android.content.res;
+
+/** {@hide} */
+oneway interface IThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+}
diff --git a/core/java/android/content/res/IThemeService.aidl b/core/java/android/content/res/IThemeService.aidl
new file mode 100644
index 0000000..e8bb5c4
--- /dev/null
+++ b/core/java/android/content/res/IThemeService.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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 android.content.res;
+
+import android.content.res.IThemeChangeListener;
+import android.content.res.IThemeProcessingListener;
+import android.graphics.Bitmap;
+
+import java.util.Map;
+
+/** {@hide} */
+interface IThemeService {
+ void requestThemeChangeUpdates(in IThemeChangeListener listener);
+ void removeUpdates(in IThemeChangeListener listener);
+
+ void requestThemeChange(in Map componentMap);
+ void applyDefaultTheme();
+ boolean isThemeApplying();
+ int getProgress();
+
+ boolean cacheComposedIcon(in Bitmap icon, String path);
+
+ boolean processThemeResources(String themePkgName);
+ boolean isThemeBeingProcessed(String themePkgName);
+ void registerThemeProcessingListener(in IThemeProcessingListener listener);
+ void unregisterThemeProcessingListener(in IThemeProcessingListener listener);
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 731903c..f6a966b 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -21,6 +21,9 @@ import android.annotation.ColorInt;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
import com.android.internal.util.GrowingArrayUtils;
+import android.app.ComposedIconInfo;
+import android.app.IconPackHelper;
+import android.app.IconPackHelper.IconCustomizer;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -45,6 +48,7 @@ import android.annotation.RawRes;
import android.annotation.StringRes;
import android.annotation.XmlRes;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageItemInfo;
import android.graphics.Movie;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -59,6 +63,7 @@ import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pools.SynchronizedPool;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
@@ -108,6 +113,20 @@ public class Resources {
private static final int ID_OTHER = 0x01000004;
+ // Package IDs for themes. Aapt will compile the res table with this id.
+ /** @hide */
+ public static final int THEME_FRAMEWORK_PKG_ID = 0x60;
+ /** @hide */
+ public static final int THEME_APP_PKG_ID = 0x61;
+ /** @hide */
+ public static final int THEME_ICON_PKG_ID = 0x62;
+ /**
+ * The common resource pkg id needs to be less than the THEME_FRAMEWORK_PKG_ID
+ * otherwise aapt will complain and fail
+ * @hide
+ */
+ public static final int THEME_COMMON_PKG_ID = THEME_FRAMEWORK_PKG_ID - 1;
+
private static final Object sSync = new Object();
// Information about preloaded resources. Note that they are not
@@ -158,6 +177,9 @@ public class Resources {
private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ private SparseArray<PackageItemInfo> mIcons;
+ private ComposedIconInfo mComposedIconInfo;
+
static {
sPreloadedDrawables = new LongSparseArray[2];
sPreloadedDrawables[0] = new LongSparseArray<>();
@@ -268,7 +290,7 @@ public class Resources {
mCompatibilityInfo = compatInfo;
}
updateConfiguration(config, metrics);
- assets.ensureStringBlocks();
+ assets.recreateStringBlocks();
}
/**
@@ -793,6 +815,19 @@ public class Resources {
*/
@Nullable
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
+ return getDrawable(id, theme, true);
+ }
+
+ /** @hide */
+ @Nullable
+ public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme, boolean supportComposedIcons)
+ throws NotFoundException {
+ //Check if an icon is themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
+
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -801,9 +836,24 @@ public class Resources {
} else {
mTmpValue = null;
}
- getValue(id, value, true);
+ getValue(id, value, true, supportComposedIcons);
+ }
+ Drawable res = null;
+ try {
+ res = loadDrawable(value, id, theme);
+ } catch (NotFoundException e) {
+ // The below statement will be true if we were trying to load a composed icon.
+ // Since we received a NotFoundException, try to load the original if this
+ // condition is true, otherwise throw the original exception.
+ if (supportComposedIcons && mComposedIconInfo != null && info != null &&
+ info.themedIcon == 0) {
+ Log.e(TAG, "Failed to retrieve composed icon.", e);
+ getValue(id, value, true, false);
+ res = loadDrawable(value, id, theme);
+ } else {
+ throw e;
+ }
}
- final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
@@ -860,6 +910,19 @@ public class Resources {
*/
@Nullable
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
+ return getDrawableForDensity(id, density, theme, true);
+ }
+
+ /** @hide */
+ @Nullable
+ public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme,
+ boolean supportComposedIcons) {
+ //Check if an icon was themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
+
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -868,7 +931,7 @@ public class Resources {
} else {
mTmpValue = null;
}
- getValueForDensity(id, density, value, true);
+ getValueForDensity(id, density, value, true, supportComposedIcons);
/*
* Pretend the requested density is actually the display density. If
@@ -1344,8 +1407,24 @@ public class Resources {
*/
public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
+ getValue(id, outValue, resolveRefs, true);
+ }
+
+ /** @hide */
+ public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs,
+ boolean supportComposedIcons) throws NotFoundException {
+ //Check if an icon was themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
+ if (supportComposedIcons && IconPackHelper.shouldComposeIcon(mComposedIconInfo)
+ && info != null && info.themedIcon == 0) {
+ Drawable dr = loadDrawable(outValue, id, null);
+ IconCustomizer.getValue(this, id, outValue, dr);
+ }
return;
}
throw new NotFoundException("Resource ID #0x"
@@ -1367,8 +1446,45 @@ public class Resources {
*/
public void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
boolean resolveRefs) throws NotFoundException {
+ getValueForDensity(id, density, outValue, resolveRefs, true);
+ }
+
+ /** @hide */
+ public void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
+ boolean resolveRefs, boolean supportComposedIcons) throws NotFoundException {
+ //Check if an icon was themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
+
boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
if (found) {
+ if (supportComposedIcons && IconPackHelper.shouldComposeIcon(mComposedIconInfo) &&
+ info != null && info.themedIcon == 0) {
+ int tmpDensity = outValue.density;
+ /*
+ * Pretend the requested density is actually the display density. If
+ * the drawable returned is not the requested density, then force it
+ * to be scaled later by dividing its density by the ratio of
+ * requested density to actual device density. Drawables that have
+ * undefined density or no density don't need to be handled here.
+ */
+ if (outValue.density > 0 && outValue.density != TypedValue.DENSITY_NONE) {
+ if (outValue.density == density) {
+ outValue.density = mMetrics.densityDpi;
+ } else {
+ outValue.density = (outValue.density * mMetrics.densityDpi) / density;
+ }
+ }
+ Drawable dr = loadDrawable(outValue, id, null);
+
+ // Return to original density. If we do not do this then
+ // the caller will get the wrong density for the given id and perform
+ // more of its own scaling in loadDrawable
+ outValue.density = tmpDensity;
+ IconCustomizer.getValue(this, id, outValue, dr);
+ }
return;
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
@@ -2083,7 +2199,15 @@ public class Resources {
mTmpConfig.setLayoutDirection(mTmpConfig.locale);
}
configChanges = mConfiguration.updateFrom(mTmpConfig);
- configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+
+ /* This is ugly, but modifying the activityInfoConfigToNative
+ * adapter would be messier */
+ if ((configChanges & ActivityInfo.CONFIG_THEME_RESOURCE) != 0) {
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ } else {
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ }
}
return configChanges;
}
@@ -2526,9 +2650,10 @@ public class Resources {
// attributes.
final ConstantState cs;
if (isColorDrawable) {
- cs = sPreloadedColorDrawables.get(key);
+ cs = mAssets.hasThemedAssets() ? null : sPreloadedColorDrawables.get(key);
} else {
- cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
+ cs = mAssets.hasThemedAssets() ? null :
+ sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
@@ -2666,7 +2791,7 @@ public class Resources {
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
final android.content.res.ConstantState<ColorStateList> factory =
- sPreloadedColorStateLists.get(key);
+ mAssets.hasThemedAssets() ? null : sPreloadedColorStateLists.get(key);
if (factory != null) {
return factory.newInstance();
}
@@ -2690,7 +2815,7 @@ public class Resources {
}
final android.content.res.ConstantState<ColorStateList> factory =
- sPreloadedColorStateLists.get(key);
+ mAssets.hasThemedAssets() ? null : sPreloadedColorStateLists.get(key);
if (factory != null) {
csl = factory.newInstance(this, theme);
}
@@ -2845,6 +2970,28 @@ public class Resources {
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
+ /** @hide */
+ public void setIconResources(SparseArray<PackageItemInfo> icons) {
+ mIcons = icons;
+ }
+
+ /** @hide */
+ public void setComposedIconInfo(ComposedIconInfo iconInfo) {
+ mComposedIconInfo = iconInfo;
+ }
+
+ /** @hide */
+ public ComposedIconInfo getComposedIconInfo() {
+ return mComposedIconInfo;
+ }
+
+ /** @hide */
+ public final void updateStringCache() {
+ synchronized (mAccessLock) {
+ mAssets.recreateStringBlocks();
+ }
+ }
+
private Resources() {
mAssets = AssetManager.getSystem();
// NOTE: Intentionally leaving this uninitialized (all values set
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 2620571..f2ed758 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -24,6 +24,7 @@ import java.util.Objects;
public final class ResourcesKey {
private final String mResDir;
private final float mScale;
+ private final boolean mIsThemeable;
private final int mHash;
public final int mDisplayId;
@@ -31,18 +32,20 @@ public final class ResourcesKey {
public final Configuration mOverrideConfiguration;
public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
- float scale) {
+ float scale, boolean isThemeable) {
mResDir = resDir;
mDisplayId = displayId;
mOverrideConfiguration = overrideConfiguration != null
? overrideConfiguration : Configuration.EMPTY;
mScale = scale;
+ mIsThemeable = isThemeable;
int hash = 17;
hash = 31 * hash + (mResDir == null ? 0 : mResDir.hashCode());
hash = 31 * hash + mDisplayId;
hash = 31 * hash + mOverrideConfiguration.hashCode();
hash = 31 * hash + Float.floatToIntBits(mScale);
+ hash = 31 * hash + (mIsThemeable ? 1 : 0);
mHash = hash;
}
@@ -74,7 +77,7 @@ public final class ResourcesKey {
if (mScale != peer.mScale) {
return false;
}
- return true;
+ return mIsThemeable == peer.mIsThemeable;
}
@Override
diff --git a/core/java/android/content/res/ThemeConfig.java b/core/java/android/content/res/ThemeConfig.java
new file mode 100644
index 0000000..1b1837d
--- /dev/null
+++ b/core/java/android/content/res/ThemeConfig.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Portions copyright (C) 2014, T-Mobile USA, Inc.
+ *
+ * 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 android.content.res;
+
+import android.content.ContentResolver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * The Theme Configuration allows lookup of a theme element (fonts, icon, overlay) for a given
+ * application. If there isn't a particular theme designated to an app, it will fallback on the
+ * default theme. If there isn't a default theme then it will simply fallback to holo.
+ *
+ * @hide
+ */
+public class ThemeConfig implements Cloneable, Parcelable, Comparable<ThemeConfig> {
+ public static final String TAG = ThemeConfig.class.getCanonicalName();
+ public static final String SYSTEM_DEFAULT = "system";
+
+ /**
+ * Special package name for theming the navbar separate from the rest of SystemUI
+ */
+ public static final String SYSTEMUI_NAVBAR_PKG = "com.android.systemui.navbar";
+ public static final String SYSTEMUI_STATUS_BAR_PKG = "com.android.systemui";
+
+ // Key for any app which does not have a specific theme applied
+ private static final String KEY_DEFAULT_PKG = "default";
+ private static final SystemConfig mSystemConfig = new SystemConfig();
+ private static final SystemAppTheme mSystemAppTheme = new SystemAppTheme();
+
+ // Maps pkgname to theme (ex com.angry.birds -> red theme)
+ protected final Map<String, AppTheme> mThemes = new HashMap<String, AppTheme>();
+
+ public ThemeConfig(Map<String, AppTheme> appThemes) {
+ mThemes.putAll(appThemes);
+ }
+
+ public String getOverlayPkgName() {
+ AppTheme theme = getDefaultTheme();
+ return theme.mOverlayPkgName;
+ }
+
+ public String getOverlayForStatusBar() {
+ return getOverlayPkgNameForApp(SYSTEMUI_STATUS_BAR_PKG);
+ }
+
+ public String getOverlayForNavBar() {
+ return getOverlayPkgNameForApp(SYSTEMUI_NAVBAR_PKG);
+ }
+
+ public String getOverlayPkgNameForApp(String appPkgName) {
+ AppTheme theme = getThemeFor(appPkgName);
+ return theme.mOverlayPkgName;
+ }
+
+ public String getIconPackPkgName() {
+ AppTheme theme = getDefaultTheme();
+ return theme.mIconPkgName;
+ }
+
+ public String getIconPackPkgNameForApp(String appPkgName) {
+ AppTheme theme = getThemeFor(appPkgName);
+ return theme.mIconPkgName;
+ }
+
+ public String getFontPkgName() {
+ AppTheme defaultTheme = getDefaultTheme();
+ return defaultTheme.mFontPkgName;
+ }
+
+ public String getFontPkgNameForApp(String appPkgName) {
+ AppTheme theme = getThemeFor(appPkgName);
+ return theme.mFontPkgName;
+ }
+
+ private AppTheme getThemeFor(String pkgName) {
+ AppTheme theme = mThemes.get(pkgName);
+ if (theme == null) theme = getDefaultTheme();
+ return theme;
+ }
+
+ private AppTheme getDefaultTheme() {
+ AppTheme theme = mThemes.get(KEY_DEFAULT_PKG);
+ if (theme == null) theme = mSystemAppTheme;
+ return theme;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof ThemeConfig) {
+ ThemeConfig o = (ThemeConfig) object;
+
+ Map<String, AppTheme> currThemes = (mThemes == null) ?
+ new HashMap<String, AppTheme>() : mThemes;
+ Map<String, AppTheme> newThemes = (o.mThemes == null) ?
+ new HashMap<String, AppTheme>() : o.mThemes;
+
+ return (currThemes.equals(newThemes));
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ if (mThemes != null) {
+ result.append("themes:");
+ result.append(mThemes);
+ }
+ return result.toString();
+ }
+
+ public String toJson() {
+ return JsonSerializer.toJson(this);
+ }
+
+ public static ThemeConfig fromJson(String json) {
+ return JsonSerializer.fromJson(json);
+ }
+
+ /**
+ * Represents the theme that the device booted into. This is used to
+ * simulate a "default" configuration based on the user's last known
+ * preference until the theme is switched at runtime.
+ */
+ public static ThemeConfig getBootTheme(ContentResolver resolver) {
+ ThemeConfig bootTheme = mSystemConfig;
+ try {
+ String json = Settings.Secure.getString(resolver,
+ Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY);
+ bootTheme = ThemeConfig.fromJson(json);
+
+ // Handle upgrade Case: Previously the theme configuration was in separate fields
+ if (bootTheme == null) {
+ String overlayPkgName = Settings.Secure.getString(resolver,
+ Configuration.THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY);
+ String iconPackPkgName = Settings.Secure.getString(resolver,
+ Configuration.THEME_ICONPACK_PACKAGE_NAME_PERSISTENCE_PROPERTY);
+ String fontPkgName = Settings.Secure.getString(resolver,
+ Configuration.THEME_FONT_PACKAGE_NAME_PERSISTENCE_PROPERTY);
+
+ Builder builder = new Builder();
+ builder.defaultOverlay(overlayPkgName);
+ builder.defaultIcon(iconPackPkgName);
+ builder.defaultFont(fontPkgName);
+ bootTheme = builder.build();
+ }
+ } catch (SecurityException e) {
+ Log.e(TAG, "Could not get boot theme", e);
+ }
+ return bootTheme;
+ }
+
+ /**
+ * Represents the system framework theme, perceived by the system as there
+ * being no theme applied.
+ */
+ public static ThemeConfig getSystemTheme() {
+ return mSystemConfig;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ String json = JsonSerializer.toJson(this);
+ dest.writeString(json);
+ }
+
+ public static final Parcelable.Creator<ThemeConfig> CREATOR =
+ new Parcelable.Creator<ThemeConfig>() {
+ public ThemeConfig createFromParcel(Parcel source) {
+ String json = source.readString();
+ return JsonSerializer.fromJson(json);
+ }
+
+ public ThemeConfig[] newArray(int size) {
+ return new ThemeConfig[size];
+ }
+ };
+
+ @Override
+ public int compareTo(ThemeConfig o) {
+ if (o == null) return -1;
+ int n = 0;
+ n = mThemes.equals(o.mThemes) ? 0 : 1;
+ return n;
+ }
+
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ Log.d(TAG, "clone not supported", e);
+ return null;
+ }
+ }
+
+ public static class AppTheme implements Cloneable, Comparable<AppTheme> {
+ // If any field is modified or added here be sure to change the serializer accordingly
+ String mOverlayPkgName;
+ String mIconPkgName;
+ String mFontPkgName;
+
+ public AppTheme(String overlayPkgName, String iconPkgName, String fontPkgName) {
+ mOverlayPkgName = overlayPkgName;
+ mIconPkgName = iconPkgName;
+ mFontPkgName = fontPkgName;
+ }
+
+ public String getIconPackPkgName() {
+ return mIconPkgName;
+ }
+
+ public String getOverlayPkgName() {
+ return mOverlayPkgName;
+ }
+
+ public String getFontPackPkgName() {
+ return mFontPkgName;
+ }
+
+ @Override
+ public synchronized int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + (mOverlayPkgName == null ? 0 : mOverlayPkgName.hashCode());
+ hash = 31 * hash + (mIconPkgName == null ? 0 : mIconPkgName.hashCode());
+ hash = 31 * hash + (mFontPkgName == null ? 0 : mIconPkgName.hashCode());
+ return hash;
+ }
+
+ @Override
+ public int compareTo(AppTheme o) {
+ if (o == null) return -1;
+ int n = 0;
+ n = mIconPkgName.compareTo(o.mIconPkgName);
+ if (n != 0) return n;
+ n = mFontPkgName.compareTo(o.mFontPkgName);
+ if (n != 0) return n;
+ n = mOverlayPkgName.equals(o.mOverlayPkgName) ? 0 : 1;
+ return n;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof AppTheme) {
+ AppTheme o = (AppTheme) object;
+ String currentOverlayPkgName = (mOverlayPkgName == null)? "" : mOverlayPkgName;
+ String newOverlayPkgName = (o.mOverlayPkgName == null)? "" : o.mOverlayPkgName;
+ String currentIconPkgName = (mIconPkgName == null)? "" : mIconPkgName;
+ String newIconPkgName = (o.mIconPkgName == null)? "" : o.mIconPkgName;
+ String currentFontPkgName = (mFontPkgName == null)? "" : mFontPkgName;
+ String newFontPkgName = (o.mFontPkgName == null)? "" : o.mFontPkgName;
+
+
+ return (currentIconPkgName.equals(newIconPkgName) &&
+ currentFontPkgName.equals(newFontPkgName) &&
+ currentOverlayPkgName.equals(newOverlayPkgName));
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ if (mOverlayPkgName != null) {
+ result.append("overlay:");
+ result.append(mOverlayPkgName);
+ }
+
+ if (!TextUtils.isEmpty(mIconPkgName)) {
+ result.append(", iconPack:");
+ result.append(mIconPkgName);
+ }
+
+ if (!TextUtils.isEmpty(mFontPkgName)) {
+ result.append(", fontPkg:");
+ result.append(mFontPkgName);
+ }
+ return result.toString();
+ }
+ }
+
+
+ public static class Builder {
+ private HashMap<String, String> mOverlays = new HashMap<String, String>();
+ private HashMap<String, String> mIcons = new HashMap<String, String>();
+ private HashMap<String, String> mFonts = new HashMap<String, String>();
+
+ public Builder() {}
+
+ public Builder(ThemeConfig theme) {
+ for(Map.Entry<String, AppTheme> entry : theme.mThemes.entrySet()) {
+ String key = entry.getKey();
+ AppTheme appTheme = entry.getValue();
+ mFonts.put(key, appTheme.getFontPackPkgName());
+ mIcons.put(key, appTheme.getIconPackPkgName());
+ mOverlays.put(key, appTheme.getOverlayPkgName());
+ }
+ }
+
+ /**
+ * For uniquely theming a specific app. ex. "Dialer gets red theme,
+ * Calculator gets blue theme"
+ */
+ public Builder defaultOverlay(String themePkgName) {
+ if (themePkgName != null) {
+ mOverlays.put(KEY_DEFAULT_PKG, themePkgName);
+ } else {
+ mOverlays.remove(KEY_DEFAULT_PKG);
+ }
+ return this;
+ }
+
+ public Builder defaultFont(String themePkgName) {
+ if (themePkgName != null) {
+ mFonts.put(KEY_DEFAULT_PKG, themePkgName);
+ } else {
+ mFonts.remove(KEY_DEFAULT_PKG);
+ }
+ return this;
+ }
+
+ public Builder defaultIcon(String themePkgName) {
+ if (themePkgName != null) {
+ mIcons.put(KEY_DEFAULT_PKG, themePkgName);
+ } else {
+ mIcons.remove(KEY_DEFAULT_PKG);
+ }
+ return this;
+ }
+
+ public Builder icon(String appPkgName, String themePkgName) {
+ if (themePkgName != null) {
+ mIcons.put(appPkgName, themePkgName);
+ } else {
+ mIcons.remove(appPkgName);
+ }
+ return this;
+ }
+
+ public Builder overlay(String appPkgName, String themePkgName) {
+ if (themePkgName != null) {
+ mOverlays.put(appPkgName, themePkgName);
+ } else {
+ mOverlays.remove(appPkgName);
+ }
+ return this;
+ }
+
+ public Builder font(String appPkgName, String themePkgName) {
+ if (themePkgName != null) {
+ mFonts.put(appPkgName, themePkgName);
+ } else {
+ mFonts.remove(appPkgName);
+ }
+ return this;
+ }
+
+ public ThemeConfig build() {
+ HashSet<String> appPkgSet = new HashSet<String>();
+ appPkgSet.addAll(mOverlays.keySet());
+ appPkgSet.addAll(mIcons.keySet());
+ appPkgSet.addAll(mFonts.keySet());
+
+ HashMap<String, AppTheme> appThemes = new HashMap<String, AppTheme>();
+ for(String appPkgName : appPkgSet) {
+ String icon = mIcons.get(appPkgName);
+ String overlay = mOverlays.get(appPkgName);
+ String font = mFonts.get(appPkgName);
+
+ AppTheme appTheme = new AppTheme(overlay, icon, font);
+ appThemes.put(appPkgName, appTheme);
+ }
+ return new ThemeConfig(appThemes);
+ }
+ }
+
+
+ public static class JsonSerializer {
+ private static final String NAME_OVERLAY_PKG = "mOverlayPkgName";
+ private static final String NAME_ICON_PKG = "mIconPkgName";
+ private static final String NAME_FONT_PKG = "mFontPkgName";
+
+ public static String toJson(ThemeConfig theme) {
+ String json = null;
+ Writer writer = null;
+ JsonWriter jsonWriter = null;
+ try {
+ writer = new StringWriter();
+ jsonWriter = new JsonWriter(writer);
+ writeTheme(jsonWriter, theme);
+ json = writer.toString();
+ } catch(IOException e) {
+ Log.e(TAG, "Could not write theme mapping", e);
+ } finally {
+ closeQuietly(writer);
+ closeQuietly(jsonWriter);
+ }
+ return json;
+ }
+
+ private static void writeTheme(JsonWriter writer, ThemeConfig theme)
+ throws IOException {
+ writer.beginObject();
+ for(Map.Entry<String, AppTheme> entry : theme.mThemes.entrySet()) {
+ String appPkgName = entry.getKey();
+ AppTheme appTheme = entry.getValue();
+ writer.name(appPkgName);
+ writeAppTheme(writer, appTheme);
+ }
+ writer.endObject();
+ }
+
+ private static void writeAppTheme(JsonWriter writer, AppTheme appTheme) throws IOException {
+ writer.beginObject();
+ writer.name(NAME_OVERLAY_PKG).value(appTheme.mOverlayPkgName);
+ writer.name(NAME_ICON_PKG).value(appTheme.mIconPkgName);
+ writer.name(NAME_FONT_PKG).value(appTheme.mFontPkgName);
+ writer.endObject();
+ }
+
+ public static ThemeConfig fromJson(String json) {
+ if (json == null) return null;
+ HashMap<String, AppTheme> map = new HashMap<String, AppTheme>();
+ StringReader reader = null;
+ JsonReader jsonReader = null;
+ try {
+ reader = new StringReader(json);
+ jsonReader = new JsonReader(reader);
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String appPkgName = jsonReader.nextName();
+ AppTheme appTheme = readAppTheme(jsonReader);
+ map.put(appPkgName, appTheme);
+ }
+ jsonReader.endObject();
+ } catch(Exception e) {
+ Log.e(TAG, "Could not parse ThemeConfig from: " + json, e);
+ } finally {
+ closeQuietly(reader);
+ closeQuietly(jsonReader);
+ }
+ return new ThemeConfig(map);
+ }
+
+ private static AppTheme readAppTheme(JsonReader reader) throws IOException {
+ String overlay = null;
+ String icon = null;
+ String font = null;
+
+ reader.beginObject();
+ while(reader.hasNext()) {
+ String name = reader.nextName();
+ if (NAME_OVERLAY_PKG.equals(name) && reader.peek() != JsonToken.NULL) {
+ overlay = reader.nextString();
+ } else if (NAME_ICON_PKG.equals(name) && reader.peek() != JsonToken.NULL) {
+ icon = reader.nextString();
+ } else if (NAME_FONT_PKG.equals(name) && reader.peek() != JsonToken.NULL) {
+ font = reader.nextString();
+ } else {
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ return new AppTheme(overlay, icon, font);
+ }
+
+ private static void closeQuietly(Reader reader) {
+ try {
+ if (reader != null) reader.close();
+ } catch(IOException e) {
+ }
+ }
+
+ private static void closeQuietly(JsonReader reader) {
+ try {
+ if (reader != null) reader.close();
+ } catch(IOException e) {
+ }
+ }
+
+ private static void closeQuietly(Writer writer) {
+ try {
+ if (writer != null) writer.close();
+ } catch(IOException e) {
+ }
+ }
+
+ private static void closeQuietly(JsonWriter writer) {
+ try {
+ if (writer != null) writer.close();
+ } catch(IOException e) {
+ }
+ }
+ }
+
+ public static class SystemConfig extends ThemeConfig {
+ public SystemConfig() {
+ super(new HashMap<String, AppTheme>());
+ }
+ }
+
+ public static class SystemAppTheme extends AppTheme {
+ public SystemAppTheme() {
+ super(SYSTEM_DEFAULT, SYSTEM_DEFAULT, SYSTEM_DEFAULT);
+ }
+
+ @Override
+ public String toString() {
+ return "No Theme Applied (Holo)";
+ }
+ }
+}
diff --git a/core/java/android/content/res/ThemeManager.java b/core/java/android/content/res/ThemeManager.java
new file mode 100644
index 0000000..a9d2fcc
--- /dev/null
+++ b/core/java/android/content/res/ThemeManager.java
@@ -0,0 +1,298 @@
+/*
+ * 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 android.content.res;
+
+import android.content.Context;
+import android.content.pm.ThemeUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * {@hide}
+ */
+public class ThemeManager {
+ private static final String TAG = ThemeManager.class.getName();
+ private Context mContext;
+ private IThemeService mService;
+ private Handler mHandler;
+
+ private Set<ThemeChangeListener> mChangeListeners =
+ new HashSet<ThemeChangeListener>();
+
+ private Set<ThemeProcessingListener> mProcessingListeners =
+ new HashSet<ThemeProcessingListener>();
+
+ public ThemeManager(Context context, IThemeService service) {
+ mContext = context;
+ mService = service;
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ private final IThemeChangeListener mThemeChangeListener = new IThemeChangeListener.Stub() {
+ @Override
+ public void onProgress(final int progress) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList
+ <ThemeChangeListener>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onProgress(progress);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFinish(final boolean isSuccess) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList
+ <ThemeChangeListener>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onFinish(isSuccess);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change listener", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+ private final IThemeProcessingListener mThemeProcessingListener =
+ new IThemeProcessingListener.Stub() {
+ @Override
+ public void onFinishedProcessing(final String pkgName) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mProcessingListeners) {
+ List<ThemeProcessingListener> listenersToRemove = new ArrayList
+ <ThemeProcessingListener>();
+ for (ThemeProcessingListener listener : mProcessingListeners) {
+ try {
+ listener.onFinishedProcessing(pkgName);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeProcessingListener listener : listenersToRemove) {
+ mProcessingListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+
+ public void addClient(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ if (mChangeListeners.contains(listener)) {
+ throw new IllegalArgumentException("Client was already added ");
+ }
+ if (mChangeListeners.size() == 0) {
+ try {
+ mService.requestThemeChangeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mChangeListeners.add(listener);
+ }
+ }
+
+ public void removeClient(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ mChangeListeners.remove(listener);
+ if (mChangeListeners.size() == 0) {
+ try {
+ mService.removeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to remove listener", e);
+ }
+ }
+ }
+ }
+
+ public void onClientPaused(ThemeChangeListener listener) {
+ removeClient(listener);
+ }
+
+ public void onClientResumed(ThemeChangeListener listener) {
+ addClient(listener);
+ }
+
+ public void onClientDestroyed(ThemeChangeListener listener) {
+ removeClient(listener);
+ }
+
+ /**
+ * Register a ThemeProcessingListener to be notified when a theme is done being processed.
+ * @param listener ThemeChangeListener to register
+ */
+ public void registerProcessingListener(ThemeProcessingListener listener) {
+ synchronized (mProcessingListeners) {
+ if (mProcessingListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener was already added ");
+ }
+ if (mProcessingListeners.size() == 0) {
+ try {
+ mService.registerThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mProcessingListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a ThemeChangeListener.
+ * @param listener ThemeChangeListener to unregister
+ */
+ public void unregisterProcessingListener(ThemeChangeListener listener) {
+ synchronized (mProcessingListeners) {
+ mProcessingListeners.remove(listener);
+ if (mProcessingListeners.size() == 0) {
+ try {
+ mService.unregisterThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to remove listener", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Convenience method. Applies the entire theme.
+ */
+ public void requestThemeChange(String pkgName) {
+ //List<String> components = ThemeUtils.getSupportedComponents(mContext, pkgName);
+ //requestThemeChange(pkgName, components);
+ }
+
+ public void requestThemeChange(String pkgName, List<String> components) {
+ Map<String, String> componentMap = new HashMap<String, String>(components.size());
+ for (String component : components) {
+ componentMap.put(component, pkgName);
+ }
+ requestThemeChange(componentMap);
+ }
+
+ public void requestThemeChange(Map<String, String> componentMap) {
+ try {
+ mService.requestThemeChange(componentMap);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public void applyDefaultTheme() {
+ try {
+ mService.applyDefaultTheme();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public boolean isThemeApplying() {
+ try {
+ return mService.isThemeApplying();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+
+ return false;
+ }
+
+ public boolean isThemeBeingProcessed(String themePkgName) {
+ try {
+ return mService.isThemeBeingProcessed(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ public int getProgress() {
+ try {
+ return mService.getProgress();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return -1;
+ }
+
+ public boolean processThemeResources(String themePkgName) {
+ try {
+ return mService.processThemeResources(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ private void logThemeServiceException(Exception e) {
+ Log.w(TAG, "Unable to access ThemeService", e);
+ }
+
+ public interface ThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+ }
+
+ public interface ThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+ }
+}
+