Merge "Doze: Fix NPE found when pulsing and !dozing." into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 90d63b0..2cd1708 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27278,7 +27278,7 @@
     method public boolean isSpeaking();
     method public int playEarcon(java.lang.String, int, android.os.Bundle, java.lang.String);
     method public deprecated int playEarcon(java.lang.String, int, java.util.HashMap<java.lang.String, java.lang.String>);
-    method public int playSilence(long, int, java.util.HashMap<java.lang.String, java.lang.String>, java.lang.String);
+    method public int playSilence(long, int, java.lang.String);
     method public deprecated int playSilence(long, int, java.util.HashMap<java.lang.String, java.lang.String>);
     method public int setAudioAttributes(android.media.AudioAttributes);
     method public deprecated int setEngineByPackageName(java.lang.String);
@@ -27366,7 +27366,7 @@
   public abstract class TextToSpeechService extends android.app.Service {
     ctor public TextToSpeechService();
     method public android.os.IBinder onBind(android.content.Intent);
-    method protected java.lang.String onGetDefaultVoiceNameFor(java.lang.String, java.lang.String, java.lang.String);
+    method public java.lang.String onGetDefaultVoiceNameFor(java.lang.String, java.lang.String, java.lang.String);
     method protected java.util.Set<java.lang.String> onGetFeaturesForLanguage(java.lang.String, java.lang.String, java.lang.String);
     method protected abstract java.lang.String[] onGetLanguage();
     method public java.util.List<android.speech.tts.Voice> onGetVoices();
@@ -27396,6 +27396,7 @@
     method public int getQuality();
     method public boolean isNetworkConnectionRequired();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int LATENCY_HIGH = 400; // 0x190
     field public static final int LATENCY_LOW = 200; // 0xc8
     field public static final int LATENCY_NORMAL = 300; // 0x12c
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 9be220e..120c9e3 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1216,17 +1216,12 @@
      *
      * @param durationInMs The duration of the silence.
      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
-     * @param params Parameters for the request. Can be null.
-     *            Engine specific parameters may be passed in but the parameter keys
-     *            must be prefixed by the name of the engine they are intended for. For example
-     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
-     *            engine named "com.svox.pico" if it is being used.
      * @param utteranceId An unique identifier for this request.
      *
      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
      */
     public int playSilence(final long durationInMs, final int queueMode,
-            final HashMap<String, String> params, final String utteranceId) {
+            final String utteranceId) {
         return runAction(new Action<Integer>() {
             @Override
             public Integer run(ITextToSpeechService service) throws RemoteException {
@@ -1258,12 +1253,12 @@
      *
      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
      * @deprecated As of API level 20, replaced by
-     *         {@link #playSilence(long, int, HashMap, String)}.
+     *         {@link #playSilence(long, int, String)}.
      */
     @Deprecated
     public int playSilence(final long durationInMs, final int queueMode,
             final HashMap<String, String> params) {
-        return playSilence(durationInMs, queueMode, params,
+        return playSilence(durationInMs, queueMode,
                            params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
     }
 
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index d00a433..079467a 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -317,7 +317,7 @@
 
      * @return A name of the default voice for a given locale.
      */
-    protected String onGetDefaultVoiceNameFor(String lang, String country, String variant) {
+    public String onGetDefaultVoiceNameFor(String lang, String country, String variant) {
         int localeStatus = onIsLanguageAvailable(lang, country, variant);
         Locale iso3Locale = null;
         switch (localeStatus) {
diff --git a/core/java/android/speech/tts/Voice.java b/core/java/android/speech/tts/Voice.java
index a1fa51d..dcf5980 100644
--- a/core/java/android/speech/tts/Voice.java
+++ b/core/java/android/speech/tts/Voice.java
@@ -91,9 +91,6 @@
         Collections.addAll(this.mFeatures, in.readStringArray());
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mName);
@@ -104,17 +101,11 @@
         dest.writeStringList(new ArrayList<String>(mFeatures));
     }
 
-    /**
-     * @hide
-     */
     @Override
     public int describeContents() {
         return 0;
     }
 
-    /**
-     * @hide
-     */
     public static final Parcelable.Creator<Voice> CREATOR = new Parcelable.Creator<Voice>() {
         @Override
         public Voice createFromParcel(Parcel in) {
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 8f8ce95..ac915d1 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.inputmethod;
 
+import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -637,6 +638,25 @@
     }
 
     /**
+     * Returns true if a package name belongs to a UID.
+     *
+     * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p>
+     * @param appOpsManager the {@link AppOpsManager} object to be used for the validation.
+     * @param uid the UID to be validated.
+     * @param packageName the package name.
+     * @return {@code true} if the package name belongs to the UID.
+     */
+    public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager,
+            final int uid, final String packageName) {
+        try {
+            appOpsManager.checkPackage(uid, packageName);
+            return true;
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
+    /**
      * Utility class for putting and getting settings for InputMethod
      * TODO: Move all putters and getters of settings to this class.
      */
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 40c9ed2..4dde217 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -458,7 +458,7 @@
                 Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
             }
             if (id != 0) {
-                if (mResources.getDrawable(id) == null) {
+                if (mResources.getDrawable(id, null) == null) {
                     throw new IllegalArgumentException(
                             "Unable to find preloaded drawable resource #0x"
                             + Integer.toHexString(id)
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index bfeac9d..d0fba20 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -76,7 +76,7 @@
             android:layout_alignParentEnd="true"
             android:background="@drawable/btn_borderless_rect"
             android:clickable="true"
-            android:contentDescription="@null"
+            android:contentDescription="@string/accessibility_desc_settings"
             android:scaleType="center"
             android:src="@drawable/ic_settings" />
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index cb9abfd..072fb29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -748,7 +748,9 @@
             transition(mQsDetailHeader, showingDetail);
             mShowingDetail = showingDetail;
             if (showingDetail) {
-                mQsDetailHeaderTitle.setText(detail.getTitle());
+                String title = mContext.getString(detail.getTitle());
+                mQsDetailHeaderTitle.setText(title);
+                announceForAccessibility(title);
                 final Boolean toggleState = detail.getToggleState();
                 if (toggleState == null) {
                     mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index eb0be05..e344954 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -55,6 +55,7 @@
     private final Object mProjectionLock = new Object();
 
     private boolean mDiscovering;
+    private boolean mCallbackRegistered;
     private MediaProjectionInfo mProjection;
 
     public CastControllerImpl(Context context) {
@@ -70,6 +71,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("CastController state:");
         pw.print("  mDiscovering="); pw.println(mDiscovering);
+        pw.print("  mCallbackRegistered="); pw.println(mCallbackRegistered);
         pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
         pw.print("  mRoutes.size="); pw.println(mRoutes.size());
         for (int i = 0; i < mRoutes.size(); i++) {
@@ -83,11 +85,17 @@
     public void addCallback(Callback callback) {
         mCallbacks.add(callback);
         fireOnCastDevicesChanged(callback);
+        synchronized (mDiscoveringLock) {
+            handleDiscoveryChangeLocked();
+        }
     }
 
     @Override
     public void removeCallback(Callback callback) {
         mCallbacks.remove(callback);
+        synchronized (mDiscoveringLock) {
+            handleDiscoveryChangeLocked();
+        }
     }
 
     @Override
@@ -96,12 +104,23 @@
             if (mDiscovering == request) return;
             mDiscovering = request;
             if (DEBUG) Log.d(TAG, "setDiscovering: " + request);
-            if (request) {
-                mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
-                        MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
-            } else {
-                mMediaRouter.removeCallback(mMediaCallback);
-            }
+            handleDiscoveryChangeLocked();
+        }
+    }
+
+    private void handleDiscoveryChangeLocked() {
+        if (mCallbackRegistered) {
+            mMediaRouter.removeCallback(mMediaCallback);
+            mCallbackRegistered = false;
+        }
+        if (mDiscovering) {
+            mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
+                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+            mCallbackRegistered = true;
+        } else if (mCallbacks.size() != 0) {
+            mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
+                    MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+            mCallbackRegistered = true;
         }
     }
 
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 060c8e3..1623eac 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -40,6 +40,7 @@
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
 import android.app.AlertDialog;
+import android.app.AppOpsManager;
 import android.app.IUserSwitchObserver;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -175,6 +176,7 @@
     private InputMethodFileManager mFileManager;
     private final HardKeyboardListener mHardKeyboardListener;
     private final WindowManagerService mWindowManagerService;
+    private final AppOpsManager mAppOpsManager;
 
     final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1);
 
@@ -643,6 +645,7 @@
             }
         }, true /*asyncHandler*/);
         mWindowManagerService = windowManager;
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mHardKeyboardListener = new HardKeyboardListener();
         mHasFeature = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_INPUT_METHODS);
@@ -1746,6 +1749,20 @@
             throw new IllegalArgumentException("Unknown id: " + id);
         }
 
+        if (mCurClient != null && mCurAttribute != null) {
+            final int uid = mCurClient.uid;
+            final String packageName = mCurAttribute.packageName;
+            if (SystemConfig.getInstance().getFixedImeApps().contains(packageName)) {
+                if (InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, uid, packageName)) {
+                    return;
+                }
+                // TODO: Do we need to lock the input method when the application reported an
+                // incorrect package name?
+                Slog.e(TAG, "Ignoring FixedImeApps due to the validation failure. uid=" + uid
+                        + " package=" + packageName);
+            }
+        }
+
         // See if we need to notify a subtype change within the same IME.
         if (id.equals(mCurMethodId)) {
             final int subtypeCount = info.getSubtypeCount();
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index 3fc9f81..d809c5b 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -104,6 +104,9 @@
 struct AaptGroupEntry
 {
 public:
+    AaptGroupEntry() {}
+    AaptGroupEntry(const ConfigDescription& config) : mParams(config) {}
+
     bool initFromDirName(const char* dir, String8* resType);
 
     inline const ConfigDescription& toParams() const { return mParams; }
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 137c85c..56d1650 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -1483,7 +1483,7 @@
     return NO_ERROR;
 }
 
-status_t postProcessImage(const sp<AaptAssets>& assets,
+status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
                           ResourceTable* table, const sp<AaptFile>& file)
 {
     String8 ext(file->getPath().getPathExtension());
@@ -1491,7 +1491,8 @@
     // At this point, now that we have all the resource data, all we need to
     // do is compile XML files.
     if (strcmp(ext.string(), ".xml") == 0) {
-        return compileXmlFile(assets, file, table);
+        String16 resourceName(parseResourceName(file->getPath().getPathLeaf()));
+        return compileXmlFile(bundle, assets, resourceName, file, table);
     }
 
     return NO_ERROR;
diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h
index 91b6554..a0a94f8 100644
--- a/tools/aapt/Images.h
+++ b/tools/aapt/Images.h
@@ -20,7 +20,7 @@
 
 status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest);
 
-status_t postProcessImage(const sp<AaptAssets>& assets,
+status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
                           ResourceTable* table, const sp<AaptFile>& file);
 
 #endif
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
index f24a023b..089dde5 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -62,4 +62,7 @@
 
 status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets,
                                 FILE* fp, bool includeRaw);
+
+android::String8 parseResourceName(const String8& pathLeaf);
+
 #endif // __MAIN_H
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index d605202..a4c9dab 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -50,7 +50,7 @@
 // ==========================================================================
 // ==========================================================================
 
-static String8 parseResourceName(const String8& leaf)
+String8 parseResourceName(const String8& leaf)
 {
     const char* firstDot = strchr(leaf.string(), '.');
     const char* str = leaf.string();
@@ -1088,7 +1088,7 @@
     manifest->addChild(app);
     root->addChild(manifest);
 
-    int err = compileXmlFile(assets, root, outFile, table);
+    int err = compileXmlFile(bundle, assets, String16(), root, outFile, table);
     if (err < NO_ERROR) {
         return err;
     }
@@ -1336,7 +1336,8 @@
         ResourceDirIterator it(layouts, String8("layout"));
         while ((err=it.next()) == NO_ERROR) {
             String8 src = it.getFile()->getPrintableSource();
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err == NO_ERROR) {
                 ResXMLTree block;
                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
@@ -1355,7 +1356,8 @@
     if (anims != NULL) {
         ResourceDirIterator it(anims, String8("anim"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1370,7 +1372,8 @@
     if (animators != NULL) {
         ResourceDirIterator it(animators, String8("animator"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1385,7 +1388,8 @@
     if (interpolators != NULL) {
         ResourceDirIterator it(interpolators, String8("interpolator"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1400,7 +1404,8 @@
     if (transitions != NULL) {
         ResourceDirIterator it(transitions, String8("transition"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1415,7 +1420,8 @@
     if (xmls != NULL) {
         ResourceDirIterator it(xmls, String8("xml"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1430,7 +1436,7 @@
     if (drawables != NULL) {
         ResourceDirIterator it(drawables, String8("drawable"));
         while ((err=it.next()) == NO_ERROR) {
-            err = postProcessImage(assets, &table, it.getFile());
+            err = postProcessImage(bundle, assets, &table, it.getFile());
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1445,7 +1451,8 @@
     if (colors != NULL) {
         ResourceDirIterator it(colors, String8("color"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1461,7 +1468,8 @@
         ResourceDirIterator it(menus, String8("menu"));
         while ((err=it.next()) == NO_ERROR) {
             String8 src = it.getFile()->getPrintableSource();
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err == NO_ERROR) {
                 ResXMLTree block;
                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
@@ -1477,6 +1485,22 @@
         err = NO_ERROR;
     }
 
+    // Now compile any generated resources.
+    std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
+    while (!workQueue.empty()) {
+        CompileResourceWorkItem& workItem = workQueue.front();
+        err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
+        if (err == NO_ERROR) {
+            assets->addResource(workItem.resPath.getPathLeaf(),
+                    workItem.resPath,
+                    workItem.file,
+                    workItem.file->getResourceType());
+        } else {
+            hasErrors = true;
+        }
+        workQueue.pop();
+    }
+
     if (table.validateLocalizations()) {
         hasErrors = true;
     }
@@ -1509,7 +1533,7 @@
     if (err < NO_ERROR) {
         return err;
     }
-    err = compileXmlFile(assets, manifestTree, manifestFile, &table);
+    err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table);
     if (err < NO_ERROR) {
         return err;
     }
@@ -1599,7 +1623,7 @@
     sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(),
             manifestFile->getGroupEntry(),
             manifestFile->getResourceType());
-    err = compileXmlFile(assets, manifestFile,
+    err = compileXmlFile(bundle, assets, String16(), manifestFile,
             outManifestFile, &table,
             XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
             | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 8c9efc9..b8c34543 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -17,7 +17,9 @@
 
 #define NOISY(x) //x
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
                         int options)
@@ -27,10 +29,12 @@
         return UNKNOWN_ERROR;
     }
 
-    return compileXmlFile(assets, root, target, table, options);
+    return compileXmlFile(bundle, assets, resourceName, root, target, table, options);
 }
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         const sp<AaptFile>& outTarget,
                         ResourceTable* table,
@@ -41,10 +45,12 @@
         return UNKNOWN_ERROR;
     }
     
-    return compileXmlFile(assets, root, outTarget, table, options);
+    return compileXmlFile(bundle, assets, resourceName, root, outTarget, table, options);
 }
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<XMLNode>& root,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
@@ -77,6 +83,10 @@
     if (hasErrors) {
         return UNKNOWN_ERROR;
     }
+
+    if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) {
+        return UNKNOWN_ERROR;
+    }
     
     NOISY(printf("Input XML Resource:\n"));
     NOISY(root->print());
@@ -4028,6 +4038,39 @@
     return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
 }
 
+sp<ResourceTable::ConfigList> ResourceTable::getConfigList(const String16& package,
+        const String16& type, const String16& name) const
+{
+    const size_t packageCount = mOrderedPackages.size();
+    for (size_t pi = 0; pi < packageCount; pi++) {
+        const sp<Package>& p = mOrderedPackages[pi];
+        if (p == NULL || p->getName() != package) {
+            continue;
+        }
+
+        const Vector<sp<Type> >& types = p->getOrderedTypes();
+        const size_t typeCount = types.size();
+        for (size_t ti = 0; ti < typeCount; ti++) {
+            const sp<Type>& t = types[ti];
+            if (t == NULL || t->getName() != type) {
+                continue;
+            }
+
+            const Vector<sp<ConfigList> >& configs = t->getOrderedConfigs();
+            const size_t configCount = configs.size();
+            for (size_t ci = 0; ci < configCount; ci++) {
+                const sp<ConfigList>& cl = configs[ci];
+                if (cl == NULL || cl->getName() != name) {
+                    continue;
+                }
+
+                return cl;
+            }
+        }
+    }
+    return NULL;
+}
+
 sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
                                                        const ResTable_config* config) const
 {
@@ -4157,6 +4200,22 @@
         (attrId & 0x0000ffff) >= (baseAttrId & 0x0000ffff);
 }
 
+static bool isMinSdkVersionLOrAbove(const Bundle* bundle) {
+    if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) {
+        const char firstChar = bundle->getMinSdkVersion()[0];
+        if (firstChar >= 'L' && firstChar <= 'Z') {
+            // L is the code-name for the v21 release.
+            return true;
+        }
+
+        const int minSdk = atoi(bundle->getMinSdkVersion());
+        if (minSdk >= SDK_L) {
+            return true;
+        }
+    }
+    return false;
+}
+
 /**
  * Modifies the entries in the resource table to account for compatibility
  * issues with older versions of Android.
@@ -4200,19 +4259,10 @@
  * attribute will be respected.
  */
 status_t ResourceTable::modifyForCompat(const Bundle* bundle) {
-    if (bundle->getMinSdkVersion() != NULL) {
+    if (isMinSdkVersionLOrAbove(bundle)) {
         // If this app will only ever run on L+ devices,
         // we don't need to do any compatibility work.
-
-        if (String8("L") == bundle->getMinSdkVersion()) {
-            // Code-name for the v21 release.
-            return NO_ERROR;
-        }
-
-        const int minSdk = atoi(bundle->getMinSdkVersion());
-        if (minSdk >= SDK_L) {
-            return NO_ERROR;
-        }
+        return NO_ERROR;
     }
 
     const String16 attr16("attr");
@@ -4309,3 +4359,104 @@
     }
     return NO_ERROR;
 }
+
+status_t ResourceTable::modifyForCompat(const Bundle* bundle,
+                                        const String16& resourceName,
+                                        const sp<AaptFile>& target,
+                                        const sp<XMLNode>& root) {
+    if (isMinSdkVersionLOrAbove(bundle)) {
+        return NO_ERROR;
+    }
+
+    if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_L) {
+        // Skip resources that have no type (AndroidManifest.xml) or are already version qualified with v21
+        // or higher.
+        return NO_ERROR;
+    }
+
+    Vector<key_value_pair_t<sp<XMLNode>, size_t> > attrsToRemove;
+
+    Vector<sp<XMLNode> > nodesToVisit;
+    nodesToVisit.push(root);
+    while (!nodesToVisit.isEmpty()) {
+        sp<XMLNode> node = nodesToVisit.top();
+        nodesToVisit.pop();
+
+        const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
+        const size_t attrCount = attrs.size();
+        for (size_t i = 0; i < attrCount; i++) {
+            const XMLNode::attribute_entry& attr = attrs[i];
+            if (isAttributeFromL(attr.nameResId)) {
+                attrsToRemove.add(key_value_pair_t<sp<XMLNode>, size_t>(node, i));
+            }
+        }
+
+        // Schedule a visit to the children.
+        const Vector<sp<XMLNode> >& children = node->getChildren();
+        const size_t childCount = children.size();
+        for (size_t i = 0; i < childCount; i++) {
+            nodesToVisit.push(children[i]);
+        }
+    }
+
+    if (attrsToRemove.isEmpty()) {
+        return NO_ERROR;
+    }
+
+    ConfigDescription newConfig(target->getGroupEntry().toParams());
+    newConfig.sdkVersion = SDK_L;
+
+    // Look to see if we already have an overriding v21 configuration.
+    sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
+            String16(target->getResourceType()), resourceName);
+    if (cl->getEntries().indexOfKey(newConfig) < 0) {
+        // We don't have an overriding entry for v21, so we must duplicate this one.
+        sp<XMLNode> newRoot = root->clone();
+        sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
+                AaptGroupEntry(newConfig), target->getResourceType());
+        String8 resPath = String8::format("res/%s/%s",
+                newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
+                target->getPath().getPathLeaf().string());
+        resPath.convertToResPath();
+
+        // Add a resource table entry.
+        SourcePos(target->getSourceFile(), -1).printf(
+                "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
+                SDK_L,
+                mAssets->getPackage().string(),
+                newFile->getResourceType().string(),
+                String8(resourceName).string(),
+                newConfig.toString().string());
+
+        addEntry(SourcePos(),
+                String16(mAssets->getPackage()),
+                String16(target->getResourceType()),
+                resourceName,
+                String16(resPath),
+                NULL,
+                &newConfig);
+
+        // Schedule this to be compiled.
+        CompileResourceWorkItem item;
+        item.resourceName = resourceName;
+        item.resPath = resPath;
+        item.file = newFile;
+        mWorkQueue.push(item);
+    }
+
+    const size_t removeCount = attrsToRemove.size();
+    for (size_t i = 0; i < removeCount; i++) {
+        sp<XMLNode> node = attrsToRemove[i].key;
+        size_t attrIndex = attrsToRemove[i].value;
+        const XMLNode::attribute_entry& ae = node->getAttributes()[attrIndex];
+        SourcePos(node->getFilename(), node->getStartLineNumber()).printf(
+                "removing attribute %s%s%s from <%s>",
+                String8(ae.ns).string(),
+                (ae.ns.size() == 0 ? "" : ":"),
+                String8(ae.name).string(),
+                String8(node->getElementName()).string());
+        node->removeAttribute(attrIndex);
+    }
+
+    return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 025a868..c548a85 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -12,8 +12,9 @@
 #include "SourcePos.h"
 #include "ResourceFilter.h"
 
-#include <set>
 #include <map>
+#include <queue>
+#include <set>
 
 using namespace std;
 
@@ -33,18 +34,24 @@
             | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
 };
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
                         int options = XML_COMPILE_STANDARD_RESOURCE);
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         const sp<AaptFile>& outTarget,
                         ResourceTable* table,
                         int options = XML_COMPILE_STANDARD_RESOURCE);
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<XMLNode>& xmlTree,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
@@ -71,6 +78,14 @@
     }
 };
 
+// Holds the necessary information to compile the
+// resource.
+struct CompileResourceWorkItem {
+    String16 resourceName;
+    String8 resPath;
+    sp<AaptFile> file;
+};
+
 class ResourceTable : public ResTable::Accessor
 {
 public:
@@ -92,6 +107,18 @@
         return mAssetsPackage;
     }
 
+    /**
+     * Returns the queue of resources that need to be compiled.
+     * This is only used for resources that have been generated
+     * during the compilation phase. If they were just added
+     * to the AaptAssets, then they may be skipped over
+     * and would mess up iteration order for the existing
+     * resources.
+     */
+    queue<CompileResourceWorkItem>& getWorkQueue() {
+        return mWorkQueue;
+    }
+
     status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets);
 
     status_t addPublic(const SourcePos& pos,
@@ -166,6 +193,10 @@
     bool hasResources() const;
 
     status_t modifyForCompat(const Bundle* bundle);
+    status_t modifyForCompat(const Bundle* bundle,
+                             const String16& resourceName,
+                             const sp<AaptFile>& file,
+                             const sp<XMLNode>& root);
 
     sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
             const bool isBase);
@@ -527,6 +558,9 @@
                        bool doSetIndex = false);
     sp<const Entry> getEntry(uint32_t resID,
                              const ResTable_config* config = NULL) const;
+    sp<ConfigList> getConfigList(const String16& package,
+                                 const String16& type,
+                                 const String16& name) const;
     const Item* getItem(uint32_t resID, uint32_t attrID) const;
     bool getItemValue(uint32_t resID, uint32_t attrID,
                       Res_value* outValue);
@@ -545,6 +579,7 @@
     
     // key = string resource name, value = set of locales in which that name is defined
     map<String16, map<String8, SourcePos> > mLocalizations;
+    queue<CompileResourceWorkItem> mWorkQueue;
 };
 
 #endif
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 607d419..51a4154 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -621,6 +621,12 @@
     return state.root;
 }
 
+XMLNode::XMLNode()
+    : mNextAttributeIndex(0x80000000)
+    , mStartLineNumber(0)
+    , mEndLineNumber(0)
+    , mUTF8(false) {}
+
 XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace)
     : mNextAttributeIndex(0x80000000)
     , mFilename(filename)
@@ -810,6 +816,32 @@
     return NO_ERROR;
 }
 
+status_t XMLNode::removeAttribute(size_t index)
+{
+    if (getType() == TYPE_CDATA) {
+        return UNKNOWN_ERROR;
+    }
+
+    if (index >= mAttributes.size()) {
+        return UNKNOWN_ERROR;
+    }
+
+    const attribute_entry& e = mAttributes[index];
+    const uint32_t key = e.nameResId ? e.nameResId : e.index;
+    mAttributeOrder.removeItem(key);
+    mAttributes.removeAt(index);
+
+    // Shift all the indices.
+    const size_t attrCount = mAttributeOrder.size();
+    for (size_t i = 0; i < attrCount; i++) {
+        size_t attrIdx = mAttributeOrder[i];
+        if (attrIdx > index) {
+            mAttributeOrder.replaceValueAt(i, attrIdx - 1);
+        }
+    }
+    return NO_ERROR;
+}
+
 void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId)
 {
     attribute_entry& e = mAttributes.editItemAt(attrIdx);
@@ -999,6 +1031,30 @@
     return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
 }
 
+sp<XMLNode> XMLNode::clone() const {
+    sp<XMLNode> copy = new XMLNode();
+    copy->mNamespacePrefix = mNamespacePrefix;
+    copy->mNamespaceUri = mNamespaceUri;
+    copy->mElementName = mElementName;
+
+    const size_t childCount = mChildren.size();
+    for (size_t i = 0; i < childCount; i++) {
+        copy->mChildren.add(mChildren[i]->clone());
+    }
+
+    copy->mAttributes = mAttributes;
+    copy->mAttributeOrder = mAttributeOrder;
+    copy->mNextAttributeIndex = mNextAttributeIndex;
+    copy->mChars = mChars;
+    memcpy(&copy->mCharsValue, &mCharsValue, sizeof(mCharsValue));
+    copy->mComment = mComment;
+    copy->mFilename = mFilename;
+    copy->mStartLineNumber = mStartLineNumber;
+    copy->mEndLineNumber = mEndLineNumber;
+    copy->mUTF8 = mUTF8;
+    return copy;
+}
+
 status_t XMLNode::flatten(const sp<AaptFile>& dest,
         bool stripComments, bool stripRawValues) const
 {
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index ccbf9f4..3161f65 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -116,6 +116,8 @@
     status_t addAttribute(const String16& ns, const String16& name,
                           const String16& value);
 
+    status_t removeAttribute(size_t index);
+
     void setAttributeResID(size_t attrIdx, uint32_t resId);
 
     status_t appendChars(const String16& chars);
@@ -137,6 +139,8 @@
     status_t flatten(const sp<AaptFile>& dest, bool stripComments,
             bool stripRawValues) const;
 
+    sp<XMLNode> clone() const;
+
     void print(int indent=0);
 
 private:
@@ -163,6 +167,9 @@
     static void XMLCALL
     commentData(void *userData, const char *comment);
     
+    // For cloning
+    XMLNode();
+
     // Creating an element node.
     XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace);