asysbang

 找回密码
 立即注册
查看: 11259|回复: 0
打印 上一主题 下一主题

系统添加主题切换功能

[复制链接]

513

主题

2

好友

6404

积分

管理员

Rank: 80Rank: 80Rank: 80Rank: 80Rank: 80

最佳新人 活跃会员 热心会员 推广达人 宣传达人 灌水之王 突出贡献 优秀版主 荣誉管理 论坛元老

跳转到指定楼层
楼主
发表于 2013-5-2 18:20:14 |只看该作者 |正序浏览
转载请注明:www.asysbang.com

这个功能主要是参考CM的实现

因为这个改动涉及的文件太多,这里先给出git 修改记录,以后有时间整理

commit c5e2f8f6aae6c3601279c1cd89374a4f9abc4817
Author:
Date:   Thu Sep 20 18:13:46 2012 +0800

    add theme support framework
   
    Reviewed-by:

diff --git a/Android.mk b/Android.mk
index 371c370..d8f458a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -147,6 +147,7 @@ LOCAL_SRC_FILES += \
     core/java/com/android/internal/app/IBatteryStats.aidl \
     core/java/com/android/internal/app/IUsageStats.aidl \
     core/java/com/android/internal/app/IMediaContainerService.aidl \
+    core/java/com/android/internal/app/IAssetRedirectionManager.aidl \
     core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
     core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
     core/java/com/android/internal/backup/IBackupTransport.aidl \
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e2992c7..def4e8f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1510,6 +1510,30 @@ public class ActivityManager {
     }

     /**
+     * @hide
+     */
+    public Configuration getConfiguration() {
+        try {
+            return ActivityManagerNative.getDefault().getConfiguration();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * @throws SecurityException Throws SecurityException if the caller does
+     * not hold the {@link android.Manifest.permission#CHANGE_CONFIGURATION} permission.
+     *
+     * @hide
+     */
+    public void updateConfiguration(Configuration values) throws SecurityException {
+        try {
+            ActivityManagerNative.getDefault().updateConfiguration(values);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Returns "true" if device is running in a test harness.
      */
     public static boolean isRunningInTestHarness() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0c761fc..c988492 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -16,12 +16,15 @@

package android.app;

+import com.android.internal.app.IAssetRedirectionManager;
+
import android.app.backup.BackupAgent;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IIntentReceiver;
@@ -29,6 +32,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
@@ -36,6 +40,8 @@ import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.CustomTheme;
+import android.content.res.PackageRedirectionMap;
import android.content.res.Resources;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
@@ -45,6 +51,7 @@ import android.graphics.Canvas;
import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.ProxyProperties;
+import android.net.Uri;
import android.opengl.GLUtils;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -60,6 +67,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
+import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -68,6 +76,7 @@ import android.util.LogPrinter;
import android.util.Slog;
import android.view.Display;
import android.view.HardwareRenderer;
+import android.view.InflateException;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
@@ -140,6 +149,7 @@ public final class ActivityThread {
     static ContextImpl mSystemContext = null;

     static IPackageManager sPackageManager;
+    static IAssetRedirectionManager sAssetRedirectionManager;

     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;

-        ResourcesKey(String resDir, float scale) {
+        ResourcesKey(String resDir, float scale, boolean isThemeable) {
             mResDir = resDir;
             mScale = scale;
-            mHash = mResDir.hashCode() << 2 + (int) (mScale * 2);
+            mIsThemeable = isThemeable;
+            mHash = mResDir.hashCode() << 3 + ((mIsThemeable ? 1 : 0) << 2) + (int) (mScale * 2);
         }

         @Override
@@ -1374,7 +1386,8 @@ public final class ActivityThread {
                 return false;
             }
             ResourcesKey peer = (ResourcesKey) obj;
-            return mResDir.equals(peer.mResDir) && mScale == peer.mScale;
+            return mResDir.equals(peer.mResDir) && mScale == peer.mScale &&
+                    mIsThemeable == peer.mIsThemeable;
         }
     }

@@ -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);
         
-            applyConfigurationToResourcesLocked(config, compat);
+            diff = applyConfigurationToResourcesLocked(config, compat);
            
             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 {
         }
     }

+        /* package */ void replace(Map newContents, FileStatus stat) {
+            synchronized (this) {
+                mLoaded = true;
+                if (newContents != null) {
+                    mMap = newContents;
+                }
+                if (stat != null) {
+                    mStatTimestamp = stat.mtime;
+                    mStatSize = stat.size;
+                }
+            }
+        }
+
     public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
         synchronized(this) {
             mListeners.put(listener, mContent);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4e5598b..7404482 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2140,6 +2140,25 @@ public class Intent implements Parcelable, Cloneable {
     public static final String ACTION_PRE_BOOT_COMPLETED =
             "android.intent.action.PRE_BOOT_COMPLETED";

+    /**
+     * 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;
     }


@@ -559,6 +585,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
         dest.writeString(backupAgentName);
         dest.writeInt(descriptionRes);
         dest.writeInt(uiOptions);
+        dest.writeInt(isThemeable? 1 : 0);
     }

     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 android.os.ParcelFileDescriptor;
import android.util.Log;
+import android.util.SparseArray;
import android.util.TypedValue;

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 {
     }

     private CompatibilityInfo(int compFlags,
-            int dens, float scale, float invertedScale) {
+            int dens, float scale, float invertedScale, boolean isThemeable) {
         mCompatibilityFlags = compFlags;
         applicationDensity = dens;
         applicationScale = scale;
         applicationInvertedScale = invertedScale;
+        this.isThemeable = isThemeable;
     }

     private CompatibilityInfo() {
         this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
                 1.0f,
-                1.0f);
+                1.0f,
+                true);
     }

     /**
@@ -519,6 +527,7 @@ public class CompatibilityInfo implements Parcelable {
             if (applicationDensity != oc.applicationDensity) return false;
             if (applicationScale != oc.applicationScale) return false;
             if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+            if (isThemeable != oc.isThemeable) return false;
             return true;
         } catch (ClassCastException e) {
             return false;
@@ -556,6 +565,7 @@ public class CompatibilityInfo implements Parcelable {
         result = 31 * result + applicationDensity;
         result = 31 * result + Float.floatToIntBits(applicationScale);
         result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+        result = 31 * result + (isThemeable ? 1 : 0);
         return result;
     }

@@ -570,6 +580,7 @@ public class CompatibilityInfo implements Parcelable {
         dest.writeInt(applicationDensity);
         dest.writeFloat(applicationScale);
         dest.writeFloat(applicationInvertedScale);
+        dest.writeInt(isThemeable ? 1 : 0);
     }

     public static final Parcelable.Creator<CompatibilityInfo> CREATOR
@@ -588,5 +599,6 @@ public class CompatibilityInfo implements Parcelable {
         applicationDensity = source.readInt();
         applicationScale = source.readFloat();
         applicationInvertedScale = source.readFloat();
+        isThemeable = source.readInt() != 0;
     }
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 5c3a17a..b245eab 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -20,6 +20,7 @@ import android.content.pm.ActivityInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.LocaleUtil;
+import android.os.SystemProperties;

import java.util.Locale;

@@ -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;
         }
         
+        if (delta.customTheme != null
+                && (customTheme == null || !customTheme.equals(delta.customTheme))) {
+            changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+            customTheme = (CustomTheme)delta.customTheme.clone();
+        }
+
         return changed;
     }

@@ -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 {

     static void preload() {
         preloadClasses();
-        preloadResources();
+        //preloadResources();
     }

     /**
@@ -261,7 +261,7 @@ public class ZygoteInit {
             runtime.setTargetHeapUtilization(0.8f);

             // Start with a clean slate.
-            System.gc();
+            //System.gc();
             runtime.runFinalizationSync();
             Debug.startAllocCounting();

@@ -474,7 +474,7 @@ public class ZygoteInit {
         String args[] = {
             "--setuid=1000",
             "--setgid=1000",
-            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007",
+            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1300,3001,3002,3003,3006,3007",
             "--capabilities=130104352,130104352",
             "--runtime-init",
             "--nice-name=system_server",
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 3a685b2..f70863b 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -87,6 +87,7 @@ LOCAL_SRC_FILES:= \
     android_util_Log.cpp \
     android_util_DisplayMetrics.cpp \
     android_util_FloatMath.cpp \
+    android_util_PackageRedirectionMap.cpp \
     android_util_Process.cpp \
     android_util_StringBlock.cpp \
     android_util_XmlBlock.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a37933a..915a6ac 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -176,6 +176,7 @@ extern int register_android_view_PointerIcon(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
+extern int register_android_content_res_PackageRedirectionMap(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);

@@ -1204,6 +1205,7 @@ static const RegJNIRec gRegJNI[] = {

     REG_JNI(register_android_content_res_ObbScanner),
     REG_JNI(register_android_content_res_Configuration),
+    REG_JNI(register_android_content_res_PackageRedirectionMap),

     REG_JNI(register_android_animation_PropertyValuesHolder),
     REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 4f8f1af..16ac2d9 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -34,9 +34,13 @@
#include <utils/Asset.h>
#include <utils/AssetManager.h>
#include <utils/ResourceTypes.h>
+#include <utils/PackageRedirectionMap.h>
+#include <utils/ZipFile.h>

#include <stdio.h>

+#define REDIRECT_NOISY(x) //x
+
namespace android {

// ----------------------------------------------------------------------------
@@ -691,17 +695,23 @@ static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject
     }
     const ResTable& res(am->getResources());

+    uint32_t ref = res.lookupRedirectionMap(ident);
+    if (ref == 0) {
+        ref = ident;
+    } else {
+        REDIRECT_NOISY(LOGW("PERFORMED REDIRECT OF ident=0x%08x FOR ref=0x%08x\n", ident, ref));
+    }
+
     Res_value value;
     ResTable_config config;
     uint32_t typeSpecFlags;
-    ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
+    ssize_t block = res.getResource(ref, &value, false, density, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
     if (block == BAD_INDEX) {
         jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
         return 0;
     }
#endif
-    uint32_t ref = ident;
     if (resolve) {
         block = res.resolveReference(&value, block, &ref);
#if THROW_ON_BAD_ID
@@ -952,6 +962,20 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
     // Now lock down the resource object and start pulling stuff from it.
     res.lock();

+    // Apply theme redirections to the referenced styles.
+    if (defStyleRes != 0) {
+        uint32_t ref = res.lookupRedirectionMap(defStyleRes);
+        if (ref != 0) {
+            defStyleRes = ref;
+        }
+    }
+    if (style != 0) {
+        uint32_t ref = res.lookupRedirectionMap(style);
+        if (ref != 0) {
+            style = ref;
+        }
+    }
+
     // Retrieve the default style bag, if requested.
     const ResTable::bag_entry* defStyleEnt = NULL;
     uint32_t defStyleTypeSetFlags = 0;
@@ -1076,6 +1100,31 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
             block = kXmlBlock;
         }

+        // 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("deep 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("deep redirect failure from 0x%08x => 0x%08x, defStyleAttr=0x%08x, defStyleRes=0x%08x, style=0x%08x\n", resid, redirect, defStyleAttr, defStyleRes, style);
+                }
+            }
+        }
+
         DEBUG_STYLES(LOGI("Attribute 0x%08x: type=0x%x, data=0x%08x",
                 curIdent, value.dataType, value.data));

@@ -1333,6 +1382,31 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz
             value.dataType = Res_value::TYPE_NULL;
         }

+        // 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;
}

+static jint android_content_AssetManager_splitThemePackage(JNIEnv* env, jobject clazz,
+        jstring srcFileName, jstring dstFileName, jobjectArray drmProtectedAssetNames)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return -1;
+    }
+
+    LOGV("splitThemePackage in %p (Java object %p)\n", am, clazz);
+
+    if (srcFileName == NULL || dstFileName == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", srcFileName == NULL ? "srcFileName" : "dstFileName");
+        return -2;
+    }
+
+    jsize size = env->GetArrayLength(drmProtectedAssetNames);
+    if (size == 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "drmProtectedAssetNames");
+        return -3;
+    }
+
+    const char* srcFileName8 = env->GetStringUTFChars(srcFileName, NULL);
+    ZipFile* srcZip = new ZipFile;
+    status_t err = srcZip->open(srcFileName8, ZipFile::kOpenReadWrite);
+    if (err != NO_ERROR) {
+        LOGV("error opening zip file %s\n", srcFileName8);
+        delete srcZip;
+        env->ReleaseStringUTFChars(srcFileName, srcFileName8);
+        return -4;
+    }
+
+    const char* dstFileName8 = env->GetStringUTFChars(dstFileName, NULL);
+    ZipFile* dstZip = new ZipFile;
+    err = dstZip->open(dstFileName8, ZipFile::kOpenReadWrite | ZipFile::kOpenTruncate | ZipFile::kOpenCreate);
+
+    if (err != NO_ERROR) {
+        LOGV("error opening zip file %s\n", dstFileName8);
+        delete srcZip;
+        delete dstZip;
+        env->ReleaseStringUTFChars(srcFileName, srcFileName8);
+        env->ReleaseStringUTFChars(dstFileName, dstFileName8);
+        return -5;
+    }
+
+    int result = 0;
+    for (int i = 0; i < size; i++) {
+        jstring javaString = (jstring)env->GetObjectArrayElement(drmProtectedAssetNames, i);
+        const char* drmProtectedAssetFileName8 = env->GetStringUTFChars(javaString, NULL);
+        ZipEntry *assetEntry = srcZip->getEntryByName(drmProtectedAssetFileName8);
+        if (assetEntry == NULL) {
+            result = 1;
+            LOGV("Invalid asset entry %s\n", drmProtectedAssetFileName8);
+        } else {
+            status_t loc_result = dstZip->add(srcZip, assetEntry, 0, NULL);
+            if (loc_result != NO_ERROR) {
+                LOGV("error copying zip entry %s\n", drmProtectedAssetFileName8);
+                result = result | 2;
+            } else {
+                loc_result = srcZip->remove(assetEntry);
+                if (loc_result != NO_ERROR) {
+                    LOGV("error removing zip entry %s\n", drmProtectedAssetFileName8);
+                    result = result | 4;
+                }
+            }
+        }
+        env->ReleaseStringUTFChars(javaString, drmProtectedAssetFileName8);
+    }
+    srcZip->flush();
+    dstZip->flush();
+
+    delete srcZip;
+    delete dstZip;
+    env->ReleaseStringUTFChars(srcFileName, srcFileName8);
+    env->ReleaseStringUTFChars(dstFileName, dstFileName8);
+
+    return (jint)result;
+}
+
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{
     AssetManager* am = new AssetManager();
@@ -1607,6 +1759,173 @@ static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env,
     return AssetManager::getGlobalCount();
}

+static jint android_content_AssetManager_getBasePackageCount(JNIEnv* env, jobject clazz)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+
+    return am->getResources().getBasePackageCount();
+}
+
+static jstring android_content_AssetManager_getBasePackageName(JNIEnv* env, jobject clazz, jint index)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+
+    String16 packageName(am->getResources().getBasePackageName(index));
+    return env->NewString((const jchar*)packageName.string(), packageName.size());
+}
+
+static jint android_content_AssetManager_getBasePackageId(JNIEnv* env, jobject clazz, jint index)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+
+    return am->getResources().getBasePackageId(index);
+}
+
+static void android_content_AssetManager_addRedirectionsNative(JNIEnv* env, jobject clazz,
+            PackageRedirectionMap* resMap)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return;
+    }
+
+    am->addRedirections(resMap);
+}
+
+static void android_content_AssetManager_clearRedirectionsNative(JNIEnv* env, jobject clazz)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return;
+    }
+
+    am->clearRedirections();
+}
+
+static jboolean android_content_AssetManager_generateStyleRedirections(JNIEnv* env, jobject clazz,
+        PackageRedirectionMap* resMap, jint sourceStyle, jint destStyle)
+{
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+
+    const ResTable& res(am->getResources());
+
+    res.lock();
+
+    // Load up a bag for the user-supplied theme.
+    const ResTable::bag_entry* themeEnt = NULL;
+    ssize_t N = res.getBagLocked(destStyle, &themeEnt);
+    const ResTable::bag_entry* endThemeEnt = themeEnt + (N >= 0 ? N : 0);
+
+    // ...and a bag for the framework default.
+    const ResTable::bag_entry* frameworkEnt = NULL;
+    N = res.getBagLocked(sourceStyle, &frameworkEnt);
+    const ResTable::bag_entry* endFrameworkEnt = frameworkEnt + (N >= 0 ? N : 0);
+
+    // Add the source => dest style redirection first.
+    jboolean ret = JNI_FALSE;
+    if (themeEnt < endThemeEnt && frameworkEnt < endFrameworkEnt) {
+        resMap->addRedirection(sourceStyle, destStyle);
+        ret = JNI_TRUE;
+    }
+
+    // Now compare them and infer resource redirections for attributes that
+    // remap to different styles.  This works by essentially lining up all the
+    // sorted attributes from each theme and detected TYPE_REFERENCE entries
+    // that point to different resources.  When we find such a mismatch, we'll
+    // create a resource redirection from the original framework resource ID to
+    // the one in the theme.  This lets us do things like automatically find
+    // redirections for @android:style/Widget.Button by looking at how the
+    // theme overrides the android:attr/buttonStyle attribute.
+    REDIRECT_NOISY(LOGW("delta between 0x%08x and 0x%08x:\n", sourceStyle, destStyle));
+    for (; frameworkEnt < endFrameworkEnt; frameworkEnt++) {
+        if (frameworkEnt->map.value.dataType != Res_value::TYPE_REFERENCE) {
+            continue;
+        }
+
+        uint32_t curIdent = frameworkEnt->map.name.ident;
+
+        // Walk along the theme entry looking for a match.
+        while (themeEnt < endThemeEnt && curIdent > themeEnt->map.name.ident) {
+            themeEnt++;
+        }
+        // Match found, compare the references.
+        if (themeEnt < endThemeEnt && curIdent == themeEnt->map.name.ident) {
+            if (themeEnt->map.value.data != frameworkEnt->map.value.data) {
+                uint32_t fromIdent = frameworkEnt->map.value.data;
+                uint32_t toIdent = themeEnt->map.value.data;
+                REDIRECT_NOISY(LOGW("   generated mapping from 0x%08x => 0x%08x (by attr 0x%08x)\n",
+                        fromIdent, toIdent, curIdent));
+                resMap->addRedirection(fromIdent, toIdent);
+            }
+            themeEnt++;
+        }
+
+        // Exhausted the theme, bail early.
+        if (themeEnt >= endThemeEnt) {
+            break;
+        }
+    }
+
+    res.unlock();
+
+    return ret;
+}
+
+static jboolean android_content_AssetManager_detachThemePath(JNIEnv* env, jobject clazz,
+            jstring packageName, jint cookie)
+{
+    if (packageName == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "packageName");
+        return JNI_FALSE;
+    }
+
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+
+    const char* name8 = env->GetStringUTFChars(packageName, NULL);
+    bool res = am->detachThemePath(String8(name8), (void *)cookie);
+    env->ReleaseStringUTFChars(packageName, name8);
+
+    return res;
+}
+
+static jint android_content_AssetManager_attachThemePath(
+            JNIEnv* env, jobject clazz, jstring path)
+{
+    if (path == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "path");
+        return JNI_FALSE;
+    }
+
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return JNI_FALSE;
+    }
+
+    const char* path8 = env->GetStringUTFChars(path, NULL);
+
+    void* cookie;
+    bool res = am->attachThemePath(String8(path8), &cookie);
+
+    env->ReleaseStringUTFChars(path, path8);
+
+    return (res) ? (jint)cookie : 0;
+}
+
// ----------------------------------------------------------------------------

/*
@@ -1716,6 +2035,28 @@ static JNINativeMethod gAssetManagerMethods[] = {
         (void*) android_content_AssetManager_getAssetAllocations },
     { "getGlobalAssetManagerCount", "()I",
         (void*) android_content_AssetManager_getGlobalAssetCount },
+
+    // Split theme package apk into two.
+    { "splitThemePackage","(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)I",
+        (void*) android_content_AssetManager_splitThemePackage },
+
+    // Dynamic theme package support.
+    { "detachThemePath", "(Ljava/lang/String;I)Z",
+        (void*) android_content_AssetManager_detachThemePath },
+    { "attachThemePath",   "(Ljava/lang/String;)I",
+        (void*) android_content_AssetManager_attachThemePath },
+    { "getBasePackageCount", "()I",
+        (void*) android_content_AssetManager_getBasePackageCount },
+    { "getBasePackageName", "(I)Ljava/lang/String;",
+        (void*) android_content_AssetManager_getBasePackageName },
+    { "getBasePackageId", "(I)I",
+        (void*) android_content_AssetManager_getBasePackageId },
+    { "addRedirectionsNative", "(I)V",
+        (void*) android_content_AssetManager_addRedirectionsNative },
+    { "clearRedirectionsNative", "()V",
+        (void*) android_content_AssetManager_clearRedirectionsNative },
+    { "generateStyleRedirections", "(III)Z",
+        (void*) android_content_AssetManager_generateStyleRedirections },
};

int register_android_content_AssetManager(JNIEnv* env)
diff --git a/core/jni/android_util_PackageRedirectionMap.cpp b/core/jni/android_util_PackageRedirectionMap.cpp
new file mode 100644
index 0000000..2391edb
--- /dev/null
+++ b/core/jni/android_util_PackageRedirectionMap.cpp
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+#include <utils/PackageRedirectionMap.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include "android_util_Binder.h"
+#include <binder/Parcel.h>
+
+#include <utils/ResourceTypes.h>
+
+#include <stdio.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static PackageRedirectionMap* PackageRedirectionMap_constructor(JNIEnv* env, jobject clazz)
+{
+    return new PackageRedirectionMap;
+}
+
+static void PackageRedirectionMap_destructor(JNIEnv* env, jobject clazz,
+        PackageRedirectionMap* resMap)
+{
+    delete resMap;
+}
+
+static PackageRedirectionMap* PackageRedirectionMap_createFromParcel(JNIEnv* env, jobject clazz,
+        jobject parcel)
+{
+    if (parcel == NULL) {
+        return NULL;
+    }
+
+    Parcel* p = parcelForJavaObject(env, parcel);
+    PackageRedirectionMap* resMap = new PackageRedirectionMap;
+
+    int32_t entryCount = p->readInt32();
+    while (entryCount-- > 0) {
+        uint32_t fromIdent = (uint32_t)p->readInt32();
+        uint32_t toIdent = (uint32_t)p->readInt32();
+        resMap->addRedirection(fromIdent, toIdent);
+    }
+
+    return resMap;
+}
+
+static jboolean PackageRedirectionMap_writeToParcel(JNIEnv* env, jobject clazz,
+        PackageRedirectionMap* resMap, jobject parcel)
+{
+    if (parcel == NULL) {
+        return JNI_FALSE;
+    }
+
+    Parcel* p = parcelForJavaObject(env, parcel);
+
+    int package = resMap->getPackage();
+    size_t nTypes = resMap->getNumberOfTypes();
+    size_t entryCount = 0;
+    for (size_t type=0; type<nTypes; type++) {
+        entryCount += resMap->getNumberOfUsedEntries(type);
+    }
+    p->writeInt32(entryCount);
+    for (size_t type=0; type<nTypes; type++) {
+        size_t nEntries = resMap->getNumberOfEntries(type);
+        for (size_t entry=0; entry<nEntries; entry++) {
+            uint32_t toIdent = resMap->getEntry(type, entry);
+            if (toIdent != 0) {
+                uint32_t fromIdent = Res_MAKEID(package-1, type, entry);
+                p->writeInt32(fromIdent);
+                p->writeInt32(toIdent);
+            }
+        }
+    }
+
+    return JNI_TRUE;
+}
+
+static void PackageRedirectionMap_addRedirection(JNIEnv* env, jobject clazz,
+        PackageRedirectionMap* resMap, jint fromIdent, jint toIdent)
+{
+    resMap->addRedirection(fromIdent, toIdent);
+}
+
+static jint PackageRedirectionMap_getPackageId(JNIEnv* env, jobject clazz,
+        PackageRedirectionMap* resMap)
+{
+    return resMap->getPackage();
+}
+
+static jint PackageRedirectionMap_lookupRedirection(JNIEnv* env, jobject clazz,
+        PackageRedirectionMap* resMap, jint fromIdent)
+{
+    return resMap->lookupRedirection(fromIdent);
+}
+
+static jintArray PackageRedirectionMap_getRedirectionKeys(JNIEnv* env, jobject clazz,
+        PackageRedirectionMap* resMap)
+{
+    int package = resMap->getPackage();
+    size_t nTypes = resMap->getNumberOfTypes();
+    size_t entryCount = 0;
+    for (size_t type=0; type<nTypes; type++) {
+        size_t usedEntries = resMap->getNumberOfUsedEntries(type);
+        entryCount += usedEntries;
+    }
+    jintArray array = env->NewIntArray(entryCount);
+    if (array == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
+        return NULL;
+    }
+    jsize index = 0;
+    for (size_t type=0; type<nTypes; type++) {
+        size_t nEntries = resMap->getNumberOfEntries(type);
+        for (size_t entry=0; entry<nEntries; entry++) {
+            uint32_t toIdent = resMap->getEntry(type, entry);
+            if (toIdent != 0) {
+                jint fromIdent = (jint)Res_MAKEID(package-1, type, entry);
+                env->SetIntArrayRegion(array, index++, 1, &fromIdent);
+            }
+        }
+    }
+    return array;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gPackageRedirectionMapMethods[] = {
+    { "nativeConstructor",      "()I",
+        (void*) PackageRedirectionMap_constructor },
+    { "nativeDestructor",       "(I)V",
+        (void*) PackageRedirectionMap_destructor },
+    { "nativeCreateFromParcel", "(Landroid/os/Parcel;)I",
+        (void*) PackageRedirectionMap_createFromParcel },
+    { "nativeWriteToParcel", "(ILandroid/os/Parcel;)Z",
+        (void*) PackageRedirectionMap_writeToParcel },
+    { "nativeAddRedirection", "(III)V",
+        (void*) PackageRedirectionMap_addRedirection },
+    { "nativeGetPackageId", "(I)I",
+        (void*) PackageRedirectionMap_getPackageId },
+    { "nativeLookupRedirection", "(II)I",
+        (void*) PackageRedirectionMap_lookupRedirection },
+    { "nativeGetRedirectionKeys", "(I)[I",
+        (void*) PackageRedirectionMap_getRedirectionKeys },
+};
+
+int register_android_content_res_PackageRedirectionMap(JNIEnv* env)
+{
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/content/res/PackageRedirectionMap",
+            gPackageRedirectionMapMethods,
+            NELEM(gPackageRedirectionMapMethods));
+}
+
+}; // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 97658a1..efb8dec 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1630,6 +1630,18 @@
             </intent-filter>
         </receiver>

+        <receiver android:name="com.android.server.AppsLaunchFailureReceiver" >
+            <intent-filter>
+                <action android:name="com.tmobile.intent.action.APP_LAUNCH_FAILURE" />
+                <action android:name="com.tmobile.intent.action.APP_LAUNCH_FAILURE_RESET" />
+                <action android:name="android.intent.action.PACKAGE_ADDED" />
+                <action android:name="android.intent.action.PACKAGE_REMOVED" />
+                <action android:name="com.tmobile.intent.action.THEME_PACKAGE_UPDATED" />
+                <category android:name="com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE" />
+                <data android:scheme="package" />
+            </intent-filter>
+        </receiver>
+
         <service android:name="com.android.internal.os.storage.ExternalStorageFormatter"
             android:permission="android.permission.MASTER_CLEAR"
             android:exported="true" />
diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h
index a8c7ddb..3946bd1 100644
--- a/include/utils/AssetManager.h
+++ b/include/utils/AssetManager.h
@@ -22,6 +22,7 @@

#include <utils/Asset.h>
#include <utils/AssetDir.h>
+#include <utils/PackageRedirectionMap.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -91,7 +92,7 @@ public:
      * then on success, *cookie is set to the value corresponding to the
      * newly-added asset source.
      */
-    bool addAssetPath(const String8& path, void** cookie);
+    bool addAssetPath(const String8& path, void** cookie, bool asSkin=false);

     /*                                                                       
      * Convenience for adding the standard system assets.  Uses the
@@ -217,14 +218,28 @@ public:
      */
     void getLocales(Vector<String8>* locales) const;

+    /*
+     * Remove existing source for assets.
+     *
+     * Also updates the ResTable object to reflect the change.
+     *
+     * Returns "true" on success, "false" on failure.
+     */
+    bool detachThemePath(const String8& packageName, void *cookie);
+    bool attachThemePath(const String8& path, void** cookie);
+    void addRedirections(PackageRedirectionMap* resMap);
+    void clearRedirections();
+
private:
     struct asset_path
     {
         String8 path;
         FileType type;
         String8 idmap;
+        bool asSkin;
     };

+    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>

@@ -1818,6 +1819,9 @@ public:
                  bool copyData=false, const void* idmap = NULL);
     status_t add(ResTable* src);

+    void addRedirections(PackageRedirectionMap* resMap);
+    void clearRedirections();
+
     status_t getError() const;

     void uninit();
@@ -1854,6 +1858,11 @@ public:
                     uint32_t* outSpecFlags = NULL,
                     ResTable_config* outConfig = NULL) const;

+    inline ssize_t getResource(uint32_t resID, Res_value* outValue, bool mayBeBag,
+            uint32_t* outSpecFlags, ResTable_config* outConfig) const {
+        return getResource(resID, outValue, mayBeBag, 0, outSpecFlags, outConfig);
+    }
+
     inline ssize_t getResource(const ResTable_ref& res, Res_value* outValue,
             uint32_t* outSpecFlags=NULL) const {
         return getResource(res.ident, outValue, false, 0, outSpecFlags, NULL);
@@ -1865,6 +1874,8 @@ public:
                              uint32_t* inoutTypeSpecFlags = NULL,
                              ResTable_config* outConfig = NULL) const;

+    uint32_t lookupRedirectionMap(uint32_t resID) const;
+
     enum {
         TMP_BUFFER_SIZE = 16
     };
@@ -2062,6 +2073,8 @@ public:

     void getLocales(Vector<String8>* locales) const;

+    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


diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp
index 22034c5..a6a1158 100644
--- a/libs/utils/AssetManager.cpp
+++ b/libs/utils/AssetManager.cpp
@@ -134,7 +134,7 @@ AssetManager::~AssetManager(void)
     delete[] mVendor;
}

-bool AssetManager::addAssetPath(const String8& path, void** cookie)
+bool AssetManager::addAssetPath(const String8& path, void** cookie, bool asSkin)
{
     AutoMutex _l(mLock);

@@ -144,6 +144,7 @@ bool AssetManager::addAssetPath(const String8& path, void** cookie)
     if (kAppZipName) {
         realPath.appendPath(kAppZipName);
     }
+    ap.asSkin = asSkin;
     ap.type = ::getFileType(realPath.string());
     if (ap.type == kFileTypeRegular) {
         ap.path = realPath;
@@ -498,9 +499,13 @@ Asset* AssetManager:pen(const char* fileName, AccessMode mode)
     size_t i = mAssetPaths.size();
     while (i > 0) {
         i--;
+        const asset_path& ap = mAssetPaths.itemAt(i);
+        if (ap.asSkin) {
+            continue;
+        }
         LOGV("Looking for asset '%s' in '%s'\n",
-                assetName.string(), mAssetPaths.itemAt(i).path.string());
-        Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+                assetName.string(), ap.path.string());
+        Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, ap);
         if (pAsset != NULL) {
             return pAsset != kExcludedAsset ? pAsset : NULL;
         }
@@ -532,9 +537,13 @@ Asset* AssetManager:penNonAsset(const char* fileName, AccessMode mode)
     size_t i = mAssetPaths.size();
     while (i > 0) {
         i--;
-        LOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
+        const asset_path& ap = mAssetPaths.itemAt(i);
+        if (ap.asSkin) {
+            continue;
+        }
+        LOGV("Looking for non-asset '%s' in '%s'\n", fileName, ap.path.string());
         Asset* pAsset = openNonAssetInPathLocked(
-            fileName, mode, mAssetPaths.itemAt(i));
+            fileName, mode, ap);
         if (pAsset != NULL) {
             return pAsset != kExcludedAsset ? pAsset : NULL;
         }
@@ -614,83 +623,87 @@ const ResTable* AssetManager::getResTable(bool required) const
     if (mCacheMode != CACHE_OFF && !mCacheValid)
         const_cast<AssetManager*>(this)->loadFileNameCacheLocked();

-    const size_t N = mAssetPaths.size();
-    for (size_t i=0; i<N; i++) {
-        Asset* ass = NULL;
-        ResTable* sharedRes = NULL;
-        bool shared = true;
-        const asset_path& ap = mAssetPaths.itemAt(i);
-        Asset* idmap = openIdmapLocked(ap);
-        LOGV("Looking for resource asset in '%s'\n", ap.path.string());
-        if (ap.type != kFileTypeDirectory) {
-            if (i == 0) {
-                // The first item is typically the framework resources,
-                // which we want to avoid parsing every time.
-                sharedRes = const_cast<AssetManager*>(this)->
-                    mZipSet.getZipResourceTable(ap.path);
-            }
-            if (sharedRes == NULL) {
+    mResources = rt = new ResTable();
+
+    if (rt) {
+        const size_t N = mAssetPaths.size();
+        for (size_t i=0; i<N; i++) {
+            const asset_path& ap = mAssetPaths.itemAt(i);
+            updateResTableFromAssetPath(rt, ap, (void*)(i+1));
+        }
+    }
+
+    if (required && !rt) LOGW("Unable to find resources file resources.arsc");
+    if (!rt) {
+        mResources = rt = new ResTable();
+    }
+
+    return rt;
+}
+
+void AssetManager::updateResTableFromAssetPath(ResTable *rt, const asset_path& ap, void *cookie) const
+{
+    Asset* ass = NULL;
+    ResTable* sharedRes = NULL;
+    bool shared = true;
+    size_t cookiePos = (size_t)cookie;
+    LOGV("Looking for resource asset in '%s'\n", ap.path.string());
+    if (ap.type != kFileTypeDirectory) {
+        if (cookiePos == 1) {
+            // The first item is typically the framework resources,
+            // which we want to avoid parsing every time.
+            sharedRes = const_cast<AssetManager*>(this)->
+                mZipSet.getZipResourceTable(ap.path);
+        }
+        if (sharedRes == NULL) {
+            ass = const_cast<AssetManager*>(this)->
+                mZipSet.getZipResourceTableAsset(ap.path);
+            if (ass == NULL) {
+                LOGV("loading resource table %s\n", ap.path.string());
                 ass = const_cast<AssetManager*>(this)->
-                    mZipSet.getZipResourceTableAsset(ap.path);
-                if (ass == NULL) {
-                    LOGV("loading resource table %s\n", ap.path.string());
+                    openNonAssetInPathLocked("resources.arsc",
+                        Asset::ACCESS_BUFFER,
+                        ap);
+                if (ass != NULL && ass != kExcludedAsset) {
                     ass = const_cast<AssetManager*>(this)->
-                        openNonAssetInPathLocked("resources.arsc",
-                                                 Asset::ACCESS_BUFFER,
-                                                 ap);
-                    if (ass != NULL && ass != kExcludedAsset) {
-                        ass = const_cast<AssetManager*>(this)->
-                            mZipSet.setZipResourceTableAsset(ap.path, ass);
-                    }
-                }
-               
-                if (i == 0 && ass != NULL) {
-                    // If this is the first resource table in the asset
-                    // manager, then we are going to cache it so that we
-                    // can quickly copy it out for others.
-                    LOGV("Creating shared resources for %s", ap.path.string());
-                    sharedRes = new ResTable();
-                    sharedRes->add(ass, (void*)(i+1), false, idmap);
-                    sharedRes = const_cast<AssetManager*>(this)->
-                        mZipSet.setZipResourceTable(ap.path, sharedRes);
+                        mZipSet.setZipResourceTableAsset(ap.path, ass);
                 }
             }
-        } else {
-            LOGV("loading resource table %s\n", ap.path.string());
-            Asset* ass = const_cast<AssetManager*>(this)->
-                openNonAssetInPathLocked("resources.arsc",
-                                         Asset::ACCESS_BUFFER,
-                                         ap);
-            shared = false;
-        }
-        if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
-            if (rt == NULL) {
-                mResources = rt = new ResTable();
-                updateResourceParamsLocked();
-            }
-            LOGV("Installing resource asset %p in to table %p\n", ass, mResources);
-            if (sharedRes != NULL) {
-                LOGV("Copying existing resources for %s", ap.path.string());
-                rt->add(sharedRes);
-            } else {
-                LOGV("Parsing resources for %s", ap.path.string());
-                rt->add(ass, (void*)(i+1), !shared, idmap);
-            }

-            if (!shared) {
-                delete ass;
+            if (cookiePos == 0 && ass != NULL) {
+                // If this is the first resource table in the asset
+                // manager, then we are going to cache it so that we
+                // can quickly copy it out for others.
+                LOGV("Creating shared resources for %s", ap.path.string());
+                sharedRes = new ResTable();
+                sharedRes->add(ass, cookie, false);
+                sharedRes = const_cast<AssetManager*>(this)->
+                    mZipSet.setZipResourceTable(ap.path, sharedRes);
             }
         }
-        if (idmap != NULL) {
-            delete idmap;
+    } else {
+        LOGV("loading resource table %s\n", ap.path.string());
+        Asset* ass = const_cast<AssetManager*>(this)->
+            openNonAssetInPathLocked("resources.arsc",
+                Asset::ACCESS_BUFFER,
+                ap);
+        shared = false;
+    }
+    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
+        updateResourceParamsLocked();
+        LOGV("Installing resource asset %p in to table %p\n", ass, mResources);
+        if (sharedRes != NULL) {
+            LOGV("Copying existing resources for %s", ap.path.string());
+            rt->add(sharedRes);
+        } else {
+            LOGV("Parsing resources for %s", ap.path.string());
+            rt->add(ass, cookie, !shared);
         }
-    }

-    if (required && !rt) LOGW("Unable to find resources file resources.arsc");
-    if (!rt) {
-        mResources = rt = new ResTable();
+        if (!shared) {
+            delete ass;
+        }
     }
-    return rt;
}

void AssetManager::updateResourceParamsLocked() const
@@ -1145,6 +1158,9 @@ AssetDir* AssetManager:penDir(const char* dirName)
     while (i > 0) {
         i--;
         const asset_path& ap = mAssetPaths.itemAt(i);
+        if (ap.asSkin) {
+            continue;
+        }
         if (ap.type == kFileTypeRegular) {
             LOGV("Adding directory %s from zip %s", dirName, ap.path.string());
             scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName);
@@ -1999,3 +2015,52 @@ int AssetManager::ZipSet::getIndex(const String8& zip) const

     return mZipPath.size()-1;
}
+
+bool AssetManager::attachThemePath(const String8& path, void** cookie)
+{
+    bool res = addAssetPath(path, cookie, true);
+    ResTable* rt = mResources;
+    if (res && rt != NULL && ((size_t)*cookie == mAssetPaths.size())) {
+        AutoMutex _l(mLock);
+        const asset_path& ap = mAssetPaths.itemAt((size_t)*cookie - 1);
+        updateResTableFromAssetPath(rt, ap, *cookie);
+    }
+    return res;
+}
+
+bool AssetManager::detachThemePath(const String8 &packageName, void* cookie)
+{
+    AutoMutex _l(mLock);
+
+    const size_t which = ((size_t)cookie)-1;
+    if (which >= mAssetPaths.size()) {
+        return false;
+    }
+
+    /* TODO: Ensure that this cookie is added with asSkin == true. */
+    mAssetPaths.removeAt(which);
+
+    ResTable* rt = mResources;
+    if (rt == NULL) {
+        LOGV("ResTable must not be NULL");
+        return false;
+    }
+
+    rt->removeAssetsByCookie(packageName, (void *)cookie);
+
+    return true;
+}
+
+void AssetManager::addRedirections(PackageRedirectionMap* resMap)
+{
+    getResources();
+    ResTable* rt = mResources;
+    rt->addRedirections(resMap);
+}
+
+void AssetManager::clearRedirections()
+{
+    getResources();
+    ResTable* rt = mResources;
+    rt->clearRedirections();
+}
diff --git a/libs/utils/PackageRedirectionMap.cpp b/libs/utils/PackageRedirectionMap.cpp
new file mode 100644
index 0000000..bf1062a
--- /dev/null
+++ b/libs/utils/PackageRedirectionMap.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified.  Portions copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.
+ */
+
+//
+// Provide access to read-only assets.
+//
+
+#define LOG_TAG "packageresmap"
+
+#include <utils/PackageRedirectionMap.h>
+#include <utils/ResourceTypes.h>
+#include <utils/misc.h>
+
+using namespace android;
+
+PackageRedirectionMap:ackageRedirectionMap()
+    : mPackage(-1), mEntriesByType(NULL)
+{
+}
+
+static void clearEntriesByType(uint32_t** entriesByType)
+{
+    SharedBuffer* buf = SharedBuffer::bufferFromData(entriesByType);
+    const size_t N = buf->size() / sizeof(entriesByType[0]);
+    for (size_t i = 0; i < N; i++) {
+        uint32_t* entries = entriesByType;
+        if (entries != NULL) {
+            SharedBuffer::bufferFromData(entries)->release();
+        }
+    }
+    buf->release();
+}
+
+PackageRedirectionMap::~PackageRedirectionMap()
+{
+    if (mEntriesByType != NULL) {
+        clearEntriesByType(mEntriesByType);
+    }
+}
+
+static void* ensureCapacity(void* data, size_t nmemb, size_t size)
+{
+    SharedBuffer* buf;
+    size_t currentSize;
+
+    if (data != NULL) {
+        buf = SharedBuffer::bufferFromData(data);
+        currentSize = buf->size();
+    } else {
+        buf = NULL;
+        currentSize = 0;
+    }
+
+    size_t minSize = nmemb * size;
+    if (minSize > currentSize) {
+        unsigned int requestSize = roundUpPower2(minSize);
+        if (buf == NULL) {
+            buf = SharedBuffer::alloc(requestSize);
+        } else {
+            buf = buf->editResize(requestSize);
+        }
+        memset((unsigned char*)buf->data()+currentSize, 0, requestSize - currentSize);
+    }
+
+    return buf->data();
+}
+
+bool PackageRedirectionMap::addRedirection(uint32_t fromIdent, uint32_t toIdent)
+{
+    const int package = Res_GETPACKAGE(fromIdent);
+    const int type = Res_GETTYPE(fromIdent);
+    const int entry = Res_GETENTRY(fromIdent);
+
+    // The first time we add a redirection we can infer the package for all
+    // future redirections.
+    if (mPackage == -1) {
+        mPackage = package+1;
+    } else if (mPackage != (package+1)) {
+        LOGW("cannot add redirection for conflicting package 0x%02x (expecting package 0x%02x)\n", package+1, mPackage);
+        return false;
+    }
+
+    mEntriesByType = (uint32_t**)ensureCapacity(mEntriesByType, type + 1, sizeof(uint32_t*));
+    uint32_t* entries = mEntriesByType[type];
+    entries = (uint32_t*)ensureCapacity(entries, entry + 1, sizeof(uint32_t));
+    entries[entry] = toIdent;
+    mEntriesByType[type] = entries;
+
+    return true;
+}
+
+uint32_t PackageRedirectionMap::lookupRedirection(uint32_t fromIdent)
+{
+    if (mPackage == -1 || mEntriesByType == NULL || fromIdent == 0) {
+        return 0;
+    }
+
+    const int package = Res_GETPACKAGE(fromIdent);
+    const int type = Res_GETTYPE(fromIdent);
+    const int entry = Res_GETENTRY(fromIdent);
+
+    if (package+1 != mPackage) {
+        return 0;
+    }
+
+    size_t nTypes = getNumberOfTypes();
+    if (type < 0 || type >= nTypes) {
+        return 0;
+    }
+    uint32_t* entries = mEntriesByType[type];
+    if (entries == NULL) {
+        return 0;
+    }
+    size_t nEntries = getNumberOfEntries(type);
+    if (entry < 0 || entry >= nEntries) {
+        return 0;
+    }
+    return entries[entry];
+}
+
+int PackageRedirectionMap::getPackage()
+{
+    return mPackage;
+}
+
+size_t PackageRedirectionMap::getNumberOfTypes()
+{
+    if (mEntriesByType == NULL) {
+        return 0;
+    } else {
+        return SharedBuffer::bufferFromData(mEntriesByType)->size() /
+                sizeof(mEntriesByType[0]);
+    }
+}
+
+size_t PackageRedirectionMap::getNumberOfUsedTypes()
+{
+    uint32_t** entriesByType = mEntriesByType;
+    size_t N = getNumberOfTypes();
+    size_t count = 0;
+    for (size_t i=0; i<N; i++) {
+        if (entriesByType != NULL) {
+            count++;
+        }
+    }
+    return count;
+}
+
+size_t PackageRedirectionMap::getNumberOfEntries(int type)
+{
+    uint32_t* entries = mEntriesByType[type];
+    if (entries == NULL) {
+        return 0;
+    } else {
+        return SharedBuffer::bufferFromData(entries)->size() /
+                sizeof(entries[0]);
+    }
+}
+
+size_t PackageRedirectionMap::getNumberOfUsedEntries(int type)
+{
+    size_t N = getNumberOfEntries(type);
+    uint32_t* entries = mEntriesByType[type];
+    size_t count = 0;
+    for (size_t i=0; i<N; i++) {
+        if (entries != 0) {
+            count++;
+        }
+    }
+    return count;
+}
+
+uint32_t PackageRedirectionMap::getEntry(int type, int entry)
+{
+    uint32_t* entries = mEntriesByType[type];
+    return entries[entry];
+}
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index 6cf01c8..9b23cb1 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -43,6 +43,7 @@
#define TABLE_SUPER_NOISY(x) //x
#define LOAD_TABLE_NOISY(x) //x
#define TABLE_THEME(x) //x
+#define REDIRECT_NOISY(x) //x

namespace android {

@@ -1550,6 +1551,13 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
     const bag_entry* bag;
     uint32_t bagTypeSpecFlags = 0;
     mTable.lock();
+    uint32_t redirect = mTable.lookupRedirectionMap(resID);
+    if (redirect != 0 || resID == 0x01030005) {
+        REDIRECT_NOISY(LOGW("applyStyle: PERFORMED REDIRECT OF ident=0x%08x FOR redirect=0x%08x\n", resID, redirect));
+    }
+    if (redirect != 0) {
+        resID = redirect;
+    }
     const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags);
     TABLE_NOISY(LOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N));
     if (N < 0) {
@@ -1997,6 +2005,8 @@ void ResTable::uninit()

     mPackageGroups.clear();
     mHeaders.clear();
+
+    clearRedirections();
}

bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const
@@ -2254,6 +2264,24 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
     return blockIndex;
}

+uint32_t ResTable::lookupRedirectionMap(uint32_t resID) const
+{
+    if (mError != NO_ERROR) {
+        return 0;
+    }
+
+    const int p = Res_GETPACKAGE(resID)+1;
+
+    const size_t N = mRedirectionMap.size();
+    for (size_t i=0; i<N; i++) {
+        PackageRedirectionMap* resMap = mRedirectionMap;
+        if (resMap->getPackage() == p) {
+            return resMap->lookupRedirection(resID);
+        }
+    }
+    return 0;
+}
+
const char16_t* ResTable::valueToString(
     const Res_value* value, size_t stringBlock,
     char16_t tmpBuffer[TMP_BUFFER_SIZE], size_t* outLen)
@@ -2461,7 +2489,19 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
         if (parent) {
             const bag_entry* parentBag;
             uint32_t parentTypeSpecFlags = 0;
-            const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
+            uint32_t parentRedirect = lookupRedirectionMap(parent);
+            uint32_t parentActual = parent;
+            if (parentRedirect != 0 || parent == 0x01030005) {
+                if (parentRedirect == resID) {
+                    REDIRECT_NOISY(LOGW("applyStyle(parent): ignoring circular redirect from parent=0x%08x to parentRedirect=0x%08x\n", parent, parentRedirect));
+                } else {
+                    REDIRECT_NOISY(LOGW("applyStyle(parent): PERFORMED REDIRECT OF parent=0x%08x FOR parentRedirect=0x%08x\n", parent, parentRedirect));
+                    if (parentRedirect != 0) {
+                        parentActual = parentRedirect;
+                    }
+                }
+            }
+            const ssize_t NP = getBagLocked(parentActual, &parentBag, &parentTypeSpecFlags);
             const size_t NT = ((NP >= 0) ? NP : 0) + N;
             set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
             if (set == NULL) {
@@ -4312,6 +4352,80 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
     return NO_ERROR;
}

+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 {
     }

     @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mBroadcastReceiver);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 911c71c..5b84889f 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1270,13 +1270,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             Context context = mContext;
             //Log.i(TAG, "addStartingWindow " + packageName + ": nonLocalizedLabel="
             //        + nonLocalizedLabel + " theme=" + Integer.toHexString(theme));
-            if (theme != context.getThemeResId() || labelRes != 0) {
-                try {
-                    context = context.createPackageContext(packageName, 0);
+            try {
+                context = context.createPackageContext(packageName, 0);
+                if (theme != 0) {
                     context.setTheme(theme);
-                } catch (PackageManager.NameNotFoundException e) {
-                    // Ignore
                 }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Ignore
             }
            
             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
         }
     }

+    private void saveThemeResourceLocked(CustomTheme t, boolean isDiff) {
+        if(isDiff){
+            SystemProperties.set(Configuration.THEME_ID_PERSISTENCE_PROPERTY, t.getThemeId());
+            SystemProperties.set(Configuration.THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY, t.getThemePackageName());
+        }
+    }
+
     // =========================================================
     // LIFETIME MANAGEMENT
     // =========================================================
diff --git a/services/java/com/android/server/pm/GrantedPermissions.java b/services/java/com/android/server/pm/GrantedPermissions.java
index c7629b9..2ad2c36 100644
--- a/services/java/com/android/server/pm/GrantedPermissions.java
+++ b/services/java/com/android/server/pm/GrantedPermissions.java
@@ -24,6 +24,10 @@ class GrantedPermissions {
     int pkgFlags;

     HashSet<String> grantedPermissions = new HashSet<String>();
+   
+    HashSet<String> revokedPermissions = new HashSet<String>();
+
+    HashSet<String> effectivePermissions = new HashSet<String>();

     int[] gids;

@@ -35,6 +39,8 @@ class GrantedPermissions {
     GrantedPermissions(GrantedPermissions base) {
         pkgFlags = base.pkgFlags;
         grantedPermissions = (HashSet<String>) base.grantedPermissions.clone();
+        revokedPermissions = (HashSet<String>) base.revokedPermissions.clone();
+        effectivePermissions = (HashSet<String>) base.effectivePermissions.clone();

         if (base.gids != null) {
             gids = base.gids.clone();
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 31b27bf..af21936 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -22,6 +22,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static libcore.io.OsConstants.S_ISLNK;

+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 (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
-                            res.removedInfo.sendBroadcast(false, true);
+                            res.removedInfo.sendBroadcast(false, true, false);
                             Bundle extras = new Bundle(1);
                             extras.putInt(Intent.EXTRA_UID, res.uid);
                             final boolean update = res.removedInfo.removedPackage != null;
                             if (update) {
                                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
                             }
+                            String category = null;
+                            if(res.pkg.mIsThemeApk) {
+                                category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+                            }
                             sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                     res.pkg.applicationInfo.packageName,
-                                    extras, null, null);
+                                    extras, null, category, null);
                             if (update) {
                                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                                         res.pkg.applicationInfo.packageName,
-                                        extras, null, null);
+                                        extras, null, category, null);
+
+                                //TODO: 2.3 not have this intent
                                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                                         null, null,
                                         res.pkg.applicationInfo.packageName, null);
@@ -877,6 +901,9 @@ public class PackageManagerService extends IPackageManager.Stub {
                 MULTIPLE_APPLICATION_UIDS
                         ? RADIO_UID : FIRST_APPLICATION_UID,
                 ApplicationInfo.FLAG_SYSTEM);
+        mSettings.addSharedUserLPw("com.tmobile.thememanager",
+                THEME_MAMANER_GUID,
+                ApplicationInfo.FLAG_SYSTEM);
         mSettings.addSharedUserLPw("android.uid.log",
                 MULTIPLE_APPLICATION_UIDS
                         ? LOG_UID : FIRST_APPLICATION_UID,
@@ -2586,6 +2613,30 @@ public class PackageManagerService extends IPackageManager.Stub {
         return list;
     }

+    public List<PackageInfo> getInstalledThemePackages() {
+        // Returns a list of theme APKs.
+        ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>();
+        List<PackageInfo> installedPackagesList = new ArrayList<PackageInfo>();
+        PackageInfo lastItem = null;
+        ParceledListSlice<PackageInfo> slice;
+
+        do {
+            final String lastKey = lastItem != null ? lastItem.packageName : null;
+            slice = getInstalledPackages(0, lastKey);
+            lastItem = slice.populateList(installedPackagesList, PackageInfo.CREATOR);
+        } while (!slice.isLastSlice());
+
+        Iterator<PackageInfo> i = installedPackagesList.iterator();
+        while (i.hasNext()) {
+            final PackageInfo pi = i.next();
+            if (pi != null && pi.isThemeApk) {
+                finalList.add(pi);
+            }
+        }
+
+        return finalList;
+    }
+
     public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags,
             String lastRead) {
         final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>();
@@ -3945,6 +3996,33 @@ public class PackageManagerService extends IPackageManager.Stub {
             }
         }
     }
+    // NOTE: this method can return null if the SystemServer is still
+    // initializing
+    public IAssetRedirectionManager getAssetRedirectionManager() {
+        if (mAssetRedirectionManager != null) {
+            return mAssetRedirectionManager;
+        }
+        IBinder b = ServiceManager.getService("assetredirection");
+        mAssetRedirectionManager = IAssetRedirectionManager.Stub.asInterface(b);
+        return mAssetRedirectionManager;
+    }
+
+    private void cleanAssetRedirections(PackageParser.Package pkg) {
+        IAssetRedirectionManager rm = getAssetRedirectionManager();
+        if (rm == null) {
+            return;
+        }
+        try {
+            if (pkg.mIsThemeApk) {
+                rm.clearRedirectionMapsByTheme(pkg.packageName, null);
+            } else {
+                rm.clearPackageRedirectionMap(pkg.packageName);
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+

     void removePackageLI(PackageParser.Package pkg, boolean chatty) {
         if (DEBUG_INSTALL) {
@@ -3954,6 +4032,8 @@ public class PackageManagerService extends IPackageManager.Stub {

         // writer
         synchronized (mPackages) {
+            cleanAssetRedirections(pkg);
+
             clearPackagePreferredActivitiesLPw(pkg.packageName);

             mPackages.remove(pkg.applicationInfo.packageName);
@@ -4738,6 +4818,11 @@ public class PackageManagerService extends IPackageManager.Stub {

     static final void sendPackageBroadcast(String action, String pkg,
             Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) {
+        sendPackageBroadcast(action, pkg, extras, targetPkg, null, finishedReceiver);
+    }
+
+    static final void sendPackageBroadcast(String action, String pkg,
+            Bundle extras, String targetPkg, String intentCategory, IIntentReceiver finishedReceiver) {
         IActivityManager am = ActivityManagerNative.getDefault();
         if (am != null) {
             try {
@@ -4750,6 +4835,9 @@ public class PackageManagerService extends IPackageManager.Stub {
                     intent.setPackage(targetPkg);
                 }
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                if (intentCategory != null) {
+                    intent.addCategory(intentCategory);
+                }
                 am.broadcastIntent(null, intent, null, finishedReceiver,
                         0, null, null, null, finishedReceiver != null, false);
             } catch (RemoteException ex) {
@@ -4820,6 +4908,7 @@ public class PackageManagerService extends IPackageManager.Stub {
             int removedUid = -1;
             String addedPackage = null;
             int addedUid = -1;
+            String category = null;

             // TODO post a message to the handler to obtain serial ordering
             synchronized (mInstallLock) {
@@ -4851,6 +4940,9 @@ public class PackageManagerService extends IPackageManager.Stub {
                 }
                 if ((event&REMOVE_EVENTS) != 0) {
                     if (p != null) {
+                        if (p.mIsThemeApk) {
+                            category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+                        }
                         removePackageLI(p, true);
                         removedPackage = p.applicationInfo.packageName;
                         removedUid = p.applicationInfo.uid;
@@ -4881,6 +4973,9 @@ public class PackageManagerService extends IPackageManager.Stub {
                             addedUid = p.applicationInfo.uid;
                         }
                     }
+                    if (p != null && p.mIsThemeApk) {
+                        category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+                    }
                 }

                 // reader
@@ -4894,13 +4989,13 @@ public class PackageManagerService extends IPackageManager.Stub {
                 extras.putInt(Intent.EXTRA_UID, removedUid);
                 extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
-                        extras, null, null);
+                        extras, null, category, null);
             }
             if (addedPackage != null) {
                 Bundle extras = new Bundle(1);
                 extras.putInt(Intent.EXTRA_UID, addedUid);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,
-                        extras, null, null);
+                        extras, null, category, null);
             }
         }

@@ -6599,6 +6694,66 @@ public class PackageManagerService extends IPackageManager.Stub {
         }
     }

+    private void deleteLockedZipFileIfExists(String originalPackagePath) {
+        String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath);
+        File zipFile = new File(lockedZipFilePath);
+        if (zipFile.exists() && zipFile.isFile()) {
+            if (!zipFile.delete()) {
+                Log.w(TAG, "Couldn't delete locked zip file: " + originalPackagePath);
+            }
+        }
+    }
+
+    private void splitThemePackage(File originalFile) {
+        final String originalPackagePath = originalFile.getPath();
+        final String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath);
+
+        try {
+            final List<String> drmProtectedEntries = new ArrayList<String>();
+            final ZipFile privateZip = new ZipFile(originalFile.getPath());
+
+            final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
+            while (privateZipEntries.hasMoreElements()) {
+                final ZipEntry zipEntry = privateZipEntries.nextElement();
+                final String zipEntryName = zipEntry.getName();
+                if (zipEntryName.startsWith("assets/") && zipEntryName.contains("/locked/")) {
+                    drmProtectedEntries.add(zipEntryName);
+                }
+            }
+            privateZip.close();
+
+            String [] args = new String[0];
+            args = drmProtectedEntries.toArray(args);
+            int code = mContext.getAssets().splitDrmProtectedThemePackage(
+                    originalPackagePath,
+                    lockedZipFilePath,
+                    args);
+            if (code != 0) {
+                Log.e("PackageManagerService",
+                        "splitDrmProtectedThemePackage returned = " + code);
+            }
+            code = FileUtils.setPermissions(
+                    lockedZipFilePath,
+                    0640,
+                    -1,
+                    THEME_MAMANER_GUID);
+            if (code != 0) {
+                Log.e("PackageManagerService",
+                        "Set permissions for " + lockedZipFilePath + " returned = " + code);
+            }
+            code = FileUtils.setPermissions(
+                    originalPackagePath,
+                    0644,
+                    -1, -1);
+            if (code != 0) {
+                Log.e("PackageManagerService",
+                        "Set permissions for " + originalPackagePath + " returned = " + code);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failure to generate new zip files for theme");
+        }
+    }
+
     private void installPackageLI(InstallArgs args,
             boolean newInstall, PackageInstalledInfo res) {
         int pFlags = args.flags;
@@ -6917,7 +7072,7 @@ public class PackageManagerService extends IPackageManager.Stub {

         if (res && sendBroadCast) {
             boolean systemUpdate = info.isRemovedPackageSystemUpdate;
-            info.sendBroadcast(deleteCodeAndResources, systemUpdate);
+            info.sendBroadcast(deleteCodeAndResources, systemUpdate, true);

             // 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);

+                String category = null;
+                if (info.isThemeApk) {
+                    category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+                }
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, null, null);
+                        extras, null, category, null);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                        extras, null, null);
+                        extras, null, category, null);
+                //TODO: 2.3 no have this intent
                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
                         null, packageName, null);
             }
@@ -6954,8 +7114,10 @@ public class PackageManagerService extends IPackageManager.Stub {
         boolean isRemovedPackageSystemUpdate = false;
         // Clean up resources deleted packages.
         InstallArgs args = null;
+        boolean isThemeApk = false;

-        void sendBroadcast(boolean fullRemove, boolean replacing) {
+        void sendBroadcast(boolean fullRemove, boolean replacing,
+                boolean deleteLockedZipFileIfExists) {
             Bundle extras = new Bundle(1);
             extras.putInt(Intent.EXTRA_UID, removedUid >= 0 ? removedUid : uid);
             extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove);
@@ -6963,8 +7125,14 @@ public class PackageManagerService extends IPackageManager.Stub {
                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
             }
             if (removedPackage != null) {
+                String category = null;
+                if (isThemeApk) {
+                    category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+                }
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
-                        extras, null, null);
+                        extras, null, category, null);
+
+                //TODO: 2.3 not has this intent
                 if (fullRemove && !replacing) {
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
                             extras, null, null);
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 58680ea..a522757 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -499,6 +499,14 @@ public class MockPackageManager extends PackageManager {
     }

     /**
+     * @hide - to match hiding in superclass
+     */
+    @Override
+    public List<PackageInfo> getInstalledThemePackages() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * @hide
      */
     @Override
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 2d1060b..c9071c7 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -36,7 +36,7 @@ public:
     Bundle(void)
         : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false),
           mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
-          mUpdate(false), mExtending(false),
+          mUpdate(false), mExtending(false), mExtendedPackageId(0),
           mRequireLocalization(false), mPseudolocalize(false),
           mWantUTF16(false), mValues(false),
           mCompressionMethod(0), mOutputAPKFile(NULL),
@@ -78,6 +78,8 @@ public:
     void setUpdate(bool val) { mUpdate = val; }
     bool getExtending(void) const { return mExtending; }
     void setExtending(bool val) { mExtending = val; }
+    int getExtendedPackageId(void) const { return mExtendedPackageId; }
+    void setExtendedPackageId(int val) { mExtendedPackageId = val; }
     bool getRequireLocalization(void) const { return mRequireLocalization; }
     void setRequireLocalization(bool val) { mRequireLocalization = val; }
     bool getPseudolocalize(void) const { return mPseudolocalize; }
@@ -226,6 +228,7 @@ private:
     bool        mMakePackageDirs;
     bool        mUpdate;
     bool        mExtending;
+    int         mExtendedPackageId;
     bool        mRequireLocalization;
     bool        mPseudolocalize;
     bool        mWantUTF16;
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 50c828d..2bccf77 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -14,6 +14,7 @@
#include <stdlib.h>
#include <getopt.h>
#include <assert.h>
+#include <ctype.h>

using namespace android;

@@ -55,7 +56,7 @@ void usage(void)
         "   xmltree          Print the compiled xmls in the given assets.\n"
         "   xmlstrings       Print the strings of the given compiled xml assets.\n\n", gProgName);
     fprintf(stderr,
-        " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n"
+        " %s p[ackage] [-d][-f][-m][-u][-v][-x[ extending-resource-id]][-z][-M AndroidManifest.xml] \\\n"
         "        [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n"
         "        [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n"
         "        [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n"
@@ -116,7 +117,7 @@ void usage(void)
#endif
         "   -u  update existing packages (add new, replace older, remove deleted files)\n"
         "   -v  verbose output\n"
-        "   -x  create extending (non-application) resource IDs\n"
+        "   -x  either create or assign (if specified) extending (non-application) resource IDs\n"
         "   -z  require localization of resource attributes marked with\n"
         "       localization=\"suggested\"\n"
         "   -A  additional directory in which to find raw asset files\n"
@@ -305,6 +306,14 @@ int main(int argc, char* const argv[])
                 break;
             case 'x':
                 bundle.setExtending(true);
+                argc--;
+                argv++;
+                if (!argc || !isdigit(argv[0][0])) {
+                    argc++;
+                    argv--;
+                } else {
+                    bundle.setExtendedPackageId(atoi(argv[0]));
+                }
                 break;
             case 'z':
                 bundle.setRequireLocalization(true);
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index fdb39ca..1dfeb8b 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3620,7 +3620,16 @@ sp<ResourceTable:ackage> ResourceTable::getPackage(const String16& package)
             mHaveAppPackage = true;
             p = new Package(package, 127);
         } else {
-            p = new Package(package, mNextPackageId);
+            int extendedPackageId = mBundle->getExtendedPackageId();
+            if (extendedPackageId != 0) {
+                if ((uint32_t)extendedPackageId < mNextPackageId) {
+                    fprintf(stderr, "Package ID %d already in use!\n", mNextPackageId);
+                    return NULL;
+                }
+                p = new Package(package, extendedPackageId);
+            } else {
+                p = new Package(package, mNextPackageId);
+            }
         }
         //printf("*** NEW PACKAGE: \"%s\" id=%d\n",
         //       String8(package).string(), p->getAssignedId());


除了上面的改动,还需要
ThemePicker

ThemeManager

com.tmobile.themes






回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

Archiver|手机版|aSys-帮 ( 京ICP备13033689号 )

GMT+8, 2024-10-5 21:14 , Processed in 0.150506 second(s), 24 queries .

Powered by Discuz! X2.5

© 2001-2012 Comsenz Inc.

回顶部