final ApplicationThread mAppThread = new ApplicationThread();
final Looper mLooper = Looper.myLooper();
@@ -1355,12 +1365,14 @@ public final class ActivityThread {
private static class ResourcesKey {
final private String mResDir;
final private float mScale;
+ final private boolean mIsThemeable;
final private int mHash;
@@ -1405,6 +1418,18 @@ public final class ActivityThread {
return sPackageManager;
}
+ // NOTE: this method can return null if the SystemServer is still
+ // initializing (for example, of another SystemServer component is accessing
+ // a resources object)
+ public static IAssetRedirectionManager getAssetRedirectionManager() {
+ if (sAssetRedirectionManager != null) {
+ return sAssetRedirectionManager;
+ }
+ IBinder b = ServiceManager.getService("assetredirection");
+ sAssetRedirectionManager = IAssetRedirectionManager.Stub.asInterface(b);
+ return sAssetRedirectionManager;
+ }
+
DisplayMetrics getDisplayMetricsLocked(CompatibilityInfo ci, boolean forceUpdate) {
DisplayMetrics dm = mDisplayMetrics.get(ci);
if (dm != null && !forceUpdate) {
@@ -1454,7 +1479,7 @@ public final class ActivityThread {
* null.
*/
Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
- ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
+ ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale, compInfo.isThemeable);
Resources r;
synchronized (mPackages) {
// Resources is app scale dependent.
@@ -1480,10 +1505,23 @@ public final class ActivityThread {
//}
AssetManager assets = new AssetManager();
+ assets.setThemeSupport(compInfo.isThemeable);
if (assets.addAssetPath(resDir) == 0) {
return null;
}
+ /* Attach theme information to the resulting AssetManager when appropriate. */
+ Configuration config = getConfiguration();
+ if (compInfo.isThemeable && config != null) {
+ if (config.customTheme == null) {
+ config.customTheme = CustomTheme.getBootTheme();
+ }
+ boolean isThemeApk = resDir.startsWith("/system/app/theme-");
+ if (!isThemeApk && !TextUtils.isEmpty(config.customTheme.getThemePackageName())) {
+ attachThemeAssets(assets, config.customTheme);
+ }
+ }
+
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics metrics = getDisplayMetricsLocked(null, false);
r = new Resources(assets, metrics, getConfiguration(), compInfo);
@@ -1509,6 +1547,81 @@ public final class ActivityThread {
}
}
+ private void detachThemeAssets(AssetManager assets) {
+ String themePackageName = assets.getThemePackageName();
+ int themeCookie = assets.getThemeCookie();
+ if (!TextUtils.isEmpty(themePackageName) && themeCookie != 0) {
+ assets.detachThemePath(themePackageName, themeCookie);
+ assets.setThemePackageName(null);
+ assets.setThemeCookie(0);
+ assets.clearRedirections();
+ }
+ }
+
+ /**
+ * 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, CustomTheme theme) {
+ IAssetRedirectionManager rm = getAssetRedirectionManager();
+ if (rm == null) {
+ return false;
+ }
+ PackageInfo pi = null;
+ try {
+ pi = getPackageManager().getPackageInfo(theme.getThemePackageName(), 0);
+ } catch (RemoteException e) {
+ }
+ if (pi != null && pi.applicationInfo != null && pi.themeInfos != null) {
+ String themeResDir = pi.applicationInfo.publicSourceDir;
+ int cookie = assets.attachThemePath(themeResDir);
+ if (cookie != 0) {
+ String themePackageName = theme.getThemePackageName();
+ String themeId = theme.getThemeId();
+ int N = assets.getBasePackageCount();
+ for (int i = 0; i < N; i++) {
+ String packageName = assets.getBasePackageName(i);
+ int packageId = assets.getBasePackageId(i);
+
+ /*
+ * For now, we only consider redirections coming from the
+ * framework or regular android packages. This excludes
+ * themes and other specialty APKs we are not aware of.
+ */
+ if (packageId != 0x01 && packageId != 0x7f) {
+ continue;
+ }
+
+ try {
+ PackageRedirectionMap map = rm.getPackageRedirectionMap(themePackageName, themeId,
+ packageName);
+ if (map != null) {
+ assets.addRedirections(map);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failure accessing package redirection map, removing theme support.");
+ assets.detachThemePath(themePackageName, cookie);
+ return false;
+ }
+ }
+
+ assets.setThemePackageName(theme.getThemePackageName());
+ assets.setThemeCookie(cookie);
+ return true;
+ } else {
+ Log.e(TAG, "Unable to attach theme assets at " + themeResDir);
+ }
+ }
+ return false;
+ }
+
/**
* Creates the top level resources for the given package.
*/
@@ -1953,6 +2066,16 @@ public final class ActivityThread {
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
+ if (e instanceof InflateException) {
+ Log.e(TAG, "Failed to inflate", e);
+ String pkg = null;
+ if (r.packageInfo != null && !TextUtils.isEmpty(r.packageInfo.getPackageName())) {
+ pkg = r.packageInfo.getPackageName();
+ }
+ Intent intent = new Intent(Intent.ACTION_APP_LAUNCH_FAILURE,
+ (pkg != null)? Uri.fromParts("package", pkg, null) : null);
+ getSystemContext().sendBroadcast(intent);
+ }
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
@@ -3478,7 +3601,13 @@ public final class ActivityThread {
}
}
- final boolean applyConfigurationToResourcesLocked(Configuration config,
+ /*
+ * Original code returned a boolean here to denote whether changes were
+ * detected. But T-Mobile must know what specifically has changed to check
+ * later if the theme had changed, so we return the changes bitmap instead.
+ * Caller beware.
+ */
+ final int applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
if (mResConfiguration == null) {
mResConfiguration = new Configuration();
@@ -3486,7 +3615,7 @@ public final class ActivityThread {
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq);
- return false;
+ return 0;
}
int changes = mResConfiguration.updateFrom(config);
DisplayMetrics dm = getDisplayMetricsLocked(null, true);
@@ -3519,7 +3648,20 @@ public final class ActivityThread {
if (r != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
+ boolean themeChanged = (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0;
+ if (themeChanged) {
+ AssetManager am = r.getAssets();
+ if (am.hasThemeSupport()) {
+ detachThemeAssets(am);
+ if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) {
+ attachThemeAssets(am, config.customTheme);
+ }
+ }
+ }
r.updateConfiguration(config, dm, compat);
+ if (themeChanged) {
+ r.updateStringCache();
+ }
//Slog.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -3528,7 +3670,7 @@ public final class ActivityThread {
}
}
- return changes != 0;
+ return changes;
}
final Configuration applyCompatConfiguration() {
@@ -3548,6 +3690,8 @@ public final class ActivityThread {
ArrayList<ComponentCallbacks2> callbacks = null;
+ int diff = 0;
+
synchronized (mPackages) {
if (mPendingConfiguration != null) {
if (!mPendingConfiguration.isOtherSeqNewer(config)) {
@@ -3563,7 +3707,7 @@ public final class ActivityThread {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
if (mConfiguration == null) {
mConfiguration = new Configuration();
@@ -3582,7 +3726,20 @@ public final class ActivityThread {
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
- performConfigurationChanged(callbacks.get(i), config);
+ ComponentCallbacks2 cb = callbacks.get(i);
+
+ // We removed the old resources object from the mActiveResources
+ // cache, now we need to trigger an update for each application.
+ if ((diff & ActivityInfo.CONFIG_THEME_RESOURCE) != 0) {
+ if (cb instanceof Activity || cb instanceof Application) {
+ Context context = ((ContextWrapper)cb).getBaseContext();
+ if (context instanceof ContextImpl) {
+ ((ContextImpl)context).refreshResourcesIfNecessary();
+ }
+ }
+ }
+
+ performConfigurationChanged(cb, config);
}
}
}
@@ -4356,7 +4513,7 @@ public final class ActivityThread {
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
- if (applyConfigurationToResourcesLocked(newConfig, null)) {
+ if (applyConfigurationToResourcesLocked(newConfig, null) != 0) {
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 180a442..ce8fd39 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -403,6 +403,16 @@ final class ApplicationPackageManager extends PackageManager {
@SuppressWarnings("unchecked")
@Override
+ public List<ackageInfo> getInstalledThemePackages() {
+ try {
+ return mPM.getInstalledThemePackages();
+ } catch (RemoteException e) {
+ throw new RuntimeException("ackage manager has died", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
public List<ApplicationInfo> getInstalledApplications(int flags) {
try {
final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2bf1fb7..74c653d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -478,6 +478,20 @@ class ContextImpl extends Context {
return mResources;
}
+ /**
+ * Refresh resources object which may have been changed by a theme
+ * configuration change.
+ */
+ /* package */ void refreshResourcesIfNecessary() {
+ if (mResources == Resources.getSystem()) {
+ return;
+ }
+
+ if (mPackageInfo.mCompatibilityInfo.get().isThemeable) {
+ mTheme = null;
+ }
+ }
+
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 615e8ce..283cccd 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -164,6 +164,19 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ /**
+ * 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_LAUNCH_FAILURE = "com.tmobile.intent.action.APP_LAUNCH_FAILURE";
+
+ /**
+ * Broadcast Action: Request to reset the unrecoverable errors count to 0.
+ * @hide
+ */
+ public static final String ACTION_APP_LAUNCH_FAILURE_RESET = "com.tmobile.intent.action.APP_LAUNCH_FAILURE_RESET";
+
+ /**
+ * Broadcast Action: Broadcast a new dBm value
+ * @hide
+ */
+ public static final String ACTION_SIGNAL_DBM_CHANGED = "com.cyanogenmod.intent.action.DBM_SIGNAL_CHANGED";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2308,6 +2327,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()).
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0e6694d..79c933b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -321,6 +321,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 65a8750..a25e7f8 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -415,6 +415,31 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public boolean enabled = true;
/**
+ * Is given application theme agnostic, i.e. behaves properly when default theme is changed.
+ * {@hide}
+ */
+ public boolean isThemeable = false;
+
+ private static final String PLUTO_SCHEMA = "http://www.w3.org/2001/pluto.html";
+
+ /**
+ * @hide
+ */
+ public static final String PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME = "isThemeable";
+
+ /**
+ * @hide
+ */
+ public static final String PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME = "handleThemeConfigChanges";
+
+ /**
+ * @hide
+ */
+ public static boolean isPlutoNamespace(String namespace) {
+ return namespace != null && namespace.equalsIgnoreCase(PLUTO_SCHEMA);
+ }
+
+ /**
* For convenient access to the current enabled setting of this app.
* @hide
*/
@@ -520,6 +545,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
manageSpaceActivityName = orig.manageSpaceActivityName;
descriptionRes = orig.descriptionRes;
uiOptions = orig.uiOptions;
+ isThemeable = orig.isThemeable;
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -597,6 +624,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
backupAgentName = source.readString();
descriptionRes = source.readInt();
uiOptions = source.readInt();
+ 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 0000000..0520f1a
--- /dev/null
+++ b/core/java/android/content/pm/BaseThemeInfo.java
@@ -0,0 +1,249 @@
+/*
+ * 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 {
+
+ /**
+ * Wallpaper drawable.
+ *
+ * @see wallpaperImage attribute
+ */
+ public int wallpaperResourceId;
+
+ /**
+ * The resource id of theme thumbnail.
+ * Specifies a theme thumbnail image resource as @drawable/foo.
+ *
+ * @see thumbnail attribute
+ *
+ */
+ public int thumbnailResourceId;
+
+ /**
+ * 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 style resource id of Android UI Style, supplied by the resource commpiler.
+ * Specifies an Android UI Style id.
+ *
+ * @see styleId attribute
+ *
+ */
+ public int styleResourceId = 0;
+
+ /**
+ * The name of the theme (as displayed by UI).
+ *
+ * @see name attribute
+ *
+ */
+ public String name;
+
+
+ public int nameId;
+
+ /**
+ * The name of the call ringtone audio file.
+ * Specifies a relative path in assets subfolder.
+ * If the parent's name is "locked" - DRM protected.
+ *
+ * @see ringtoneFileName attribute
+ *
+ */
+ public String ringtoneFileName;
+
+ /**
+ * The name of the call ringtone as shown to user.
+ *
+ * @see ringtoneName attribute
+ *
+ */
+ public String ringtoneName;
+
+ /**
+ * The name of the notification ringtone audio file.
+ * Specifies a relative path in assets subfolder.
+ * If the parent's name is "locked" - DRM protected.
+ *
+ * @see notificationRingtoneFileName attribute
+ *
+ */
+ public String notificationRingtoneFileName;
+
+ /**
+ * The name of the notification ringtone as shown to user.
+ *
+ * @see notificationRingtoneName attribute
+ *
+ */
+ public String notificationRingtoneName;
+
+ /**
+ * The author name of the theme package.
+ *
+ * @see author attribute
+ *
+ */
+ public String author;
+
+ /**
+ * The copyright text.
+ *
+ * @see copyright attribute
+ *
+ */
+ public String copyright;
+
+ /**
+ * {@hide}
+ */
+ // There is no corresposponding flag in manifest file
+ // This flag is set to true iff any media resource is DRM protected
+ public boolean isDrmProtected = false;
+
+ /**
+ * The name of the "main" theme style (as displayed by UI).
+ *
+ * @see themeStyleName attribute
+ *
+ */
+ public String themeStyleName;
+
+ /**
+ * Preview image drawable.
+ *
+ * @see preview attribute
+ */
+ public int previewResourceId;
+
+ /**
+ * The name of a sound pack.
+ *
+ * @see soundpack attribute
+ *
+ */
+ public String soundPackName;
+
+
+ private static final String LOCKED_NAME = "locked/";
+
+ /*
+ * 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.writeInt(wallpaperResourceId);
+ dest.writeInt(thumbnailResourceId);
+ dest.writeString(themeId);
+ dest.writeInt(styleResourceId);
+ dest.writeString(name);
+ dest.writeInt(nameId);
+ dest.writeString(ringtoneFileName);
+ dest.writeString(notificationRingtoneFileName);
+ dest.writeString(ringtoneName);
+ dest.writeString(notificationRingtoneName);
+ dest.writeString(author);
+ dest.writeString(copyright);
+ dest.writeInt(isDrmProtected? 1 : 0);
+ dest.writeString(soundPackName);
+ dest.writeString(themeStyleName);
+ dest.writeInt(previewResourceId);
+ }
+
+ /** @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) {
+ wallpaperResourceId = source.readInt();
+ thumbnailResourceId = source.readInt();
+ themeId = source.readString();
+ styleResourceId = source.readInt();
+ name = source.readString();
+ nameId = source.readInt();
+ ringtoneFileName = source.readString();
+ notificationRingtoneFileName = source.readString();
+ ringtoneName = source.readString();
+ notificationRingtoneName = source.readString();
+ author = source.readString();
+ copyright = source.readString();
+ isDrmProtected = (source.readInt() != 0);
+ soundPackName = source.readString();
+ themeStyleName = source.readString();
+ previewResourceId = source.readInt();
+ }
+
+ protected void changeDrmFlagIfNeeded(String resourcePath) {
+ if (resourcePath != null && resourcePath.contains(LOCKED_NAME)) {
+ isDrmProtected = true;
+ }
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index decb974..ee5b248 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -121,6 +121,8 @@ interface IPackageManager {
*/
ParceledListSlice getInstalledPackages(int flags, in String lastRead);
+ List<ackageInfo> getInstalledThemePackages();
+
/**
* This implements getInstalledApplications via a "last returned row"
* mechanism that is not exposed in the API. This is to get around the IPC
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index eb05d76..fbf3c1c 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -194,9 +194,69 @@ public class PackageInfo implements Parcelable {
*/
public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
+ // Is Theme Apk
+ /**
+ * {@hide}
+ */
+ public boolean isThemeApk = false;
+
+ // ThemeInfo
+ /**
+ * {@hide}
+ */
+ public ThemeInfo [] themeInfos;
+
public PackageInfo() {
}
+ /*
+ * Is Theme Apk is DRM protected (contains DRM-protected resources)
+ *
+ */
+ private boolean drmProtectedThemeApk = false;
+
+ /**
+ * @hide
+ *
+ * @return Is Theme Apk is DRM protected (contains DRM-protected resources)
+ */
+ public boolean isDrmProtectedThemeApk() {
+ return drmProtectedThemeApk;
+ }
+
+ /**
+ * @hide
+ *
+ * @param value if Theme Apk is DRM protected (contains DRM-protected resources)
+ */
+ public void setDrmProtectedThemeApk(boolean value) {
+ drmProtectedThemeApk = value;
+ }
+
+ /*
+ * If isThemeApk and isDrmProtectedThemeApk are true - path to hidden locked zip file
+ *
+ */
+ private String lockedZipFilePath;
+
+ /**
+ * @hide
+ *
+ * @return path for hidden locked zip file
+ */
+ public String getLockedZipFilePath() {
+ return lockedZipFilePath;
+ }
+
+ /**
+ * @hide
+ *
+ * @param value path for hidden locked zip file
+ */
+ public void setLockedZipFilePath(String value) {
+ lockedZipFilePath = value;
+ }
+
public String toString() {
return "ackageInfo{"
+ Integer.toHexString(System.identityHashCode(this))
@@ -233,6 +293,12 @@ public class PackageInfo implements Parcelable {
dest.writeTypedArray(configPreferences, parcelableFlags);
dest.writeTypedArray(reqFeatures, parcelableFlags);
dest.writeInt(installLocation);
+
+ /* Theme-specific. */
+ dest.writeInt((isThemeApk)? 1 : 0);
+ dest.writeInt((drmProtectedThemeApk)? 1 : 0);
+ dest.writeTypedArray(themeInfos, parcelableFlags);
+ dest.writeString(lockedZipFilePath);
}
public static final Parcelable.Creator<ackageInfo> CREATOR
@@ -270,5 +336,11 @@ public class PackageInfo implements Parcelable {
configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
installLocation = source.readInt();
+
+ /* Theme-specific. */
+ isThemeApk = (source.readInt() != 0);
+ drmProtectedThemeApk = (source.readInt() != 0);
+ themeInfos = source.createTypedArray(ThemeInfo.CREATOR);
+ lockedZipFilePath = source.readString();
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8541748..11bb0a1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1407,6 +1407,17 @@ public abstract class PackageManager {
public abstract List<ackageInfo> getInstalledPackages(int flags);
/**
+ * Return a List of all theme packages that are installed
+ * on the device.
+ *
+ * @return A List of PackageInfo objects, one for each theme package
+ * that is installed on the device.
+ *
+ * @hide
+ */
+ public abstract List<ackageInfo> getInstalledThemePackages();
+
+ /**
* Check whether a particular package has been granted a particular
* permission.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e593d5b..4496a4a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -200,6 +200,17 @@ public class PackageParser {
return name.endsWith(".apk");
}
+ public static String getLockedZipFilePath(String path) {
+ if (path == null) {
+ return null;
+ }
+ if (isPackageFilename(path)) {
+ return path.substring(0, path.length() - 4) + ".locked.zip";
+ } else {
+ return path + ".locked.zip";
+ }
+ }
+
/**
* Generate and return the {@link PackageInfo} for a parsed package.
*
@@ -215,6 +226,21 @@ public class PackageParser {
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
+ pi.isThemeApk = p.mIsThemeApk;
+ pi.setDrmProtectedThemeApk(false);
+ if (pi.isThemeApk) {
+ int N = p.mThemeInfos.size();
+ if (N > 0) {
+ pi.themeInfos = new ThemeInfo[N];
+ for (int i = 0; i < N; i++) {
+ pi.themeInfos = p.mThemeInfos.get(i);
+ pi.setDrmProtectedThemeApk(pi.isDrmProtectedThemeApk() || pi.themeInfos.isDrmProtected);
+ }
+ if (pi.isDrmProtectedThemeApk()) {
+ pi.setLockedZipFilePath(PackageParser.getLockedZipFilePath(p.mPath));
+ }
+ }
+ }
pi.applicationInfo = generateApplicationInfo(p, flags);
pi.installLocation = p.installLocation;
pi.firstInstallTime = firstInstallTime;
@@ -1180,7 +1206,10 @@ public class PackageParser {
// Just skip this tag
XmlUtils.skipCurrentTag(parser);
continue;
-
+ } else if (tagName.equals("theme")) {
+ // this is a theme apk.
+ pkg.mIsThemeApk = true;
+ pkg.mThemeInfos.add(new ThemeInfo(parser, res, attrs));
} else if (RIGID_PARSER) {
outError[0] = "Bad element under <manifest>: "
+ parser.getName();
@@ -1253,6 +1282,9 @@ public class PackageParser {
>= android.os.Build.VERSION_CODES.DONUT)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
}
+ if (pkg.mIsThemeApk) {
+ pkg.applicationInfo.isThemeable = false;
+ }
return pkg;
}
@@ -1536,12 +1568,43 @@ public class PackageParser {
return a;
}
+ private void parseApplicationThemeAttributes(XmlPullParser parser, AttributeSet attrs,
+ ApplicationInfo appInfo) {
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) {
+ continue;
+ }
+ String attrName = attrs.getAttributeName(i);
+ if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME)) {
+ appInfo.isThemeable = attrs.getAttributeBooleanValue(i, false);
+ return;
+ }
+ }
+ }
+
+ private void parseActivityThemeAttributes(XmlPullParser parser, AttributeSet attrs,
+ ActivityInfo ai) {
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) {
+ continue;
+ }
+ String attrName = attrs.getAttributeName(i);
+ if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME)) {
+ ai.configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ }
+ }
+ }
+
private boolean parseApplication(Package owner, Resources res,
XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
throws XmlPullParserException, IOException {
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;
+ parseApplicationThemeAttributes(parser, attrs, ai);
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestApplication);
@@ -2045,6 +2108,8 @@ public class PackageParser {
return null;
}
+ parseActivityThemeAttributes(parser, attrs, a.info);
+
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2982,6 +3047,12 @@ public class PackageParser {
// For use by package manager to keep track of where it has done dexopt.
public boolean mDidDexOpt;
+ // Is Theme Apk
+ public boolean mIsThemeApk = false;
+
+ // Theme info
+ public final ArrayList<ThemeInfo> mThemeInfos = new ArrayList<ThemeInfo>(0);
+
// User set enabled state.
public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
diff --git a/core/java/android/content/pm/ThemeInfo.aidl b/core/java/android/content/pm/ThemeInfo.aidl
new file mode 100755
index 0000000..acbc85e
--- /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 0000000..ebe27a7
--- /dev/null
+++ b/core/java/android/content/pm/ThemeInfo.java
@@ -0,0 +1,206 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParser;
+
+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 (theme tag).
+ *
+ * Below is an example of theme tag
+ * <theme
+ * pluto:name="luto Default"
+ * pluto:preview="@drawable/preview"
+ * pluto:author="John Doe"
+ * pluto:ringtoneFileName="media/audio/ringtone.mp3"
+ * pluto:notificationRingtoneFileName="media/audio/locked/notification.mp3"
+ * pluto:copyright="T-Mobile, 2009"
+ * />
+ *
+ * @hide
+ */
+public final class ThemeInfo extends BaseThemeInfo {
+ private enum AttributeIndex {
+ THEME_PACKAGE_INDEX,
+ PREVIEW_INDEX,
+ AUTHOR_INDEX,
+ THEME_INDEX,
+ THEME_STYLE_NAME_INDEX,
+ THUMBNAIL_INDEX,
+ RINGTONE_FILE_NAME_INDEX,
+ NOTIFICATION_RINGTONE_FILE_NAME_INDEX,
+ WALLPAPER_IMAGE_INDEX,
+ COPYRIGHT_INDEX,
+ RINGTONE_NAME_INDEX,
+ NOTIFICATION_RINGTONE_NAME_INDEX,
+ STYLE_INDEX;
+
+ public static AttributeIndex get(int ordinal) {
+ return values()[ordinal];
+ }
+ };
+
+ private static final String [] compulsoryAttributes = new String [] {
+ "name",
+ "preview",
+ "author",
+ "themeId",
+ "styleName",
+ };
+
+ private static final String [] optionalAttributes = new String [] {
+ "thumbnail",
+ "ringtoneFileName",
+ "notificationRingtoneFileName",
+ "wallpaperImage",
+ "copyright",
+ "ringtoneName",
+ "notificationRingtoneName",
+ "styleId",
+ };
+
+ private static final Map<String, AttributeIndex> sAttributesLookupTable;
+
+ static {
+ sAttributesLookupTable = new HashMap<String, AttributeIndex>();
+ for (int i = 0; i < compulsoryAttributes.length; i++) {
+ sAttributesLookupTable.put(compulsoryAttributes, AttributeIndex.get(i));
+ }
+
+ for (int i = 0; i < optionalAttributes.length; i++) {
+ sAttributesLookupTable.put(optionalAttributes,
+ AttributeIndex.get(compulsoryAttributes.length + i));
+ }
+ }
+
+ public ThemeInfo(XmlPullParser parser, Resources res, AttributeSet attrs) throws XmlPullParserException {
+ super();
+
+ Map<String, AttributeIndex> tempMap =
+ new HashMap<String, AttributeIndex>(sAttributesLookupTable);
+ int numberOfCompulsoryAttributes = 0;
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) {
+ continue;
+ }
+ String key = attrs.getAttributeName(i);
+ if (tempMap.containsKey(key)) {
+ AttributeIndex index = tempMap.get(key);
+ tempMap.remove(key);
+
+ if (index.ordinal() < compulsoryAttributes.length) {
+ numberOfCompulsoryAttributes++;
+ }
+ switch (index) {
+ case THEME_PACKAGE_INDEX:
+ // theme name
+ name = getResolvedString(res, attrs, i);
+ nameId = attrs.getAttributeResourceValue(i, 0);
+ break;
+
+ case THUMBNAIL_INDEX:
+ // theme thumbprint
+ thumbnailResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+
+ case AUTHOR_INDEX:
+ // theme author
+ author = getResolvedString(res, attrs, i);
+ break;
+
+ case THEME_INDEX:
+ // androidUiStyle attribute
+ themeId = attrs.getAttributeValue(i);
+ break;
+
+ case THEME_STYLE_NAME_INDEX:
+ themeStyleName = getResolvedString(res, attrs, i);
+ break;
+
+ case RINGTONE_FILE_NAME_INDEX:
+ // ringtone
+ ringtoneFileName = attrs.getAttributeValue(i);
+ changeDrmFlagIfNeeded(ringtoneFileName);
+ break;
+
+ case NOTIFICATION_RINGTONE_FILE_NAME_INDEX:
+ // notification ringtone
+ notificationRingtoneFileName = attrs.getAttributeValue(i);
+ changeDrmFlagIfNeeded(notificationRingtoneFileName);
+ break;
+
+ case WALLPAPER_IMAGE_INDEX:
+ // wallpaperImage attribute
+ wallpaperResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+
+ case COPYRIGHT_INDEX:
+ // themeCopyright attribute
+ copyright = getResolvedString(res, attrs, i);
+ break;
+
+ case RINGTONE_NAME_INDEX:
+ // ringtone UI name
+ ringtoneName = attrs.getAttributeValue(i);
+ break;
+
+ case NOTIFICATION_RINGTONE_NAME_INDEX:
+ // notification ringtone UI name
+ notificationRingtoneName = attrs.getAttributeValue(i);
+ break;
+
+ case STYLE_INDEX:
+ styleResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+
+ case PREVIEW_INDEX:
+ // theme thumbprint
+ previewResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+ }
+ }
+ }
+ if (numberOfCompulsoryAttributes < compulsoryAttributes.length) {
+ throw new XmlPullParserException("Not all compulsory attributes are specified in <theme>");
+ }
+ }
+
+ 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/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index ffefaa2..c70bc9f 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -18,6 +18,7 @@ package android.content.res;
import java.io.FileNotFoundException;
@@ -77,6 +78,20 @@ public final class AssetManager {
private boolean mOpen = true;
private HashMap<Integer, RuntimeException> mRefStacks;
+ private String mAssetDir;
+ private String mAppName;
+
+ private boolean mThemeSupport;
+ private String mThemePackageName;
+ private int mThemeCookie;
+
+ /**
+ * Organize all added redirection maps using Java strong references to keep
+ * the native layer cleanup simple (that is, finalize() in Java will be
+ * responsible for delete in C++).
+ */
+ private SparseArray<ackageRedirectionMap> mRedirections;
+
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
@@ -252,6 +267,13 @@ public final class AssetManager {
}
}
+ /*package*/ final void recreateStringBlocks() {
+ synchronized (this) {
+ makeStringBlocks(true);
+ }
+ }
+
+ //TODO: cm7 is private
/*package*/ final void makeStringBlocks(boolean copyFromSystem) {
final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0;
final int num = getStringBlockCount();
@@ -460,6 +482,18 @@ public final class AssetManager {
/**
* {@hide}
+ * Split a theme package with DRM-protected resources into two files.
+ *
+ * @param packageFileName Original theme package file name.
+ * @param lockedFileName Name of the new "locked" file with DRM resources.
+ * @param drmProtectedresources Array of names of DRM-protected assets.
+ */
+ public final int splitDrmProtectedThemePackage(String packageFileName, String lockedFileName, String [] drmProtectedresources) {
+ return splitThemePackage(packageFileName, lockedFileName, drmProtectedresources);
+ }
+
+ /**
+ * {@hide}
* Retrieve a non-asset as a compiled XML file. Not for use by
* applications.
*
@@ -625,6 +659,110 @@ public final class AssetManager {
}
/**
+ * Delete a set of theme assets from the asset manager. Not for use by
+ * applications. Returns true if succeeded or false on failure.
+ *
+ * @hide
+ */
+ public native final boolean detachThemePath(String packageName, int cookie);
+
+ /**
+ * Attach a set of theme assets to the asset manager. If necessary, this
+ * method will forcefully update the internal ResTable data structure.
+ *
+ * @return Cookie of the added asset or 0 on failure.
+ * @hide
+ */
+ public native final int attachThemePath(String path);
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Apply a heuristic to match-up all attributes from the source style with
+ * attributes in the destination style. For each match, an entry in the
+ * package redirection map will be inserted.
+ *
+ * {@hide}
+ */
+ public native final boolean generateStyleRedirections(int resMapNative, int sourceStyle,
+ int destStyle);
+
+ /**
+ * 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 int getThemeCookie() {
+ return mThemeCookie;
+ }
+
+ /**
+ * Sets asset cookie for current theme (0 if not a themed asset manager).
+ * {@hide}
+ */
+ public void setThemeCookie(int cookie) {
+ mThemeCookie = cookie;
+ }
+
+ /**
+ * Add a redirection map to the asset manager. All future resource lookups
+ * will consult this map.
+ * {@hide}
+ */
+ public void addRedirections(PackageRedirectionMap map) {
+ if (mRedirections == null) {
+ mRedirections = new SparseArray<ackageRedirectionMap>(2);
+ }
+ mRedirections.append(map.getPackageId(), map);
+ addRedirectionsNative(map.getNativePointer());
+ }
+
+ /**
+ * Clear redirection map for the asset manager.
+ * {@hide}
+ */
+ public void clearRedirections() {
+ if (mRedirections != null) {
+ mRedirections.clear();
+ }
+ clearRedirectionsNative();
+ }
+
+ /**
* 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.
@@ -741,6 +879,26 @@ public final class AssetManager {
private native final int[] getArrayStringInfo(int arrayRes);
/*package*/ native final int[] getArrayIntResource(int arrayRes);
+ private native final int splitThemePackage(String srcFileName, String dstFileName, String [] drmProtectedAssetNames);
+
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageCount();
+
+ /**
+ * {@hide}
+ */
+ public native final String getBasePackageName(int index);
+
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageId(int index);
+
+ private native final void addRedirectionsNative(int redirectionMapNativePointer);
+ private native final void clearRedirectionsNative();
+
private native final void init();
private native final void destroy();
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 1c9285e..0c48042 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 {
}
@@ -56,6 +57,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public Locale locale;
/**
+ * @hide
+ */
+ public CustomTheme customTheme;
+
+ /**
* Locale should persist on setting. This is hidden because it is really
* questionable whether this is the right way to expose the functionality.
* @hide
@@ -216,6 +222,21 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public static final int ORIENTATION_SQUARE = 3;
/**
+ * @hide
+ */
+ public static final int THEME_UNDEFINED = 0;
+
+ /**
+ * @hide
+ */
+ public static final String THEME_ID_PERSISTENCE_PROPERTY = "persist.sys.themeId";
+
+ /**
+ * @hide
+ */
+ public static final String THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY = "persist.sys.themePackageName";
+
+ /**
* Overall orientation of the screen. May be one of
* {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT},
* or {@link #ORIENTATION_SQUARE}.
@@ -327,6 +348,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatScreenHeightDp = o.compatScreenHeightDp;
compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
seq = o.seq;
+ if (o.customTheme != null) {
+ customTheme = (CustomTheme) o.customTheme.clone();
+ }
}
public String toString() {
@@ -444,6 +468,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
sb.append(" s.");
sb.append(seq);
}
+ sb.append(" themeResource=");
+ sb.append(customTheme);
sb.append('}');
return sb.toString();
}
@@ -470,6 +496,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
textLayoutDirection = LocaleUtil.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE;
seq = 0;
+ customTheme = null;
}
/** {@hide} */
@@ -590,6 +617,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
seq = delta.seq;
}
@@ -685,6 +718,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& smallestScreenWidthDp != delta.smallestScreenWidthDp) {
changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
+ if (delta.customTheme != null &&
+ (customTheme == null || !customTheme.equals(delta.customTheme))) {
+ changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ }
return changed;
}
@@ -701,7 +738,8 @@ 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;
}
/**
@@ -774,6 +812,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(compatSmallestScreenWidthDp);
dest.writeInt(textLayoutDirection);
dest.writeInt(seq);
+
+ if (customTheme == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeString(customTheme.getThemeId());
+ dest.writeString(customTheme.getThemePackageName());
+ }
}
public void readFromParcel(Parcel source) {
@@ -802,6 +848,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatSmallestScreenWidthDp = source.readInt();
textLayoutDirection = source.readInt();
seq = source.readInt();
+
+ if (source.readInt() != 0) {
+ String themeId = source.readString();
+ String themePackage = source.readString();
+ customTheme = new CustomTheme(themeId, themePackage);
+ }
}
public static final Parcelable.Creator<Configuration> CREATOR
@@ -868,6 +920,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (n != 0) return n;
n = this.smallestScreenWidthDp - that.smallestScreenWidthDp;
//if (n != 0) return n;
+ if (this.customTheme == null) {
+ if (that.customTheme != null) return 1;
+ } else if (that.customTheme == null) {
+ return -1;
+ } else {
+ n = this.customTheme.getThemeId().compareTo(that.customTheme.getThemeId());
+ if (n != 0) return n;
+ n = this.customTheme.getThemePackageName().compareTo(that.customTheme.getThemePackageName());
+ if (n != 0) return n;
+ }
+
return n;
}
@@ -903,6 +966,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
result = 31 * result + screenWidthDp;
result = 31 * result + screenHeightDp;
result = 31 * result + smallestScreenWidthDp;
+ result = 31 * result + (customTheme != null ? customTheme.hashCode() : 0);
return result;
}
}
diff --git a/core/java/android/content/res/CustomTheme.java b/core/java/android/content/res/CustomTheme.java
new file mode 100644
index 0000000..364fb11
--- /dev/null
+++ b/core/java/android/content/res/CustomTheme.java
@@ -0,0 +1,117 @@
+/*
+ * 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.res;
+
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+/**
+ * @hide
+ */
+public final class CustomTheme implements Cloneable {
+ private final String mThemeId;
+ private final String mThemePackageName;
+
+ private static final CustomTheme sBootTheme = new CustomTheme();
+ private static final CustomTheme sSystemTheme = new CustomTheme("", "");
+
+ private CustomTheme() {
+ mThemeId = SystemProperties.get("persist.sys.themeId");
+ mThemePackageName = SystemProperties.get("persist.sys.themePackageName");
+ }
+
+ public CustomTheme(String themeId, String packageName) {
+ mThemeId = themeId;
+ mThemePackageName = packageName;
+ }
+
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof CustomTheme) {
+ CustomTheme o = (CustomTheme) object;
+ if (!mThemeId.equals(o.mThemeId)) {
+ return false;
+ }
+ String currentPackageName = (mThemePackageName == null)? "" : mThemePackageName;
+ String newPackageName = (o.mThemePackageName == null)? "" : o.mThemePackageName;
+ String currentThemeId = (mThemeId == null)? "" : mThemeId;
+ String newThemeId = (o.mThemeId == null)? "" : o.mThemeId;
+
+ /* uhh, why are we trimming here instead of when the object is
+ * constructed? actually, why are we trimming at all? */
+ return (currentPackageName.trim().equalsIgnoreCase(newPackageName.trim())) &&
+ (currentThemeId.trim().equalsIgnoreCase(newThemeId.trim()));
+ }
+ return false;
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder result = new StringBuilder();
+ if (!TextUtils.isEmpty(mThemePackageName) && !TextUtils.isEmpty(mThemeId)) {
+ result.append(mThemePackageName);
+ result.append('(');
+ result.append(mThemeId);
+ result.append(')');
+ } else {
+ result.append("system");
+ }
+ return result.toString();
+ }
+
+ @Override
+ public synchronized int hashCode() {
+ return mThemeId.hashCode() + mThemePackageName.hashCode();
+ }
+
+ public String getThemeId() {
+ return mThemeId;
+ }
+
+ public String getThemePackageName() {
+ return mThemePackageName;
+ }
+
+ /**
+ * 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 CustomTheme getBootTheme() {
+ return sBootTheme;
+ }
+
+ /**
+ * Represents the system framework theme, perceived by the system as there
+ * being no theme applied.
+ */
+ public static CustomTheme getSystemTheme() {
+ return sSystemTheme;
+ }
+}
diff --git a/core/java/android/content/res/PackageRedirectionMap.aidl b/core/java/android/content/res/PackageRedirectionMap.aidl
new file mode 100644
index 0000000..4f47525
--- /dev/null
+++ b/core/java/android/content/res/PackageRedirectionMap.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2011, 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;
+
+/**
+ * @hide
+ */
+parcelable PackageRedirectionMap;
diff --git a/core/java/android/content/res/PackageRedirectionMap.java b/core/java/android/content/res/PackageRedirectionMap.java
new file mode 100644
index 0000000..55c4282
--- /dev/null
+++ b/core/java/android/content/res/PackageRedirectionMap.java
@@ -0,0 +1,90 @@
+package android.content.res;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Native transport for package asset redirection information coming from the
+ * AssetRedirectionManagerService.
+ *
+ * @hide
+ */
+public class PackageRedirectionMap implements Parcelable {
+ private final int mNativePointer;
+
+ public static final Parcelable.Creator<PackageRedirectionMap> CREATOR
+ = new Parcelable.Creator<PackageRedirectionMap>() {
+ public PackageRedirectionMap createFromParcel(Parcel in) {
+ return new PackageRedirectionMap(in);
+ }
+
+ public PackageRedirectionMap[] newArray(int size) {
+ return new PackageRedirectionMap[size];
+ }
+ };
+
+ public PackageRedirectionMap() {
+ this(nativeConstructor());
+ }
+
+ private PackageRedirectionMap(Parcel in) {
+ this(nativeCreateFromParcel(in));
+ }
+
+ private PackageRedirectionMap(int nativePointer) {
+ if (nativePointer == 0) {
+ throw new RuntimeException();
+ }
+ mNativePointer = nativePointer;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDestructor(mNativePointer);
+ }
+
+ public int getNativePointer() {
+ return mNativePointer;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (!nativeWriteToParcel(mNativePointer, dest)) {
+ throw new RuntimeException();
+ }
+ }
+
+ public int getPackageId() {
+ return nativeGetPackageId(mNativePointer);
+ }
+
+ public void addRedirection(int fromIdent, int toIdent) {
+ nativeAddRedirection(mNativePointer, fromIdent, toIdent);
+ }
+
+ // Used for debugging purposes only.
+ public int[] getRedirectionKeys() {
+ return nativeGetRedirectionKeys(mNativePointer);
+ }
+
+ // Used for debugging purposes only.
+ public int lookupRedirection(int fromIdent) {
+ return nativeLookupRedirection(mNativePointer, fromIdent);
+ }
+
+ private static native int nativeConstructor();
+ private static native void nativeDestructor(int nativePointer);
+
+ private static native int nativeCreateFromParcel(Parcel p);
+ private static native boolean nativeWriteToParcel(int nativePointer, Parcel p);
+
+ private native void nativeAddRedirection(int nativePointer, int fromIdent, int toIdent);
+ private native int nativeGetPackageId(int nativePointer);
+ private native int[] nativeGetRedirectionKeys(int nativePointer);
+ private native int nativeLookupRedirection(int nativePointer, int fromIdent);
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 4f19010..7ad4e57 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1448,7 +1448,8 @@ public class Resources {
mTmpConfig.locale = Locale.getDefault();
}
configChanges = mConfiguration.updateFrom(mTmpConfig);
- configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ //TODO: fix theme cache not completely clear
+ // configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
}
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
@@ -1510,6 +1511,18 @@ public class Resources {
private void clearDrawableCache(
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;
+ }
int N = cache.size();
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
@@ -1861,7 +1874,16 @@ public class Resources {
flushLayoutCache();
}
}
-
+
+ /**
+ * {@hide}
+ */
+ public final void updateStringCache() {
+ synchronized (mTmpValue) {
+ mAssets.recreateStringBlocks();
+ }
+ }
+
/*package*/ Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index 619bf8d..8250874 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -124,4 +124,49 @@ public class SystemProperties
}
native_set(key, val);
}
+
+ /**
+ * Get the value for the given key.
+ * @return def string if the key isn't found
+ */
+ public static String getLongString(String key, String def) {
+ if (key.length() + 1 > PROP_NAME_MAX) {
+ throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
+ }
+ int chunks = getInt(key + '0', 0);
+ if (chunks == 0) {
+ return def;
+ }
+ StringBuffer sb = new StringBuffer();
+ for (int i = 1; i <= chunks; i++) {
+ sb.append(native_get(key + Integer.toString(i)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Set the value for the given key.
+ * @throws IllegalArgumentException if the key exceeds 32 characters
+ */
+ public static void setLongString(String key, String val) {
+ if (key.length() + 1 > PROP_NAME_MAX) {
+ throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
+ }
+ int chunks = 0;
+ if (val != null && val.length() > 0) {
+ chunks = 1 + val.length() / (PROP_VALUE_MAX + 1);
+ }
+ native_set(key + '0', Integer.toString(chunks));
+ if (chunks > 0) {
+ for (int i = 1, start = 0; i <= chunks; i++) {
+ int end = start + PROP_VALUE_MAX;
+ if (end > val.length()) {
+ end = val.length();
+ }
+ native_set(key + Integer.toString(i), val.substring(start, end));
+ start = end;
+ }
+ }
+ }
+
}
diff --git a/core/java/com/android/internal/app/IAssetRedirectionManager.aidl b/core/java/com/android/internal/app/IAssetRedirectionManager.aidl
new file mode 100644
index 0000000..8b47f0b
--- /dev/null
+++ b/core/java/com/android/internal/app/IAssetRedirectionManager.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011, 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 com.android.internal.app;
+
+import android.content.res.PackageRedirectionMap;
+
+/**
+ * Interface used to interact with the AssetRedirectionManagerService.
+ */
+interface IAssetRedirectionManager {
+ /**
+ * Access the package redirection map for the supplied package name given a
+ * particular theme.
+ */
+ PackageRedirectionMap getPackageRedirectionMap(in String themePackageName,
+ String themeId, in String targetPackageName);
+
+ /**
+ * Clear all redirection maps for the given theme.
+ */
+ void clearRedirectionMapsByTheme(in String themePackageName,
+ in String themeId);
+
+ /**
+ * Clear all redirection maps for the given target package.
+ */
+ void clearPackageRedirectionMap(in String targetPackageName);
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9c45dc6..356962e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -97,7 +97,7 @@ public class ZygoteInit {
private static final String PRELOADED_CLASSES = "preloaded-classes";
/** Controls whether we should preload resources during zygote init. */
- private static final boolean PRELOAD_RESOURCES = true;
+ private static final boolean PRELOAD_RESOURCES = false;
/**
* Invokes a static "main(argv[]) method on class "className".
@@ -230,7 +230,7 @@ public class ZygoteInit {
+ // One final test for a resource redirection from the applied theme.
+ if (resid != 0) {
+ uint32_t redirect = res.lookupRedirectionMap(resid);
+ if (redirect != 0) {
+ REDIRECT_NOISY(LOGW("array REDIRECT 0x%08x => 0x%08x\n", resid, redirect));
+ ssize_t newBlock = res.getResource(redirect, &value, true, &typeSetFlags, &config);
+ if (newBlock >= 0) {
+ newBlock = res.resolveReference(&value, newBlock, &redirect, &typeSetFlags, &config);
+#if THROW_ON_BAD_ID
+ if (newBlock == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return JNI_FALSE;
+ }
+#endif
+ if (newBlock >= 0) {
+ block = newBlock;
+ resid = redirect;
+ }
+ }
+ if (resid != redirect) {
+ LOGW("array redirect failure from 0x%08x => 0x%08x, array id=0x%08x", resid, redirect, id);
+ }
+ }
+ }
+
//printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
// Write the final value back to Java.
@@ -1561,6 +1635,84 @@ static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, j
return array;
}
+ void updateResTableFromAssetPath(ResTable* rt, const asset_path& ap, void* cookie) const;
Asset* openInPathLocked(const char* fileName, AccessMode mode,
const asset_path& path);
Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
diff --git a/include/utils/PackageRedirectionMap.h b/include/utils/PackageRedirectionMap.h
new file mode 100644
index 0000000..9e6435b
--- /dev/null
+++ b/include/utils/PackageRedirectionMap.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+#ifndef ANDROID_PACKAGEREDIRECTIONMAP_H
+#define ANDROID_PACKAGEREDIRECTIONMAP_H
+
+#include <binder/Parcel.h>
+
+// ---------------------------------------------------------------------------
+
+namespace android {
+
+class PackageRedirectionMap
+{
+public:
+ PackageRedirectionMap();
+ ~PackageRedirectionMap();
+
+ bool addRedirection(uint32_t fromIdent, uint32_t toIdent);
+ uint32_t lookupRedirection(uint32_t fromIdent);
+
+ // If there are no redirections present in this map, this method will
+ // return -1.
+ int getPackage();
+
+ // Usage of the following methods is intended to be used only by the JNI
+ // methods for the purpose of parceling.
+ size_t getNumberOfTypes();
+ size_t getNumberOfUsedTypes();
+
+ size_t getNumberOfEntries(int type);
+ size_t getNumberOfUsedEntries(int type);
+
+ // Similar to lookupRedirection, but with no sanity checking.
+ uint32_t getEntry(int type, int entry);
+
+private:
+ int mPackage;
+
+ /*
+ * Sparse array organized into two layers: first by type, then by entry.
+ * The result of each lookup will be a qualified resource ID in the theme
+ * package scope.
+ *
+ * Underneath each layer is a SharedBuffer which
+ * indicates the array size.
+ */
+ uint32_t** mEntriesByType;
+};
+
+} // namespace android
+
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_PACKAGEREDIRECTIONMAP_H
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 612ff93..d30fd66 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -23,6 +23,7 @@
#include <utils/Asset.h>
#include <utils/ByteOrder.h>
#include <utils/Errors.h>
+#include <utils/PackageRedirectionMap.h>
#include <utils/String16.h>
#include <utils/Vector.h>
+ void removeAssetsByCookie(const String8 &packageName, void* cookie);
+
// Generate an idmap.
//
// Return value: on success: NO_ERROR; caller is responsible for free-ing
@@ -2121,6 +2134,11 @@ private:
// Mapping from resource package IDs to indices into the internal
// package array.
uint8_t mPackageMap[256];
+
+ // Resource redirection mapping provided by the applied theme (if there is
+ // one). Resources requested which are found in this map will be
+ // automatically redirected to the appropriate themed value.
+ Vector<PackageRedirectionMap*> mRedirectionMap;
};
} // namespace android
diff --git a/include/utils/ZipEntry.h b/include/utils/ZipEntry.h
new file mode 100644
index 0000000..7f721b4
--- /dev/null
+++ b/include/utils/ZipEntry.h
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace android {
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself. (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void)
+ : mDeleted(false), mMarked(false)
+ {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Returns "true" if the data is compressed.
+ */
+ bool isCompressed(void) const {
+ return mCDE.mCompressionMethod != kCompressStored;
+ }
+ int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+ /*
+ * Return the uncompressed length.
+ */
+ off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+ /*
+ * Return the compressed length. For uncompressed data, this returns
+ * the same thing as getUncompresesdLen().
+ */
+ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+ /*
+ * Return the absolute file offset of the start of the compressed or
+ * uncompressed data.
+ */
+ off_t getFileOffset(void) const {
+ return mCDE.mLocalHeaderRelOffset +
+ LocalFileHeader::kLFHLen +
+ mLFH.mFileNameLength +
+ mLFH.mExtraFieldLength;
+ }
+
+ /*
+ * Return the data CRC.
+ */
+ unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+ /*
+ * Return file modification time in UNIX seconds-since-epoch.
+ */
+ time_t getModWhen(void) const;
+
+ /*
+ * Return the archived file name.
+ */
+ const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+ /*
+ * Application-defined "mark". Can be useful when synchronizing the
+ * contents of an archive with contents on disk.
+ */
+ bool getMarked(void) const { return mMarked; }
+ void setMarked(bool val) { mMarked = val; }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+ static inline void putLongLE(unsigned char* buf, long val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ buf[2] = (unsigned char) (val >> 16);
+ buf[3] = (unsigned char) (val >> 24);
+ }
+
+ /* defined for Zip archives */
+ enum {
+ kCompressStored = 0, // no compression
+ // shrunk = 1,
+ // reduced 1 = 2,
+ // reduced 2 = 3,
+ // reduced 3 = 4,
+ // reduced 4 = 5,
+ // imploded = 6,
+ // tokenized = 7,
+ kCompressDeflated = 8, // standard deflate
+ // Deflate64 = 9,
+ // lib imploded = 10,
+ // reserved = 11,
+ // bzip2 = 12,
+ };
+
+ /*
+ * Deletion flag. If set, the entry will be removed on the next
+ * call to "flush".
+ */
+ bool getDeleted(void) const { return mDeleted; }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry.
+ */
+ status_t initFromCDE(FILE* fp);
+
+ /*
+ * Initialize the structure for a new file. We need the filename
+ * and comment so that we can properly size the LFH area. The
+ * filename is mandatory, the comment is optional.
+ */
+ void initNew(const char* fileName, const char* comment);
+
+ /*
+ * Initialize the structure with the contents of a ZipEntry from
+ * another file.
+ */
+ status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry);
+
+ /*
+ * Add some pad bytes to the LFH. We do this by adding or resizing
+ * the "extra" field.
+ */
+ status_t addPadding(int padding);
+
+ /*
+ * Set information about the data for this entry.
+ */
+ void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod);
+
+ /*
+ * Set the modification date.
+ */
+ void setModWhen(time_t when);
+
+ /*
+ * Return the offset of the local file header.
+ */
+ off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+ /*
+ * Set the offset of the local file header, relative to the start of
+ * the current file.
+ */
+ void setLFHOffset(off_t offset) {
+ mCDE.mLocalHeaderRelOffset = (long) offset;
+ }
+
+ /* mark for deletion; used by ZipFile::remove() */
+ void setDeleted(void) { mDeleted = true; }
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /* returns "true" if the CDE and the LFH agree */
+ bool compareHeaders(void) const;
+ void copyCDEtoLFH(void);
+
+ bool mDeleted; // set if entry is pending deletion
+ bool mMarked; // app-defined marker
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) :
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileName(NULL),
+ mExtraField(NULL)
+ {}
+ virtual ~LocalFileHeader(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+
+ void dump(void) const;
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mVersionMadeBy(0),
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileCommentLength(0),
+ mDiskNumberStart(0),
+ mInternalAttrs(0),
+ mExternalAttrs(0),
+ mLocalHeaderRelOffset(0),
+ mFileName(NULL),
+ mExtraField(NULL),
+ mFileComment(NULL)
+ {}
+ virtual ~CentralDirEntry(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionMadeBy;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned short mFileCommentLength;
+ unsigned short mDiskNumberStart;
+ unsigned short mInternalAttrs;
+ unsigned long mExternalAttrs;
+ unsigned long mLocalHeaderRelOffset;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+ unsigned char* mFileComment;
+
+ void dump(void) const;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ enum {
+ //kDataDescriptorSignature = 0x08074b50, // currently unused
+ kDataDescriptorLen = 16, // four 32-bit fields
+
+ kDefaultVersion = 20, // need deflate, nothing much else
+ kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
+ kUsesDataDescr = 0x0008, // GPBitFlag bit 3
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/include/utils/ZipFile.h b/include/utils/ZipFile.h
new file mode 100644
index 0000000..dbbd072
--- /dev/null
+++ b/include/utils/ZipFile.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// General-purpose Zip archive access. This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include <utils/Vector.h>
+#include <utils/Errors.h>
+#include <stdio.h>
+
+#include "ZipEntry.h"
+
+namespace android {
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes. Because we're only interested
+ * in using this for packaging, we don't worry about such things. Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void)
+ : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+ {}
+ ~ZipFile(void) {
+ if (!mReadOnly)
+ flush();
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ discardEntries();
+ }
+
+ /*
+ * Open a new or existing archive.
+ */
+ typedef enum {
+ kOpenReadOnly = 0x01,
+ kOpenReadWrite = 0x02,
+ kOpenCreate = 0x04, // create if it doesn't exist
+ kOpenTruncate = 0x08, // if it exists, empty it
+ };
+ status_t open(const char* zipFileName, int flags);
+
+ /*
+ * Add a file to the end of the archive. Specify whether you want the
+ * library to try to store it compressed.
+ *
+ * If "storageName" is specified, the archive will use that instead
+ * of "fileName".
+ *
+ * If there is already an entry with the same name, the call fails.
+ * Existing entries with the same name must be removed first.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const char* fileName, int compressionMethod,
+ ZipEntry** ppEntry)
+ {
+ return add(fileName, fileName, compressionMethod, ppEntry);
+ }
+ status_t add(const char* fileName, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add a file that is already compressed with gzip.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t addGzip(const char* fileName, const char* storageName,
+ ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressDeflated,
+ ZipEntry::kCompressDeflated, ppEntry);
+ }
+
+ /*
+ * Add a file from an in-memory data buffer.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const void* data, size_t size, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(NULL, data, size, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry);
+
+ /*
+ * Mark an entry as having been removed. It is not actually deleted
+ * from the archive or our internal data structures until flush() is
+ * called.
+ */
+ status_t remove(ZipEntry* pEntry);
+
+ /*
+ * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
+ */
+ status_t flush(void);
+
+ /*
+ * Expand the data into the buffer provided. The buffer must hold
+ * at least <uncompressed len> bytes. Variation expands directly
+ * to a file.
+ *
+ * Returns "false" if an error was encountered in the compressed data.
+ */
+ //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+ //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+ void* uncompress(const ZipEntry* pEntry);
+
+ /*
+ * Get an entry, by name. Returns NULL if not found.
+ *
+ * Does not return entries pending deletion.
+ */
+ ZipEntry* getEntryByName(const char* fileName) const;
+
+ /*
+ * Get the Nth entry in the archive.
+ *
+ * This will return an entry that is pending deletion.
+ */
+ int getNumEntries(void) const { return mEntries.size(); }
+ ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) :
+ mDiskNumber(0),
+ mDiskWithCentralDir(0),
+ mNumEntries(0),
+ mTotalNumEntries(0),
+ mCentralDirSize(0),
+ mCentralDirOffset(0),
+ mCommentLen(0),
+ mComment(NULL)
+ {}
+ virtual ~EndOfCentralDir(void) {
+ delete[] mComment;
+ }
+
+ status_t readBuf(const unsigned char* buf, int len);
+ status_t write(FILE* fp);
+
+ //unsigned long mSignature;
+ unsigned short mDiskNumber;
+ unsigned short mDiskWithCentralDir;
+ unsigned short mNumEntries;
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirSize;
+ unsigned long mCentralDirOffset; // offset from first disk
+ unsigned short mCommentLen;
+ unsigned char* mComment;
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+
+ void dump(void) const;
+ };
+
+
+ /* read all entries in the central dir */
+ status_t readCentralDir(void);
+
+ /* crunch deleted entries out */
+ status_t crunchArchive(void);
+
+ /* clean up mEntries */
+ void discardEntries(void);
+
+ /* common handler for all "add" functions */
+ status_t addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry);
+
+ /* copy all of "srcFp" into "dstFp" */
+ status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+ /* copy all of "data" into "dstFp" */
+ status_t copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+ /* copy some of "srcFp" into "dstFp" */
+ status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32);
+ /* like memmove(), but on parts of a single file */
+ status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+ /* compress all of "srcFp" into "dstFp", using Deflate */
+ status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+
+ /* get modification date from a file descriptor */
+ time_t getModTime(int fd);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+
+ /* did we open this read-only? */
+ bool mReadOnly;
+
+ /* set this when we trash the central dir */
+ bool mNeedCDRewrite;
+
+ /*
+ * One ZipEntry per entry in the zip file. I'm using pointers instead
+ * of objects because it's easier than making operator= work for the
+ * classes and sub-classes.
+ */
+ Vector<ZipEntry*> mEntries;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index 831d9e3..aa8a852 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -29,6 +29,7 @@ commonSources:= \
Flattenable.cpp \
LinearTransform.cpp \
ObbFile.cpp \
+ PackageRedirectionMap.cpp \
PropertyMap.cpp \
RefBase.cpp \
ResourceTypes.cpp \
@@ -49,6 +50,8 @@ commonSources:= \
ZipFileCRO.cpp \
ZipFileRO.cpp \
ZipUtils.cpp \
+ ../../tools/aapt/ZipFile.cpp \
+ ../../tools/aapt/ZipEntry.cpp \
misc.cpp
+void ResTable::removeAssetsByCookie(const String8 &packageName, void* cookie)
+{
+ mError = NO_ERROR;
+
+ size_t N = mHeaders.size();
+ for (size_t i = 0; i < N; i++) {
+ Header* header = mHeaders;
+ if ((size_t)header->cookie == (size_t)cookie) {
+ if (header->ownedData != NULL) {
+ free(header->ownedData);
+ }
+ mHeaders.removeAt(i);
+ break;
+ }
+ }
+ size_t pgCount = mPackageGroups.size();
+ for (size_t pgIndex = 0; pgIndex < pgCount; pgIndex++) {
+ PackageGroup* pg = mPackageGroups[pgIndex];
+
+ size_t pkgCount = pg->packages.size();
+ size_t index = pkgCount;
+ for (size_t pkgIndex = 0; pkgIndex < pkgCount; pkgIndex++) {
+ const Package* pkg = pg->packages[pkgIndex];
+ if (String8(String16(pkg->package->name)).compare(packageName) == 0) {
+ index = pkgIndex;
+ LOGV("Delete Package %d id=%d name=%s\n",
+ (int)pkgIndex, pkg->package->id,
+ String8(String16(pkg->package->name)).string());
+ break;
+ }
+ }
+ if (index < pkgCount) {
+ const Package* pkg = pg->packages[index];
+ uint32_t id = dtohl(pkg->package->id);
+ if (id != 0 && id < 256) {
+ mPackageMap[id] = 0;
+ }
+ if (pkgCount == 1) {
+ LOGV("Delete Package Group %d id=%d packageCount=%d name=%s\n",
+ (int)pgIndex, pg->id, (int)pg->packages.size(),
+ String8(pg->name).string());
+ mPackageGroups.removeAt(pgIndex);
+ delete pg;
+ } else {
+ pg->packages.removeAt(index);
+ delete pkg;
+ }
+ return;
+ }
+ }
+}
+
+/*
+ * Load the redirection map from the supplied map path.
+ *
+ * The path is expected to be a directory containing individual map cache files
+ * for each package that is to have resources redirected. Only those packages
+ * that are included in this ResTable will be loaded into the redirection map.
+ * For this reason, this method should be called only after all resource
+ * bundles have been added to the table.
+ */
+void ResTable::addRedirections(PackageRedirectionMap* resMap)
+{
+ // TODO: Replace an existing entry matching the same package.
+ mRedirectionMap.add(resMap);
+}
+
+void ResTable::clearRedirections()
+{
+ /* This memory is being managed by strong references at the Java layer. */
+ mRedirectionMap.clear();
+}
+
+
status_t ResTable::createIdmap(const ResTable& overlay, uint32_t originalCrc, uint32_t overlayCrc,
void** outData, size_t* outSize) const
{
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index d7a5056..81db781 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -32,9 +32,16 @@ import android.os.ServiceManager;
import android.util.Slog;
import android.view.IWindowManager;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
+import android.util.Log;
public class SystemUIService extends Service {
static final String TAG = "SystemUIService";
-
+ private static final String DATA_TYPE_TMOBILE_STYLE = "vnd.tmobile.cursor.item/style";
+ private static final String DATA_TYPE_TMOBILE_THEME = "vnd.tmobile.cursor.item/theme";
+ private static final String ACTION_TMOBILE_THEME_CHANGED = "com.tmobile.intent.action.THEME_CHANGED";
/**
* The class names of the stuff to start.
*/
@@ -92,8 +99,31 @@ public class SystemUIService extends Service {
Slog.d(TAG, "running: " + mServices);
mServices.start();
}
+
+ try {
+ IntentFilter tMoFilter = new IntentFilter(ACTION_TMOBILE_THEME_CHANGED);
+ tMoFilter.addDataType(DATA_TYPE_TMOBILE_THEME);
+ tMoFilter.addDataType(DATA_TYPE_TMOBILE_STYLE);
+ registerReceiver(mBroadcastReceiver, tMoFilter);
+ } catch (MalformedMimeTypeException e) {
+ Slog.e(TAG, "Could not set T-Mo mime types", e);
+ }
}
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_TMOBILE_THEME_CHANGED.equals(action)) {
+ // Normally it will restart on its own, but sometimes it doesn't. Other times it's slow.
+ // This will help it restart reliably and faster.
+ PendingIntent restartIntent = PendingIntent.getService(SystemUIService.this, 0, new Intent(SystemUIService.this, SystemUIService.class), 0);
+ AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
+ alarmMgr.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, restartIntent);
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+ }
+ };
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
for (SystemUI ui: mServices) {
@@ -110,6 +140,12 @@ public class SystemUIService extends Service {
}
Window win = PolicyManager.makeNewWindow(context);
diff --git a/services/java/com/android/server/AppsLaunchFailureReceiver.java b/services/java/com/android/server/AppsLaunchFailureReceiver.java
new file mode 100644
index 0000000..6ef07aa
--- /dev/null
+++ b/services/java/com/android/server/AppsLaunchFailureReceiver.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.CustomTheme;
+import android.util.Log;
+import android.app.ActivityManager;
+import android.os.SystemClock;
+
+public class AppsLaunchFailureReceiver extends BroadcastReceiver {
+
+ private static final int FAILURES_THRESHOLD = 5;
+ private static final int EXPIRATION_TIME_IN_MILLISECONDS = 30000; // 30 seconds
+
+ private int mFailuresCount = 0;
+ private long mStartTime = 0;
+
+ // This function implements the following logic.
+ // If after a theme was applied the number of application launch failures
+ // at any moment was equal to FAILURES_THRESHOLD
+ // in less than EXPIRATION_TIME_IN_MILLISECONDS
+ // the default theme is applied unconditionally.
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE)) {
+ long currentTime = SystemClock.uptimeMillis();
+ if (currentTime - mStartTime > EXPIRATION_TIME_IN_MILLISECONDS) {
+ // reset both the count and the timer
+ mStartTime = currentTime;
+ mFailuresCount = 0;
+ }
+ if (mFailuresCount <= FAILURES_THRESHOLD) {
+ mFailuresCount++;
+ if (mFailuresCount == FAILURES_THRESHOLD) {
+ CustomTheme defaultTheme = CustomTheme.getSystemTheme();
+ ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+ Configuration currentConfig = am.getConfiguration();
+ currentConfig.customTheme = new CustomTheme(
+ defaultTheme.getThemeId(),
+ defaultTheme.getThemePackageName());
+ am.updateConfiguration(currentConfig);
+ }
+ }
+ } else if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE_RESET)) {
+ mFailuresCount = 0;
+ mStartTime = SystemClock.uptimeMillis();
+ } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) ||
+ action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
+ mFailuresCount = 0;
+ mStartTime = SystemClock.uptimeMillis();
+ }
+ }
+
+}
diff --git a/services/java/com/android/server/AssetRedirectionManagerService.java b/services/java/com/android/server/AssetRedirectionManagerService.java
new file mode 100644
index 0000000..1e124b9
--- /dev/null
+++ b/services/java/com/android/server/AssetRedirectionManagerService.java
@@ -0,0 +1,397 @@
+package com.android.server;
+
+import com.android.internal.app.IAssetRedirectionManager;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ThemeInfo;
+import android.content.res.AssetManager;
+import android.content.res.PackageRedirectionMap;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+public class AssetRedirectionManagerService extends IAssetRedirectionManager.Stub {
+ private static final String TAG = "AssetRedirectionManager";
+
+ private final Context mContext;
+
+ /*
+ * TODO: This data structure should have some way to expire very old cache
+ * entries. Would be nice to optimize for the removal path as well.
+ */
+ private final HashMap<RedirectionKey, PackageRedirectionMap> mRedirections =
+ new HashMap<RedirectionKey, PackageRedirectionMap>();
+
+ public AssetRedirectionManagerService(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void clearRedirectionMapsByTheme(String themePackageName, String themeId)
+ throws RemoteException {
+ synchronized (mRedirections) {
+ Set<RedirectionKey> keys = mRedirections.keySet();
+ Iterator<RedirectionKey> iter = keys.iterator();
+ while (iter.hasNext()) {
+ RedirectionKey key = iter.next();
+ if (themePackageName.equals(key.themePackageName) &&
+ (themeId == null || themeId.equals(key.themeId))) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void clearPackageRedirectionMap(String targetPackageName) throws RemoteException {
+ synchronized (mRedirections) {
+ Set<RedirectionKey> keys = mRedirections.keySet();
+ Iterator<RedirectionKey> iter = keys.iterator();
+ while (iter.hasNext()) {
+ RedirectionKey key = iter.next();
+ if (targetPackageName.equals(key.targetPackageName)) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public PackageRedirectionMap getPackageRedirectionMap(String themePackageName,
+ String themeId, String targetPackageName) throws RemoteException {
+ synchronized (mRedirections) {
+ RedirectionKey key = new RedirectionKey();
+ key.themePackageName = themePackageName;
+ key.themeId = themeId;
+ key.targetPackageName = targetPackageName;
+
+ PackageRedirectionMap map = mRedirections.get(key);
+ if (map != null) {
+ return map;
+ } else {
+ map = generatePackageRedirectionMap(key);
+ if (map != null) {
+ mRedirections.put(key, map);
+ }
+ return map;
+ }
+ }
+ }
+
+ private PackageRedirectionMap generatePackageRedirectionMap(RedirectionKey key) {
+ AssetManager assets = new AssetManager();
+
+ boolean frameworkAssets = key.targetPackageName.equals("android");
+
+ if (!frameworkAssets) {
+ PackageInfo pi = getPackageInfo(mContext, key.targetPackageName);
+ if (pi == null || pi.applicationInfo == null ||
+ assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) {
+ Log.w(TAG, "Unable to attach target package assets for " + key.targetPackageName);
+ return null;
+ }
+ }
+
+ PackageInfo pi = getPackageInfo(mContext, key.themePackageName);
+ if (pi == null || pi.applicationInfo == null || pi.themeInfos == null ||
+ assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) {
+ Log.w(TAG, "Unable to attach theme package assets from " + key.themePackageName);
+ return null;
+ }
+
+ PackageRedirectionMap resMap = new PackageRedirectionMap();
+
+ /*
+ * Apply a special redirection hack for the highest level <style>
+ * replacing @android:style/Theme.
+ */
+ if (frameworkAssets) {
+ int themeResourceId = findThemeResourceId(pi.themeInfos, key.themeId);
+ assets.generateStyleRedirections(resMap.getNativePointer(), android.R.style.Theme,
+ themeResourceId);
+ }
+
+ Resources res = new Resources(assets, null, null);
+ generateExplicitRedirections(resMap, res, key.themePackageName, key.targetPackageName);
+
+ return resMap;
+ }
+
+ private void generateExplicitRedirections(PackageRedirectionMap resMap, Resources res,
+ String themePackageName, String targetPackageName) {
+ /*
+ * XXX: We should be parsing the <theme> tag's <meta-data>! Instead,
+ * we're just assuming that res/xml/<package>.xml exists and describes
+ * the redirects we want!
+ */
+ String redirectXmlName = targetPackageName.replace('.', '_');
+ int redirectXmlResId = res.getIdentifier(redirectXmlName, "xml", themePackageName);
+ if (redirectXmlResId == 0) {
+ return;
+ }
+
+ ResourceRedirectionsProcessor processor = new ResourceRedirectionsProcessor(res,
+ redirectXmlResId, themePackageName, targetPackageName, resMap);
+ processor.process();
+ }
+
+ private static PackageInfo getPackageInfo(Context context, String packageName) {
+ try {
+ return context.getPackageManager().getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Searches for the high-level theme resource id for the specific
+ * <theme> tag being applied.
+ * <p>
+ * An individual theme package can contain multiple <theme> tags, each
+ * representing a separate theme choice from the user's perspective, even
+ * though the most common case is for there to be only 1.
+ *
+ * @return The style resource id or 0 if no match was found.
+ */
+ private static int findThemeResourceId(ThemeInfo[] themeInfos, String needle) {
+ if (themeInfos != null && !TextUtils.isEmpty(needle)) {
+ int n = themeInfos.length;
+ for (int i = 0; i < n; i++) {
+ ThemeInfo info = themeInfos;
+ if (needle.equals(info.themeId)) {
+ return info.styleResourceId;
+ }
+ }
+ }
+ return 0;
+ }
+
+ private static Resources getUnredirectedResourcesForPackage(Context context, String packageName) {
+ AssetManager assets = new AssetManager();
+
+ if (!packageName.equals("android")) {
+ PackageInfo pi = getPackageInfo(context, packageName);
+ if (pi == null || pi.applicationInfo == null ||
+ assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) {
+ Log.w(TAG, "Unable to get resources for package " + packageName);
+ return null;
+ }
+ }
+
+ return new Resources(assets, null, null);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ synchronized (mRedirections) {
+ final ArrayList<RedirectionKey> filteredKeySet = new ArrayList<RedirectionKey>();
+ for (Map.Entry<RedirectionKey, PackageRedirectionMap> entry: mRedirections.entrySet()) {
+ PackageRedirectionMap map = entry.getValue();
+ if (map != null && map.getPackageId() != -1) {
+ filteredKeySet.add(entry.getKey());
+ }
+ }
+ Collections.sort(filteredKeySet, new Comparator<RedirectionKey>() {
+ @Override
+ public int compare(RedirectionKey a, RedirectionKey b) {
+ int comp = a.themePackageName.compareTo(b.themePackageName);
+ if (comp != 0) {
+ return comp;
+ }
+ comp = a.themeId.compareTo(b.themeId);
+ if (comp != 0) {
+ return comp;
+ }
+ return a.targetPackageName.compareTo(b.targetPackageName);
+ }
+ });
+
+ pw.println("Theme asset redirections:");
+ String lastPackageName = null;
+ String lastId = null;
+ Resources themeRes = null;
+ for (RedirectionKey key: filteredKeySet) {
+ if (lastPackageName == null || !lastPackageName.equals(key.themePackageName)) {
+ pw.println("* Theme package " + key.themePackageName + ":");
+ lastPackageName = key.themePackageName;
+ themeRes = getUnredirectedResourcesForPackage(mContext, key.themePackageName);
+ }
+ if (lastId == null || !lastId.equals(key.themeId)) {
+ pw.println(" theme id #" + key.themeId + ":");
+ lastId = key.themeId;
+ }
+ pw.println(" " + key.targetPackageName + ":");
+ Resources targetRes = getUnredirectedResourcesForPackage(mContext, key.targetPackageName);
+ PackageRedirectionMap resMap = mRedirections.get(key);
+ int[] fromIdents = resMap.getRedirectionKeys();
+ int N = fromIdents.length;
+ for (int i = 0; i < N; i++) {
+ int fromIdent = fromIdents;
+ int toIdent = resMap.lookupRedirection(fromIdent);
+ String fromName = targetRes != null ? targetRes.getResourceName(fromIdent) : null;
+ String toName = themeRes != null ? themeRes.getResourceName(toIdent) : null;
+ pw.println(String.format(" %s (0x%08x) => %s (0x%08x)", fromName, fromIdent,
+ toName, toIdent));
+ }
+ }
+ }
+ }
+
+ private static class RedirectionKey {
+ public String themePackageName;
+ public String themeId;
+ public String targetPackageName;
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof RedirectionKey)) return false;
+ final RedirectionKey oo = (RedirectionKey)o;
+ if (!nullSafeEquals(themePackageName, oo.themePackageName)) {
+ return false;
+ }
+ if (!nullSafeEquals(themeId, oo.themeId)) {
+ return false;
+ }
+ if (!nullSafeEquals(targetPackageName, oo.targetPackageName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return themePackageName.hashCode() +
+ themeId.hashCode() +
+ targetPackageName.hashCode();
+ }
+
+ private static boolean nullSafeEquals(Object a, Object b) {
+ if (a == null) {
+ return b == a;
+ } else if (b == null) {
+ return false;
+ } else {
+ return a.equals(b);
+ }
+ }
+ }
+
+ /**
+ * Parses and processes explicit redirection XML files.
+ */
+ private static class ResourceRedirectionsProcessor {
+ private final Resources mResources;
+ private final XmlPullParser mParser;
+ private final int mResourceId;
+ private final String mThemePackageName;
+ private final String mTargetPackageName;
+ private final PackageRedirectionMap mResMap;
+
+ public ResourceRedirectionsProcessor(Resources res, int resourceId,
+ String themePackageName, String targetPackageName,
+ PackageRedirectionMap outMap) {
+ mResources = res;
+ mParser = res.getXml(resourceId);
+ mResourceId = resourceId;
+ mThemePackageName = themePackageName;
+ mTargetPackageName = targetPackageName;
+ mResMap = outMap;
+ }
+
+ public void process() {
+ XmlPullParser parser = mParser;
+ int type;
+ try {
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // just loop...
+ }
+
+ String tagName = parser.getName();
+ if (parser.getName().equals("resource-redirections")) {
+ processResourceRedirectionsTag();
+ } else {
+ Log.w(TAG, "Unknown root element: " + tagName + " at " + getResourceLabel() + " " +
+ parser.getPositionDescription());
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Malformed theme redirection meta at " + getResourceLabel());
+ } catch (IOException e) {
+ Log.w(TAG, "Unknown error reading redirection meta at " + getResourceLabel());
+ }
+ }
+
+ private void processResourceRedirectionsTag() throws XmlPullParserException, IOException {
+ XmlPullParser parser = mParser;
+ int type;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT &&
+ (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("item")) {
+ processItemTag();
+ } else {
+ Log.w(TAG, "Unknown element under <resource-redirections>: " + tagName
+ + " at " + getResourceLabel() + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+ }
+
+ private void processItemTag() throws XmlPullParserException, IOException {
+ XmlPullParser parser = mParser;
+ String fromName = parser.getAttributeValue(null, "name");
+ if (TextUtils.isEmpty(fromName)) {
+ Log.w(TAG, "Missing android:name attribute on <item> tag at " + getResourceLabel() + " " +
+ parser.getPositionDescription());
+ return;
+ }
+ String toName = parser.nextText();
+ if (TextUtils.isEmpty(toName)) {
+ Log.w(TAG, "Missing <item> text at " + getResourceLabel() + " " +
+ parser.getPositionDescription());
+ return;
+ }
+ int fromIdent = mResources.getIdentifier(fromName, null, mTargetPackageName);
+ if (fromIdent == 0) {
+ Log.w(TAG, "No such resource found for " + mTargetPackageName + ":" + fromName);
+ return;
+ }
+ int toIdent = mResources.getIdentifier(toName, null, mThemePackageName);
+ if (toIdent == 0) {
+ Log.w(TAG, "No such resource found for " + mThemePackageName + ":" + toName);
+ return;
+ }
+ mResMap.addRedirection(fromIdent, toIdent);
+ }
+
+ private String getResourceLabel() {
+ return "resource #0x" + Integer.toHexString(mResourceId);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3ae62ad..453a01a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -24,6 +24,7 @@ import android.content.ContentResolver;
import android.content.ContentService;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.res.Configuration;
import android.media.AudioService;
@@ -222,7 +223,8 @@ class ServerThread extends Thread {
// Skip Bluetooth if we have an emulator kernel
// TODO: Use a more reliable check to see if this product should
// support Bluetooth - see bug 988521
- if (SystemProperties.get("ro.kernel.qemu").equals("1")) {
+ if (SystemProperties.get("ro.kernel.qemu").equals("1") ||
+ SystemProperties.get("ro.bluetooth.disable").equals("1")) {
Slog.i(TAG, "No Bluetooh Service (emulator)");
} else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {
Slog.i(TAG, "No Bluetooth Service (factory test)");
@@ -535,6 +537,13 @@ class ServerThread extends Thread {
}
try {
+ Slog.i(TAG, "AssetRedirectionManager Service");
+ ServiceManager.addService("assetredirection", new AssetRedirectionManagerService(context));
+ } catch (Throwable e) {
+ reportWtf("starting AssetRedirectionManager Service", e);
+ }
+
+ try {
// need to add this service even if SamplingProfilerIntegration.isEnabled()
// is false, because it is this service that detects system property change and
// turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work,
@@ -612,6 +621,15 @@ class ServerThread extends Thread {
reportWtf("making Package Manager Service ready", e);
}
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE);
+ filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE_RESET);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addCategory(Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE);
+ filter.addDataScheme("package");
+ context.registerReceiver(new AppsLaunchFailureReceiver(), filter);
+
// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 4925a4e..24bb414 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -360,10 +360,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
}
public void clearWallpaperLocked(boolean defaultFailed) {
- File f = WALLPAPER_FILE;
- if (f.exists()) {
- f.delete();
- }
+ // File f = WALLPAPER_FILE;
+ // if (f.exists()) {
+ // f.delete();
+ // }
final long ident = Binder.clearCallingIdentity();
RuntimeException e = null;
try {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index aeeb3b0..a51c6a1 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -78,6 +78,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.CustomTheme;
import android.graphics.Bitmap;
import android.net.Proxy;
import android.net.ProxyProperties;
@@ -13389,6 +13390,11 @@ public final class ActivityManagerService extends ActivityManagerNative
values.userSetLocale);
}
+ if (values.customTheme != null) {
+ saveThemeResourceLocked(values.customTheme,
+ !values.customTheme.equals(mConfiguration.customTheme));
+ }
+
mConfigurationSeq++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
@@ -13481,6 +13487,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+import com.android.internal.app.IAssetRedirectionManager;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.NativeLibraryHelper;
@@ -183,6 +184,21 @@ public class PackageManagerService extends IPackageManager.Stub {
// package apks to install directory.
private static final String INSTALL_PACKAGE_SUFFIX = "-";
+ /**
+ * Indicates the state of installation. Used by PackageManager to
+ * figure out incomplete installations. Say a package is being installed
+ * (the state is set to PKG_INSTALL_INCOMPLETE) and remains so till
+ * the package installation is successful or unsuccesful lin which case
+ * the PackageManager will no longer maintain state information associated
+ * with the package. If some exception(like device freeze or battery being
+ * pulled out) occurs during installation of a package, the PackageManager
+ * needs this information to clean up the previously failed installation.
+ */
+ private static final int PKG_INSTALL_INCOMPLETE = 0;
+ private static final int PKG_INSTALL_COMPLETE = 1;
+
+ private static final int THEME_MAMANER_GUID = 1300;
+
static final int SCAN_MONITOR = 1<<0;
static final int SCAN_NO_DEX = 1<<1;
static final int SCAN_FORCE_DEX = 1<<2;
@@ -374,6 +390,8 @@ public class PackageManagerService extends IPackageManager.Stub {
ComponentName mResolveComponentName;
PackageParser.Package mPlatformPackage;
+ IAssetRedirectionManager mAssetRedirectionManager;
+
// Set of pending broadcasts for aggregating enable/disable of components.
final HashMap<String, ArrayList<String>> mPendingBroadcasts
= new HashMap<String, ArrayList<String>>();
@@ -663,20 +681,26 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageInstalledInfo res = data.res;
// If the removed package was a system update, the old system packaged
// was re-enabled; we need to broadcast this information
@@ -6926,10 +7081,15 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putInt(Intent.EXTRA_UID, info.removedUid >= 0 ? info.removedUid : info.uid);
extras.putBoolean(Intent.EXTRA_REPLACING, true);