summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/app/ActivityManager.java25
-rw-r--r--core/java/android/app/ActivityThread.java28
-rw-r--r--core/java/android/app/ApplicationPackageManager.java72
-rw-r--r--core/java/android/app/ComposedIconInfo.aidl19
-rw-r--r--core/java/android/app/ComposedIconInfo.java96
-rw-r--r--core/java/android/app/ContextImpl.java55
-rw-r--r--core/java/android/app/IWallpaperManager.aidl19
-rw-r--r--core/java/android/app/IWallpaperManagerCallback.aidl5
-rw-r--r--core/java/android/app/IconPackHelper.java848
-rw-r--r--core/java/android/app/LoadedApk.java3
-rw-r--r--core/java/android/app/ResourcesManager.java409
-rw-r--r--core/java/android/app/WallpaperManager.java183
-rw-r--r--core/java/android/content/Context.java30
-rw-r--r--core/java/android/content/ContextWrapper.java15
-rw-r--r--core/java/android/content/Intent.java66
-rw-r--r--core/java/android/content/pm/ActivityInfo.java7
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java10
-rw-r--r--core/java/android/content/pm/BaseThemeInfo.java111
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl6
-rw-r--r--core/java/android/content/pm/PackageInfo.java48
-rw-r--r--core/java/android/content/pm/PackageInfoLite.java3
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java14
-rw-r--r--core/java/android/content/pm/PackageManager.java62
-rw-r--r--core/java/android/content/pm/PackageParser.java190
-rw-r--r--core/java/android/content/pm/ThemeInfo.aidl3
-rw-r--r--core/java/android/content/pm/ThemeInfo.java65
-rw-r--r--core/java/android/content/pm/ThemeUtils.java718
-rw-r--r--core/java/android/content/res/AssetManager.java225
-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.java175
-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
-rw-r--r--core/java/android/os/Process.java7
-rwxr-xr-xcore/java/android/provider/Settings.java29
-rw-r--r--core/java/android/provider/ThemesContract.java565
-rw-r--r--core/java/android/view/IWindowManager.aidl12
-rw-r--r--core/java/android/view/IWindowSession.aidl10
-rw-r--r--core/java/android/view/WindowManagerPolicy.java3
-rw-r--r--core/java/android/widget/RemoteViews.java10
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java10
-rw-r--r--core/java/com/android/internal/util/cm/ImageUtils.java297
-rw-r--r--core/jni/android_util_AssetManager.cpp186
-rw-r--r--core/res/AndroidManifest.xml46
-rw-r--r--core/res/res/anim/lock_screen_wallpaper_exit_noop.xml32
-rw-r--r--core/res/res/values/attrs_manifest.xml2
-rwxr-xr-xcore/res/res/values/cm_strings.xml27
-rw-r--r--core/res/res/values/cm_symbols.xml30
-rw-r--r--core/res/res/values/symbols.xml1
53 files changed, 5749 insertions, 69 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 37e8aa4939c..01ea22918d0 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -38,6 +38,7 @@ import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -56,6 +57,7 @@ import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Size;
import android.util.Slog;
+
import org.xmlpull.v1.XmlSerializer;
import java.io.FileDescriptor;
@@ -2206,6 +2208,16 @@ public class ActivityManager {
return null;
}
}
+ /**
+ * @hide
+ */
+ public Configuration getConfiguration() {
+ try {
+ return ActivityManagerNative.getDefault().getConfiguration();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
/**
* Returns a list of application processes that are running on the device.
@@ -2786,4 +2798,17 @@ public class ActivityManager {
}
}
}
+
+ /**
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not hold the {@link android.Manifest.permission#CHANGE_CONFIGURATION} permission.
+ *
+ * @hide
+ */
+ public void updateConfiguration(Configuration values) throws SecurityException {
+ try {
+ ActivityManagerNative.getDefault().updateConfiguration(values);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dd49009281f..3ee7621ec62 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,6 +43,7 @@ import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Typeface;
import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
@@ -71,6 +72,7 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -83,6 +85,7 @@ import android.util.Slog;
import android.util.SuperNotCalledException;
import android.view.Display;
import android.view.HardwareRenderer;
+import android.view.InflateException;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
@@ -1621,9 +1624,19 @@ public final class ActivityThread {
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, Configuration overrideConfiguration,
- LoadedApk pkgInfo) {
+ LoadedApk pkgInfo, Context context, String pkgName) {
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
- displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
+ displayId, pkgName, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null,
+ context);
+ }
+
+ /**
+ * Creates the top level resources for the given package.
+ */
+ Resources getTopLevelThemedResources(String resDir, int displayId, LoadedApk pkgInfo,
+ String pkgName, String themePkgName) {
+ return mResourcesManager.getTopLevelThemedResources(resDir, displayId, pkgName,
+ themePkgName, pkgInfo.getCompatibilityInfo(), null);
}
final Handler getHandler() {
@@ -1731,8 +1744,7 @@ public final class ActivityThread {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
- if (packageInfo == null || (packageInfo.mResources != null
- && !packageInfo.mResources.getAssets().isUpToDate())) {
+ if (packageInfo == null) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
@@ -1750,6 +1762,10 @@ public final class ActivityThread {
new WeakReference<LoadedApk>(packageInfo));
}
}
+ if (packageInfo.mResources != null
+ && !packageInfo.mResources.getAssets().isUpToDate()) {
+ packageInfo.mResources = null;
+ }
return packageInfo;
}
}
@@ -4088,8 +4104,10 @@ public final class ActivityThread {
if (configDiff != 0) {
// Ask text layout engine to free its caches if there is a locale change
boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0);
- if (hasLocaleConfigChange) {
+ boolean hasThemeConfigChange = ((configDiff & ActivityInfo.CONFIG_THEME_RESOURCE) != 0);
+ if (hasLocaleConfigChange || hasThemeConfigChange) {
Canvas.freeTextLayoutCaches();
+ Typeface.recreateDefaults();
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Cleared TextLayout Caches");
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 93cc521e3dc..05f915c153f 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -18,6 +18,7 @@ package android.app;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -925,12 +926,13 @@ final class ApplicationPackageManager extends PackageManager {
if (app.packageName.equals("system")) {
return mContext.mMainThread.getSystemContext().getResources();
}
+
final boolean sameUid = (app.uid == Process.myUid());
Resources r = mContext.mMainThread.getTopLevelResources(
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
- null, mContext.mPackageInfo);
+ null, mContext.mPackageInfo, mContext, app.packageName);
if (r != null) {
return r;
}
@@ -965,6 +967,48 @@ final class ApplicationPackageManager extends PackageManager {
throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");
}
+ /** @hide */
+ @Override public Resources getThemedResourcesForApplication(
+ ApplicationInfo app, String themePkgName) throws NameNotFoundException {
+ if (app.packageName.equals("system")) {
+ return mContext.mMainThread.getSystemContext().getResources();
+ }
+
+ Resources r = mContext.mMainThread.getTopLevelThemedResources(
+ app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,
+ Display.DEFAULT_DISPLAY, mContext.mPackageInfo, app.packageName, themePkgName);
+ if (r != null) {
+ return r;
+ }
+ throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
+ }
+
+ /** @hide */
+ @Override public Resources getThemedResourcesForApplication(
+ String appPackageName, String themePkgName) throws NameNotFoundException {
+ return getThemedResourcesForApplication(
+ getApplicationInfo(appPackageName, 0), themePkgName);
+ }
+
+ /** @hide */
+ @Override
+ public Resources getThemedResourcesForApplicationAsUser(String appPackageName,
+ String themePackageName, int userId) throws NameNotFoundException {
+ if (userId < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user #" + userId);
+ }
+ try {
+ ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, 0, userId);
+ if (ai != null) {
+ return getThemedResourcesForApplication(ai, themePackageName);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");
+ }
+
int mCachedSafeMode = -1;
@Override public boolean isSafeMode() {
try {
@@ -1770,4 +1814,30 @@ final class ApplicationPackageManager extends PackageManager {
= new ArrayMap<ResourceName, WeakReference<Drawable.ConstantState>>();
private static ArrayMap<ResourceName, WeakReference<CharSequence>> sStringCache
= new ArrayMap<ResourceName, WeakReference<CharSequence>>();
+
+ /**
+ * @hide
+ */
+ @Override
+ public void updateIconMaps(String pkgName) {
+ try {
+ mPM.updateIconMapping(pkgName);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Failed to update icon maps", re);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int processThemeResources(String themePkgName) {
+ try {
+ return mPM.processThemeResources(themePkgName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to process theme resources for " + themePkgName, e);
+ }
+
+ return 0;
+ }
}
diff --git a/core/java/android/app/ComposedIconInfo.aidl b/core/java/android/app/ComposedIconInfo.aidl
new file mode 100644
index 00000000000..8a1bab52d8c
--- /dev/null
+++ b/core/java/android/app/ComposedIconInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app;
+
+/** @hide */
+parcelable ComposedIconInfo;
diff --git a/core/java/android/app/ComposedIconInfo.java b/core/java/android/app/ComposedIconInfo.java
new file mode 100644
index 00000000000..7fab85241b1
--- /dev/null
+++ b/core/java/android/app/ComposedIconInfo.java
@@ -0,0 +1,96 @@
+/*
+ * 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.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class ComposedIconInfo implements Parcelable {
+ public int iconUpon, iconMask;
+ public int[] iconBacks;
+ public float iconScale;
+ public int iconDensity;
+ public int iconSize;
+ public float[] colorFilter;
+
+ public ComposedIconInfo() {
+ super();
+ }
+
+ private ComposedIconInfo(Parcel source) {
+ iconScale = source.readFloat();
+ iconDensity = source.readInt();
+ iconSize = source.readInt();
+ int backCount = source.readInt();
+ if (backCount > 0) {
+ iconBacks = new int[backCount];
+ for (int i = 0; i < backCount; i++) {
+ iconBacks[i] = source.readInt();
+ }
+ }
+ iconMask = source.readInt();
+ iconUpon = source.readInt();
+ int colorFilterSize = source.readInt();
+ if (colorFilterSize > 0) {
+ colorFilter = new float[colorFilterSize];
+ for (int i = 0; i < colorFilterSize; i++) {
+ colorFilter[i] = source.readFloat();
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(iconScale);
+ dest.writeInt(iconDensity);
+ dest.writeInt(iconSize);
+ dest.writeInt(iconBacks != null ? iconBacks.length : 0);
+ if (iconBacks != null) {
+ for (int resId : iconBacks) {
+ dest.writeInt(resId);
+ }
+ }
+ dest.writeInt(iconMask);
+ dest.writeInt(iconUpon);
+ if (colorFilter != null) {
+ dest.writeInt(colorFilter.length);
+ for (float val : colorFilter) {
+ dest.writeFloat(val);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ public static final Creator<ComposedIconInfo> CREATOR
+ = new Creator<ComposedIconInfo>() {
+ @Override
+ public ComposedIconInfo createFromParcel(Parcel source) {
+ return new ComposedIconInfo(source);
+ }
+
+ @Override
+ public ComposedIconInfo[] newArray(int size) {
+ return new ComposedIconInfo[0];
+ }
+ };
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d607f81adec..3539b7ffe7c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,6 +19,8 @@ package android.app;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageStatsManager;
import android.appwidget.AppWidgetManager;
+import android.content.res.IThemeService;
+import android.content.res.ThemeManager;
import android.os.Build;
import android.service.persistentdata.IPersistentDataBlockService;
@@ -769,6 +771,14 @@ class ContextImpl extends Context {
return new ProfileManager (outerContext, ctx.mMainThread.getHandler());
}
});
+
+ registerService(THEME_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(THEME_SERVICE);
+ IThemeService service = IThemeService.Stub.asInterface(b);
+ return new ThemeManager(ctx.getOuterContext(),
+ service);
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -2100,13 +2110,19 @@ class ContextImpl extends Context {
@Override
public Context createApplicationContext(ApplicationInfo application, int flags)
throws NameNotFoundException {
+ return createApplicationContext(application, null, flags);
+ }
+
+ @Override
+ public Context createApplicationContext(ApplicationInfo application, String themePackageName,
+ int flags) throws NameNotFoundException {
LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE);
if (pi != null) {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
new UserHandle(UserHandle.getUserId(application.uid)), restricted,
- mDisplay, mOverrideConfiguration);
+ mDisplay, mOverrideConfiguration, themePackageName);
if (c.mResources != null) {
return c;
}
@@ -2119,24 +2135,30 @@ class ContextImpl extends Context {
@Override
public Context createPackageContext(String packageName, int flags)
throws NameNotFoundException {
- return createPackageContextAsUser(packageName, flags,
+ return createPackageContextAsUser(packageName, null, flags,
mUser != null ? mUser : Process.myUserHandle());
}
@Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
throws NameNotFoundException {
+ return createPackageContextAsUser(packageName, null, flags, user);
+ }
+
+ @Override
+ public Context createPackageContextAsUser(String packageName, String themePackageName,
+ int flags, UserHandle user) throws NameNotFoundException {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
if (packageName.equals("system") || packageName.equals("android")) {
return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, mOverrideConfiguration, themePackageName);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, mOverrideConfiguration, themePackageName);
if (c.mResources != null) {
return c;
}
@@ -2208,7 +2230,7 @@ class ContextImpl extends Context {
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread,
- packageInfo, null, null, false, null, null);
+ packageInfo, null, null, false, null, null, null);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
return context;
@@ -2217,7 +2239,7 @@ class ContextImpl extends Context {
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
- packageInfo, null, null, false, null, null);
+ packageInfo, null, null, false, null, null, null);
}
static ContextImpl createActivityContext(ActivityThread mainThread,
@@ -2225,12 +2247,19 @@ class ContextImpl extends Context {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
if (activityToken == null) throw new IllegalArgumentException("activityInfo");
return new ContextImpl(null, mainThread,
- packageInfo, activityToken, null, false, null, null);
+ packageInfo, activityToken, null, false, null, null, null);
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration) {
+ this(container, mainThread, packageInfo, activityToken, user, restricted, display,
+ overrideConfiguration, null);
+ }
+
+ private ContextImpl(ContextImpl container, ActivityThread mainThread,
+ LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
+ Display display, Configuration overrideConfiguration, String themePackageName) {
mOuterContext = this;
mMainThread = mainThread;
@@ -2260,15 +2289,19 @@ class ContextImpl extends Context {
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
- if (activityToken != null
+ if (activityToken != null || themePackageName != null
|| displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
- resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
- packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
+ resources = themePackageName == null ? mResourcesManager.getTopLevelResources(
+ packageInfo.getResDir(), packageInfo.getSplitResDirs(),
+ packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
- overrideConfiguration, compatInfo, activityToken);
+ packageInfo.getAppDir(), overrideConfiguration, compatInfo, activityToken,
+ mOuterContext) :
+ mResourcesManager.getTopLevelThemedResources(packageInfo.getResDir(), displayId,
+ packageInfo.getPackageName(), themePackageName, compatInfo ,activityToken);
}
}
mResources = resources;
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 3b5900b4a5d..3117c2d4388 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -30,6 +30,12 @@ interface IWallpaperManager {
* Set the wallpaper.
*/
ParcelFileDescriptor setWallpaper(String name);
+
+ /**
+ * Set the keyguard wallpaper.
+ * @hide
+ */
+ ParcelFileDescriptor setKeyguardWallpaper(String name);
/**
* Set the live wallpaper.
@@ -41,6 +47,13 @@ interface IWallpaperManager {
*/
ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
out Bundle outParams);
+
+ /**
+ * Get the keyguard wallpaper.
+ * @hide
+ */
+ ParcelFileDescriptor getKeyguardWallpaper(IWallpaperManagerCallback cb,
+ out Bundle outParams);
/**
* Get information about a live wallpaper.
@@ -52,6 +65,12 @@ interface IWallpaperManager {
*/
void clearWallpaper();
+ /*
+ * Clear the keyguard wallpaper.
+ * @hide
+ */
+ void clearKeyguardWallpaper();
+
/**
* Return whether there is a wallpaper set with the given name.
*/
diff --git a/core/java/android/app/IWallpaperManagerCallback.aidl b/core/java/android/app/IWallpaperManagerCallback.aidl
index 991b2bc924b..b217318291d 100644
--- a/core/java/android/app/IWallpaperManagerCallback.aidl
+++ b/core/java/android/app/IWallpaperManagerCallback.aidl
@@ -28,4 +28,9 @@ oneway interface IWallpaperManagerCallback {
* Called when the wallpaper has changed
*/
void onWallpaperChanged();
+
+ /**
+ * Called when the keygaurd wallpaper has changed
+ */
+ void onKeyguardWallpaperChanged();
}
diff --git a/core/java/android/app/IconPackHelper.java b/core/java/android/app/IconPackHelper.java
new file mode 100644
index 00000000000..057633f89ee
--- /dev/null
+++ b/core/java/android/app/IconPackHelper.java
@@ -0,0 +1,848 @@
+/*
+ * 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.app;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import android.content.pm.PackageInfo;
+import android.content.res.IThemeService;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.graphics.drawable.VectorDrawable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.TypedValue;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+
+/** @hide */
+public class IconPackHelper {
+ private static final String TAG = IconPackHelper.class.getSimpleName();
+ private static final String ICON_MASK_TAG = "iconmask";
+ private static final String ICON_BACK_TAG = "iconback";
+ private static final String ICON_UPON_TAG = "iconupon";
+ private static final String ICON_SCALE_TAG = "scale";
+ private static final String ICON_BACK_FORMAT = "iconback%d";
+
+ private static final ComponentName ICON_BACK_COMPONENT;
+ private static final ComponentName ICON_MASK_COMPONENT;
+ private static final ComponentName ICON_UPON_COMPONENT;
+ private static final ComponentName ICON_SCALE_COMPONENT;
+
+ private static final float DEFAULT_SCALE = 1.0f;
+ private static final int COMPOSED_ICON_COOKIE = 128;
+
+ private final Context mContext;
+ private Map<ComponentName, String> mIconPackResourceMap;
+ private String mLoadedIconPackName;
+ private Resources mLoadedIconPackResource;
+ private ComposedIconInfo mComposedIconInfo;
+ private int mIconBackCount = 0;
+ private ColorFilterUtils.Builder mFilterBuilder;
+
+ static {
+ ICON_BACK_COMPONENT = new ComponentName(ICON_BACK_TAG, "");
+ ICON_MASK_COMPONENT = new ComponentName(ICON_MASK_TAG, "");
+ ICON_UPON_COMPONENT = new ComponentName(ICON_UPON_TAG, "");
+ ICON_SCALE_COMPONENT = new ComponentName(ICON_SCALE_TAG, "");
+ }
+
+ public IconPackHelper(Context context) {
+ mContext = context;
+ mIconPackResourceMap = new HashMap<ComponentName, String>();
+ ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mComposedIconInfo = new ComposedIconInfo();
+ mComposedIconInfo.iconSize = am.getLauncherLargeIconSize();
+ mComposedIconInfo.iconDensity = am.getLauncherLargeIconDensity();
+ mFilterBuilder = new ColorFilterUtils.Builder();
+ }
+
+ private void loadResourcesFromXmlParser(XmlPullParser parser,
+ Map<ComponentName, String> iconPackResources)
+ throws XmlPullParserException, IOException {
+ mIconBackCount = 0;
+ int eventType = parser.getEventType();
+ do {
+
+ if (eventType != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (parseComposedIconComponent(parser, iconPackResources)) {
+ continue;
+ }
+
+ if (ColorFilterUtils.parseIconFilter(parser, mFilterBuilder)) {
+ continue;
+ }
+
+ if (parser.getName().equalsIgnoreCase(ICON_SCALE_TAG)) {
+ String factor = parser.getAttributeValue(null, "factor");
+ if (factor == null) {
+ if (parser.getAttributeCount() == 1) {
+ factor = parser.getAttributeValue(0);
+ }
+ }
+ iconPackResources.put(ICON_SCALE_COMPONENT, factor);
+ continue;
+ }
+
+ if (!parser.getName().equalsIgnoreCase("item")) {
+ continue;
+ }
+
+ String component = parser.getAttributeValue(null, "component");
+ String drawable = parser.getAttributeValue(null, "drawable");
+
+ // Validate component/drawable exist
+ if (TextUtils.isEmpty(component) || TextUtils.isEmpty(drawable)) {
+ continue;
+ }
+
+ // Validate format/length of component
+ if (!component.startsWith("ComponentInfo{") || !component.endsWith("}")
+ || component.length() < 16 || drawable.length() == 0) {
+ continue;
+ }
+
+ // Sanitize stored value
+ component = component.substring(14, component.length() - 1).toLowerCase();
+
+ ComponentName name = null;
+ if (!component.contains("/")) {
+ // Package icon reference
+ name = new ComponentName(component.toLowerCase(), "");
+ } else {
+ name = ComponentName.unflattenFromString(component);
+ }
+
+ if (name != null) {
+ iconPackResources.put(name, drawable);
+ }
+ } while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT);
+ }
+
+ private boolean isComposedIconComponent(String tag) {
+ return tag.equalsIgnoreCase(ICON_MASK_TAG) ||
+ tag.equalsIgnoreCase(ICON_BACK_TAG) ||
+ tag.equalsIgnoreCase(ICON_UPON_TAG);
+ }
+
+ private boolean parseComposedIconComponent(XmlPullParser parser,
+ Map<ComponentName, String> iconPackResources) {
+ String icon;
+ String tag = parser.getName();
+ if (!isComposedIconComponent(tag)) {
+ return false;
+ }
+
+ if (parser.getAttributeCount() >= 1) {
+ if (tag.equalsIgnoreCase(ICON_BACK_TAG)) {
+ mIconBackCount = parser.getAttributeCount();
+ for (int i = 0; i < mIconBackCount; i++) {
+ tag = String.format(ICON_BACK_FORMAT, i);
+ icon = parser.getAttributeValue(i);
+ iconPackResources.put(new ComponentName(tag, ""), icon);
+ }
+ } else {
+ icon = parser.getAttributeValue(0);
+ iconPackResources.put(new ComponentName(tag, ""),
+ icon);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ public void loadIconPack(String packageName) throws NameNotFoundException {
+ if (packageName == null) {
+ mLoadedIconPackResource = null;
+ mLoadedIconPackName = null;
+ mComposedIconInfo.iconBacks = null;
+ mComposedIconInfo.iconMask = mComposedIconInfo.iconUpon = 0;
+ mComposedIconInfo.iconScale = 0;
+ mComposedIconInfo.colorFilter = null;
+ } else {
+ mIconBackCount = 0;
+ Resources res = createIconResource(mContext, packageName);
+ mIconPackResourceMap = getIconResMapFromXml(res, packageName);
+ mLoadedIconPackResource = res;
+ mLoadedIconPackName = packageName;
+ loadComposedIconComponents();
+ ColorMatrix cm = mFilterBuilder.build();
+ if (cm != null) {
+ mComposedIconInfo.colorFilter = cm.getArray().clone();
+ }
+ }
+ }
+
+ public ComposedIconInfo getComposedIconInfo() {
+ return mComposedIconInfo;
+ }
+
+ private void loadComposedIconComponents() {
+ mComposedIconInfo.iconMask = getResourceIdForName(ICON_MASK_COMPONENT);
+ mComposedIconInfo.iconUpon = getResourceIdForName(ICON_UPON_COMPONENT);
+
+ // Take care of loading iconback which can have multiple images
+ if (mIconBackCount > 0) {
+ mComposedIconInfo.iconBacks = new int[mIconBackCount];
+ for (int i = 0; i < mIconBackCount; i++) {
+ mComposedIconInfo.iconBacks[i] =
+ getResourceIdForName(
+ new ComponentName(String.format(ICON_BACK_FORMAT, i), ""));
+ }
+ }
+
+ // Get the icon scale from this pack
+ String scale = mIconPackResourceMap.get(ICON_SCALE_COMPONENT);
+ if (scale != null) {
+ try {
+ mComposedIconInfo.iconScale = Float.valueOf(scale);
+ } catch (NumberFormatException e) {
+ mComposedIconInfo.iconScale = DEFAULT_SCALE;
+ }
+ } else {
+ mComposedIconInfo.iconScale = DEFAULT_SCALE;
+ }
+ }
+
+ private int getResourceIdForName(ComponentName component) {
+ String item = mIconPackResourceMap.get(component);
+ if (!TextUtils.isEmpty(item)) {
+ return getResourceIdForDrawable(item);
+ }
+ return 0;
+ }
+
+ public static Resources createIconResource(Context context, String packageName)
+ throws NameNotFoundException {
+ PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+ String themeApk = info.applicationInfo.publicSourceDir;
+
+ String prefixPath;
+ String iconApkPath;
+ String iconResPath;
+ if (info.isLegacyIconPackApk) {
+ iconResPath = "";
+ iconApkPath = "";
+ prefixPath = "";
+ } else {
+ prefixPath = ThemeUtils.ICONS_PATH; //path inside APK
+ iconApkPath = ThemeUtils.getIconPackApkPath(packageName);
+ iconResPath = ThemeUtils.getIconPackResPath(packageName);
+ }
+
+ AssetManager assets = new AssetManager();
+ assets.addIconPath(themeApk, iconApkPath,
+ prefixPath, Resources.THEME_ICON_PKG_ID);
+
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ Configuration config = context.getResources().getConfiguration();
+ Resources res = new Resources(assets, dm, config);
+ return res;
+ }
+
+ public Map<ComponentName, String> getIconResMapFromXml(Resources res, String packageName) {
+ XmlPullParser parser = null;
+ InputStream inputStream = null;
+ Map<ComponentName, String> iconPackResources = new HashMap<ComponentName, String>();
+
+ try {
+ inputStream = res.getAssets().open("appfilter.xml");
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ parser = factory.newPullParser();
+ parser.setInput(inputStream, "UTF-8");
+ } catch (Exception e) {
+ // Catch any exception since we want to fall back to parsing the xml/
+ // resource in all cases
+ int resId = res.getIdentifier("appfilter", "xml", packageName);
+ if (resId != 0) {
+ parser = res.getXml(resId);
+ }
+ }
+
+ if (parser != null) {
+ try {
+ loadResourcesFromXmlParser(parser, iconPackResources);
+ return iconPackResources;
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ // Cleanup resources
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser) parser).close();
+ } else if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ // Application uses a different theme format (most likely launcher pro)
+ int arrayId = res.getIdentifier("theme_iconpack", "array", packageName);
+ if (arrayId == 0) {
+ arrayId = res.getIdentifier("icon_pack", "array", packageName);
+ }
+
+ if (arrayId != 0) {
+ String[] iconPack = res.getStringArray(arrayId);
+ ComponentName compName = null;
+ for (String entry : iconPack) {
+
+ if (TextUtils.isEmpty(entry)) {
+ continue;
+ }
+
+ String icon = entry;
+ entry = entry.replaceAll("_", ".");
+
+ compName = new ComponentName(entry.toLowerCase(), "");
+ iconPackResources.put(compName, icon);
+
+ int activityIndex = entry.lastIndexOf(".");
+ if (activityIndex <= 0 || activityIndex == entry.length() - 1) {
+ continue;
+ }
+
+ String iconPackage = entry.substring(0, activityIndex);
+ if (TextUtils.isEmpty(iconPackage)) {
+ continue;
+ }
+
+ String iconActivity = entry.substring(activityIndex + 1);
+ if (TextUtils.isEmpty(iconActivity)) {
+ continue;
+ }
+
+ // Store entries as lower case to ensure match
+ iconPackage = iconPackage.toLowerCase();
+ iconActivity = iconActivity.toLowerCase();
+
+ iconActivity = iconPackage + "." + iconActivity;
+ compName = new ComponentName(iconPackage, iconActivity);
+ iconPackResources.put(compName, icon);
+ }
+ }
+ return iconPackResources;
+ }
+
+ boolean isIconPackLoaded() {
+ return mLoadedIconPackResource != null &&
+ mLoadedIconPackName != null &&
+ mIconPackResourceMap != null;
+ }
+
+ private int getResourceIdForDrawable(String resource) {
+ int resId =
+ mLoadedIconPackResource.getIdentifier(resource, "drawable",mLoadedIconPackName);
+ return resId;
+ }
+
+ public int getResourceIdForActivityIcon(ActivityInfo info) {
+ if (!isIconPackLoaded()) {
+ return 0;
+ }
+ ComponentName compName = new ComponentName(info.packageName.toLowerCase(),
+ info.name.toLowerCase());
+ String drawable = mIconPackResourceMap.get(compName);
+ if (drawable != null) {
+ int resId = getResourceIdForDrawable(drawable);
+ if (resId != 0) return resId;
+ }
+
+ // Icon pack doesn't have an icon for the activity, fallback to package icon
+ compName = new ComponentName(info.packageName.toLowerCase(), "");
+ drawable = mIconPackResourceMap.get(compName);
+ if (drawable == null) {
+ return 0;
+ }
+ return getResourceIdForDrawable(drawable);
+ }
+
+ public int getResourceIdForApp(String pkgName) {
+ ActivityInfo info = new ActivityInfo();
+ info.packageName = pkgName;
+ info.name = "";
+ return getResourceIdForActivityIcon(info);
+ }
+
+ public Drawable getDrawableForActivity(ActivityInfo info) {
+ int id = getResourceIdForActivityIcon(info);
+ if (id == 0) return null;
+ return mLoadedIconPackResource.getDrawable(id, null, false);
+ }
+
+ public Drawable getDrawableForActivityWithDensity(ActivityInfo info, int density) {
+ int id = getResourceIdForActivityIcon(info);
+ if (id == 0) return null;
+ return mLoadedIconPackResource.getDrawableForDensity(id, density, null, false);
+ }
+
+ public static boolean shouldComposeIcon(ComposedIconInfo iconInfo) {
+ return iconInfo != null &&
+ (iconInfo.iconBacks != null || iconInfo.iconMask != 0 ||
+ iconInfo.iconUpon != 0 || iconInfo.colorFilter != null);
+ }
+
+ public static class IconCustomizer {
+ private static final Random sRandom = new Random();
+ private static final IThemeService sThemeService;
+
+ static {
+ sThemeService = IThemeService.Stub.asInterface(
+ ServiceManager.getService(Context.THEME_SERVICE));
+ }
+
+ public static Drawable getComposedIconDrawable(Drawable icon, Context context,
+ ComposedIconInfo iconInfo) {
+ final Resources res = context.getResources();
+ return getComposedIconDrawable(icon, res, iconInfo);
+ }
+
+ public static Drawable getComposedIconDrawable(Drawable icon, Resources res,
+ ComposedIconInfo iconInfo) {
+ if (iconInfo == null) return icon;
+ int back = 0;
+ if (iconInfo.iconBacks != null && iconInfo.iconBacks.length > 0) {
+ back = iconInfo.iconBacks[sRandom.nextInt(iconInfo.iconBacks.length)];
+ }
+ Bitmap bmp = createIconBitmap(icon, res, back, iconInfo.iconMask, iconInfo.iconUpon,
+ iconInfo.iconScale, iconInfo.iconSize, iconInfo.colorFilter);
+ return bmp != null ? new BitmapDrawable(res, bmp): null;
+ }
+
+ public static void getValue(Resources res, int resId, TypedValue outValue,
+ Drawable baseIcon) {
+ final String pkgName = res.getAssets().getAppName();
+ TypedValue tempValue = new TypedValue();
+ tempValue.setTo(outValue);
+ outValue.assetCookie = COMPOSED_ICON_COOKIE;
+ outValue.data = resId & (COMPOSED_ICON_COOKIE << 24 | 0x00ffffff);
+ outValue.string = getCachedIconPath(pkgName, resId, outValue.density);
+
+ if (!(new File(outValue.string.toString()).exists())) {
+ // compose the icon and cache it
+ final ComposedIconInfo iconInfo = res.getComposedIconInfo();
+ int back = 0;
+ if (iconInfo.iconBacks != null && iconInfo.iconBacks.length > 0) {
+ back = iconInfo.iconBacks[(outValue.string.hashCode() & 0x7fffffff)
+ % iconInfo.iconBacks.length];
+ }
+ Bitmap bmp = createIconBitmap(baseIcon, res, back, iconInfo.iconMask,
+ iconInfo.iconUpon, iconInfo.iconScale, iconInfo.iconSize,
+ iconInfo.colorFilter);
+ if (!cacheComposedIcon(bmp, getCachedIconName(pkgName, resId, outValue.density))) {
+ Log.w(TAG, "Unable to cache icon " + outValue.string);
+ // restore the original TypedValue
+ outValue.setTo(tempValue);
+ }
+ }
+ }
+
+ private static Bitmap createIconBitmap(Drawable icon, Resources res, int iconBack,
+ int iconMask, int iconUpon, float scale, int iconSize, float[] colorFilter) {
+ if (iconSize <= 0) return null;
+
+ final Canvas canvas = new Canvas();
+ canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+
+ int width = 0, height = 0;
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(iconSize);
+ painter.setIntrinsicHeight(iconSize);
+
+ // A PaintDrawable does not have an exact size
+ width = iconSize;
+ height = iconSize;
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(res.getDisplayMetrics());
+ }
+ canvas.setDensity(bitmap.getDensity());
+
+ // If the original size of the icon isn't greater
+ // than twice the size of recommended large icons
+ // respect the original size of the icon
+ // otherwise enormous icons can easily create
+ // OOM situations.
+ if ((bitmap.getWidth() < (iconSize * 2))
+ && (bitmap.getHeight() < (iconSize * 2))) {
+ width = bitmap.getWidth();
+ height = bitmap.getHeight();
+ } else {
+ width = iconSize;
+ height = iconSize;
+ }
+ } else if (icon instanceof VectorDrawable) {
+ width = height = iconSize;
+ }
+
+ if (width <= 0 || height <= 0) return null;
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ canvas.setBitmap(bitmap);
+
+ // Scale the original
+ Rect oldBounds = new Rect();
+ oldBounds.set(icon.getBounds());
+ icon.setBounds(0, 0, width, height);
+ canvas.save();
+ canvas.scale(scale, scale, width / 2, height / 2);
+ if (colorFilter != null) {
+ Paint p = null;
+ if (icon instanceof BitmapDrawable) {
+ p = ((BitmapDrawable) icon).getPaint();
+ } else if (icon instanceof PaintDrawable) {
+ p = ((PaintDrawable) icon).getPaint();
+ }
+ if (p != null) p.setColorFilter(new ColorMatrixColorFilter(colorFilter));
+ }
+ icon.draw(canvas);
+ canvas.restore();
+
+ // Mask off the original if iconMask is not null
+ if (iconMask != 0) {
+ Drawable mask = res.getDrawable(iconMask);
+ if (mask != null) {
+ mask.setBounds(icon.getBounds());
+ ((BitmapDrawable) mask).getPaint().setXfermode(
+ new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ mask.draw(canvas);
+ }
+ }
+ // Draw the iconBacks if not null and then the original (scaled and masked) icon on top
+ if (iconBack != 0) {
+ Drawable back = res.getDrawable(iconBack);
+ if (back != null) {
+ back.setBounds(icon.getBounds());
+ ((BitmapDrawable) back).getPaint().setXfermode(
+ new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
+ back.draw(canvas);
+ }
+ }
+ // Finally draw the foreground if one was supplied
+ if (iconUpon != 0) {
+ Drawable upon = res.getDrawable(iconUpon);
+ if (upon != null) {
+ upon.setBounds(icon.getBounds());
+ upon.draw(canvas);
+ }
+ }
+ icon.setBounds(oldBounds);
+ bitmap.setDensity(canvas.getDensity());
+
+ return bitmap;
+ }
+
+ private static boolean cacheComposedIcon(Bitmap bmp, String path) {
+ try {
+ return sThemeService.cacheComposedIcon(bmp, path);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to cache icon.", e);
+ }
+
+ return false;
+ }
+
+ private static String getCachedIconPath(String pkgName, int resId, int density) {
+ return String.format("%s/%s", ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR,
+ getCachedIconName(pkgName, resId, density));
+ }
+
+ private static String getCachedIconName(String pkgName, int resId, int density) {
+ return String.format("%s_%08x_%d.png", pkgName, resId, density);
+ }
+ }
+
+ public static class ColorFilterUtils {
+ private static final String TAG_FILTER = "filter";
+ private static final String FILTER_HUE = "hue";
+ private static final String FILTER_SATURATION = "saturation";
+ private static final String FILTER_INVERT = "invert";
+ private static final String FILTER_BRIGHTNESS = "brightness";
+ private static final String FILTER_CONTRAST = "contrast";
+ private static final String FILTER_ALPHA = "alpha";
+ private static final String FILTER_TINT = "tint";
+
+ private static final int MIN_HUE = -180;
+ private static final int MAX_HUE = 180;
+ private static final int MIN_SATURATION = 0;
+ private static final int MAX_SATURATION = 200;
+ private static final int MIN_BRIGHTNESS = 0;
+ private static final int MAX_BRIGHTNESS = 200;
+ private static final int MIN_CONTRAST = -100;
+ private static final int MAX_CONTRAST = 100;
+ private static final int MIN_ALPHA = 0;
+ private static final int MAX_ALPHA = 100;
+
+ public static boolean parseIconFilter(XmlPullParser parser, Builder builder)
+ throws IOException, XmlPullParserException {
+ String tag = parser.getName();
+ if (!TAG_FILTER.equals(tag)) return false;
+
+ int attrCount = parser.getAttributeCount();
+ String attrName;
+ String attr = null;
+ int intValue;
+ while (attrCount-- > 0) {
+ attrName = parser.getAttributeName(attrCount);
+ if (attrName.equals("name")) {
+ attr = parser.getAttributeValue(attrCount);
+ }
+ }
+ String content = parser.nextText();
+ if (attr != null && content != null && content.length() > 0) {
+ content = content.trim();
+ if (FILTER_HUE.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 0),MIN_HUE, MAX_HUE);
+ builder.hue(intValue);
+ } else if (FILTER_SATURATION.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 100),
+ MIN_SATURATION, MAX_SATURATION);
+ builder.saturate(intValue);
+ } else if (FILTER_INVERT.equalsIgnoreCase(attr)) {
+ if ("true".equalsIgnoreCase(content)) {
+ builder.invertColors();
+ }
+ } else if (FILTER_BRIGHTNESS.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 100),
+ MIN_BRIGHTNESS, MAX_BRIGHTNESS);
+ builder.brightness(intValue);
+ } else if (FILTER_CONTRAST.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 0),
+ MIN_CONTRAST, MAX_CONTRAST);
+ builder.contrast(intValue);
+ } else if (FILTER_ALPHA.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 100), MIN_ALPHA, MAX_ALPHA);
+ builder.alpha(intValue);
+ } else if (FILTER_TINT.equalsIgnoreCase(attr)) {
+ try {
+ intValue = Color.parseColor(content);
+ builder.tint(intValue);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Cannot apply tint, invalid argument: " + content);
+ }
+ }
+ }
+ return true;
+ }
+
+ private static int getInt(String value, int defaultValue) {
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ private static int clampValue(int value, int min, int max) {
+ return Math.min(max, Math.max(min, value));
+ }
+
+ /**
+ * See the following links for reference
+ * http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953
+ * http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html
+ * @param value
+ */
+ public static ColorMatrix adjustHue(float value) {
+ ColorMatrix cm = new ColorMatrix();
+ value = value / 180 * (float) Math.PI;
+ if (value != 0) {
+ float cosVal = (float) Math.cos(value);
+ float sinVal = (float) Math.sin(value);
+ float lumR = 0.213f;
+ float lumG = 0.715f;
+ float lumB = 0.072f;
+ float[] mat = new float[]{
+ lumR + cosVal * (1 - lumR) + sinVal * (-lumR),
+ lumG + cosVal * (-lumG) + sinVal * (-lumG),
+ lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0,
+ lumR + cosVal * (-lumR) + sinVal * (0.143f),
+ lumG + cosVal * (1 - lumG) + sinVal * (0.140f),
+ lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0,
+ lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)),
+ lumG + cosVal * (-lumG) + sinVal * (lumG),
+ lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0,
+ 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 1};
+ cm.set(mat);
+ }
+ return cm;
+ }
+
+ public static ColorMatrix adjustSaturation(float saturation) {
+ saturation = saturation / 100;
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(saturation);
+
+ return cm;
+ }
+
+ public static ColorMatrix invertColors() {
+ float[] matrix = {
+ -1, 0, 0, 0, 255, //red
+ 0, -1, 0, 0, 255, //green
+ 0, 0, -1, 0, 255, //blue
+ 0, 0, 0, 1, 0 //alpha
+ };
+
+ return new ColorMatrix(matrix);
+ }
+
+ public static ColorMatrix adjustBrightness(float brightness) {
+ brightness = brightness / 100;
+ ColorMatrix cm = new ColorMatrix();
+ cm.setScale(brightness, brightness, brightness, 1);
+
+ return cm;
+ }
+
+ public static ColorMatrix adjustContrast(float contrast) {
+ contrast = contrast / 100 + 1;
+ float o = (-0.5f * contrast + 0.5f) * 255;
+ float[] matrix = {
+ contrast, 0, 0, 0, o, //red
+ 0, contrast, 0, 0, o, //green
+ 0, 0, contrast, 0, o, //blue
+ 0, 0, 0, 1, 0 //alpha
+ };
+
+ return new ColorMatrix(matrix);
+ }
+
+ public static ColorMatrix adjustAlpha(float alpha) {
+ alpha = alpha / 100;
+ ColorMatrix cm = new ColorMatrix();
+ cm.setScale(1, 1, 1, alpha);
+
+ return cm;
+ }
+
+ public static ColorMatrix applyTint(int color) {
+ float alpha = Color.alpha(color) / 255f;
+ float red = Color.red(color) * alpha;
+ float green = Color.green(color) * alpha;
+ float blue = Color.blue(color) * alpha;
+
+ float[] matrix = {
+ 1, 0, 0, 0, red, //red
+ 0, 1, 0, 0, green, //green
+ 0, 0, 1, 0, blue, //blue
+ 0, 0, 0, 1, 0 //alpha
+ };
+
+ return new ColorMatrix(matrix);
+ }
+
+ public static class Builder {
+ private List<ColorMatrix> mMatrixList;
+
+ public Builder() {
+ mMatrixList = new ArrayList<ColorMatrix>();
+ }
+
+ public Builder hue(float value) {
+ mMatrixList.add(adjustHue(value));
+ return this;
+ }
+
+ public Builder saturate(float saturation) {
+ mMatrixList.add(adjustSaturation(saturation));
+ return this;
+ }
+
+ public Builder brightness(float brightness) {
+ mMatrixList.add(adjustBrightness(brightness));
+ return this;
+ }
+
+ public Builder contrast(float contrast) {
+ mMatrixList.add(adjustContrast(contrast));
+ return this;
+ }
+
+ public Builder alpha(float alpha) {
+ mMatrixList.add(adjustAlpha(alpha));
+ return this;
+ }
+
+ public Builder invertColors() {
+ mMatrixList.add(ColorFilterUtils.invertColors());
+ return this;
+ }
+
+ public Builder tint(int color) {
+ mMatrixList.add(applyTint(color));
+ return this;
+ }
+
+ public ColorMatrix build() {
+ if (mMatrixList == null || mMatrixList.size() == 0) return null;
+
+ ColorMatrix colorMatrix = new ColorMatrix();
+ for (ColorMatrix cm : mMatrixList) {
+ colorMatrix.postConcat(cm);
+ }
+ return colorMatrix;
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index aa98e973855..c8aa72053eb 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -531,7 +531,8 @@ public final class LoadedApk {
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
- mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
+ mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this,
+ mainThread.getSystemContext(), mPackageName);
}
return mResources;
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1691d8e28f5..26f9887458e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -18,21 +18,36 @@ package android.app;
import static android.app.ActivityThread.DEBUG_CONFIGURATION;
+import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ThemeUtils;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
import android.content.res.Resources;
import android.content.res.ResourcesKey;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
import java.lang.ref.WeakReference;
+import java.util.List;
import java.util.Locale;
/** @hide */
@@ -49,6 +64,7 @@ public class ResourcesManager {
= new ArrayMap<DisplayAdjustments, DisplayMetrics>();
CompatibilityInfo mResCompatibilityInfo;
+ static IPackageManager sPackageManager;
Configuration mResConfiguration;
final Configuration mTmpConfig = new Configuration();
@@ -150,10 +166,13 @@ public class ResourcesManager {
* @param token the application token for determining stack bounds.
*/
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
- String[] overlayDirs, String[] libDirs, int displayId,
- Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
+ String[] overlayDirs, String[] libDirs, int displayId, String packageName,
+ Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token,
+ Context context) {
final float scale = compatInfo.applicationScale;
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
+ final boolean isThemeable = compatInfo.isThemeable;
+ ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
+ isThemeable, token);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
@@ -178,6 +197,8 @@ public class ResourcesManager {
//}
AssetManager assets = new AssetManager();
+ assets.setAppName(packageName);
+ assets.setThemeSupport(compatInfo.isThemeable);
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
@@ -197,7 +218,7 @@ public class ResourcesManager {
if (overlayDirs != null) {
for (String idmapPath : overlayDirs) {
- assets.addOverlayPath(idmapPath);
+ assets.addOverlayPath(idmapPath, null, null, null, null);
}
}
@@ -226,7 +247,29 @@ public class ResourcesManager {
} else {
config = getConfiguration();
}
+
+ boolean iconsAttached = false;
+ /* Attach theme information to the resulting AssetManager when appropriate. */
+ if (compatInfo.isThemeable && config != null && !context.getPackageManager().isSafeMode()) {
+ if (config.themeConfig == null) {
+ try {
+ config.themeConfig = ThemeConfig.getBootTheme(context.getContentResolver());
+ } catch (Exception e) {
+ Slog.d(TAG, "ThemeConfig.getBootTheme failed, falling back to system theme", e);
+ config.themeConfig = ThemeConfig.getSystemTheme();
+ }
+ }
+
+ if (config.themeConfig != null) {
+ attachThemeAssets(assets, config.themeConfig);
+ attachCommonAssets(assets, config.themeConfig);
+ iconsAttached = attachIconAssets(assets, config.themeConfig);
+ }
+ }
+
r = new Resources(assets, dm, config, compatInfo, token);
+ if (iconsAttached) setActivityIcons(r);
+
if (false) {
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale="
@@ -249,6 +292,115 @@ public class ResourcesManager {
}
}
+ /**
+ * Creates the top level Resources for applications with the given compatibility info.
+ *
+ * @param resDir the resource directory.
+ * @param compatInfo the compability info. Must not be null.
+ * @param token the application token for determining stack bounds.
+ *
+ * @hide
+ */
+ public Resources getTopLevelThemedResources(String resDir, int displayId,
+ String packageName,
+ String themePackageName,
+ CompatibilityInfo compatInfo, IBinder token) {
+ Resources r;
+
+ AssetManager assets = new AssetManager();
+ assets.setAppName(packageName);
+ assets.setThemeSupport(true);
+ if (assets.addAssetPath(resDir) == 0) {
+ return null;
+ }
+
+ //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
+ DisplayMetrics dm = getDisplayMetricsLocked(displayId);
+ Configuration config;
+ boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ if (!isDefaultDisplay) {
+ config = new Configuration(getConfiguration());
+ applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
+ } else {
+ config = getConfiguration();
+ }
+
+ /* Attach theme information to the resulting AssetManager when appropriate. */
+ ThemeConfig.Builder builder = new ThemeConfig.Builder();
+ builder.defaultOverlay(themePackageName);
+ builder.defaultIcon(themePackageName);
+ builder.defaultFont(themePackageName);
+
+ ThemeConfig themeConfig = builder.build();
+ attachThemeAssets(assets, themeConfig);
+ attachCommonAssets(assets, themeConfig);
+ attachIconAssets(assets, themeConfig);
+
+ r = new Resources(assets, dm, config, compatInfo, token);
+ setActivityIcons(r);
+
+ return r;
+ }
+
+ /**
+ * Creates a map between an activity & app's icon ids to its component info. This map
+ * is then stored in the resource object.
+ * When resource.getDrawable(id) is called it will check this mapping and replace
+ * the id with the themed resource id if one is available
+ * @param context
+ * @param pkgName
+ * @param r
+ */
+ private void setActivityIcons(Resources r) {
+ SparseArray<PackageItemInfo> iconResources = new SparseArray<PackageItemInfo>();
+ String pkgName = r.getAssets().getAppName();
+ PackageInfo pkgInfo = null;
+ ApplicationInfo appInfo = null;
+
+ try {
+ pkgInfo = getPackageManager().getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Unable to get pkg " + pkgName, e1);
+ return;
+ }
+
+ final ThemeConfig themeConfig = r.getConfiguration().themeConfig;
+ if (pkgName != null && themeConfig != null &&
+ pkgName.equals(themeConfig.getIconPackPkgName())) {
+ return;
+ }
+
+ //Map application icon
+ if (pkgInfo != null && pkgInfo.applicationInfo != null) {
+ appInfo = pkgInfo.applicationInfo;
+ if (appInfo.themedIcon != 0 || iconResources.get(appInfo.icon) == null) {
+ iconResources.put(appInfo.icon, appInfo);
+ }
+ }
+
+ //Map activity icons.
+ if (pkgInfo != null && pkgInfo.activities != null) {
+ for (ActivityInfo ai : pkgInfo.activities) {
+ if (ai.icon != 0 && (ai.themedIcon != 0 || iconResources.get(ai.icon) == null)) {
+ iconResources.put(ai.icon, ai);
+ } else if (appInfo != null && appInfo.icon != 0 &&
+ (ai.themedIcon != 0 || iconResources.get(appInfo.icon) == null)) {
+ iconResources.put(appInfo.icon, ai);
+ }
+ }
+ }
+
+ r.setIconResources(iconResources);
+ final IPackageManager pm = getPackageManager();
+ try {
+ ComposedIconInfo iconInfo = pm.getComposedIconInfo();
+ r.setComposedIconInfo(iconInfo);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Failed to retrieve ComposedIconInfo", e);
+ }
+ }
+
public final boolean applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
if (mResConfiguration == null) {
@@ -293,6 +445,22 @@ public class ResourcesManager {
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
DisplayMetrics dm = defaultDisplayMetrics;
final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
+ boolean themeChanged = (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0;
+ if (themeChanged) {
+ AssetManager am = r.getAssets();
+ if (am.hasThemeSupport()) {
+ r.setIconResources(null);
+ r.setComposedIconInfo(null);
+ detachThemeAssets(am);
+ if (config.themeConfig != null) {
+ attachThemeAssets(am, config.themeConfig);
+ attachCommonAssets(am, config.themeConfig);
+ if (attachIconAssets(am, config.themeConfig)) {
+ setActivityIcons(r);
+ }
+ }
+ }
+ }
if (!isDefaultDisplay || hasOverrideConfiguration) {
if (tmpConfig == null) {
tmpConfig = new Configuration();
@@ -309,6 +477,9 @@ public class ResourcesManager {
} else {
r.updateConfiguration(config, dm, compat);
}
+ if (themeChanged) {
+ r.updateStringCache();
+ }
//Slog.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -320,4 +491,234 @@ public class ResourcesManager {
return changes != 0;
}
+ public static IPackageManager getPackageManager() {
+ if (sPackageManager != null) {
+ return sPackageManager;
+ }
+ IBinder b = ServiceManager.getService("package");
+ sPackageManager = IPackageManager.Stub.asInterface(b);
+ return sPackageManager;
+ }
+
+
+ /**
+ * Attach the necessary theme asset paths and meta information to convert an
+ * AssetManager to being globally "theme-aware".
+ *
+ * @param assets
+ * @param theme
+ * @return true if the AssetManager is now theme-aware; false otherwise.
+ * This can fail, for example, if the theme package has been been
+ * removed and the theme manager has yet to revert formally back to
+ * the framework default.
+ */
+ private boolean attachThemeAssets(AssetManager assets, ThemeConfig theme) {
+ PackageInfo piTheme = null;
+ PackageInfo piTarget = null;
+ PackageInfo piAndroid = null;
+
+ // Some apps run in process of another app (eg keyguard/systemUI) so we must get the
+ // package name from the res tables. The 0th base package name will be the android group.
+ // The 1st base package name will be the app group if one is attached. Check if it is there
+ // first or else the system will crash!
+ String basePackageName = null;
+ String resourcePackageName = null;
+ int count = assets.getBasePackageCount();
+ if (count > 1) {
+ basePackageName = assets.getBasePackageName(1);
+ resourcePackageName = assets.getBaseResourcePackageName(1);
+ } else if (count == 1) {
+ basePackageName = assets.getBasePackageName(0);
+ } else {
+ return false;
+ }
+
+ try {
+ piTheme = getPackageManager().getPackageInfo(
+ theme.getOverlayPkgNameForApp(basePackageName), 0,
+ UserHandle.getCallingUserId());
+ piTarget = getPackageManager().getPackageInfo(
+ basePackageName, 0, UserHandle.getCallingUserId());
+
+ // Handle special case where a system app (ex trebuchet) may have had its pkg name
+ // renamed during an upgrade. basePackageName would be the manifest value which will
+ // fail on getPackageInfo(). resource pkg is assumed to have the original name
+ if (piTarget == null && resourcePackageName != null) {
+ piTarget = getPackageManager().getPackageInfo(resourcePackageName,
+ 0, UserHandle.getCallingUserId());
+ }
+ piAndroid = getPackageManager().getPackageInfo("android", 0,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ }
+
+ if (piTheme == null || piTheme.applicationInfo == null ||
+ piTarget == null || piTarget.applicationInfo == null ||
+ piAndroid == null || piAndroid.applicationInfo == null ||
+ piTheme.mOverlayTargets == null) {
+ return false;
+ }
+
+ String themePackageName = basePackageName;
+ String themePath = piTheme.applicationInfo.publicSourceDir;
+ if (!piTarget.isThemeApk && piTheme.mOverlayTargets.contains(basePackageName)) {
+ String targetPackagePath = piTarget.applicationInfo.sourceDir;
+ String prefixPath = ThemeUtils.getOverlayPathToTarget(basePackageName);
+
+ String resCachePath = ThemeUtils.getTargetCacheDir(piTarget.packageName, piTheme);
+ String resApkPath = resCachePath + "/resources.apk";
+ String idmapPath = ThemeUtils.getIdmapPath(piTarget.packageName, piTheme.packageName);
+ int cookie = assets.addOverlayPath(idmapPath, themePath, resApkPath,
+ targetPackagePath, prefixPath);
+
+ if (cookie != 0) {
+ assets.setThemePackageName(basePackageName);
+ assets.addThemeCookie(cookie);
+ }
+ }
+
+ if (!piTarget.isThemeApk && piTheme.mOverlayTargets.contains("android")) {
+ String resCachePath= ThemeUtils.getTargetCacheDir(piAndroid.packageName, piTheme);
+ String prefixPath = ThemeUtils.getOverlayPathToTarget(piAndroid.packageName);
+ String targetPackagePath = piAndroid.applicationInfo.publicSourceDir;
+ String resApkPath = resCachePath + "/resources.apk";
+ String idmapPath = ThemeUtils.getIdmapPath("android", piTheme.packageName);
+ int cookie = assets.addOverlayPath(idmapPath, themePath,
+ resApkPath, targetPackagePath, prefixPath);
+ if (cookie != 0) {
+ assets.setThemePackageName(themePackageName);
+ assets.addThemeCookie(cookie);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Attach the necessary icon asset paths. Icon assets should be in a different
+ * namespace than the standard 0x7F.
+ *
+ * @param assets
+ * @param theme
+ * @return true if succes, false otherwise
+ */
+ private boolean attachIconAssets(AssetManager assets, ThemeConfig theme) {
+ PackageInfo piIcon = null;
+ try {
+ piIcon = getPackageManager().getPackageInfo(theme.getIconPackPkgName(), 0,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ }
+
+ if (piIcon == null || piIcon.applicationInfo == null) {
+ return false;
+ }
+
+ String iconPkg = theme.getIconPackPkgName();
+ if (iconPkg != null && !iconPkg.isEmpty()) {
+ String themeIconPath = piIcon.applicationInfo.publicSourceDir;
+ String prefixPath = ThemeUtils.ICONS_PATH;
+ String iconDir = ThemeUtils.getIconPackDir(iconPkg);
+ String resTablePath = iconDir + "/resources.arsc";
+ String resApkPath = iconDir + "/resources.apk";
+
+ // Legacy Icon packs have everything in their APK
+ if (piIcon.isLegacyIconPackApk) {
+ prefixPath = "";
+ resApkPath = "";
+ resTablePath = "";
+ }
+
+ int cookie = assets.addIconPath(themeIconPath, resApkPath, prefixPath,
+ Resources.THEME_ICON_PKG_ID);
+ if (cookie != 0) {
+ assets.setIconPackCookie(cookie);
+ assets.setIconPackageName(iconPkg);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Attach the necessary common asset paths. Common assets should be in a different
+ * namespace than the standard 0x7F.
+ *
+ * @param assets
+ * @param theme
+ * @return true if succes, false otherwise
+ */
+ private boolean attachCommonAssets(AssetManager assets, ThemeConfig theme) {
+ // Some apps run in process of another app (eg keyguard/systemUI) so we must get the
+ // package name from the res tables. The 0th base package name will be the android group.
+ // The 1st base package name will be the app group if one is attached. Check if it is there
+ // first or else the system will crash!
+ String basePackageName;
+ int count = assets.getBasePackageCount();
+ if (count > 1) {
+ basePackageName = assets.getBasePackageName(1);
+ } else if (count == 1) {
+ basePackageName = assets.getBasePackageName(0);
+ } else {
+ return false;
+ }
+
+ PackageInfo piTheme = null;
+ try {
+ piTheme = getPackageManager().getPackageInfo(
+ theme.getOverlayPkgNameForApp(basePackageName), 0,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ }
+
+ if (piTheme == null || piTheme.applicationInfo == null) {
+ return false;
+ }
+
+ String themePackageName =
+ ThemeUtils.getCommonPackageName(piTheme.applicationInfo.packageName);
+ if (themePackageName != null && !themePackageName.isEmpty()) {
+ String themePath = piTheme.applicationInfo.publicSourceDir;
+ String prefixPath = ThemeUtils.COMMON_RES_PATH;
+ String resCachePath =
+ ThemeUtils.getTargetCacheDir(ThemeUtils.COMMON_RES_TARGET, piTheme);
+ String resApkPath = resCachePath + "/resources.apk";
+ int cookie = assets.addCommonOverlayPath(themePath, resApkPath,
+ prefixPath);
+ if (cookie != 0) {
+ assets.setCommonResCookie(cookie);
+ assets.setCommonResPackageName(themePackageName);
+ }
+ }
+
+ return true;
+ }
+
+ private void detachThemeAssets(AssetManager assets) {
+ String themePackageName = assets.getThemePackageName();
+ String iconPackageName = assets.getIconPackageName();
+ String commonResPackageName = assets.getCommonResPackageName();
+
+ //Remove Icon pack if it exists
+ if (!TextUtils.isEmpty(iconPackageName) && assets.getIconPackCookie() > 0) {
+ assets.removeOverlayPath(iconPackageName, assets.getIconPackCookie());
+ assets.setIconPackageName(null);
+ assets.setIconPackCookie(0);
+ }
+ //Remove common resources if it exists
+ if (!TextUtils.isEmpty(commonResPackageName) && assets.getCommonResCookie() > 0) {
+ assets.removeOverlayPath(commonResPackageName, assets.getCommonResCookie());
+ assets.setCommonResPackageName(null);
+ assets.setCommonResCookie(0);
+ }
+ final List<Integer> themeCookies = assets.getThemeCookies();
+ if (!TextUtils.isEmpty(themePackageName) && !themeCookies.isEmpty()) {
+ // remove overlays in reverse order
+ for (int i = themeCookies.size() - 1; i >= 0; i--) {
+ assets.removeOverlayPath(themePackageName, themeCookies.get(i));
+ }
+ }
+ assets.getThemeCookies().clear();
+ assets.setThemePackageName(null);
+ }
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8bfe6d3859a..1e52d86ff76 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -32,6 +32,7 @@ import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@@ -226,9 +227,10 @@ public class WallpaperManager {
private IWallpaperManager mService;
private Bitmap mWallpaper;
private Bitmap mDefaultWallpaper;
+ private Bitmap mKeyguardWallpaper;
private static final int MSG_CLEAR_WALLPAPER = 1;
-
+
Globals(Looper looper) {
IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
mService = IWallpaperManager.Stub.asInterface(b);
@@ -246,6 +248,12 @@ public class WallpaperManager {
}
}
+ public void onKeyguardWallpaperChanged() {
+ synchronized (this) {
+ mKeyguardWallpaper = null;
+ }
+ }
+
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
synchronized (this) {
if (mWallpaper != null) {
@@ -272,6 +280,23 @@ public class WallpaperManager {
}
}
+ /**
+ * @hide
+ */
+ public Bitmap peekKeyguardWallpaperBitmap(Context context) {
+ synchronized (this) {
+ if (mKeyguardWallpaper != null) {
+ return mKeyguardWallpaper;
+ }
+ try {
+ mKeyguardWallpaper = getCurrentKeyguardWallpaperLocked(context);
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "No memory load current keyguard wallpaper", e);
+ }
+ return mKeyguardWallpaper;
+ }
+ }
+
public void forgetLoadedWallpaper() {
synchronized (this) {
mWallpaper = null;
@@ -308,7 +333,33 @@ public class WallpaperManager {
}
return null;
}
-
+
+ private Bitmap getCurrentKeyguardWallpaperLocked(Context context) {
+ try {
+ Bundle params = new Bundle();
+ ParcelFileDescriptor fd = mService.getKeyguardWallpaper(this, params);
+ if (fd != null) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ Bitmap bm = BitmapFactory.decodeFileDescriptor(
+ fd.getFileDescriptor(), null, options);
+ return bm;
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "Can't decode file", e);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ return null;
+ }
+
private Bitmap getDefaultWallpaperLocked(Context context) {
InputStream is = openDefaultWallpaper(context);
if (is != null) {
@@ -327,6 +378,18 @@ public class WallpaperManager {
}
return null;
}
+
+ /** @hide */
+ public void clearKeyguardWallpaper() {
+ synchronized (this) {
+ try {
+ mService.clearKeyguardWallpaper();
+ } catch (RemoteException e) {
+ // ignore
+ }
+ mKeyguardWallpaper = null;
+ }
+ }
}
private static final Object sSync = new Object[0];
@@ -586,6 +649,15 @@ public class WallpaperManager {
return null;
}
+ /** @hide */
+ public Drawable getFastKeyguardDrawable() {
+ Bitmap bm = sGlobals.peekKeyguardWallpaperBitmap(mContext);
+ if (bm != null) {
+ return new FastBitmapDrawable(bm);
+ }
+ return null;
+ }
+
/**
* Like {@link #getFastDrawable()}, but if there is no wallpaper set,
* a null pointer is returned.
@@ -611,6 +683,13 @@ public class WallpaperManager {
}
/**
+ * @hide
+ */
+ public Bitmap getKeyguardBitmap() {
+ return sGlobals.peekKeyguardWallpaperBitmap(mContext);
+ }
+
+ /**
* Remove all internal references to the last loaded wallpaper. Useful
* for apps that want to reduce memory usage when they only temporarily
* need to have the wallpaper. After calling, the next request for the
@@ -771,6 +850,35 @@ public class WallpaperManager {
}
/**
+ * @param bitmap
+ * @throws IOException
+ * @hide
+ */
+ public void setKeyguardBitmap(Bitmap bitmap) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setKeyguardWallpaper(null);
+ if (fd == null) {
+ return;
+ }
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
+ } finally {
+ if (fos != null) {
+ fos.close();
+ }
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ /**
* Change the current system wallpaper to a specific byte stream. The
* give InputStream is copied into persistent storage and will now be
* used as the wallpaper. Currently it must be either a JPEG or PNG
@@ -809,6 +917,33 @@ public class WallpaperManager {
}
}
+ /**
+ * @hide
+ */
+ public void setKeyguardStream(InputStream data) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setKeyguardWallpaper(null);
+ if (fd == null) {
+ return;
+ }
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ setWallpaper(data, fos);
+ } finally {
+ if (fos != null) {
+ fos.close();
+ }
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
private void setWallpaper(InputStream data, FileOutputStream fos)
throws IOException {
byte[] buffer = new byte[32768];
@@ -1029,7 +1164,29 @@ public class WallpaperManager {
mWallpaperXStep = xStep;
mWallpaperYStep = yStep;
}
-
+
+ /** @hide */
+ public int getLastWallpaperX() {
+ try {
+ return WindowManagerGlobal.getWindowSession().getLastWallpaperX();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+
+ return -1;
+ }
+
+ /** @hide */
+ public int getLastWallpaperY() {
+ try {
+ return WindowManagerGlobal.getWindowSession().getLastWallpaperY();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+
+ return -1;
+ }
+
/**
* Send an arbitrary command to the current active wallpaper.
*
@@ -1086,7 +1243,25 @@ public class WallpaperManager {
* wallpaper.
*/
public void clear() throws IOException {
- setStream(openDefaultWallpaper(mContext));
+ clear(true);
+ }
+
+ /** @hide */
+ public void clear(boolean setToDefault) throws IOException {
+ if (setToDefault) {
+ setStream(openDefaultWallpaper(mContext));
+ } else {
+ Bitmap blackBmp = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
+ blackBmp.setPixel(0, 0, mContext.getResources().getColor(android.R.color.black));
+ setBitmap(blackBmp);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void clearKeyguardWallpaper() {
+ sGlobals.clearKeyguardWallpaper();
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 57b6df14679..9831d915107 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2706,6 +2706,16 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.res.ThemeManager} for accessing theme service.
+ *
+ * @see #getSystemService
+ * @see android.content.res.ThemeManager
+ * @hide
+ */
+ public static final String THEME_SERVICE = "themes";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.nfc.NfcManager} for using NFC.
*
* @see #getSystemService
@@ -3368,6 +3378,26 @@ public abstract class Context {
int flags) throws PackageManager.NameNotFoundException;
/**
+ * Similar to {@link #createPackageContext(String, int)}, but with a
+ * different {@link UserHandle}. For example, {@link #getContentResolver()}
+ * will open any {@link Uri} as the given user. A theme package can be
+ * specified which will be used when adding resources to this context
+ *
+ * @hide
+ */
+ public abstract Context createPackageContextAsUser(
+ String packageName, String themePackageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Creates a context given an {@link android.content.pm.ApplicationInfo}.
+ *
+ * @hide
+ */
+ public abstract Context createApplicationContext(ApplicationInfo application,
+ String themePackageName, int flags) throws PackageManager.NameNotFoundException;
+
+ /**
* Get the userId associated with this context
* @return user id
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index ad7c350aa24..837208619d7 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -668,7 +668,20 @@ public class ContextWrapper extends Context {
/** @hide */
public Context createApplicationContext(ApplicationInfo application,
int flags) throws PackageManager.NameNotFoundException {
- return mBase.createApplicationContext(application, flags);
+ return createApplicationContext(application, null, flags);
+ }
+
+ /** @hide */
+ public Context createApplicationContext(ApplicationInfo application,
+ String themePackageName, int flags) throws PackageManager.NameNotFoundException {
+ return mBase.createApplicationContext(application, themePackageName, flags);
+ }
+
+ /** @hide */
+ @Override
+ public Context createPackageContextAsUser(String packageName, String themePackageName,
+ int flags, UserHandle user) throws PackageManager.NameNotFoundException {
+ return mBase.createPackageContextAsUser(packageName, themePackageName, flags, user);
}
/** @hide */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02c7395cd42..21fe8d60cdb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 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.
@@ -1847,6 +1848,15 @@ public class Intent implements Parcelable, Cloneable {
*/
@Deprecated @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
+
+ /**
+ * Broadcast Action: The current keyguard wallpaper configuration
+ * has changed and should be re-read.
+ * {@hide}
+ */
+ public static final String ACTION_KEYGUARD_WALLPAPER_CHANGED =
+ "android.intent.action.KEYGUARD_WALLPAPER_CHANGED";
+
/**
* Broadcast Action: The current device {@link android.content.res.Configuration}
* (orientation, locale, etc) has changed. When such a change happens, the
@@ -2611,6 +2621,21 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.QUICK_CLOCK";
/**
+ * Broadcast Action: Indicate that unrecoverable error happened during app launch.
+ * Could indicate that curently applied theme is malicious.
+ * @hide
+ */
+ public static final String ACTION_APP_FAILURE =
+ "com.tmobile.intent.action.APP_FAILURE";
+
+ /**
+ * Broadcast Action: Request to reset the unrecoverable errors count to 0.
+ * @hide
+ */
+ public static final String ACTION_APP_FAILURE_RESET =
+ "com.tmobile.intent.action.APP_FAILURE_RESET";
+
+ /**
* Activity Action: Shows the brightness setting dialog.
* @hide
*/
@@ -2706,6 +2731,19 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
/**
+ * Broadcast Action: A theme's resources were cached. Includes two extra fields,
+ * {@link #EXTRA_THEME_PACKAGE_NAME}, containing the package name of the theme that was
+ * processed, and {@link #EXTRA_THEME_RESULT}, containing the result code.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.</p>
+ *
+ * @hide
+ */
+ public static final String ACTION_THEME_RESOURCES_CACHED =
+ "android.intent.action.THEME_RESOURCES_CACHED";
+
+ /**
* Activity Action: Allow the user to pick a directory subtree. When
* invoked, the system will display the various {@link DocumentsProvider}
* instances installed on the device, letting the user navigate through
@@ -2923,6 +2961,14 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
+ /**
+ * Used to indicate that a theme package has been installed or un-installed.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE =
+ "com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Application launch intent categories (see addCategory()).
@@ -3432,6 +3478,26 @@ public class Intent implements Parcelable, Cloneable {
/** {@hide} */
public static final String EXTRA_REASON = "android.intent.extra.REASON";
+ /**
+ * Extra for {@link #ACTION_THEME_RESOURCES_CACHED} that provides the return value
+ * from processThemeResources. A value of 0 indicates a successful caching of resources.
+ * Error results are:
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_AAPT_ERROR}
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_IDMAP_ERROR}
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_UNKNOWN_ERROR}
+ *
+ * @hide
+ */
+ public static final String EXTRA_THEME_RESULT = "android.intent.extra.RESULT";
+
+ /**
+ * Extra for {@link #ACTION_THEME_RESOURCES_CACHED} that provides the package name of the
+ * theme that was processed.
+ *
+ * @hide
+ */
+ public static final String EXTRA_THEME_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 641f843ea2d..1ff75531a14 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2007 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.
* You may obtain a copy of the License at
@@ -492,6 +493,10 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int CONFIG_ORIENTATION = 0x0080;
/**
+ * @hide
+ */
+ public static final int CONFIG_THEME_RESOURCE = 0x008000;
+ /**
* Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the screen layout. Set from the
* {@link android.R.attr#configChanges} attribute.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 76d71c17845..5e342c9c5f0 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 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.
@@ -588,6 +589,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public int installLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
/**
+ * Is given application theme agnostic, i.e. behaves properly when default theme is changed.
+ * {@hide}
+ */
+ public boolean isThemeable = false;
+
+ /**
* When true, indicates that any one component within this application is
* protected.
* @hide
@@ -717,6 +724,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
uiOptions = orig.uiOptions;
backupAgentName = orig.backupAgentName;
protect = orig.protect;
+ isThemeable = orig.isThemeable;
}
@@ -768,6 +776,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(descriptionRes);
dest.writeInt(uiOptions);
dest.writeInt(protect ? 1 : 0);
+ dest.writeInt(isThemeable ? 1 : 0);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -818,6 +827,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
descriptionRes = source.readInt();
uiOptions = source.readInt();
protect = source.readInt() != 0;
+ isThemeable = source.readInt() != 0;
}
/**
diff --git a/core/java/android/content/pm/BaseThemeInfo.java b/core/java/android/content/pm/BaseThemeInfo.java
new file mode 100644
index 00000000000..8ece42d1933
--- /dev/null
+++ b/core/java/android/content/pm/BaseThemeInfo.java
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ * 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.pm;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+import android.util.AttributeSet;
+import android.content.res.Resources;
+
+/**
+ * @hide
+ */
+public class BaseThemeInfo implements Parcelable {
+ /**
+ * The theme id, which does not change when the theme is modified.
+ * Specifies an Android UI Style using style name.
+ *
+ * @see themeId attribute
+ *
+ */
+ public String themeId;
+
+ /**
+ * The name of the theme (as displayed by UI).
+ *
+ * @see name attribute
+ *
+ */
+ public String name;
+
+ /**
+ * The author name of the theme package.
+ *
+ * @see author attribute
+ *
+ */
+ public String author;
+
+ /*
+ * Describe the kinds of special objects contained in this Parcelable's
+ * marshalled representation.
+ *
+ * @return a bitmask indicating the set of special object types marshalled
+ * by the Parcelable.
+ *
+ * @see android.os.Parcelable#describeContents()
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /*
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ *
+ * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(themeId);
+ dest.writeString(name);
+ dest.writeString(author);
+ }
+
+ /** @hide */
+ public static final Parcelable.Creator<BaseThemeInfo> CREATOR
+ = new Parcelable.Creator<BaseThemeInfo>() {
+ public BaseThemeInfo createFromParcel(Parcel source) {
+ return new BaseThemeInfo(source);
+ }
+
+ public BaseThemeInfo[] newArray(int size) {
+ return new BaseThemeInfo[size];
+ }
+ };
+
+ /** @hide */
+ public final String getResolvedString(Resources res, AttributeSet attrs, int index) {
+ int resId = attrs.getAttributeResourceValue(index, 0);
+ if (resId !=0 ) {
+ return res.getString(resId);
+ }
+ return attrs.getAttributeValue(index);
+ }
+
+ protected BaseThemeInfo() {
+ }
+
+ protected BaseThemeInfo(Parcel source) {
+ themeId = source.readString();
+ name = source.readString();
+ author = source.readString();
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5700e44341f..a24ccad7b90 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -17,6 +17,7 @@
package android.content.pm;
+import android.app.ComposedIconInfo;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -459,4 +460,9 @@ interface IPackageManager {
/** Protected Apps */
void setComponentProtectedSetting(in ComponentName componentName,
in boolean newState, int userId);
+
+ /** Themes */
+ void updateIconMapping(String pkgName);
+ ComposedIconInfo getComposedIconInfo();
+ int processThemeResources(String themePkgName);
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index b66bd017275..ed6d734d5a4 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 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.
@@ -16,6 +17,11 @@
package android.content.pm;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
import android.os.Parcel;
import android.os.Parcelable;
@@ -237,6 +243,34 @@ public class PackageInfo implements Parcelable {
/** @hide */
public boolean coreApp;
+ // Is Theme Apk
+ /**
+ * {@hide}
+ */
+ public boolean isThemeApk = false;
+
+ /**
+ * {@hide}
+ */
+ public boolean hasIconPack = false;
+
+ /**
+ * {@hide}
+ */
+ public ArrayList<String> mOverlayTargets;
+
+ // Is Legacy Icon Apk
+ /**
+ * {@hide}
+ */
+ public boolean isLegacyIconPackApk = false;
+
+ // ThemeInfo
+ /**
+ * {@hide}
+ */
+ public ThemeInfo themeInfo;
+
/** @hide */
public boolean requiredForAllUsers;
@@ -301,6 +335,13 @@ public class PackageInfo implements Parcelable {
dest.writeString(restrictedAccountType);
dest.writeString(requiredAccountType);
dest.writeString(overlayTarget);
+
+ /* Theme-specific. */
+ dest.writeInt((isThemeApk) ? 1 : 0);
+ dest.writeStringList(mOverlayTargets);
+ dest.writeParcelable(themeInfo, parcelableFlags);
+ dest.writeInt(hasIconPack ? 1 : 0);
+ dest.writeInt((isLegacyIconPackApk) ? 1 : 0);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -346,5 +387,12 @@ public class PackageInfo implements Parcelable {
restrictedAccountType = source.readString();
requiredAccountType = source.readString();
overlayTarget = source.readString();
+
+ /* Theme-specific. */
+ isThemeApk = (source.readInt() != 0);
+ mOverlayTargets = source.createStringArrayList();
+ themeInfo = source.readParcelable(null);
+ hasIconPack = source.readInt() == 1;
+ isLegacyIconPackApk = source.readInt() == 1;
}
}
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index e336c5f752a..3622df4d174 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -52,6 +52,7 @@ public class PackageInfoLite implements Parcelable {
*/
public int recommendedInstallLocation;
public int installLocation;
+ public boolean isTheme;
public VerifierInfo[] verifiers;
@@ -74,6 +75,7 @@ public class PackageInfoLite implements Parcelable {
dest.writeInt(recommendedInstallLocation);
dest.writeInt(installLocation);
dest.writeInt(multiArch ? 1 : 0);
+ dest.writeInt(isTheme ? 1 : 0);
if (verifiers == null || verifiers.length == 0) {
dest.writeInt(0);
@@ -100,6 +102,7 @@ public class PackageInfoLite implements Parcelable {
recommendedInstallLocation = source.readInt();
installLocation = source.readInt();
multiArch = (source.readInt() != 0);
+ isTheme = source.readInt() == 1 ? true : false;
final int verifiersLength = source.readInt();
if (verifiersLength == 0) {
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index cacdf8e3625..cbd6198607b 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -66,7 +66,14 @@ public class PackageItemInfo {
* component's icon. From the "icon" attribute or, if not set, 0.
*/
public int icon;
-
+
+ /**
+ * A drawable resource identifier in the icon pack's resources
+ * If there isn't an icon pack or not set, then 0.
+ * @hide
+ */
+ public int themedIcon;
+
/**
* A drawable resource identifier (in the package's resources) of this
* component's banner. From the "banner" attribute or, if not set, 0.
@@ -110,6 +117,7 @@ public class PackageItemInfo {
logo = orig.logo;
metaData = orig.metaData;
showUserIcon = orig.showUserIcon;
+ themedIcon = orig.themedIcon;
}
/**
@@ -292,8 +300,9 @@ public class PackageItemInfo {
dest.writeBundle(metaData);
dest.writeInt(banner);
dest.writeInt(showUserIcon);
+ dest.writeInt(themedIcon);
}
-
+
protected PackageItemInfo(Parcel source) {
name = source.readString();
packageName = source.readString();
@@ -305,6 +314,7 @@ public class PackageItemInfo {
metaData = source.readBundle();
banner = source.readInt();
showUserIcon = source.readInt();
+ themedIcon = source.readInt();
}
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c37322a1a5a..cbdf40b9598 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -782,6 +782,38 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_ABORTED = -115;
/**
+ * Used by themes
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the theme because aapt could not compile the app
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_THEME_AAPT_ERROR = -400;
+
+ /**
+ * Used by themes
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the theme because idmap failed
+ * apps.
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_THEME_IDMAP_ERROR = -401;
+
+ /**
+ * Used by themes
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the theme for an unknown reason
+ * apps.
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_THEME_UNKNOWN_ERROR = -402;
+
+ /**
* Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
* package's data directory.
*
@@ -3121,6 +3153,18 @@ public abstract class PackageManager {
public abstract Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
throws NameNotFoundException;
+ /** @hide */
+ public abstract Resources getThemedResourcesForApplication(ApplicationInfo app,
+ String themePkgName) throws NameNotFoundException;
+
+ /** @hide */
+ public abstract Resources getThemedResourcesForApplication(String appPackageName,
+ String themePkgName) throws NameNotFoundException;
+
+ /** @hide */
+ public abstract Resources getThemedResourcesForApplicationAsUser(String appPackageName,
+ String themePkgName, int userId) throws NameNotFoundException;
+
/**
* Retrieve overall information about an application package defined
* in a package archive file
@@ -4123,4 +4167,22 @@ public abstract class PackageManager {
}
}
}
+
+ /**
+ * Updates the theme icon res id for the new theme
+ * @hide
+ */
+ public abstract void updateIconMaps(String pkgName);
+
+ /**
+ * Used to compile theme resources for a given theme
+ * @param themePkgName
+ * @return A value of 0 indicates success. Possible errors returned are:
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_AAPT_ERROR},
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_IDMAP_ERROR}, or
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_UNKNOWN_ERROR}
+ *
+ * @hide
+ */
+ public abstract int processThemeResources(String themePkgName);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index afb439247fe..23bf2789ca9 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 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.
@@ -58,6 +59,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -74,13 +76,17 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
@@ -110,6 +116,17 @@ public class PackageParser {
/** File name in an APK for the Android manifest. */
private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
+ /** Path to overlay directory in a theme APK */
+ private static final String OVERLAY_PATH = "assets/overlays/";
+ /** Path to icon directory in a theme APK */
+ private static final String ICON_PATH = "assets/icons/";
+
+ private static final String PACKAGE_REDIRECTIONS_XML = "res/xml/redirections.xml";
+
+ private static final String TAG_PACKAGE_REDIRECTIONS = "package-redirections";
+ private static final String TAG_RESOURCE_REDIRECTIONS = "resource-redirections";
+ private static final String TAG_ITEM = "item";
+ private static final String ATTRIBUTE_ITEM_NAME = "name";
/** @hide */
public static class NewPermissionInfo {
@@ -246,6 +263,7 @@ public class PackageParser {
public final int versionCode;
public final int installLocation;
public final VerifierInfo[] verifiers;
+ public boolean isTheme;
/** Names of any split APKs, ordered by parsed splitName */
public final String[] splitNames;
@@ -265,6 +283,7 @@ public class PackageParser {
public final boolean coreApp;
public final boolean multiArch;
+
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
String[] splitCodePaths) {
this.packageName = baseApk.packageName;
@@ -277,6 +296,7 @@ public class PackageParser {
this.splitCodePaths = splitCodePaths;
this.coreApp = baseApk.coreApp;
this.multiArch = baseApk.multiArch;
+ this.isTheme = baseApk.isTheme;
}
public List<String> getAllCodePaths() {
@@ -302,10 +322,11 @@ public class PackageParser {
public final Signature[] signatures;
public final boolean coreApp;
public final boolean multiArch;
+ public final boolean isTheme;
public ApkLite(String codePath, String packageName, String splitName, int versionCode,
int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
- boolean coreApp, boolean multiArch) {
+ boolean coreApp, boolean multiArch, boolean isTheme) {
this.codePath = codePath;
this.packageName = packageName;
this.splitName = splitName;
@@ -315,6 +336,7 @@ public class PackageParser {
this.signatures = signatures;
this.coreApp = coreApp;
this.multiArch = multiArch;
+ this.isTheme = isTheme;
}
}
@@ -413,6 +435,14 @@ public class PackageParser {
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
+ pi.isThemeApk = p.mIsThemeApk;
+ pi.hasIconPack = p.hasIconPack;
+ pi.isLegacyIconPackApk = p.mIsLegacyIconPackApk;
+
+ if (pi.isThemeApk) {
+ pi.mOverlayTargets = p.mOverlayTargets;
+ pi.themeInfo = p.mThemeInfo;
+ }
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
pi.coreApp = p.coreApp;
@@ -874,6 +904,18 @@ public class PackageParser {
pkg.baseCodePath = apkPath;
pkg.mSignatures = null;
+ // If the pkg is a theme, we need to know what themes it overlays
+ // and determine if it has an icon pack
+ if (pkg.mIsThemeApk) {
+ //Determine existance of Overlays
+ ArrayList<String> overlayTargets = scanPackageOverlays(apkFile);
+ for(String overlay : overlayTargets) {
+ pkg.mOverlayTargets.add(overlay);
+ }
+
+ pkg.hasIconPack = packageHasIconPack(apkFile);
+ }
+
return pkg;
} catch (PackageParserException e) {
@@ -996,6 +1038,51 @@ public class PackageParser {
return pkg;
}
+
+ private ArrayList<String> scanPackageOverlays(File originalFile) {
+ Set<String> overlayTargets = new HashSet<String>();
+
+ try {
+ final ZipFile privateZip = new ZipFile(originalFile.getPath());
+ final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
+ while (privateZipEntries.hasMoreElements()) {
+ final ZipEntry zipEntry = privateZipEntries.nextElement();
+ final String zipEntryName = zipEntry.getName();
+
+ if (zipEntryName.startsWith(OVERLAY_PATH) && zipEntryName.length() > 16) {
+ String[] subdirs = zipEntryName.split("/");
+ overlayTargets.add(subdirs[2]);
+ }
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ overlayTargets.clear();
+ }
+
+ ArrayList<String> overlays = new ArrayList<String>();
+ overlays.addAll(overlayTargets);
+ return overlays;
+ }
+
+ private boolean packageHasIconPack(File originalFile) {
+ try {
+ final ZipFile privateZip = new ZipFile(originalFile.getPath());
+ final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
+ while (privateZipEntries.hasMoreElements()) {
+ final ZipEntry zipEntry = privateZipEntries.nextElement();
+ final String zipEntryName = zipEntry.getName();
+
+ if (zipEntryName.startsWith(ICON_PATH) &&
+ zipEntryName.length() > ICON_PATH.length()) {
+ return true;
+ }
+ }
+ } catch(Exception e) {
+ Log.e(TAG, "Could not read zip entries while checking if apk has icon pack", e);
+ }
+ return false;
+ }
+
/**
* Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the
* APK. If it successfully scanned the package and found the
@@ -1275,6 +1362,9 @@ public class PackageParser {
// Only search the tree when the tag is directly below <manifest>
int type;
final int searchDepth = parser.getDepth() + 1;
+ // Search for category and actions inside <intent-filter>
+ final int iconPackSearchDepth = parser.getDepth() + 4;
+ boolean isTheme = false;
final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1299,10 +1389,52 @@ public class PackageParser {
}
}
}
+
+ if (parser.getDepth() == searchDepth && "meta-data".equals(parser.getName())) {
+ for (int i=0; i < parser.getAttributeCount(); i++) {
+ if ("name".equals(parser.getAttributeName(i)) &&
+ ThemeInfo.META_TAG_NAME.equals(parser.getAttributeValue(i))) {
+ isTheme = true;
+ installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ break;
+ }
+ }
+ }
+
+ if (parser.getDepth() == searchDepth && "theme".equals(parser.getName())) {
+ isTheme = true;
+ installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ }
+
+ if (parser.getDepth() == iconPackSearchDepth && isLegacyIconPack(parser)) {
+ isTheme = true;
+ installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ }
}
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
- installLocation, verifiers, signatures, coreApp, multiArch);
+ installLocation, verifiers, signatures, coreApp, multiArch, isTheme);
+ }
+
+ private static boolean isLegacyIconPack(XmlPullParser parser) {
+ boolean isAction = "action".equals(parser.getName());
+ boolean isCategory = "category".equals(parser.getName());
+ String[] items = isAction ? ThemeUtils.sSupportedActions
+ : (isCategory ? ThemeUtils.sSupportedCategories : null);
+
+ if (items != null) {
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ if ("name".equals(parser.getAttributeName(i))) {
+ final String value = parser.getAttributeValue(i);
+ for (String item : items) {
+ if (item.equals(value)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
}
/**
@@ -1354,6 +1486,8 @@ public class PackageParser {
}
final Package pkg = new Package(pkgName);
+ Bundle metaDataBundle = new Bundle();
+
boolean foundApp = false;
TypedArray sa = res.obtainAttributes(attrs,
@@ -1759,6 +1893,11 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
continue;
+ } else if (parser.getName().equals("meta-data")) {
+ if ((metaDataBundle=parseMetaData(res, parser, attrs, metaDataBundle,
+ outError)) == null) {
+ return null;
+ }
} else if (RIGID_PARSER) {
outError[0] = "Bad element under <manifest>: "
+ parser.getName();
@@ -1849,6 +1988,9 @@ public class PackageParser {
>= android.os.Build.VERSION_CODES.DONUT)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
}
+ if (pkg.mIsThemeApk || pkg.mIsLegacyIconPackApk) {
+ pkg.applicationInfo.isThemeable = false;
+ }
/*
* b/8528162: Ignore the <uses-permission android:required> attribute if
@@ -1861,6 +2003,14 @@ public class PackageParser {
}
}
+ //Is this pkg a theme?
+ if (metaDataBundle.containsKey(ThemeInfo.META_TAG_NAME)) {
+ pkg.mIsThemeApk = true;
+ pkg.mTrustedOverlay = true;
+ pkg.mOverlayPriority = 1;
+ pkg.mThemeInfo = new ThemeInfo(metaDataBundle);
+ }
+
return pkg;
}
@@ -2389,6 +2539,9 @@ public class PackageParser {
final ApplicationInfo ai = owner.applicationInfo;
final String pkgName = owner.applicationInfo.packageName;
+ // assume that this package is themeable unless explicitly set to false.
+ ai.isThemeable = true;
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestApplication);
@@ -3175,6 +3328,26 @@ public class PackageParser {
if (!parseIntent(res, parser, attrs, true, intent, outError)) {
return null;
}
+
+ // Check if package is a legacy icon pack
+ if (!owner.mIsLegacyIconPackApk) {
+ for(String action : ThemeUtils.sSupportedActions) {
+ if (intent.hasAction(action)) {
+ owner.mIsLegacyIconPackApk = true;
+ break;
+ }
+
+ }
+ }
+ if (!owner.mIsLegacyIconPackApk) {
+ for(String category : ThemeUtils.sSupportedCategories) {
+ if (intent.hasCategory(category)) {
+ owner.mIsLegacyIconPackApk = true;
+ break;
+ }
+ }
+ }
+
if (intent.countActions() == 0) {
Slog.w(TAG, "No actions in intent filter at "
+ mArchiveSourcePath + " "
@@ -4246,6 +4419,17 @@ public class PackageParser {
// For use by package manager to keep track of when a package was last used.
public long mLastPackageUsageTimeInMills;
+ // Is Theme Apk
+ public boolean mIsThemeApk = false;
+ public final ArrayList<String> mOverlayTargets = new ArrayList<String>(0);
+ public Map<String, Map<String, String>> mPackageRedirections
+ = new HashMap<String, Map<String, String>>();
+
+ // Theme info
+ public ThemeInfo mThemeInfo = null;
+
+ // Legacy icon pack
+ public boolean mIsLegacyIconPackApk = false;
// // User set enabled state.
// public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -4291,6 +4475,8 @@ public class PackageParser {
public int mOverlayPriority;
public boolean mTrustedOverlay;
+ public boolean hasIconPack;
+
/**
* Data used to feed the KeySetManagerService
*/
diff --git a/core/java/android/content/pm/ThemeInfo.aidl b/core/java/android/content/pm/ThemeInfo.aidl
new file mode 100644
index 00000000000..acbc85e9c8b
--- /dev/null
+++ b/core/java/android/content/pm/ThemeInfo.aidl
@@ -0,0 +1,3 @@
+package android.content.pm;
+
+parcelable ThemeInfo;
diff --git a/core/java/android/content/pm/ThemeInfo.java b/core/java/android/content/pm/ThemeInfo.java
new file mode 100644
index 00000000000..ab798db7b00
--- /dev/null
+++ b/core/java/android/content/pm/ThemeInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ * 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.pm;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParser;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.content.res.Resources;
+
+/**
+ * Overall information about "theme" package. This corresponds
+ * to the information collected from AndroidManifest.xml
+ *
+ * Below is an example of the manifest:
+ *
+ * <meta-data android:name="org.cyanogenmod.theme.name" android:value="Foobar's Theme"/>
+ * <meta-data android:name="org.cyanogenmod.theme.author" android:value="Mr.Foo" />
+ *
+ * @hide
+ */
+public final class ThemeInfo extends BaseThemeInfo {
+
+ public static final String META_TAG_NAME = "org.cyanogenmod.theme.name";
+ public static final String META_TAG_AUTHOR = "org.cyanogenmod.theme.author";
+
+ public ThemeInfo(Bundle bundle) {
+ super();
+ name = bundle.getString(META_TAG_NAME);
+ themeId = name;
+ author = bundle.getString(META_TAG_AUTHOR);
+ }
+
+ public static final Parcelable.Creator<ThemeInfo> CREATOR
+ = new Parcelable.Creator<ThemeInfo>() {
+ public ThemeInfo createFromParcel(Parcel source) {
+ return new ThemeInfo(source);
+ }
+
+ public ThemeInfo[] newArray(int size) {
+ return new ThemeInfo[size];
+ }
+ };
+
+ private ThemeInfo(Parcel source) {
+ super(source);
+ }
+}
diff --git a/core/java/android/content/pm/ThemeUtils.java b/core/java/android/content/pm/ThemeUtils.java
new file mode 100644
index 00000000000..38391d481b1
--- /dev/null
+++ b/core/java/android/content/pm/ThemeUtils.java
@@ -0,0 +1,718 @@
+/*
+ * 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.pm;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.IntentFilter;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import static android.content.res.ThemeConfig.SYSTEM_DEFAULT;
+
+/**
+ * @hide
+ */
+public class ThemeUtils {
+ private static final String TAG = "ThemeUtils";
+
+ /* Path inside a theme APK to the overlay folder */
+ public static final String OVERLAY_PATH = "assets/overlays/";
+ public static final String ICONS_PATH = "assets/icons/";
+ public static final String COMMON_RES_PATH = "assets/overlays/common/";
+ public static final String FONT_XML = "fonts.xml";
+ public static final String RESOURCE_CACHE_DIR = "/data/resource-cache/";
+ public static final String IDMAP_SUFFIX = "@idmap";
+ public static final String COMMON_RES_SUFFIX = ".common";
+ public static final String COMMON_RES_TARGET = "common";
+ public static final String ICON_HASH_FILENAME = "hash";
+
+ // path to external theme resources, i.e. bootanimation.zip
+ public static final String SYSTEM_THEME_PATH = "/data/system/theme";
+ public static final String SYSTEM_THEME_FONT_PATH = SYSTEM_THEME_PATH + File.separator + "fonts";
+ public static final String SYSTEM_THEME_RINGTONE_PATH = SYSTEM_THEME_PATH
+ + File.separator + "ringtones";
+ public static final String SYSTEM_THEME_NOTIFICATION_PATH = SYSTEM_THEME_PATH
+ + File.separator + "notifications";
+ public static final String SYSTEM_THEME_ALARM_PATH = SYSTEM_THEME_PATH
+ + File.separator + "alarms";
+ public static final String SYSTEM_THEME_ICON_CACHE_DIR = SYSTEM_THEME_PATH
+ + File.separator + "icons";
+ // internal path to bootanimation.zip inside theme apk
+ public static final String THEME_BOOTANIMATION_PATH = "assets/bootanimation/bootanimation.zip";
+
+ public static final String SYSTEM_MEDIA_PATH = "/system/media/audio";
+ public static final String SYSTEM_ALARMS_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "alarms";
+ public static final String SYSTEM_RINGTONES_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "ringtones";
+ public static final String SYSTEM_NOTIFICATIONS_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "notifications";
+
+ private static final String MEDIA_CONTENT_URI = "content://media/internal/audio/media";
+
+ public static final String ACTION_THEME_CHANGED = "org.cyanogenmod.intent.action.THEME_CHANGED";
+
+ public static final String CATEGORY_THEME_COMPONENT_PREFIX = "org.cyanogenmod.intent.category.";
+
+ public static final int SYSTEM_TARGET_API = 0;
+
+ private static final String SETTINGS_DB =
+ "/data/data/com.android.providers.settings/databases/settings.db";
+ private static final String SETTINGS_SECURE_TABLE = "secure";
+
+ // Actions in manifests which identify legacy icon packs
+ public static final String[] sSupportedActions = new String[] {
+ "org.adw.launcher.THEMES",
+ "com.gau.go.launcherex.theme"
+ };
+
+ // Categories in manifests which identify legacy icon packs
+ public static final String[] sSupportedCategories = new String[] {
+ "com.fede.launcher.THEME_ICONPACK",
+ "com.anddoes.launcher.THEME",
+ "com.teslacoilsw.launcher.THEME"
+ };
+
+
+ /**
+ * Get the root path of the resource cache for the given theme
+ * @param themePkgName
+ * @return Root resource cache path for the given theme
+ */
+ public static String getOverlayResourceCacheDir(String themePkgName) {
+ return RESOURCE_CACHE_DIR + themePkgName;
+ }
+
+ /**
+ * Get the path of the resource cache for the given target and theme
+ * @param targetPkgName
+ * @param themePkg
+ * @return Path to the resource cache for this target and theme
+ */
+ public static String getTargetCacheDir(String targetPkgName, PackageInfo themePkg) {
+ return getTargetCacheDir(targetPkgName, themePkg.packageName);
+ }
+
+ public static String getTargetCacheDir(String targetPkgName, PackageParser.Package themePkg) {
+ return getTargetCacheDir(targetPkgName, themePkg.packageName);
+ }
+
+ public static String getTargetCacheDir(String targetPkgName, String themePkgName) {
+ return getOverlayResourceCacheDir(themePkgName) + File.separator + targetPkgName;
+ }
+
+ /**
+ * Get the path to the icons for the given theme
+ * @param pkgName
+ * @return
+ */
+ public static String getIconPackDir(String pkgName) {
+ return getOverlayResourceCacheDir(pkgName) + File.separator + "icons";
+ }
+
+ public static String getIconHashFile(String pkgName) {
+ return getIconPackDir(pkgName) + File.separator + ICON_HASH_FILENAME;
+ }
+
+ public static String getIconPackApkPath(String pkgName) {
+ return getIconPackDir(pkgName) + "/resources.apk";
+ }
+
+ public static String getIconPackResPath(String pkgName) {
+ return getIconPackDir(pkgName) + "/resources.arsc";
+ }
+
+ public static String getIdmapPath(String targetPkgName, String overlayPkgName) {
+ return getTargetCacheDir(targetPkgName, overlayPkgName) + File.separator + "idmap";
+ }
+
+ public static String getOverlayPathToTarget(String targetPkgName) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(OVERLAY_PATH);
+ sb.append(targetPkgName);
+ sb.append('/');
+ return sb.toString();
+ }
+
+ public static String getCommonPackageName(String themePackageName) {
+ if (TextUtils.isEmpty(themePackageName)) return null;
+
+ return COMMON_RES_TARGET;
+ }
+
+ public static void createCacheDirIfNotExists() throws IOException {
+ File file = new File(RESOURCE_CACHE_DIR);
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ public static void createResourcesDirIfNotExists(String targetPkgName, String overlayPkgName)
+ throws IOException {
+ createDirIfNotExists(getOverlayResourceCacheDir(overlayPkgName));
+ File file = new File(getTargetCacheDir(targetPkgName, overlayPkgName));
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ public static void createIconDirIfNotExists(String pkgName) throws IOException {
+ createDirIfNotExists(getOverlayResourceCacheDir(pkgName));
+ File file = new File(getIconPackDir(pkgName));
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ private static boolean dirExists(String dirPath) {
+ final File dir = new File(dirPath);
+ return dir.exists() && dir.isDirectory();
+ }
+
+ private static void createDirIfNotExists(String dirPath) {
+ if (!dirExists(dirPath)) {
+ File dir = new File(dirPath);
+ if (dir.mkdir()) {
+ FileUtils.setPermissions(dir, FileUtils.S_IRWXU |
+ FileUtils.S_IRWXG| FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+ }
+ }
+
+ /**
+ * Create SYSTEM_THEME_PATH directory if it does not exist
+ */
+ public static void createThemeDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_PATH);
+ }
+
+ /**
+ * Create SYSTEM_FONT_PATH directory if it does not exist
+ */
+ public static void createFontDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_FONT_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_RINGTONE_PATH directory if it does not exist
+ */
+ public static void createRingtoneDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_RINGTONE_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_NOTIFICATION_PATH directory if it does not exist
+ */
+ public static void createNotificationDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_NOTIFICATION_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_ALARM_PATH directory if it does not exist
+ */
+ public static void createAlarmDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_ALARM_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_ICON_CACHE_DIR directory if it does not exist
+ */
+ public static void createIconCacheDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_ICON_CACHE_DIR);
+ }
+
+ public static void clearIconCache() {
+ FileUtils.deleteContents(new File(SYSTEM_THEME_ICON_CACHE_DIR));
+ }
+
+ public static InputStream getInputStreamFromAsset(Context ctx, String path) throws IOException {
+ if (ctx == null || path == null)
+ return null;
+ InputStream is = null;
+ String ASSET_BASE = "file:///android_asset/";
+ path = path.substring(ASSET_BASE.length());
+ AssetManager assets = ctx.getAssets();
+ is = assets.open(path);
+ return is;
+ }
+
+ public static void closeQuietly(InputStream stream) {
+ if (stream == null)
+ return;
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+
+ public static void closeQuietly(OutputStream stream) {
+ if (stream == null)
+ return;
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+
+ /**
+ * Scale the boot animation to better fit the device by editing the desc.txt found
+ * in the bootanimation.zip
+ * @param context Context to use for getting an instance of the WindowManager
+ * @param input InputStream of the original bootanimation.zip
+ * @param dst Path to store the newly created bootanimation.zip
+ * @throws IOException
+ */
+ public static void copyAndScaleBootAnimation(Context context, InputStream input, String dst)
+ throws IOException {
+ final OutputStream os = new FileOutputStream(dst);
+ final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
+ final ZipInputStream bootAni = new ZipInputStream(new BufferedInputStream(input));
+ ZipEntry ze;
+
+ zos.setMethod(ZipOutputStream.STORED);
+ final byte[] bytes = new byte[4096];
+ int len;
+ while ((ze = bootAni.getNextEntry()) != null) {
+ ZipEntry entry = new ZipEntry(ze.getName());
+ entry.setMethod(ZipEntry.STORED);
+ entry.setCrc(ze.getCrc());
+ entry.setSize(ze.getSize());
+ entry.setCompressedSize(ze.getSize());
+ if (!ze.getName().equals("desc.txt")) {
+ // just copy this entry straight over into the output zip
+ zos.putNextEntry(entry);
+ while ((len = bootAni.read(bytes)) > 0) {
+ zos.write(bytes, 0, len);
+ }
+ } else {
+ String line;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(bootAni));
+ final String[] info = reader.readLine().split(" ");
+
+ int scaledWidth;
+ int scaledHeight;
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics dm = new DisplayMetrics();
+ wm.getDefaultDisplay().getRealMetrics(dm);
+ // just in case the device is in landscape orientation we will
+ // swap the values since most (if not all) animations are portrait
+ if (dm.widthPixels > dm.heightPixels) {
+ scaledWidth = dm.heightPixels;
+ scaledHeight = dm.widthPixels;
+ } else {
+ scaledWidth = dm.widthPixels;
+ scaledHeight = dm.heightPixels;
+ }
+
+ int width = Integer.parseInt(info[0]);
+ int height = Integer.parseInt(info[1]);
+
+ if (width == height)
+ scaledHeight = scaledWidth;
+ else {
+ // adjust scaledHeight to retain original aspect ratio
+ float scale = (float)scaledWidth / (float)width;
+ int newHeight = (int)((float)height * scale);
+ if (newHeight < scaledHeight)
+ scaledHeight = newHeight;
+ }
+
+ CRC32 crc32 = new CRC32();
+ int size = 0;
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ line = String.format("%d %d %s\n", scaledWidth, scaledHeight, info[2]);
+ buffer.put(line.getBytes());
+ size += line.getBytes().length;
+ crc32.update(line.getBytes());
+ while ((line = reader.readLine()) != null) {
+ line = String.format("%s\n", line);
+ buffer.put(line.getBytes());
+ size += line.getBytes().length;
+ crc32.update(line.getBytes());
+ }
+ entry.setCrc(crc32.getValue());
+ entry.setSize(size);
+ entry.setCompressedSize(size);
+ zos.putNextEntry(entry);
+ zos.write(buffer.array(), 0, size);
+ }
+ zos.closeEntry();
+ }
+ zos.close();
+ }
+
+ public static boolean isValidAudible(String fileName) {
+ return (fileName != null &&
+ (fileName.endsWith(".mp3") || fileName.endsWith(".ogg")));
+ }
+
+ public static boolean setAudible(Context context, File ringtone, int type, String name) {
+ final String path = ringtone.getAbsolutePath();
+ final String mimeType = name.endsWith(".ogg") ? "audio/ogg" : "audio/mp3";
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, path);
+ values.put(MediaStore.MediaColumns.TITLE, name);
+ values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
+ values.put(MediaStore.MediaColumns.SIZE, ringtone.length());
+ values.put(MediaStore.Audio.Media.IS_RINGTONE, type == RingtoneManager.TYPE_RINGTONE);
+ values.put(MediaStore.Audio.Media.IS_NOTIFICATION,
+ type == RingtoneManager.TYPE_NOTIFICATION);
+ values.put(MediaStore.Audio.Media.IS_ALARM, type == RingtoneManager.TYPE_ALARM);
+ values.put(MediaStore.Audio.Media.IS_MUSIC, false);
+
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(path);
+ Uri newUri = null;
+ Cursor c = context.getContentResolver().query(uri,
+ new String[] {MediaStore.MediaColumns._ID},
+ MediaStore.MediaColumns.DATA + "='" + path + "'",
+ null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ long id = c.getLong(0);
+ c.close();
+ newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), "" + id);
+ context.getContentResolver().update(uri, values,
+ MediaStore.MediaColumns._ID + "=" + id, null);
+ }
+ if (newUri == null)
+ newUri = context.getContentResolver().insert(uri, values);
+ try {
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri);
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean setDefaultAudible(Context context, int type) {
+ final String audiblePath = getDefaultAudiblePath(type);
+ if (audiblePath != null) {
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(audiblePath);
+ Cursor c = context.getContentResolver().query(uri,
+ new String[] {MediaStore.MediaColumns._ID},
+ MediaStore.MediaColumns.DATA + "='" + audiblePath + "'",
+ null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ long id = c.getLong(0);
+ c.close();
+ uri = Uri.withAppendedPath(
+ Uri.parse(MEDIA_CONTENT_URI), "" + id);
+ }
+ if (uri != null)
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, uri);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ public static String getDefaultAudiblePath(int type) {
+ final String name;
+ final String path;
+ switch (type) {
+ case RingtoneManager.TYPE_ALARM:
+ name = SystemProperties.get("ro.config.alarm_alert", null);
+ path = name != null ? SYSTEM_ALARMS_PATH + File.separator + name : null;
+ break;
+ case RingtoneManager.TYPE_NOTIFICATION:
+ name = SystemProperties.get("ro.config.notification_sound", null);
+ path = name != null ? SYSTEM_NOTIFICATIONS_PATH + File.separator + name : null;
+ break;
+ case RingtoneManager.TYPE_RINGTONE:
+ name = SystemProperties.get("ro.config.ringtone", null);
+ path = name != null ? SYSTEM_RINGTONES_PATH + File.separator + name : null;
+ break;
+ default:
+ path = null;
+ break;
+ }
+ return path;
+ }
+
+ public static void clearAudibles(Context context, String audiblePath) {
+ final File audibleDir = new File(audiblePath);
+ if (audibleDir.exists()) {
+ String[] files = audibleDir.list();
+ final ContentResolver resolver = context.getContentResolver();
+ for (String s : files) {
+ final String filePath = audiblePath + File.separator + s;
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(filePath);
+ resolver.delete(uri, MediaStore.MediaColumns.DATA + "=\""
+ + filePath + "\"", null);
+ (new File(filePath)).delete();
+ }
+ }
+ }
+
+ public static Context createUiContext(final Context context) {
+ try {
+ Context uiContext = context.createPackageContext("com.android.systemui",
+ Context.CONTEXT_RESTRICTED);
+ return new ThemedUiContext(uiContext, context.getPackageName());
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ return null;
+ }
+
+ public static void registerThemeChangeReceiver(final Context context,
+ final BroadcastReceiver receiver) {
+ IntentFilter filter = new IntentFilter(ACTION_THEME_CHANGED);
+
+ context.registerReceiver(receiver, filter);
+ }
+
+ public static String getLockscreenWallpaperPath(AssetManager assetManager) throws IOException {
+ String[] assets = assetManager.list("lockscreen");
+ String asset = getFirstNonEmptyAsset(assets);
+ if (asset == null) return null;
+ return "lockscreen/" + asset;
+ }
+
+ public static String getWallpaperPath(AssetManager assetManager) throws IOException {
+ String[] assets = assetManager.list("wallpapers");
+ String asset = getFirstNonEmptyAsset(assets);
+ if (asset == null) return null;
+ return "wallpapers/" + asset;
+ }
+
+ // Returns the first non-empty asset name. Empty assets can occur if the APK is built
+ // with folders included as zip entries in the APK. Searching for files inside "folderName" via
+ // assetManager.list("folderName") can cause these entries to be included as empty strings.
+ private static String getFirstNonEmptyAsset(String[] assets) {
+ if (assets == null) return null;
+ String filename = null;
+ for(String asset : assets) {
+ if (!asset.isEmpty()) {
+ filename = asset;
+ break;
+ }
+ }
+ return filename;
+ }
+
+ public static String getDefaultThemePackageName(Context context) {
+ final String defaultThemePkg = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.DEFAULT_THEME_PACKAGE);
+ if (!TextUtils.isEmpty(defaultThemePkg)) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ if (pm.getPackageInfo(defaultThemePkg, 0) != null) {
+ return defaultThemePkg;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // doesn't exist so system will be default
+ Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e);
+ }
+ }
+
+ return SYSTEM_DEFAULT;
+ }
+
+ private static class ThemedUiContext extends ContextWrapper {
+ private String mPackageName;
+
+ public ThemedUiContext(Context context, String packageName) {
+ super(context);
+ mPackageName = packageName;
+ }
+
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+ }
+
+ // Returns a mutable list of all theme components
+ public static List<String> getAllComponents() {
+ List<String> components = new ArrayList<String>(9);
+ components.add(ThemesColumns.MODIFIES_FONTS);
+ components.add(ThemesColumns.MODIFIES_LAUNCHER);
+ components.add(ThemesColumns.MODIFIES_ALARMS);
+ components.add(ThemesColumns.MODIFIES_BOOT_ANIM);
+ components.add(ThemesColumns.MODIFIES_ICONS);
+ components.add(ThemesColumns.MODIFIES_LOCKSCREEN);
+ components.add(ThemesColumns.MODIFIES_NOTIFICATIONS);
+ components.add(ThemesColumns.MODIFIES_OVERLAYS);
+ components.add(ThemesColumns.MODIFIES_RINGTONES);
+ components.add(ThemesColumns.MODIFIES_STATUS_BAR);
+ components.add(ThemesColumns.MODIFIES_NAVIGATION_BAR);
+ return components;
+ }
+
+ /**
+ * Returns a mutable list of all the theme components supported by a given package
+ * NOTE: This queries the themes content provider. If there isn't a provider installed
+ * or if it is too early in the boot process this method will not work.
+ */
+ public static List<String> getSupportedComponents(Context context, String pkgName) {
+ List<String> supportedComponents = new ArrayList<String>();
+
+ String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = new String[]{ pkgName };
+ Cursor c = context.getContentResolver().query(ThemesContract.ThemesColumns.CONTENT_URI,
+ null, selection, selectionArgs, null);
+
+ if (c != null && c.moveToFirst()) {
+ List<String> allComponents = getAllComponents();
+ for(String component : allComponents) {
+ int index = c.getColumnIndex(component);
+ if (c.getInt(index) == 1) {
+ supportedComponents.add(component);
+ }
+ }
+ }
+ return supportedComponents;
+ }
+
+ /**
+ * Get the components from the default theme. If the default theme is not SYSTEM then any
+ * components that are not in the default theme will come from SYSTEM to create a complete
+ * component map.
+ * @param context
+ * @return
+ */
+ public static Map<String, String> getDefaultComponents(Context context) {
+ String defaultThemePkg = getDefaultThemePackageName(context);
+ List<String> defaultComponents = null;
+ List<String> systemComponents = getSupportedComponents(context, SYSTEM_DEFAULT);
+ if (!SYSTEM_DEFAULT.equals(defaultThemePkg)) {
+ defaultComponents = getSupportedComponents(context, defaultThemePkg);
+ }
+
+ Map<String, String> componentMap = new HashMap<String, String>(systemComponents.size());
+ if (defaultComponents != null) {
+ for (String component : defaultComponents) {
+ componentMap.put(component, defaultThemePkg);
+ }
+ }
+ for (String component : systemComponents) {
+ if (!componentMap.containsKey(component)) {
+ componentMap.put(component, SYSTEM_DEFAULT);
+ }
+ }
+
+ return componentMap;
+ }
+
+ /**
+ * Takes an existing component map and adds any missing components from the default
+ * map of components.
+ * @param context
+ * @param componentMap An existing component map
+ */
+ public static void completeComponentMap(Context context,
+ Map<String, String> componentMap) {
+ if (componentMap == null) return;
+
+ Map<String, String> defaultComponents = getDefaultComponents(context);
+ for (String component : defaultComponents.keySet()) {
+ if (!componentMap.containsKey(component)) {
+ componentMap.put(component, defaultComponents.get(component));
+ }
+ }
+ }
+
+ /**
+ * Get the boot theme by accessing the settings.db directly instead of using a content resolver.
+ * Only use this when the system is starting up and the settings content provider is not ready.
+ *
+ * Note: This method will only succeed if the system is calling this since normal apps will not
+ * be able to access the settings db path.
+ *
+ * @return The boot theme or null if unable to read the database or get the entry for theme
+ * config
+ */
+ public static ThemeConfig getBootThemeDirty() {
+ ThemeConfig config = null;
+ SQLiteDatabase db = null;
+ try {
+ db = SQLiteDatabase.openDatabase(SETTINGS_DB, null,
+ SQLiteDatabase.OPEN_READONLY);
+ if (db != null) {
+ String selection = "name=?";
+ String[] selectionArgs =
+ { Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY };
+ String[] columns = {"value"};
+ Cursor c = db.query(SETTINGS_SECURE_TABLE, columns, selection, selectionArgs,
+ null, null, null);
+ if (c != null) {
+ if (c.getCount() > 0) {
+ c.moveToFirst();
+ String json = c.getString(0);
+ if (json != null) {
+ config = ThemeConfig.fromJson(json);
+ }
+ }
+ c.close();
+ }
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to open " + SETTINGS_DB, e);
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+
+ return config;
+ }
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index e413ed29be8..74a906ffb4e 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -21,9 +21,11 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -77,6 +79,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 +264,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();
@@ -629,17 +647,72 @@ public final class AssetManager implements AutoCloseable {
*
* {@hide}
*/
- public final int addOverlayPath(String idmapPath) {
+ // TODO: change the signature of this method to match addOverlayPathNative
+ public final int addOverlayPath(String idmapPath, String themeApkPath,
+ String resApkPath, String targetPkgPath, String prefixPath) {
synchronized (this) {
- int res = addOverlayPathNative(idmapPath);
- if (mStringBlocks != null) {
- makeStringBlocks(mStringBlocks);
+ return addOverlayPathNative(idmapPath, themeApkPath, resApkPath, targetPkgPath,
+ prefixPath);
+ }
+ }
+
+ private native final int addOverlayPathNative(String idmapPath, String themeApkPath,
+ String resApkPath, String targetPkgPath, String prefixPath);
+
+ /**
+ * Add a set of common assets.
+ *
+ * {@hide}
+ */
+ public final int addCommonOverlayPath(String themeApkPath,
+ String resApkPath, String prefixPath) {
+ synchronized (this) {
+ if ((new File(themeApkPath).exists()) && (new File(resApkPath).exists())) {
+ return addCommonOverlayPathNative(themeApkPath, resApkPath, prefixPath);
}
- return res;
+
+ return 0;
}
}
- private native final int addOverlayPathNative(String idmapPath);
+ private native final int addCommonOverlayPathNative(String themeApkPath,
+ 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
@@ -662,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.
@@ -788,6 +981,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 da35ee92267..47d5d0551ee 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 27bbb242f57..121c1375b55 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
@@ -412,7 +418,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}.
@@ -644,8 +690,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("{");
@@ -780,6 +829,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();
}
@@ -806,6 +857,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} */
@@ -948,7 +1000,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;
}
@@ -1058,7 +1116,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;
}
@@ -1074,7 +1135,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;
}
/**
@@ -1147,6 +1210,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) {
@@ -1175,6 +1239,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
@@ -1242,7 +1307,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;
}
@@ -1279,6 +1349,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 00000000000..a2e2abd6def
--- /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 00000000000..2e1c16e53bf
--- /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 00000000000..e8bb5c4329e
--- /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 7f276c24dc7..cc8a6049298 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,6 +16,9 @@
package android.content.res;
+import android.app.ComposedIconInfo;
+import android.app.IconPackHelper;
+import android.app.IconPackHelper.IconCustomizer;
import android.util.Pools.SynchronizedPool;
import android.view.ViewDebug;
import com.android.internal.util.XmlUtils;
@@ -25,6 +28,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageItemInfo;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
@@ -38,6 +42,7 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.util.LongSparseArray;
@@ -86,6 +91,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
@@ -136,6 +155,9 @@ public class Resources {
@SuppressWarnings("unused")
private WeakReference<IBinder> mToken;
+ private SparseArray<PackageItemInfo> mIcons;
+ private ComposedIconInfo mComposedIconInfo;
+
static {
sPreloadedDrawables = new LongSparseArray[2];
sPreloadedDrawables[0] = new LongSparseArray<ConstantState>();
@@ -230,7 +252,7 @@ public class Resources {
}
mToken = new WeakReference<IBinder>(token);
updateConfiguration(config, metrics);
- assets.ensureStringBlocks();
+ assets.recreateStringBlocks();
}
/**
@@ -745,6 +767,18 @@ public class Resources {
* not exist.
*/
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
+ return getDrawable(id, theme, true);
+ }
+
+ /** @hide */
+ public Drawable getDrawable(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;
@@ -753,9 +787,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;
@@ -808,6 +857,18 @@ public class Resources {
* not exist.
*/
public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme) {
+ return getDrawableForDensity(id, density, theme, true);
+ }
+
+ /** @hide */
+ public Drawable getDrawableForDensity(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;
@@ -816,7 +877,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
@@ -1226,8 +1287,24 @@ public class Resources {
*/
public void getValue(int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
+ getValue(id, outValue, resolveRefs, true);
+ }
+
+ /** @hide */
+ public void getValue(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"
@@ -1249,8 +1326,44 @@ public class Resources {
*/
public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
+ getValueForDensity(id, density, outValue, resolveRefs, true);
+ }
+
+ /** @hide */
+ public void getValueForDensity(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));
@@ -1776,7 +1889,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);
+ }
}
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
@@ -1848,6 +1969,19 @@ public class Resources {
private void clearDrawableCacheLocked(
LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) {
+ /*
+ * Quick test to find out if the config change that occurred should
+ * trigger a full cache wipe.
+ */
+ if (Configuration.needNewResources(configChanges, 0)) {
+ if (DEBUG_CONFIG) {
+ Log.d(TAG, "Clear drawable cache from config changes: 0x"
+ + Integer.toHexString(configChanges));
+ }
+ cache.clear();
+ return;
+ }
+
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
+ Integer.toHexString(configChanges));
@@ -2278,6 +2412,13 @@ public class Resources {
return true;
}
+ /** @hide */
+ public final void updateStringCache() {
+ synchronized (mAccessLock) {
+ mAssets.recreateStringBlocks();
+ }
+ }
+
/*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -2316,9 +2457,10 @@ public class Resources {
// themeable 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);
}
final Drawable dr;
@@ -2496,7 +2638,7 @@ public class Resources {
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- csl = sPreloadedColorStateLists.get(key);
+ csl = mAssets.hasThemedAssets() ? null : sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -2517,7 +2659,7 @@ public class Resources {
return csl;
}
- csl = sPreloadedColorStateLists.get(key);
+ csl = mAssets.hasThemedAssets() ? null : sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -2664,6 +2806,21 @@ public class Resources {
}
}
+ /** @hide */
+ public void setIconResources(SparseArray<PackageItemInfo> icons) {
+ mIcons = icons;
+ }
+
+ /** @hide */
+ public void setComposedIconInfo(ComposedIconInfo iconInfo) {
+ mComposedIconInfo = iconInfo;
+ }
+
+ /** @hide */
+ public ComposedIconInfo getComposedIconInfo() {
+ return mComposedIconInfo;
+ }
+
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 e0f1b3a3f07..e3ab161e754 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -22,6 +22,7 @@ import android.os.IBinder;
public final class ResourcesKey {
final String mResDir;
final float mScale;
+ final private boolean mIsThemeable;
private final int mHash;
private final IBinder mToken;
@@ -29,13 +30,14 @@ public final class ResourcesKey {
public final Configuration mOverrideConfiguration = new Configuration();
public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
- float scale, IBinder token) {
+ float scale, boolean isThemeable, IBinder token) {
mResDir = resDir;
mDisplayId = displayId;
if (overrideConfiguration != null) {
mOverrideConfiguration.setTo(overrideConfiguration);
}
mScale = scale;
+ mIsThemeable = isThemeable;
mToken = token;
int hash = 17;
@@ -44,6 +46,7 @@ public final class ResourcesKey {
hash = 31 * hash + (mOverrideConfiguration != null
? mOverrideConfiguration.hashCode() : 0);
hash = 31 * hash + Float.floatToIntBits(mScale);
+ hash = 31 * hash + (mIsThemeable ? 1 : 0);
mHash = hash;
}
@@ -83,7 +86,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 00000000000..1b1837d7420
--- /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 00000000000..a9d2fcc942a
--- /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);
+ }
+}
+
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 3ada9bb002e..855ae8c19ec 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -486,11 +486,12 @@ public class Process {
String abi,
String instructionSet,
String appDataDir,
+ boolean refreshTheme,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
debugFlags, mountExternal, targetSdkVersion, seInfo,
- abi, instructionSet, appDataDir, zygoteArgs);
+ abi, instructionSet, appDataDir, refreshTheme, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -609,6 +610,7 @@ public class Process {
String abi,
String instructionSet,
String appDataDir,
+ boolean refreshTheme,
String[] extraArgs)
throws ZygoteStartFailedEx {
synchronized(Process.class) {
@@ -639,6 +641,9 @@ public class Process {
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
argsForZygote.add("--mount-external-multiuser-all");
}
+ if (refreshTheme) {
+ argsForZygote.add("--refresh_theme");
+ }
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
//TODO optionally enable debuger
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 91053d52549..1b8db12721a 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5216,6 +5216,35 @@ public final class Settings {
public static final String PROTECTED_COMPONENTS = "protected_components";
/**
+ * Default theme to use. If empty, use holo.
+ * @hide
+ */
+ public static final String DEFAULT_THEME_PACKAGE = "default_theme_package";
+
+ /**
+ * A '|' delimited list of theme components to apply from the default theme on first boot.
+ * Components can be one or more of the "mods_XXXXXXX" found in
+ * {@link ThemesContract$ThemesColumns}. Leaving this field blank assumes all components
+ * will be applied.
+ *
+ * ex: mods_icons|mods_overlays|mods_homescreen
+ *
+ * @hide
+ */
+ public static final String DEFAULT_THEME_COMPONENTS = "default_theme_components";
+
+ /**
+ * This will be set to the system's current theme API version when ThemeService starts.
+ * It is useful for when an upgrade from one version of CM to another occurs.
+ * For example, after a user upgrades from CM11 to CM12, the value of this field
+ * might be 19. ThemeService would then change the value to 21. This is useful
+ * when an API change breaks a theme. Themeservice can identify old themes and
+ * unapply them from the system.
+ * @hide
+ */
+ public static final String THEME_PREV_BOOT_API_LEVEL = "theme_prev_boot_api_level";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
diff --git a/core/java/android/provider/ThemesContract.java b/core/java/android/provider/ThemesContract.java
new file mode 100644
index 00000000000..33fb09df512
--- /dev/null
+++ b/core/java/android/provider/ThemesContract.java
@@ -0,0 +1,565 @@
+package android.provider;
+
+import android.net.Uri;
+
+/**
+ * @hide
+ */
+public class ThemesContract {
+ public static final String AUTHORITY = "com.cyanogenmod.themes";
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ public static class ThemesColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "themes");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The user visible title.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * Unique text to identify the apk pkg. ie "com.foo.bar"
+ * <P>Type: TEXT</P>
+ */
+ public static final String PKG_NAME = "pkg_name";
+
+ /**
+ * A 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PRIMARY_COLOR = "primary_color";
+
+ /**
+ * A 2nd 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SECONDARY_COLOR = "secondary_color";
+
+ /**
+ * Name of the author of the theme
+ * <P>Type: TEXT</P>
+ */
+ public static final String AUTHOR = "author";
+
+ /**
+ * The time that this row was created on its originating client (msecs
+ * since the epoch).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DATE_CREATED = "created";
+
+ /**
+ * URI to an image that shows the homescreen with the theme applied
+ * since the epoch).
+ * <P>Type: TEXT</P>
+ */
+ public static final String HOMESCREEN_URI = "homescreen_uri";
+
+ /**
+ * URI to an image that shows the lockscreen with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String LOCKSCREEN_URI = "lockscreen_uri";
+
+ /**
+ * URI to an image that shows the style (aka skin) with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String STYLE_URI = "style_uri";
+
+ /**
+ * TODO: Figure structure for actual animation instead of static
+ * URI to an image of the boot_anim.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BOOT_ANIM_URI = "bootanim_uri";
+
+ /**
+ * URI to an image of the status bar for this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STATUSBAR_URI = "status_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FONT_URI = "font_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ICON_URI = "icon_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String OVERLAYS_URI = "overlays_uri";
+
+ /**
+ * 1 if theme modifies the launcher/homescreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LAUNCHER = "mods_homescreen";
+
+ /**
+ * 1 if theme modifies the lockscreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LOCKSCREEN = "mods_lockscreen";
+
+ /**
+ * 1 if theme modifies icons else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ICONS = "mods_icons";
+
+ /**
+ * 1 if theme modifies fonts
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_FONTS = "mods_fonts";
+
+ /**
+ * 1 if theme modifies boot animation
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_BOOT_ANIM = "mods_bootanim";
+
+ /**
+ * 1 if theme modifies notifications
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NOTIFICATIONS = "mods_notifications";
+
+ /**
+ * 1 if theme modifies alarm sounds
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ALARMS = "mods_alarms";
+
+ /**
+ * 1 if theme modifies ringtones
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_RINGTONES = "mods_ringtones";
+
+ /**
+ * 1 if theme has overlays
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_OVERLAYS = "mods_overlays";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/StatusBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_STATUS_BAR = "mods_status_bar";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/NavBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NAVIGATION_BAR = "mods_navigation_bar";
+
+ /**
+ * URI to the theme's wallpaper. We should support multiple wallpaper
+ * but for now we will just have 1.
+ * <P>Type: TEXT</P>
+ */
+ public static final String WALLPAPER_URI = "wallpaper_uri";
+
+ /**
+ * 1 if this row should actually be presented as a theme to the user.
+ * For example if a "theme" only modifies one component (ex icons) then
+ * we do not present it to the user under the themes table.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String PRESENT_AS_THEME = "present_as_theme";
+
+ /**
+ * 1 if this theme is a legacy theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_THEME = "is_legacy_theme";
+
+ /**
+ * 1 if this theme is the system default theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_DEFAULT_THEME = "is_default_theme";
+
+ /**
+ * 1 if this theme is a legacy iconpack. A legacy icon pack is an APK that was written
+ * for Trebuchet or a 3rd party launcher.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_ICONPACK = "is_legacy_iconpack";
+
+ /**
+ * install/update time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo. It is used for syncing to PM
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String LAST_UPDATE_TIME = "updateTime";
+
+ /**
+ * install time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String INSTALL_TIME = "install_time";
+
+ /**
+ * The target API this theme supports
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String TARGET_API = "target_api";
+ }
+
+ /**
+ * Key-value table which assigns a component (ex wallpaper) to a theme's package
+ */
+ public static class MixnMatchColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "mixnmatch");
+
+ /**
+ * The unique key for a row. See the KEY_* constants
+ * for valid examples
+ * <P>Type: TEXT</P>
+ */
+ public static final String COL_KEY = "key";
+
+ /**
+ * The package name that corresponds to a given component.
+ * <P>Type: String</P>
+ */
+ public static final String COL_VALUE = "value";
+
+ /**
+ * Valid keys
+ */
+ public static final String KEY_HOMESCREEN = "mixnmatch_homescreen";
+ public static final String KEY_LOCKSCREEN = "mixnmatch_lockscreen";
+ public static final String KEY_ICONS = "mixnmatch_icons";
+ public static final String KEY_STATUS_BAR = "mixnmatch_status_bar";
+ public static final String KEY_BOOT_ANIM = "mixnmatch_boot_anim";
+ public static final String KEY_FONT = "mixnmatch_font";
+ public static final String KEY_ALARM = "mixnmatch_alarm";
+ public static final String KEY_NOTIFICATIONS = "mixnmatch_notifications";
+ public static final String KEY_RINGTONE = "mixnmatch_ringtone";
+ public static final String KEY_OVERLAYS = "mixnmatch_overlays";
+ public static final String KEY_NAVIGATION_BAR = "mixnmatch_navigation_bar";
+
+ public static final String[] ROWS = { KEY_HOMESCREEN,
+ KEY_LOCKSCREEN,
+ KEY_ICONS,
+ KEY_STATUS_BAR,
+ KEY_BOOT_ANIM,
+ KEY_FONT,
+ KEY_NOTIFICATIONS,
+ KEY_RINGTONE,
+ KEY_ALARM,
+ KEY_OVERLAYS,
+ KEY_NAVIGATION_BAR
+ };
+
+ /**
+ * For a given key value in the MixNMatch table, return the column
+ * associated with it in the Themes Table. This is useful for URI based
+ * elements like wallpaper where the caller wishes to determine the
+ * wallpaper URI.
+ */
+ public static String componentToImageColName(String component) {
+ if (component.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.HOMESCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.LOCKSCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.BOOT_ANIM_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.FONT_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.ICON_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.STATUSBAR_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ throw new IllegalArgumentException("Notifications mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ throw new IllegalArgumentException("Ringtone mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.OVERLAYS_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ throw new IllegalArgumentException(
+ "Status bar mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ throw new IllegalArgumentException(
+ "Navigation bar mixnmatch component does not have a related column");
+ }
+ return null;
+ }
+
+ /**
+ * A component in the themes table (IE "mods_wallpaper") has an
+ * equivalent key in mixnmatch table
+ */
+ public static String componentToMixNMatchKey(String component) {
+ if (component.equals(ThemesColumns.MODIFIES_LAUNCHER)) {
+ return MixnMatchColumns.KEY_HOMESCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_ICONS)) {
+ return MixnMatchColumns.KEY_ICONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_LOCKSCREEN)) {
+ return MixnMatchColumns.KEY_LOCKSCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_FONTS)) {
+ return MixnMatchColumns.KEY_FONT;
+ } else if (component.equals(ThemesColumns.MODIFIES_BOOT_ANIM)) {
+ return MixnMatchColumns.KEY_BOOT_ANIM;
+ } else if (component.equals(ThemesColumns.MODIFIES_ALARMS)) {
+ return MixnMatchColumns.KEY_ALARM;
+ } else if (component.equals(ThemesColumns.MODIFIES_NOTIFICATIONS)) {
+ return MixnMatchColumns.KEY_NOTIFICATIONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_RINGTONES)) {
+ return MixnMatchColumns.KEY_RINGTONE;
+ } else if (component.equals(ThemesColumns.MODIFIES_OVERLAYS)) {
+ return MixnMatchColumns.KEY_OVERLAYS;
+ } else if (component.equals(ThemesColumns.MODIFIES_STATUS_BAR)) {
+ return MixnMatchColumns.KEY_STATUS_BAR;
+ } else if (component.equals(ThemesColumns.MODIFIES_NAVIGATION_BAR)) {
+ return MixnMatchColumns.KEY_NAVIGATION_BAR;
+ }
+ return null;
+ }
+
+ /**
+ * A mixnmatch key in has an
+ * equivalent value in the themes table
+ */
+ public static String mixNMatchKeyToComponent(String mixnmatchKey) {
+ if (mixnmatchKey.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.MODIFIES_LAUNCHER;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.MODIFIES_ICONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.MODIFIES_LOCKSCREEN;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.MODIFIES_FONTS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.MODIFIES_BOOT_ANIM;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ALARM)) {
+ return ThemesColumns.MODIFIES_ALARMS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ return ThemesColumns.MODIFIES_NOTIFICATIONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ return ThemesColumns.MODIFIES_RINGTONES;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.MODIFIES_OVERLAYS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.MODIFIES_STATUS_BAR;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ return ThemesColumns.MODIFIES_NAVIGATION_BAR;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Table containing cached preview blobs for a given theme
+ */
+ public static class PreviewColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "previews");
+
+ /**
+ * Uri for retrieving the previews for the currently applied components.
+ * Querying the themes provider using this URI will return a cursor with a single row
+ * containing all the previews for the components that are currently applied.
+ */
+ public static final Uri APPLIED_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "applied_previews");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The unique ID for the theme these previews belong to.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String THEME_ID = "theme_id";
+
+ /**
+ * Cached image of the themed status bar background.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BACKGROUND = "statusbar_background";
+
+ /**
+ * Cached image of the themed bluetooth status icon.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BLUETOOTH_ICON = "statusbar_bluetooth_icon";
+
+ /**
+ * Cached image of the themed wifi status icon.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_WIFI_ICON = "statusbar_wifi_icon";
+
+ /**
+ * Cached image of the themed cellular signal status icon.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_SIGNAL_ICON = "statusbar_signal_icon";
+
+ /**
+ * Cached image of the themed battery using portrait style.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BATTERY_PORTRAIT = "statusbar_battery_portrait";
+
+ /**
+ * Cached image of the themed battery using landscape style.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BATTERY_LANDSCAPE = "statusbar_battery_landscape";
+
+ /**
+ * Cached image of the themed battery using circle style.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BATTERY_CIRCLE = "statusbar_battery_circle";
+
+ /**
+ * The themed margin value between the wifi and rssi signal icons.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_WIFI_COMBO_MARGIN_END = "wifi_combo_margin_end";
+
+ /**
+ * The themed color used for clock text in the status bar.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_CLOCK_TEXT_COLOR = "statusbar_clock_text_color";
+
+ /**
+ * Cached image of the themed navigation bar background.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_BACKGROUND = "navbar_background";
+
+ /**
+ * Cached image of the themed back button.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_BACK_BUTTON = "navbar_back_button";
+
+ /**
+ * Cached image of the themed home button.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_HOME_BUTTON = "navbar_home_button";
+
+ /**
+ * Cached image of the themed recents button.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_RECENT_BUTTON = "navbar_recent_button";
+
+ /**
+ * Cached image of the 1/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_1 = "icon_preview_1";
+
+ /**
+ * Cached image of the 2/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_2 = "icon_preview_2";
+
+ /**
+ * Cached image of the 3/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_3 = "icon_preview_3";
+
+ /**
+ * Cached image of the 4/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_4 = "icon_preview_4";
+
+ /**
+ * Cached preview of UI controls representing the theme's style
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STYLE_PREVIEW = "style_preview";
+
+ /**
+ * Cached thumbnail preview of UI controls representing the theme's style
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STYLE_THUMBNAIL = "style_thumbnail";
+
+ /**
+ * Cached thumbnail of the theme's boot animation
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String BOOTANIMATION_THUMBNAIL = "bootanimation_thumbnail";
+
+ /**
+ * Cached thumbnail of the theme's wallpaper
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String WALLPAPER_THUMBNAIL = "wallpaper_thumbnail";
+
+ /**
+ * Cached preview of the theme's wallpaper which is larger than the thumbnail
+ * but smaller than the full sized wallpaper.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String WALLPAPER_PREVIEW = "wallpaper_preview";
+
+ /**
+ * Cached thumbnail of the theme's lockscreen wallpaper
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String LOCK_WALLPAPER_THUMBNAIL = "lock_wallpaper_thumbnail";
+
+ /**
+ * Cached preview of the theme's lockscreen wallpaper which is larger than the thumbnail
+ * but smaller than the full sized lockscreen wallpaper.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String LOCK_WALLPAPER_PREVIEW = "lock_wallpaper_preview";
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6aa86c7a0be..d5afdf8f421 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -125,7 +125,7 @@ interface IWindowManager
boolean inKeyguardRestrictedInputMode();
void dismissKeyguard();
void keyguardGoingAway(boolean disableWindowAnimations,
- boolean keyguardGoingToNotificationShade);
+ boolean keyguardGoingToNotificationShade, boolean keyguardShowingMedia);
void closeSystemDialogs(String reason);
@@ -223,6 +223,16 @@ interface IWindowManager
int maxHeight, boolean force565);
/**
+ * Get the current x offset for the wallpaper
+ */
+ int getLastWallpaperX();
+
+ /**
+ * Get the current y offset for the wallpaper
+ */
+ int getLastWallpaperY();
+
+ /**
* Called by the status bar to notify Views of changes to System UI visiblity.
*/
oneway void statusBarVisibilityChanged(int visibility);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 037ed281be1..789f8ad8b64 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -182,6 +182,16 @@ interface IWindowSession {
*/
void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
+ /**
+ * Get the current x offset for the wallpaper
+ */
+ int getLastWallpaperX();
+
+ /**
+ * Get the current y offset for the wallpaper
+ */
+ int getLastWallpaperY();
+
Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, in Bundle extras, boolean sync);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 673f075e6c1..a1e0dc775fd 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -758,7 +758,8 @@ public interface WindowManagerPolicy {
* Create and return an animation to let the wallpaper disappear after being shown on a force
* hiding window.
*/
- public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade);
+ public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade,
+ boolean keyguardShowingMedia);
/**
* Called from the input reader thread before a key is enqueued.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7cb3c378ace..2c0fca1bc9e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2516,6 +2516,12 @@ public class RemoteViews implements Parcelable, Filter {
/** @hide */
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
+ return apply(context, parent, handler, null);
+ }
+
+ /** @hide */
+ public View apply(Context context, ViewGroup parent, OnClickHandler handler,
+ String themePackageName) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
@@ -2523,7 +2529,7 @@ public class RemoteViews implements Parcelable, Filter {
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
// are loaded without requiring cross user persmissions.
- final Context contextForResources = getContextForResources(context);
+ final Context contextForResources = getContextForResources(context, themePackageName);
Context inflationContext = new ContextWrapper(context) {
@Override
public Resources getResources() {
@@ -2589,7 +2595,7 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- private Context getContextForResources(Context context) {
+ private Context getContextForResources(Context context, String themePackageName) {
if (mApplication != null) {
if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
&& context.getPackageName().equals(mApplication.packageName)) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 2ef8a20f7d9..272788d362f 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.graphics.Typeface;
import android.net.Credentials;
import android.net.LocalSocket;
import android.os.Process;
@@ -214,6 +215,10 @@ class ZygoteConnection {
ZygoteInit.setCloseOnExec(serverPipeFd, true);
}
+ if (parsedArgs.refreshTheme) {
+ Typeface.recreateDefaults();
+ }
+
/**
* In order to avoid leaking descriptors to the Zygote child,
* the native code must close the two Zygote socket descriptors
@@ -410,6 +415,9 @@ class ZygoteConnection {
*/
String appDataDir;
+ /** from --refresh_theme */
+ boolean refreshTheme;
+
/**
* Constructs instance and parses args
* @param args zygote command-line args
@@ -569,6 +577,8 @@ class ZygoteConnection {
instructionSet = arg.substring(arg.indexOf('=') + 1);
} else if (arg.startsWith("--app-data-dir=")) {
appDataDir = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--refresh_theme")) {
+ refreshTheme = true;
} else {
break;
}
diff --git a/core/java/com/android/internal/util/cm/ImageUtils.java b/core/java/com/android/internal/util/cm/ImageUtils.java
new file mode 100644
index 00000000000..d7803840fe9
--- /dev/null
+++ b/core/java/com/android/internal/util/cm/ImageUtils.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2013-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 com.android.internal.util.cm;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.net.Uri;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.URLUtil;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import libcore.io.IoUtils;
+
+public class ImageUtils {
+ private static final String TAG = ImageUtils.class.getSimpleName();
+
+ private static final String ASSET_URI_PREFIX = "file:///android_asset/";
+ private static final int DEFAULT_IMG_QUALITY = 100;
+
+ /**
+ * Gets the Width and Height of the image
+ *
+ * @param inputStream The input stream of the image
+ *
+ * @return A point structure that holds the Width and Height (x and y)/*"
+ */
+ public static Point getImageDimension(InputStream inputStream) {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("'inputStream' cannot be null!");
+ }
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+ Point point = new Point(options.outWidth,options.outHeight);
+ return point;
+ }
+
+ /**
+ * Crops the input image and returns a new InputStream of the cropped area
+ *
+ * @param inputStream The input stream of the image
+ * @param imageWidth Width of the input image
+ * @param imageHeight Height of the input image
+ * @param inputStream Desired Width
+ * @param inputStream Desired Width
+ *
+ * @return a new InputStream of the cropped area/*"
+ */
+ public static InputStream cropImage(InputStream inputStream, int imageWidth, int imageHeight,
+ int outWidth, int outHeight) throws IllegalArgumentException {
+ if (inputStream == null){
+ throw new IllegalArgumentException("inputStream cannot be null");
+ }
+
+ if (imageWidth <= 0 || imageHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("imageWidth and imageHeight must be > 0: imageWidth=%d" +
+ " imageHeight=%d", imageWidth, imageHeight));
+ }
+
+ if (outWidth <= 0 || outHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("outWidth and outHeight must be > 0: outWidth=%d" +
+ " outHeight=%d", imageWidth, outHeight));
+ }
+
+ int scaleDownSampleSize = Math.min(imageWidth / outWidth, imageHeight / outHeight);
+ if (scaleDownSampleSize > 0) {
+ imageWidth /= scaleDownSampleSize;
+ imageHeight /= scaleDownSampleSize;
+ } else {
+ float ratio = (float) outWidth / outHeight;
+ if (imageWidth < imageHeight * ratio) {
+ outWidth = imageWidth;
+ outHeight = (int) (outWidth / ratio);
+ } else {
+ outHeight = imageHeight;
+ outWidth = (int) (outHeight * ratio);
+ }
+ }
+ int left = (imageWidth - outWidth) / 2;
+ int top = (imageHeight - outHeight) / 2;
+ InputStream compressed = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
+ if (bitmap == null) {
+ return null;
+ }
+ Bitmap cropped = Bitmap.createBitmap(bitmap, left, top, outWidth, outHeight);
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (cropped.compress(Bitmap.CompressFormat.PNG, DEFAULT_IMG_QUALITY, tmpOut)) {
+ byte[] outByteArray = tmpOut.toByteArray();
+ compressed = new ByteArrayInputStream(outByteArray);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ }
+ return compressed;
+ }
+
+ /**
+ * Crops the lock screen image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedKeyguardStream(String pkgName, Context context)
+ throws IllegalArgumentException {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ /**
+ * Crops the wallpaper image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedWallpaperStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalWallpaperStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalWallpaperStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ private static InputStream getOriginalKeyguardStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ try {
+ //Get input WP stream from the theme
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ String wpPath = ThemeUtils.getLockscreenWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.w(TAG, "Not setting lockscreen wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error setting lockscreen wp for pkg " + pkgName, e);
+ }
+ return inputStream;
+ }
+
+ private static InputStream getOriginalWallpaperStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = {pkgName};
+ Cursor c = context.getContentResolver().query(ThemesColumns.CONTENT_URI,
+ null, selection,
+ selectionArgs, null);
+ if (c == null || c.getCount() < 1) {
+ if (c != null) c.close();
+ return null;
+ } else {
+ c.moveToFirst();
+ }
+
+ try {
+ Context themeContext = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ boolean isLegacyTheme = c.getInt(
+ c.getColumnIndex(ThemesColumns.IS_LEGACY_THEME)) == 1;
+ String wallpaper = c.getString(
+ c.getColumnIndex(ThemesColumns.WALLPAPER_URI));
+ if (wallpaper != null) {
+ if (URLUtil.isAssetUrl(wallpaper)) {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeContext, wallpaper);
+ } else {
+ inputStream = context.getContentResolver().openInputStream(
+ Uri.parse(wallpaper));
+ }
+ } else {
+ // try and get the wallpaper directly from the apk if the URI was null
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ String wpPath = ThemeUtils.getWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.e(TAG, "Not setting wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "getWallpaperStream: " + e);
+ } finally {
+ c.close();
+ }
+
+ return inputStream;
+ }
+}
+
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 0dd35ea8e76..386718b233d 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -518,24 +518,138 @@ static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz
}
static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz,
- jstring idmapPath)
+ jstring idmapPath,
+ jstring packagePath,
+ jstring resApkPath, jstring targetPkgPath,
+ jstring prefixPath)
{
ScopedUtfChars idmapPath8(env, idmapPath);
if (idmapPath8.c_str() == NULL) {
return 0;
}
+ ScopedUtfChars packagePath8(env, packagePath);
+ if (packagePath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars resApkPath8(env, resApkPath);
+ if (resApkPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars prefixPath8(env, prefixPath);
+ if (prefixPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars targetPkgPath8(env, targetPkgPath);
+ if (targetPkgPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return 0;
+ }
+
+ int32_t cookie;
+ bool res = am->addOverlayPath(
+ String8(idmapPath8.c_str()),
+ String8(packagePath8.c_str()),
+ &cookie,
+ String8(resApkPath8.c_str()),
+ String8(targetPkgPath8.c_str()),
+ String8(prefixPath8.c_str()));
+
+ return (res) ? (jint)cookie : 0;
+}
+
+static jint android_content_AssetManager_addCommonOverlayPath(JNIEnv* env, jobject clazz,
+ jstring packagePath,
+ jstring resApkPath, jstring prefixPath)
+{
+ ScopedUtfChars packagePath8(env, packagePath);
+ if (packagePath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars resApkPath8(env, resApkPath);
+ if (resApkPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars prefixPath8(env, prefixPath);
+ if (prefixPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return 0;
+ }
+
+ int32_t cookie;
+ bool res = am->addCommonOverlayPath(String8(packagePath8.c_str()), &cookie,
+ String8(resApkPath8.c_str()),
+ String8(prefixPath8.c_str()));
+
+ return (res) ? (jint)cookie : 0;
+}
+
+static jint android_content_AssetManager_addIconPath(JNIEnv* env, jobject clazz,
+ jstring packagePath,
+ jstring resApkPath, jstring prefixPath,
+ jint pkgIdOverride)
+{
+ ScopedUtfChars packagePath8(env, packagePath);
+ if (packagePath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars resApkPath8(env, resApkPath);
+ if (resApkPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars prefixPath8(env, prefixPath);
+ if (prefixPath8.c_str() == NULL) {
+ return 0;
+ }
+
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
int32_t cookie;
- bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie);
+ bool res = am->addIconPath(String8(packagePath8.c_str()), &cookie,
+ String8(resApkPath8.c_str()),
+ String8(prefixPath8.c_str()), pkgIdOverride);
return (res) ? (jint)cookie : 0;
}
+static jboolean android_content_AssetManager_removeOverlayPath(JNIEnv* env, jobject clazz,
+ jstring packageName, jint cookie)
+{
+ if (packageName == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", "packageName");
+ return JNI_FALSE;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ const char* name8 = env->GetStringUTFChars(packageName, NULL);
+ bool res = am->removeOverlayPath(String8(name8), cookie);
+ env->ReleaseStringUTFChars(packageName, name8);
+
+ return res;
+}
+
static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
@@ -1077,7 +1191,7 @@ static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject c
const ResTable::bag_entry* defStyleEnt = NULL;
uint32_t defStyleTypeSetFlags = 0;
ssize_t bagOff = defStyleRes != 0
- ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags) : -1;
+ ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags, true) : -1;
defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
const ResTable::bag_entry* endDefStyleEnt = defStyleEnt +
(bagOff >= 0 ? bagOff : 0);;
@@ -1286,7 +1400,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
const ResTable::bag_entry* defStyleEnt = NULL;
uint32_t defStyleTypeSetFlags = 0;
ssize_t bagOff = defStyleRes != 0
- ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags) : -1;
+ ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags, true) : -1;
defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
const ResTable::bag_entry* endDefStyleEnt = defStyleEnt +
(bagOff >= 0 ? bagOff : 0);
@@ -1294,7 +1408,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
// Retrieve the style class bag, if requested.
const ResTable::bag_entry* styleEnt = NULL;
uint32_t styleTypeSetFlags = 0;
- bagOff = style != 0 ? res.getBagLocked(style, &styleEnt, &styleTypeSetFlags) : -1;
+ bagOff = style != 0 ? res.getBagLocked(style, &styleEnt, &styleTypeSetFlags, true) : -1;
styleTypeSetFlags |= styleBagTypeSetFlags;
const ResTable::bag_entry* endStyleEnt = styleEnt +
(bagOff >= 0 ? bagOff : 0);
@@ -1631,7 +1745,7 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz
const ResTable::bag_entry* arrayEnt = NULL;
uint32_t arrayTypeSetFlags = 0;
- ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags);
+ ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags, true);
const ResTable::bag_entry* endArrayEnt = arrayEnt +
(bagOff >= 0 ? bagOff : 0);
@@ -1968,6 +2082,50 @@ static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env,
return AssetManager::getGlobalCount();
}
+static jint android_content_AssetManager_getBasePackageCount(JNIEnv* env, jobject clazz)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ return am->getResources().getBasePackageCount();
+}
+
+static jstring android_content_AssetManager_getBasePackageName(JNIEnv* env, jobject clazz,
+ jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ String16 packageName(am->getBasePackageName(index));
+ return env->NewString((const jchar*)packageName.string(), packageName.size());
+}
+
+static jstring android_content_AssetManager_getBaseResourcePackageName(JNIEnv* env, jobject clazz,
+ jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ String16 packageName(am->getResources().getBasePackageName(index));
+ return env->NewString((const jchar*)packageName.string(), packageName.size());
+}
+
+static jint android_content_AssetManager_getBasePackageId(JNIEnv* env, jobject clazz, jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ return am->getResources().getBasePackageId(index);
+}
+
// ----------------------------------------------------------------------------
/*
@@ -2001,8 +2159,22 @@ static JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;)I",
(void*) android_content_AssetManager_addAssetPath },
- { "addOverlayPathNative", "(Ljava/lang/String;)I",
+ { "addOverlayPathNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
(void*) android_content_AssetManager_addOverlayPath },
+ { "addCommonOverlayPathNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*) android_content_AssetManager_addCommonOverlayPath },
+ { "addIconPathNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)I",
+ (void*) android_content_AssetManager_addIconPath },
+ { "removeOverlayPathNative", "(Ljava/lang/String;I)Z",
+ (void*) android_content_AssetManager_removeOverlayPath },
+ { "getBasePackageCount", "()I",
+ (void*) android_content_AssetManager_getBasePackageCount },
+ { "getBasePackageName", "(I)Ljava/lang/String;",
+ (void*) android_content_AssetManager_getBasePackageName },
+ { "getBaseResourcePackageName", "(I)Ljava/lang/String;",
+ (void*) android_content_AssetManager_getBaseResourcePackageName },
+ { "getBasePackageId", "(I)I",
+ (void*) android_content_AssetManager_getBasePackageId },
{ "isUpToDate", "()Z",
(void*) android_content_AssetManager_isUpToDate },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9069fbb8d15..ce22eccd649 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -291,6 +291,8 @@
<protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_PROGRESS" />
<protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+ <protected-broadcast android:name="org.cyanogenmod.intent.action.THEME_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.THEME_RESOURCES_CACHED" />
<!-- ====================================== -->
<!-- Permissions for things that cost money -->
@@ -1539,6 +1541,14 @@
android:label="@string/permlab_setWallpaper"
android:description="@string/permdesc_setWallpaper" />
+ <!-- Allows applications to set the keyguard wallpaper
+ @hide -->
+ <permission android:name="android.permission.SET_KEYGUARD_WALLPAPER"
+ android:permissionGroup="android.permission-group.WALLPAPER"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_setKeyguardWallpaper"
+ android:description="@string/permdesc_setKeyguardWallpaper" />
+
<!-- Allows applications to set the wallpaper hints -->
<permission android:name="android.permission.SET_WALLPAPER_HINTS"
android:permissionGroup="android.permission-group.WALLPAPER"
@@ -2877,6 +2887,30 @@
android:label="@string/permlab_changePhoneBlacklist"
android:description="@string/permdesc_changePhoneBlacklist" />
+ <!-- Allows an application to use the Theme Service
+ @hide -->
+ <permission android:name="android.permission.ACCESS_THEME_MANAGER"
+ android:label="@string/permlab_accessThemeService"
+ android:description="@string/permdesc_accessThemeService"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to read the current theme configuration and
+ get information about the various themes currently installed
+ @hide -->
+ <permission android:name="android.permission.READ_THEMES"
+ android:label="@string/permlab_readThemes"
+ android:description="@string/permdesc_readThemesDesc"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to write the current theme configuration and
+ write information about the various themes currently installed.
+ Changing themes should be done through the service ACCESS_THEME_MANAGER
+ @hide -->
+ <permission android:name="android.permission.WRITE_THEMES"
+ android:label="@string/permlab_writeThemes"
+ android:description="@string/permdesc_writeThemesDesc"
+ android:protectionLevel="system|signature" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
@@ -3074,6 +3108,18 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.AppsFailureReceiver" >
+ <intent-filter>
+ <action android:name="com.tmobile.intent.action.APP_FAILURE" />
+ <action android:name="com.tmobile.intent.action.APP_FAILURE_RESET" />
+ <action android:name="android.intent.action.PACKAGE_ADDED" />
+ <action android:name="android.intent.action.PACKAGE_REMOVED" />
+ <action android:name="org.cyanogenmod.intent.action.THEME_CHANGED" />
+ <category android:name="com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE" />
+ <data android:scheme="package" />
+ </intent-filter>
+ </receiver>
+
<service android:name="com.android.internal.os.storage.ExternalStorageFormatter"
android:permission="android.permission.MASTER_CLEAR"
android:exported="true" />
diff --git a/core/res/res/anim/lock_screen_wallpaper_exit_noop.xml b/core/res/res/anim/lock_screen_wallpaper_exit_noop.xml
new file mode 100644
index 00000000000..4cc5c70aae3
--- /dev/null
+++ b/core/res/res/anim/lock_screen_wallpaper_exit_noop.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" android:startOffset="100">
+ <alpha
+ android:fromAlpha="0.0" android:toAlpha="0.0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_linear_in"
+ android:duration="150"/>
+
+ <!-- Empty animation so the animation has same duration as lock_screen_behind_enter animation
+ -->
+ <translate android:fromYDelta="0" android:toYDelta="0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:duration="300" />
+</set> \ No newline at end of file
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 10c2518ec19..73887a49d7e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -745,6 +745,8 @@
<flag name="smallestScreenSize" value="0x0800" />
<!-- The layout direction has changed. For example going from LTR to RTL. -->
<flag name="layoutDirection" value="0x2000" />
+ <!-- Theme has changed -->
+ <flag name="themeChange" value="0x8000" />
<!-- The font scaling factor has changed, that is the user has
selected a new global font size. -->
<flag name="fontScale" value="0x40000000" />
diff --git a/core/res/res/values/cm_strings.xml b/core/res/res/values/cm_strings.xml
index 9ba01c5b90a..a2d16c0fa65 100755
--- a/core/res/res/values/cm_strings.xml
+++ b/core/res/res/values/cm_strings.xml
@@ -49,6 +49,23 @@
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_security">Permissions related to device security information.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessThemeService">access theme service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessThemeService">Allows an app to access the theme service. Should never be needed for normal apps.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_readThemes">read your theme info</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_readThemesDesc">Allows the app to read your themes and
+ determine which theme you have applied.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_writeThemes">modify your themes</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_writeThemesDesc">Allows the app to insert new themes and
+ modify which theme you have applied.</string>
+
<!-- [CHAR LIMIT=NONE] Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readPhoneBlacklist">read phone blacklist</string>
<!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -58,4 +75,14 @@
<string name="permlab_changePhoneBlacklist">change phone blacklist</string>
<!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_changePhoneBlacklist">Allows an app to change the phone numbers that are blocked for incoming calls or messages.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want the application to do this. -->
+ <string name="permlab_setKeyguardWallpaper">set keyguard wallpaper</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_setKeyguardWallpaper">Allows an app to change the lock screen wallpaper.</string>
+
+ <!-- Theme installation error notification -->
+ <string name="theme_install_error_title">Failed to install theme</string>
+ <string name="theme_install_error_message">%s failed to install</string>
+ <string name="theme_reset_notification_title">Theme reset</string>
+ <string name="theme_reset_notification_body">System theme restored due to multiple app crashes.</string>
</resources>
diff --git a/core/res/res/values/cm_symbols.xml b/core/res/res/values/cm_symbols.xml
new file mode 100644
index 00000000000..1e5c2003cd4
--- /dev/null
+++ b/core/res/res/values/cm_symbols.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2012, 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.
+*/
+-->
+<resources>
+ <!-- We don't want to publish private symbols in android.R as part of the
+ SDK. Instead, put them here. -->
+ <private-symbols package="com.android.internal" />
+
+ <!-- Theme install failure notification -->
+ <java-symbol type="string" name="theme_install_error_title" />
+ <java-symbol type="string" name="theme_install_error_message" />
+
+ <!-- Theme reset notification -->
+ <java-symbol type="string" name="theme_reset_notification_title" />
+ <java-symbol type="string" name="theme_reset_notification_body" />
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f135a75a2cf..348874b6935 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1870,6 +1870,7 @@
<java-symbol type="anim" name="lock_screen_behind_enter_wallpaper" />
<java-symbol type="anim" name="lock_screen_behind_enter_fade_in" />
<java-symbol type="anim" name="lock_screen_wallpaper_exit" />
+ <java-symbol type="anim" name="lock_screen_wallpaper_exit_noop" />
<java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
<java-symbol type="dimen" name="status_bar_icon_size" />