Merge "Fix clipping and outline for top quick settings header."
diff --git a/api/current.txt b/api/current.txt
index 380d641..40e3ad2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1047,6 +1047,7 @@
     field public static final int spinnerStyle = 16842881; // 0x1010081
     field public static final int spinnersShown = 16843595; // 0x101034b
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
+    field public static final int splitTrack = 16843858; // 0x1010452
     field public static final int src = 16843033; // 0x1010119
     field public static final int ssp = 16843747; // 0x10103e3
     field public static final int sspPattern = 16843749; // 0x10103e5
@@ -13340,6 +13341,45 @@
     method public void stop();
   }
 
+  public final class AudioAttributes {
+    method public int getContentType();
+    method public int getFlags();
+    method public java.util.Set<java.lang.String> getTags();
+    method public int getUsage();
+    field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+    field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+    field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+    field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+    field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+    field public static final int USAGE_ALARM = 4; // 0x4
+    field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+    field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+    field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+    field public static final int USAGE_GAME = 14; // 0xe
+    field public static final int USAGE_MEDIA = 1; // 0x1
+    field public static final int USAGE_NOTIFICATION = 5; // 0x5
+    field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+    field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+    field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+    field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+    field public static final int USAGE_NOTIFICATION_TELEPHONY_RINGTONE = 6; // 0x6
+    field public static final int USAGE_UNKNOWN = 0; // 0x0
+    field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+    field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+  }
+
+  public static class AudioAttributes.Builder {
+    ctor public AudioAttributes.Builder();
+    ctor public AudioAttributes.Builder(android.media.AudioAttributes);
+    method public android.media.AudioAttributes.Builder addTag(java.lang.String);
+    method public android.media.AudioAttributes build();
+    method public android.media.AudioAttributes.Builder setContentType(int);
+    method public android.media.AudioAttributes.Builder setFlags(int);
+    method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
+    method public android.media.AudioAttributes.Builder setUsage(int);
+  }
+
   public class AudioFormat {
     ctor public AudioFormat();
     field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1
@@ -34347,9 +34387,11 @@
     ctor public AbsSeekBar(android.content.Context, android.util.AttributeSet, int);
     ctor public AbsSeekBar(android.content.Context, android.util.AttributeSet, int, int);
     method public int getKeyProgressIncrement();
+    method public boolean getSplitTrack();
     method public android.graphics.drawable.Drawable getThumb();
     method public int getThumbOffset();
     method public void setKeyProgressIncrement(int);
+    method public void setSplitTrack(boolean);
     method public void setThumb(android.graphics.drawable.Drawable);
     method public void setThumbOffset(int);
   }
@@ -36154,6 +36196,7 @@
     ctor public Switch(android.content.Context, android.util.AttributeSet);
     ctor public Switch(android.content.Context, android.util.AttributeSet, int);
     ctor public Switch(android.content.Context, android.util.AttributeSet, int, int);
+    method public boolean getSplitTrack();
     method public int getSwitchMinWidth();
     method public int getSwitchPadding();
     method public java.lang.CharSequence getTextOff();
@@ -36162,6 +36205,7 @@
     method public int getThumbTextPadding();
     method public android.graphics.drawable.Drawable getTrackDrawable();
     method public void onMeasure(int, int);
+    method public void setSplitTrack(boolean);
     method public void setSwitchMinWidth(int);
     method public void setSwitchPadding(int);
     method public void setSwitchTextAppearance(android.content.Context, int);
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 804f8ee..862d59e6 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -45,6 +45,7 @@
     protected NetworkInfo mNetworkInfo;
     protected LinkProperties mLinkProperties;
     protected LinkCapabilities mLinkCapabilities;
+    protected Network mNetwork = new Network(ConnectivityManager.INVALID_NET_ID);
 
     private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
     private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
@@ -201,4 +202,14 @@
     public void stopSampling(SamplingDataTracker.SamplingSnapshot s) {
         // nothing to do
     }
+
+    @Override
+    public void setNetId(int netId) {
+        mNetwork = new Network(netId);
+    }
+
+    @Override
+    public Network getNetwork() {
+        return mNetwork;
+    }
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 30d7043..3e00250 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -408,6 +408,11 @@
      */
     public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000;
 
+    /**
+     * @hide
+     */
+    public final static int INVALID_NET_ID = 0;
+
     private final IConnectivityManager mService;
 
     private final String mPackageName;
diff --git a/core/java/android/net/Network.aidl b/core/java/android/net/Network.aidl
new file mode 100644
index 0000000..73ba1af
--- /dev/null
+++ b/core/java/android/net/Network.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright (C) 2014 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.
+*/
+
+package android.net;
+
+parcelable Network;
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
new file mode 100644
index 0000000..f82bc22
--- /dev/null
+++ b/core/java/android/net/Network.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+
+/**
+ * Identifies the Network.
+ * @hide
+ */
+public class Network implements Parcelable {
+
+    public final int netId;
+
+    public Network(int netId) {
+        this.netId = netId;
+    }
+
+    public Network(Network that) {
+        this.netId = that.netId;
+    }
+
+    // implement the Parcelable interface
+    public int describeContents() {
+        return 0;
+    }
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(netId);
+    }
+
+    public static final Creator<Network> CREATOR =
+        new Creator<Network>() {
+            public Network createFromParcel(Parcel in) {
+                int netId = in.readInt();
+
+                return new Network(netId);
+            }
+
+            public Network[] newArray(int size) {
+                return new Network[size];
+            }
+    };
+}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index c49b1d1..29b57a5 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -250,4 +250,14 @@
      */
     public void stopSampling(SamplingDataTracker.SamplingSnapshot s);
 
+    /*
+     * Record the current netId
+     */
+    public void setNetId(int netId);
+
+    /*
+     * ?
+     */
+    public Network getNetwork();
+
 }
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index f5ff185..9e03f95 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -455,4 +455,14 @@
      * Check whether the mobile radio is currently active.
      */
     boolean isNetworkActive();
+
+    /**
+     * setup a new network
+     */
+    void createNetwork(int netId, String iface);
+
+    /**
+     * remove a network
+     */
+    void removeNetwork(int netId);
 }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 565ea13..d9a4f57 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -743,7 +743,7 @@
          *
          * @param view The view whose scroll state is being reported
          *
-         * @param scrollState The current scroll state. One of 
+         * @param scrollState The current scroll state. One of
          * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
          */
         public void onScrollStateChanged(AbsListView view, int scrollState);
@@ -3305,18 +3305,22 @@
 
     private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
         int rawDeltaY = y - mMotionY;
+        int scrollOffsetCorrection = 0;
+        int scrollConsumedCorrection = 0;
+        if (mLastY == Integer.MIN_VALUE) {
+            rawDeltaY -= mMotionCorrection;
+        }
         if (dispatchNestedPreScroll(0, rawDeltaY, mScrollConsumed, mScrollOffset)) {
             rawDeltaY -= mScrollConsumed[1];
-            mMotionCorrection -= mScrollOffset[1];
-            if (mLastY != Integer.MIN_VALUE) {
-                mLastY -= mScrollOffset[1] + mScrollConsumed[1];
-            }
+            scrollOffsetCorrection -= mScrollOffset[1];
+            scrollConsumedCorrection -= mScrollConsumed[1];
             if (vtev != null) {
                 vtev.offsetLocation(0, mScrollOffset[1]);
             }
         }
-        final int deltaY = rawDeltaY - mMotionCorrection;
-        int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+        final int deltaY = rawDeltaY;
+        int incrementalDeltaY =
+                mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
         int lastYCorrection = 0;
 
         if (mTouchMode == TOUCH_MODE_SCROLL) {
@@ -3378,7 +3382,6 @@
                                 (motionViewRealTop - motionViewPrevTop);
                         if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
                                 mScrollOffset)) {
-                            mMotionCorrection -= mScrollOffset[1];
                             lastYCorrection -= mScrollOffset[1];
                             if (vtev != null) {
                                 vtev.offsetLocation(0, mScrollOffset[1]);
@@ -3421,9 +3424,9 @@
                             }
                         }
                     }
-                    mMotionY = y;
+                    mMotionY = y + scrollOffsetCorrection;
                 }
-                mLastY = y + lastYCorrection;
+                mLastY = y + lastYCorrection + scrollOffsetCorrection;
             }
         } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
             if (y != mLastY) {
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 225cd6d..4f2d9c6 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Insets;
 import android.graphics.Rect;
+import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.AttributeSet;
@@ -32,9 +34,12 @@
 import com.android.internal.R;
 
 public abstract class AbsSeekBar extends ProgressBar {
+    private final Rect mTempRect = new Rect();
+
     private Drawable mThumb;
     private int mThumbOffset;
-    
+    private boolean mSplitTrack;
+
     /**
      * On touch, this offset plus the scaled value from the position of the
      * touch will form the progress value. Usually 0.
@@ -51,10 +56,10 @@
      * progress.
      */
     private int mKeyProgressIncrement = 1;
-    
+
     private static final int NO_ALPHA = 0xFF;
     private float mDisabledAlpha;
-    
+
     private int mScaledTouchSlop;
     private float mTouchDownX;
     private boolean mIsDragging;
@@ -76,12 +81,16 @@
 
         TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.SeekBar, defStyleAttr, defStyleRes);
-        Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
-        setThumb(thumb); // will guess mThumbOffset if thumb != null...
-        // ...but allow layout to override this
-        int thumbOffset = a.getDimensionPixelOffset(
+
+        final Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
+        setThumb(thumb);
+
+        // Guess thumb offset if thumb != null, but allow layout to override.
+        final int thumbOffset = a.getDimensionPixelOffset(
                 com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
         setThumbOffset(thumbOffset);
+
+        mSplitTrack = a.getBoolean(com.android.internal.R.styleable.SeekBar_splitTrack, false);
         a.recycle();
 
         a = context.obtainStyledAttributes(attrs,
@@ -97,7 +106,7 @@
      * <p>
      * If the thumb is a valid drawable (i.e. not null), half its width will be
      * used as the new thumb offset (@see #setThumbOffset(int)).
-     * 
+     *
      * @param thumb Drawable representing the thumb
      */
     public void setThumb(Drawable thumb) {
@@ -132,7 +141,7 @@
         mThumb = thumb;
         invalidate();
         if (needUpdate) {
-            updateThumbPos(getWidth(), getHeight());
+            updateThumbAndTrackPos(getWidth(), getHeight());
             if (thumb != null && thumb.isStateful()) {
                 // Note that if the states are different this won't work.
                 // For now, let's consider that an app bug.
@@ -162,7 +171,7 @@
     /**
      * Sets the thumb offset that allows the thumb to extend out of the range of
      * the track.
-     * 
+     *
      * @param thumbOffset The offset amount in pixels.
      */
     public void setThumbOffset(int thumbOffset) {
@@ -171,8 +180,27 @@
     }
 
     /**
+     * Specifies whether the track should be split by the thumb. When true,
+     * the thumb's optical bounds will be clipped out of the track drawable,
+     * then the thumb will be drawn into the resulting gap.
+     *
+     * @param splitTrack Whether the track should be split by the thumb
+     */
+    public void setSplitTrack(boolean splitTrack) {
+        mSplitTrack = splitTrack;
+        invalidate();
+    }
+
+    /**
+     * Returns whether the track should be split by the thumb.
+     */
+    public boolean getSplitTrack() {
+        return mSplitTrack;
+    }
+
+    /**
      * Sets the amount of progress changed via the arrow keys.
-     * 
+     *
      * @param increment The amount to increment or decrement when the user
      *            presses the arrow keys.
      */
@@ -184,14 +212,14 @@
      * Returns the amount of progress changed via the arrow keys.
      * <p>
      * By default, this will be a value that is derived from the max progress.
-     * 
+     *
      * @return The amount to increment or decrement when the user presses the
      *         arrow keys. This will be positive.
      */
     public int getKeyProgressIncrement() {
         return mKeyProgressIncrement;
     }
-    
+
     @Override
     public synchronized void setMax(int max) {
         super.setMax(max);
@@ -217,79 +245,85 @@
     @Override
     protected void drawableStateChanged() {
         super.drawableStateChanged();
-        
-        Drawable progressDrawable = getProgressDrawable();
+
+        final Drawable progressDrawable = getProgressDrawable();
         if (progressDrawable != null) {
             progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
         }
-        
-        if (mThumb != null && mThumb.isStateful()) {
-            int[] state = getDrawableState();
-            mThumb.setState(state);
+
+        final Drawable thumb = mThumb;
+        if (thumb != null && thumb.isStateful()) {
+            thumb.setState(getDrawableState());
         }
     }
-    
+
     @Override
     void onProgressRefresh(float scale, boolean fromUser) {
         super.onProgressRefresh(scale, fromUser);
-        Drawable thumb = mThumb;
+
+        final Drawable thumb = mThumb;
         if (thumb != null) {
             setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
-            /*
-             * Since we draw translated, the drawable's bounds that it signals
-             * for invalidation won't be the actual bounds we want invalidated,
-             * so just invalidate this whole view.
-             */
+
+            // Since we draw translated, the drawable's bounds that it signals
+            // for invalidation won't be the actual bounds we want invalidated,
+            // so just invalidate this whole view.
             invalidate();
         }
     }
-    
-    
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
-        updateThumbPos(w, h);
+
+        updateThumbAndTrackPos(w, h);
     }
 
-    private void updateThumbPos(int w, int h) {
-        Drawable d = getCurrentDrawable();
-        Drawable thumb = mThumb;
-        int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
+    private void updateThumbAndTrackPos(int w, int h) {
+        final Drawable track = getCurrentDrawable();
+        final Drawable thumb = mThumb;
+
         // The max height does not incorporate padding, whereas the height
-        // parameter does
-        int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
-        
-        int max = getMax();
-        float scale = max > 0 ? (float) getProgress() / (float) max : 0;
-        
+        // parameter does.
+        final int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
+        final int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
+
+        // Apply offset to whichever item is taller.
+        final int trackOffset;
+        final int thumbOffset;
         if (thumbHeight > trackHeight) {
-            if (thumb != null) {
-                setThumbPos(w, thumb, scale, 0);
-            }
-            int gapForCenteringTrack = (thumbHeight - trackHeight) / 2;
-            if (d != null) {
-                // Canvas will be translated by the padding, so 0,0 is where we start drawing
-                d.setBounds(0, gapForCenteringTrack, 
-                        w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack
-                        - mPaddingTop);
-            }
+            trackOffset = (thumbHeight - trackHeight) / 2;
+            thumbOffset = 0;
         } else {
-            if (d != null) {
-                // Canvas will be translated by the padding, so 0,0 is where we start drawing
-                d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom
-                        - mPaddingTop);
-            }
-            int gap = (trackHeight - thumbHeight) / 2;
-            if (thumb != null) {
-                setThumbPos(w, thumb, scale, gap);
-            }
+            trackOffset = 0;
+            thumbOffset = (trackHeight - thumbHeight) / 2;
+        }
+
+        if (track != null) {
+            track.setBounds(0, trackOffset, w - mPaddingRight - mPaddingLeft,
+                    h - mPaddingBottom - trackOffset - mPaddingTop);
+        }
+
+        if (thumb != null) {
+            setThumbPos(w, thumb, getScale(), thumbOffset);
         }
     }
 
+    private float getScale() {
+        final int max = getMax();
+        return max > 0 ? getProgress() / (float) max : 0;
+    }
+
     /**
-     * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
+     * Updates the thumb drawable bounds.
+     *
+     * @param w Width of the view, including padding
+     * @param thumb Drawable used for the thumb
+     * @param scale Current progress between 0 and 1
+     * @param offset Vertical offset for centering. If set to
+     *            {@link Integer#MIN_VALUE}, the current offset will be used.
      */
-    private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
+    private void setThumbPos(int w, Drawable thumb, float scale, int offset) {
         int available = w - mPaddingLeft - mPaddingRight;
         final int thumbWidth = thumb.getIntrinsicWidth();
         final int thumbHeight = thumb.getIntrinsicHeight();
@@ -301,13 +335,13 @@
         final int thumbPos = (int) (scale * available + 0.5f);
 
         final int top, bottom;
-        if (gap == Integer.MIN_VALUE) {
+        if (offset == Integer.MIN_VALUE) {
             final Rect oldBounds = thumb.getBounds();
             top = oldBounds.top;
             bottom = oldBounds.bottom;
         } else {
-            top = gap;
-            bottom = gap + thumbHeight;
+            top = offset;
+            bottom = offset + thumbHeight;
         }
 
         final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
@@ -342,6 +376,33 @@
     protected synchronized void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
+        drawThumb(canvas);
+    }
+
+    @Override
+    void drawTrack(Canvas canvas) {
+        final Drawable thumbDrawable = mThumb;
+        if (thumbDrawable != null && mSplitTrack) {
+            final Insets insets = thumbDrawable.getOpticalInsets();
+            final Rect tempRect = mTempRect;
+            thumbDrawable.copyBounds(tempRect);
+            tempRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop);
+            tempRect.left += insets.left;
+            tempRect.right -= insets.right;
+
+            final int saveCount = canvas.save();
+            canvas.clipRect(tempRect, Op.DIFFERENCE);
+            super.drawTrack(canvas);
+            canvas.restoreToCount(saveCount);
+        } else {
+            super.drawTrack(canvas);
+        }
+    }
+
+    /**
+     * Draw the thumb.
+     */
+    void drawThumb(Canvas canvas) {
         if (mThumb != null) {
             canvas.save();
             // Translate the padding. For the x, we need to allow the thumb to
@@ -366,17 +427,17 @@
         }
         dw += mPaddingLeft + mPaddingRight;
         dh += mPaddingTop + mPaddingBottom;
-        
+
         setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
                 resolveSizeAndState(dh, heightMeasureSpec, 0));
     }
-    
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (!mIsUserSeekable || !isEnabled()) {
             return false;
         }
-        
+
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 if (isInScrollingContainer()) {
@@ -391,7 +452,7 @@
                     attemptClaimDrag();
                 }
                 break;
-                
+
             case MotionEvent.ACTION_MOVE:
                 if (mIsDragging) {
                     trackTouchEvent(event);
@@ -408,7 +469,7 @@
                     }
                 }
                 break;
-                
+
             case MotionEvent.ACTION_UP:
                 if (mIsDragging) {
                     trackTouchEvent(event);
@@ -426,7 +487,7 @@
                 // value has not apparently changed)
                 invalidate();
                 break;
-                
+
             case MotionEvent.ACTION_CANCEL:
                 if (mIsDragging) {
                     onStopTrackingTouch();
@@ -493,7 +554,7 @@
             mParent.requestDisallowInterceptTouchEvent(true);
         }
     }
-    
+
     /**
      * This is called when the user has started touching this widget.
      */
@@ -526,7 +587,7 @@
                     setProgress(progress - mKeyProgressIncrement, true);
                     onKeyChange();
                     return true;
-            
+
                 case KeyEvent.KEYCODE_DPAD_RIGHT:
                     if (progress >= getMax()) break;
                     setProgress(progress + mKeyProgressIncrement, true);
@@ -595,17 +656,13 @@
     public void onRtlPropertiesChanged(int layoutDirection) {
         super.onRtlPropertiesChanged(layoutDirection);
 
-        int max = getMax();
-        float scale = max > 0 ? (float) getProgress() / (float) max : 0;
-
-        Drawable thumb = mThumb;
+        final Drawable thumb = mThumb;
         if (thumb != null) {
-            setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
-            /*
-             * Since we draw translated, the drawable's bounds that it signals
-             * for invalidation won't be the actual bounds we want invalidated,
-             * so just invalidate this whole view.
-             */
+            setThumbPos(getWidth(), thumb, getScale(), Integer.MIN_VALUE);
+
+            // Since we draw translated, the drawable's bounds that it signals
+            // for invalidation won't be the actual bounds we want invalidated,
+            // so just invalidate this whole view.
             invalidate();
         }
     }
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index f7e81b8..0c3715d 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -1066,21 +1066,30 @@
     protected synchronized void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
-        Drawable d = mCurrentDrawable;
+        drawTrack(canvas);
+    }
+
+    /**
+     * Draws the progress bar track.
+     */
+    void drawTrack(Canvas canvas) {
+        final Drawable d = mCurrentDrawable;
         if (d != null) {
             // Translate canvas so a indeterminate circular progress bar with padding
             // rotates properly in its animation
-            canvas.save();
+            final int saveCount = canvas.save();
+
             if(isLayoutRtl() && mMirrorForRtl) {
                 canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
                 canvas.scale(-1.0f, 1.0f);
             } else {
                 canvas.translate(mPaddingLeft, mPaddingTop);
             }
-            long time = getDrawingTime();
+
+            final long time = getDrawingTime();
             if (mHasAnimation) {
                 mAnimation.getTransformation(time, mTransformation);
-                float scale = mTransformation.getAlpha();
+                final float scale = mTransformation.getAlpha();
                 try {
                     mInDrawing = true;
                     d.setLevel((int) (scale * MAX_LEVEL));
@@ -1089,8 +1098,10 @@
                 }
                 postInvalidateOnAnimation();
             }
+
             d.draw(canvas);
-            canvas.restore();
+            canvas.restoreToCount(saveCount);
+
             if (mShouldStartAnimationDrawable && d instanceof Animatable) {
                 ((Animatable) d).start();
                 mShouldStartAnimationDrawable = false;
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 08af4de..438e164 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -22,9 +22,11 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.text.Layout;
 import android.text.StaticLayout;
@@ -85,6 +87,7 @@
     private int mThumbTextPadding;
     private int mSwitchMinWidth;
     private int mSwitchPadding;
+    private boolean mSplitTrack;
     private CharSequence mTextOn;
     private CharSequence mTextOff;
 
@@ -174,13 +177,13 @@
         super(context, attrs, defStyleAttr, defStyleRes);
 
         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
-        Resources res = getResources();
+
+        final Resources res = getResources();
         mTextPaint.density = res.getDisplayMetrics().density;
         mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
 
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes);
-
         mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
         mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
         mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
@@ -191,15 +194,16 @@
                 com.android.internal.R.styleable.Switch_switchMinWidth, 0);
         mSwitchPadding = a.getDimensionPixelSize(
                 com.android.internal.R.styleable.Switch_switchPadding, 0);
+        mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
 
-        int appearance = a.getResourceId(
+        final int appearance = a.getResourceId(
                 com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
         if (appearance != 0) {
             setSwitchTextAppearance(context, appearance);
         }
         a.recycle();
 
-        ViewConfiguration config = ViewConfiguration.get(context);
+        final ViewConfiguration config = ViewConfiguration.get(context);
         mTouchSlop = config.getScaledTouchSlop();
         mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
 
@@ -469,6 +473,29 @@
     }
 
     /**
+     * Specifies whether the track should be split by the thumb. When true,
+     * the thumb's optical bounds will be clipped out of the track drawable,
+     * then the thumb will be drawn into the resulting gap.
+     *
+     * @param splitTrack Whether the track should be split by the thumb
+     *
+     * @attr ref android.R.styleable#Switch_splitTrack
+     */
+    public void setSplitTrack(boolean splitTrack) {
+        mSplitTrack = splitTrack;
+        invalidate();
+    }
+
+    /**
+     * Returns whether the track should be split by the thumb.
+     *
+     * @attr ref android.R.styleable#Switch_splitTrack
+     */
+    public boolean getSplitTrack() {
+        return mSplitTrack;
+    }
+
+    /**
      * Returns the text displayed when the button is in the checked state.
      *
      * @attr ref android.R.styleable#Switch_textOn
@@ -518,13 +545,15 @@
 
         mTrackDrawable.getPadding(mTempRect);
 
-        final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
+        final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+                + mThumbTextPadding * 2;
+        mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
+
         final int switchWidth = Math.max(mSwitchMinWidth,
-                maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
+                2 * mThumbWidth + mTempRect.left + mTempRect.right);
         final int switchHeight = Math.max(mTrackDrawable.getIntrinsicHeight(),
                 mThumbDrawable.getIntrinsicHeight());
 
-        mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
 
         mSwitchWidth = switchWidth;
         mSwitchHeight = switchHeight;
@@ -777,7 +806,7 @@
         final Drawable trackDrawable = mTrackDrawable;
         final Drawable thumbDrawable = mThumbDrawable;
 
-        // Draw the switch
+        // Layout the track.
         final int switchLeft = mSwitchLeft;
         final int switchTop = mSwitchTop;
         final int switchRight = mSwitchRight;
@@ -793,9 +822,10 @@
         // Relies on mTempRect, MUST be called first!
         final int thumbPos = getThumbOffset();
 
+        // Layout the thumb.
         thumbDrawable.getPadding(tempRect);
-        int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
-        int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
+        final int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
+        final int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
         thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
 
         final Drawable background = getBackground();
@@ -805,20 +835,32 @@
 
         super.onDraw(canvas);
 
-        trackDrawable.draw(canvas);
+        if (mSplitTrack) {
+            final Insets insets = thumbDrawable.getOpticalInsets();
+            thumbDrawable.copyBounds(tempRect);
+            tempRect.left += insets.left;
+            tempRect.right -= insets.right;
+
+            final int saveCount = canvas.save();
+            canvas.clipRect(tempRect, Op.DIFFERENCE);
+            trackDrawable.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        } else {
+            trackDrawable.draw(canvas);
+        }
 
         final int saveCount = canvas.save();
         canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
         thumbDrawable.draw(canvas);
 
-        final int drawableState[] = getDrawableState();
-        if (mTextColors != null) {
-            mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
-        }
-        mTextPaint.drawableState = drawableState;
-
         final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
         if (switchText != null) {
+            final int drawableState[] = getDrawableState();
+            if (mTextColors != null) {
+                mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
+            }
+            mTextPaint.drawableState = drawableState;
+
             final int left = (thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2;
             final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
             canvas.translate(left, top);
@@ -889,12 +931,16 @@
     protected void drawableStateChanged() {
         super.drawableStateChanged();
 
-        int[] myDrawableState = getDrawableState();
+        final int[] myDrawableState = getDrawableState();
 
-        // Set the state of the Drawable
-        // Drawable may be null when checked state is set from XML, from super constructor
-        if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
-        if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
+        if (mThumbDrawable != null && mThumbDrawable.setState(myDrawableState)) {
+            // Handle changes to thumb width and height.
+            requestLayout();
+        }
+
+        if (mTrackDrawable != null) {
+            mTrackDrawable.setState(myDrawableState);
+        }
 
         invalidate();
     }
diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java
index 52281d9..34f62ba 100644
--- a/core/java/com/android/internal/util/AsyncChannel.java
+++ b/core/java/com/android/internal/util/AsyncChannel.java
@@ -450,6 +450,7 @@
     public void disconnect() {
         if ((mConnection != null) && (mSrcContext != null)) {
             mSrcContext.unbindService(mConnection);
+            mConnection = null;
         }
         try {
             // Send the DISCONNECTED, although it may not be received
@@ -463,10 +464,12 @@
         // Tell source we're disconnected.
         if (mSrcHandler != null) {
             replyDisconnected(STATUS_SUCCESSFUL);
+            mSrcHandler = null;
         }
         // Unlink only when bindService isn't used
         if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) {
             mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0);
+            mDeathMonitor = null;
         }
     }
 
diff --git a/core/res/res/drawable-hdpi/scrubber_control_off_qntm_alpha.png b/core/res/res/drawable-hdpi/scrubber_control_off_qntm_alpha.png
index f1023ea..5a99528 100644
--- a/core/res/res/drawable-hdpi/scrubber_control_off_qntm_alpha.png
+++ b/core/res/res/drawable-hdpi/scrubber_control_off_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_on_qntm_alpha.png b/core/res/res/drawable-hdpi/scrubber_control_on_qntm_alpha.png
index 15ceeee..79de664 100644
--- a/core/res/res/drawable-hdpi/scrubber_control_on_qntm_alpha.png
+++ b/core/res/res/drawable-hdpi/scrubber_control_on_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-hdpi/switch_off_qntm_alpha.9.png
index 90b1498..73e8f1c 100644
--- a/core/res/res/drawable-hdpi/switch_off_qntm_alpha.9.png
+++ b/core/res/res/drawable-hdpi/switch_off_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-hdpi/switch_on_qntm_alpha.9.png
index b535758..ff6affe 100644
--- a/core/res/res/drawable-hdpi/switch_on_qntm_alpha.9.png
+++ b/core/res/res/drawable-hdpi/switch_on_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_off_qntm_alpha.png b/core/res/res/drawable-mdpi/scrubber_control_off_qntm_alpha.png
index 1833704..e40cba8 100644
--- a/core/res/res/drawable-mdpi/scrubber_control_off_qntm_alpha.png
+++ b/core/res/res/drawable-mdpi/scrubber_control_off_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_on_qntm_alpha.png b/core/res/res/drawable-mdpi/scrubber_control_on_qntm_alpha.png
index e64d3f2..437a3e3 100644
--- a/core/res/res/drawable-mdpi/scrubber_control_on_qntm_alpha.png
+++ b/core/res/res/drawable-mdpi/scrubber_control_on_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-mdpi/switch_off_qntm_alpha.9.png
index ffd6c39..8949b52 100644
--- a/core/res/res/drawable-mdpi/switch_off_qntm_alpha.9.png
+++ b/core/res/res/drawable-mdpi/switch_off_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-mdpi/switch_on_qntm_alpha.9.png
index 15faff0..d727683 100644
--- a/core/res/res/drawable-mdpi/switch_on_qntm_alpha.9.png
+++ b/core/res/res/drawable-mdpi/switch_on_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_off_qntm_alpha.png b/core/res/res/drawable-xhdpi/scrubber_control_off_qntm_alpha.png
index ad72f06..729e0bf 100644
--- a/core/res/res/drawable-xhdpi/scrubber_control_off_qntm_alpha.png
+++ b/core/res/res/drawable-xhdpi/scrubber_control_off_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_on_qntm_alpha.png b/core/res/res/drawable-xhdpi/scrubber_control_on_qntm_alpha.png
index 7aceed1..d018a7c 100644
--- a/core/res/res/drawable-xhdpi/scrubber_control_on_qntm_alpha.png
+++ b/core/res/res/drawable-xhdpi/scrubber_control_on_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-xhdpi/switch_off_qntm_alpha.9.png
index 309b528..a7a972c 100644
--- a/core/res/res/drawable-xhdpi/switch_off_qntm_alpha.9.png
+++ b/core/res/res/drawable-xhdpi/switch_off_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-xhdpi/switch_on_qntm_alpha.9.png
index 139795e..dd8910b 100644
--- a/core/res/res/drawable-xhdpi/switch_on_qntm_alpha.9.png
+++ b/core/res/res/drawable-xhdpi/switch_on_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_off_qntm_alpha.png b/core/res/res/drawable-xxhdpi/scrubber_control_off_qntm_alpha.png
index c11b0ae..a2b5716 100644
--- a/core/res/res/drawable-xxhdpi/scrubber_control_off_qntm_alpha.png
+++ b/core/res/res/drawable-xxhdpi/scrubber_control_off_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_on_qntm_alpha.png b/core/res/res/drawable-xxhdpi/scrubber_control_on_qntm_alpha.png
index cde797e..caabc2c 100644
--- a/core/res/res/drawable-xxhdpi/scrubber_control_on_qntm_alpha.png
+++ b/core/res/res/drawable-xxhdpi/scrubber_control_on_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-xxhdpi/switch_off_qntm_alpha.9.png
index 9e234af..8d79a13 100644
--- a/core/res/res/drawable-xxhdpi/switch_off_qntm_alpha.9.png
+++ b/core/res/res/drawable-xxhdpi/switch_off_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-xxhdpi/switch_on_qntm_alpha.9.png
index b371eab..e0e4ef9 100644
--- a/core/res/res/drawable-xxhdpi/switch_on_qntm_alpha.9.png
+++ b/core/res/res/drawable-xxhdpi/switch_on_qntm_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable/scrubber_progress_horizontal_quantum.xml b/core/res/res/drawable/scrubber_progress_horizontal_quantum.xml
index d172b05..f82fe7a 100644
--- a/core/res/res/drawable/scrubber_progress_horizontal_quantum.xml
+++ b/core/res/res/drawable/scrubber_progress_horizontal_quantum.xml
@@ -16,22 +16,26 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false">
-        <bitmap android:src="@drawable/scrubber_track_qntm_alpha"
+        <nine-patch android:src="@drawable/scrubber_track_qntm_alpha"
             android:tint="?attr/colorControlNormal" />
     </item>
     <item>
         <layer-list>
             <item android:id="@id/background">
-                <bitmap android:src="@drawable/scrubber_track_qntm_alpha"
+                <nine-patch android:src="@drawable/scrubber_track_qntm_alpha"
                     android:tint="?attr/colorControlNormal" />
             </item>
             <item android:id="@id/secondaryProgress">
-                <bitmap android:src="@drawable/scrubber_primary_qntm_alpha"
-                    android:tint="?attr/colorControlNormal" />
+                <scale android:scaleWidth="100%">
+                    <nine-patch android:src="@drawable/scrubber_primary_qntm_alpha"
+                        android:tint="?attr/colorControlNormal" />
+                </scale>
             </item>
             <item android:id="@id/progress">
-                <bitmap android:src="@drawable/scrubber_primary_qntm_alpha"
-                    android:tint="?attr/colorControlActivated" />
+                <scale android:scaleWidth="100%">
+                    <nine-patch android:src="@drawable/scrubber_primary_qntm_alpha"
+                        android:tint="?attr/colorControlActivated" />
+                </scale>
             </item>
         </layer-list>
     </item>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7a6832e..cedb92d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3343,6 +3343,8 @@
         <attr name="thumb" format="reference" />
         <!-- An offset for the thumb that allows it to extend out of the range of the track. -->
         <attr name="thumbOffset" format="dimension" />
+        <!-- Whether to split the track and leave a gap for the thumb drawable. -->
+        <attr name="splitTrack" format="boolean" />
     </declare-styleable>
 
     <declare-styleable name="StackView">
@@ -6393,6 +6395,8 @@
         <attr name="switchMinWidth" format="dimension" />
         <!-- Minimum space between the switch and caption text -->
         <attr name="switchPadding" format="dimension" />
+        <!-- Whether to split the track and leave a gap for the thumb drawable. -->
+        <attr name="splitTrack" />
     </declare-styleable>
 
     <declare-styleable name="Pointer">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ecb22ae..dc5efea 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2169,6 +2169,7 @@
   <public type="attr" name="toId" />
   <public type="attr" name="fromId" />
   <public type="attr" name="reversible" />
+  <public type="attr" name="splitTrack" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index 88a2a9f..e693673 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -455,10 +455,10 @@
     <style name="Widget.Quantum.CompoundButton.Switch">
         <item name="track">@drawable/switch_track_quantum</item>
         <item name="thumb">@drawable/switch_inner_quantum</item>
+        <item name="splitTrack">true</item>
         <item name="switchTextAppearance">@style/TextAppearance.Quantum.Widget.Switch</item>
         <item name="textOn"></item>
         <item name="textOff"></item>
-        <item name="thumbTextPadding">12dip</item>
         <item name="switchMinWidth">72dip</item>
         <item name="switchPadding">16dip</item>
         <item name="background">?attr/selectableItemBackground</item>
@@ -572,10 +572,8 @@
         <item name="indeterminateOnly">false</item>
         <item name="progressDrawable">@drawable/scrubber_progress_horizontal_quantum</item>
         <item name="indeterminateDrawable">@drawable/scrubber_progress_horizontal_quantum</item>
-        <item name="minHeight">13dip</item>
-        <item name="maxHeight">13dip</item>
         <item name="thumb">@drawable/scrubber_control_selector_quantum</item>
-        <item name="thumbOffset">16dip</item>
+        <item name="splitTrack">true</item>
         <item name="focusable">true</item>
         <item name="paddingStart">16dip</item>
         <item name="paddingEnd">16dip</item>
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 098753b..6aad5fb 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2452,15 +2452,19 @@
 
     if (mcc != 0) {
         if (res.size() > 0) res.append("-");
-        res.appendFormat("%dmcc", dtohs(mcc));
+        res.appendFormat("mcc%d", dtohs(mcc));
     }
     if (mnc != 0) {
         if (res.size() > 0) res.append("-");
-        res.appendFormat("%dmnc", dtohs(mnc));
+        res.appendFormat("mnc%d", dtohs(mnc));
     }
+
     char localeStr[RESTABLE_MAX_LOCALE_LEN];
     getBcp47Locale(localeStr);
-    res.append(localeStr);
+    if (strlen(localeStr) > 0) {
+        if (res.size() > 0) res.append("-");
+        res.append(localeStr);
+    }
 
     if ((screenLayout&MASK_LAYOUTDIR) != 0) {
         if (res.size() > 0) res.append("-");
@@ -2627,6 +2631,20 @@
                 break;
         }
     }
+    if ((inputFlags&MASK_KEYSHIDDEN) != 0) {
+        if (res.size() > 0) res.append("-");
+        switch (inputFlags&MASK_KEYSHIDDEN) {
+            case ResTable_config::KEYSHIDDEN_NO:
+                res.append("keysexposed");
+                break;
+            case ResTable_config::KEYSHIDDEN_YES:
+                res.append("keyshidden");
+                break;
+            case ResTable_config::KEYSHIDDEN_SOFT:
+                res.append("keyssoft");
+                break;
+        }
+    }
     if (keyboard != KEYBOARD_ANY) {
         if (res.size() > 0) res.append("-");
         switch (keyboard) {
@@ -2644,17 +2662,18 @@
                 break;
         }
     }
-    if ((inputFlags&MASK_KEYSHIDDEN) != 0) {
+    if ((inputFlags&MASK_NAVHIDDEN) != 0) {
         if (res.size() > 0) res.append("-");
-        switch (inputFlags&MASK_KEYSHIDDEN) {
-            case ResTable_config::KEYSHIDDEN_NO:
-                res.append("keysexposed");
+        switch (inputFlags&MASK_NAVHIDDEN) {
+            case ResTable_config::NAVHIDDEN_NO:
+                res.append("navexposed");
                 break;
-            case ResTable_config::KEYSHIDDEN_YES:
-                res.append("keyshidden");
+            case ResTable_config::NAVHIDDEN_YES:
+                res.append("navhidden");
                 break;
-            case ResTable_config::KEYSHIDDEN_SOFT:
-                res.append("keyssoft");
+            default:
+                res.appendFormat("inputFlagsNavHidden=%d",
+                        dtohs(inputFlags&MASK_NAVHIDDEN));
                 break;
         }
     }
@@ -2678,21 +2697,6 @@
                 break;
         }
     }
-    if ((inputFlags&MASK_NAVHIDDEN) != 0) {
-        if (res.size() > 0) res.append("-");
-        switch (inputFlags&MASK_NAVHIDDEN) {
-            case ResTable_config::NAVHIDDEN_NO:
-                res.append("navsexposed");
-                break;
-            case ResTable_config::NAVHIDDEN_YES:
-                res.append("navhidden");
-                break;
-            default:
-                res.appendFormat("inputFlagsNavHidden=%d",
-                        dtohs(inputFlags&MASK_NAVHIDDEN));
-                break;
-        }
-    }
     if (screenSize != 0) {
         if (res.size() > 0) res.append("-");
         res.appendFormat("%dx%d", dtohs(screenWidth), dtohs(screenHeight));
@@ -5503,7 +5507,25 @@
         if (package == NULL) {
             return (mError=NO_MEMORY);
         }
-        
+
+        if (idmap_id == 0) {
+            err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
+                                           header->dataEnd-(base+dtohl(pkg->typeStrings)));
+            if (err != NO_ERROR) {
+                delete group;
+                delete package;
+                return (mError=err);
+            }
+
+            err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
+                                          header->dataEnd-(base+dtohl(pkg->keyStrings)));
+            if (err != NO_ERROR) {
+                delete group;
+                delete package;
+                return (mError=err);
+            }
+        }
+
         if (id == 0) {
             // This is a library so assign an ID
             id = mNextPackageId++;
@@ -5521,21 +5543,6 @@
                 return (mError=NO_MEMORY);
             }
 
-            err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
-                                           header->dataEnd-(base+dtohl(pkg->typeStrings)));
-            if (err != NO_ERROR) {
-                delete group;
-                delete package;
-                return (mError=err);
-            }
-            err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
-                                          header->dataEnd-(base+dtohl(pkg->keyStrings)));
-            if (err != NO_ERROR) {
-                delete group;
-                delete package;
-                return (mError=err);
-            }
-
             //printf("Adding new package id %d at index %d\n", id, idx);
             err = mPackageGroups.add(group);
             if (err < NO_ERROR) {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
new file mode 100644
index 0000000..bb23a36
--- /dev/null
+++ b/media/java/android/media/AudioAttributes.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A class to encapsulate a collection of attributes describing information about an audio
+ * player or recorder.
+ */
+public final class AudioAttributes {
+    private final static String TAG = "AudioAttributes";
+
+    /**
+     * Content type value to use when the content type is unknown, or other than the ones defined.
+     */
+    public final static int CONTENT_TYPE_UNKNOWN = 0;
+    /**
+     * Content type value to use when the content type is speech.
+     */
+    public final static int CONTENT_TYPE_SPEECH = 1;
+    /**
+     * Content type value to use when the content type is music.
+     */
+    public final static int CONTENT_TYPE_MUSIC = 2;
+    /**
+     * Content type value to use when the content type is a soundtrack, typically accompanying
+     * a movie or TV program.
+     */
+    public final static int CONTENT_TYPE_MOVIE = 3;
+    /**
+     * Content type value to use when the content type is a sound used to accompany a user
+     * action, such as a beep or sound effect expressing a key click, or event, such as the
+     * type of a sound for a bonus being received in a game. These sounds are mostly synthesized
+     * or short Foley sounds.
+     */
+    public final static int CONTENT_TYPE_SONIFICATION = 4;
+
+    /**
+     * Usage value to use when the usage is unknown.
+     */
+    public final static int USAGE_UNKNOWN = 0;
+    /**
+     * Usage value to use when the usage is media, such as music, or movie
+     * soundtracks.
+     */
+    public final static int USAGE_MEDIA = 1;
+    /**
+     * Usage value to use when the usage is voice communications, such as telephony
+     * or VoIP.
+     */
+    public final static int USAGE_VOICE_COMMUNICATION = 2;
+    /**
+     * Usage value to use when the usage is in-call signalling, such as with
+     * a "busy" beep, or DTMF tones.
+     */
+    public final static int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3;
+    /**
+     * Usage value to use when the usage is an alarm (e.g. wake-up alarm).
+     */
+    public final static int USAGE_ALARM = 4;
+    /**
+     * Usage value to use when the usage is notification. See other
+     * notification usages for more specialized uses.
+     */
+    public final static int USAGE_NOTIFICATION = 5;
+    /**
+     * Usage value to use when the usage is telephony ringtone.
+     */
+    public final static int USAGE_NOTIFICATION_TELEPHONY_RINGTONE = 6;
+    /**
+     * Usage value to use when the usage is a request to enter/end a
+     * communication, such as a VoIP communication or video-conference.
+     */
+    public final static int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7;
+    /**
+     * Usage value to use when the usage is notification for an "instant"
+     * communication such as a chat, or SMS.
+     */
+    public final static int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8;
+    /**
+     * Usage value to use when the usage is notification for a
+     * non-immediate type of communication such as e-mail.
+     */
+    public final static int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9;
+    /**
+     * Usage value to use when the usage is to attract the user's attention,
+     * such as a reminder or low battery warning.
+     */
+    public final static int USAGE_NOTIFICATION_EVENT = 10;
+    /**
+     * Usage value to use when the usage is for accessibility, such as with
+     * a screen reader.
+     */
+    public final static int USAGE_ASSISTANCE_ACCESSIBILITY = 11;
+    /**
+     * Usage value to use when the usage is driving or navigation directions.
+     */
+    public final static int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12;
+    /**
+     * Usage value to use when the usage is sonification, such as  with user
+     * interface sounds.
+     */
+    public final static int USAGE_ASSISTANCE_SONIFICATION = 13;
+    /**
+     * Usage value to use when the usage is for game audio.
+     */
+    public final static int USAGE_GAME = 14;
+
+    /**
+     * Flag defining a behavior where the audibility of the sound will be ensured by the system.
+     */
+    public final static int FLAG_AUDIBILITY_ENFORCED = 0x1 << 0;
+    /**
+     * @hide
+     * Flag defining a behavior where the playback of the sound is ensured without
+     * degradation only when going to a secure sink.
+     */
+    // FIXME not guaranteed yet
+    // TODO  add OR to getFlags() when supported and in public API
+    public final static int FLAG_SECURE = 0x1 << 1;
+    /**
+     * @hide
+     * Flag to enable when the stream is associated with SCO usage.
+     * Internal use only for dealing with legacy STREAM_BLUETOOTH_SCO
+     */
+    public final static int FLAG_SCO = 0x1 << 2;
+
+
+    private int mUsage = USAGE_UNKNOWN;
+    private int mContentType = CONTENT_TYPE_UNKNOWN;
+    private int mFlags = 0x0;
+    private HashSet<String> mTags;
+
+    private AudioAttributes() {
+    }
+
+    /**
+     * Return the content type.
+     * @return one of the values that can be set in {@link Builder#setContentType(int)}
+     */
+    public int getContentType() {
+        return mContentType;
+    }
+
+    /**
+     * Return the usage.
+     * @return one of the values that can be set in {@link Builder#setUsage(int)}
+     */
+    public int getUsage() {
+        return mUsage;
+    }
+
+    /**
+     * Return the flags.
+     * @return a combined mask of all flags
+     */
+    public int getFlags() {
+        // only return the flags that are public
+        return (mFlags & (FLAG_AUDIBILITY_ENFORCED));
+    }
+
+    /**
+     * @hide
+     * Return all the flags, even the non-public ones.
+     * Internal use only
+     * @return a combined mask of all flags
+     */
+    public int getAllFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Return the set of tags.
+     * @return a read-only set of all tags stored as strings.
+     */
+    public Set<String> getTags() {
+        return Collections.unmodifiableSet(mTags);
+    }
+
+    /**
+     * Builder class for {@link AudioAttributes} objects.
+     */
+    public static class Builder {
+        private int mUsage = USAGE_UNKNOWN;
+        private int mContentType = CONTENT_TYPE_UNKNOWN;
+        private int mFlags = 0x0;
+        private HashSet<String> mTags = new HashSet<String>();
+
+        /**
+         * Constructs a new Builder with the defaults.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructs a new Builder from a given AudioAttributes
+         * @param aa the AudioAttributes object whose data will be reused in the new Builder.
+         */
+        @SuppressWarnings("unchecked") // for cloning of mTags
+        public Builder(AudioAttributes aa) {
+            mUsage = aa.mUsage;
+            mContentType = aa.mContentType;
+            mFlags = aa.mFlags;
+            mTags = (HashSet<String>) aa.mTags.clone();
+        }
+
+        /**
+         * Combines all of the attributes that have been set and return a new
+         * {@link AudioAttributes} object.
+         * @return a new {@link AudioAttributes} object
+         */
+        @SuppressWarnings("unchecked") // for cloning of mTags
+        public AudioAttributes build() {
+            AudioAttributes aa = new AudioAttributes();
+            aa.mContentType = mContentType;
+            aa.mUsage = mUsage;
+            aa.mFlags = mFlags;
+            aa.mTags = (HashSet<String>) mTags.clone();
+            return aa;
+        }
+
+        /**
+         * Sets the attribute describing what is the intended use of the the audio signal,
+         * such as alarm or ringtone.
+         * @param usage one of {@link AudioAttributes#USAGE_UNKNOWN},
+         *     {@link AudioAttributes#USAGE_MEDIA},
+         *     {@link AudioAttributes#USAGE_VOICE_COMMUNICATION},
+         *     {@link AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING},
+         *     {@link AudioAttributes#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION},
+         *     {@link AudioAttributes#USAGE_NOTIFICATION_TELEPHONY_RINGTONE},
+         *     {@link AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST},
+         *     {@link AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT},
+         *     {@link AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED},
+         *     {@link AudioAttributes#USAGE_NOTIFICATION_EVENT},
+         *     {@link AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY},
+         *     {@link AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE},
+         *     {@link AudioAttributes#USAGE_ASSISTANCE_SONIFICATION},
+         *     {@link AudioAttributes#USAGE_GAME}.
+         * @return the same Builder instance.
+         */
+        public Builder setUsage(@AttributeUsage int usage) {
+            switch (usage) {
+                case USAGE_UNKNOWN:
+                case USAGE_MEDIA:
+                case USAGE_VOICE_COMMUNICATION:
+                case USAGE_VOICE_COMMUNICATION_SIGNALLING:
+                case USAGE_ALARM:
+                case USAGE_NOTIFICATION:
+                case USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
+                case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+                case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+                case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+                case USAGE_NOTIFICATION_EVENT:
+                case USAGE_ASSISTANCE_ACCESSIBILITY:
+                case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+                case USAGE_ASSISTANCE_SONIFICATION:
+                case USAGE_GAME:
+                     mUsage = usage;
+                     break;
+                default:
+                     mUsage = USAGE_UNKNOWN;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the attribute describing the content type of the audio signal, such as speech,
+         * or music.
+         * @param contentType the content type values, one of
+         *     {@link AudioAttributes#CONTENT_TYPE_MOVIE},
+         *     {@link AudioAttributes#CONTENT_TYPE_MUSIC},
+         *     {@link AudioAttributes#CONTENT_TYPE_SONIFICATION},
+         *     {@link AudioAttributes#CONTENT_TYPE_SPEECH},
+         *     {@link AudioAttributes#CONTENT_TYPE_UNKNOWN}.
+         * @return the same Builder instance.
+         */
+        public Builder setContentType(@AttributeContentType int contentType) {
+            switch (contentType) {
+                case CONTENT_TYPE_UNKNOWN:
+                case CONTENT_TYPE_MOVIE:
+                case CONTENT_TYPE_MUSIC:
+                case CONTENT_TYPE_SONIFICATION:
+                case CONTENT_TYPE_SPEECH:
+                     mContentType = contentType;
+                     break;
+                default:
+                     mUsage = CONTENT_TYPE_UNKNOWN;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the combination of flags.
+         * @param flags the {@link AudioAttributes#FLAG_AUDIBILITY_ENFORCED} flag.
+         * @return the same Builder instance.
+         */
+        public Builder setFlags(int flags) {
+            flags &= (AudioAttributes.FLAG_AUDIBILITY_ENFORCED | AudioAttributes.FLAG_SCO
+                    | AudioAttributes.FLAG_SECURE);
+            mFlags |= flags;
+            return this;
+        }
+
+        /**
+         * Add a custom tag stored as a string
+         * @param tag
+         * @return the same Builder instance.
+         */
+        public Builder addTag(String tag) {
+            mTags.add(tag);
+            return this;
+        }
+
+        /**
+         * Adds attributes inferred from the legacy stream types.
+         * @param streamType one of {@link AudioManager#STREAM_VOICE_CALL},
+         *   {@link AudioManager#STREAM_SYSTEM}, {@link AudioManager#STREAM_RING},
+         *   {@link AudioManager#STREAM_MUSIC}, {@link AudioManager#STREAM_ALARM},
+         *    or {@link AudioManager#STREAM_NOTIFICATION}.
+         * @return the same Builder instance.
+         */
+        public Builder setLegacyStreamType(int streamType) {
+            return setInternalLegacyStreamType(streamType);
+        }
+
+        /**
+         * @hide
+         * For internal framework use only, enables building from hidden stream types.
+         * @param streamType
+         * @return the same Builder instance.
+         */
+        public Builder setInternalLegacyStreamType(int streamType) {
+            switch(streamType) {
+                case AudioSystem.STREAM_VOICE_CALL:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    mUsage = USAGE_VOICE_COMMUNICATION;
+                    break;
+                case AudioSystem.STREAM_SYSTEM_ENFORCED:
+                    mFlags |= FLAG_AUDIBILITY_ENFORCED;
+                    // intended fall through, attributes in common with STREAM_SYSTEM
+                case AudioSystem.STREAM_SYSTEM:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    mUsage = USAGE_ASSISTANCE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_RING:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    mUsage = USAGE_NOTIFICATION_TELEPHONY_RINGTONE;
+                    break;
+                case AudioSystem.STREAM_MUSIC:
+                    mContentType = CONTENT_TYPE_MUSIC;
+                    mUsage = USAGE_MEDIA;
+                    break;
+                case AudioSystem.STREAM_ALARM:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    mUsage = USAGE_ALARM;
+                    break;
+                case AudioSystem.STREAM_NOTIFICATION:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    mUsage = USAGE_NOTIFICATION;
+                    break;
+                case AudioSystem.STREAM_BLUETOOTH_SCO:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    mUsage = USAGE_VOICE_COMMUNICATION;
+                    mFlags |= FLAG_SCO;
+                    break;
+                case AudioSystem.STREAM_DTMF:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    mUsage = USAGE_VOICE_COMMUNICATION_SIGNALLING;
+                    break;
+                case AudioSystem.STREAM_TTS:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    mUsage = USAGE_ASSISTANCE_ACCESSIBILITY;
+                    break;
+                default:
+                    Log.e(TAG, "Invalid stream type " + streamType + " in for AudioAttributes");
+            }
+            return this;
+        }
+    };
+
+    /** @hide */
+    @Override
+    public String toString () {
+        return new String("AudioAttributes:"
+                + " usage=" + mUsage
+                + " content=" + mContentType
+                + " flags=0x" + Integer.toHexString(mFlags)
+                + " tags=" + mTags);
+    }
+
+    /** @hide */
+    @IntDef({
+        USAGE_UNKNOWN,
+        USAGE_MEDIA,
+        USAGE_VOICE_COMMUNICATION,
+        USAGE_VOICE_COMMUNICATION_SIGNALLING,
+        USAGE_ALARM,
+        USAGE_NOTIFICATION,
+        USAGE_NOTIFICATION_TELEPHONY_RINGTONE,
+        USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+        USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+        USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+        USAGE_NOTIFICATION_EVENT,
+        USAGE_ASSISTANCE_ACCESSIBILITY,
+        USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+        USAGE_ASSISTANCE_SONIFICATION,
+        USAGE_GAME
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttributeUsage {}
+
+    /** @hide */
+    @IntDef({
+        CONTENT_TYPE_UNKNOWN,
+        CONTENT_TYPE_SPEECH,
+        CONTENT_TYPE_MUSIC,
+        CONTENT_TYPE_MOVIE,
+        CONTENT_TYPE_SONIFICATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttributeContentType {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 7306c83..bd9de82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -82,7 +82,7 @@
         mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
                 MAX_ITEMS_IN_TOP_STACK,
                 mTopStackPeekSize,
-                mTopStackTotalSize,
+                mTopStackTotalSize - mTopStackPeekSize,
                 0.5f);
         mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
                 MAX_ITEMS_IN_BOTTOM_STACK,
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index dfffa8a..4ea33db 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -65,6 +65,7 @@
 import android.net.LinkProperties.CompareResult;
 import android.net.LinkQualityInfo;
 import android.net.MobileDataStateTracker;
+import android.net.Network;
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
@@ -165,6 +166,8 @@
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLSession;
 
+import static android.net.ConnectivityManager.INVALID_NET_ID;
+
 /**
  * @hide
  */
@@ -442,6 +445,10 @@
 
     TelephonyManager mTelephonyManager;
 
+    private final static int MIN_NET_ID = 10; // some reserved marks
+    private final static int MAX_NET_ID = 65535;
+    private int mNextNetId = MIN_NET_ID;
+
     public ConnectivityService(Context context, INetworkManagementService netd,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         // Currently, omitting a NetworkFactory will create one internally
@@ -706,6 +713,12 @@
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
     }
 
+    private synchronized int nextNetId() {
+        int netId = mNextNetId;
+        if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID;
+        return netId;
+    }
+
     /**
      * Factory that creates {@link NetworkStateTracker} instances using given
      * {@link NetworkConfig}.
@@ -1984,6 +1997,7 @@
         int prevNetType = info.getType();
 
         mNetTrackers[prevNetType].setTeardownRequested(false);
+        int thisNetId = mNetTrackers[prevNetType].getNetwork().netId;
 
         // Remove idletimer previously setup in {@code handleConnect}
         if (mNetConfigs[prevNetType].isDefault()) {
@@ -2069,6 +2083,13 @@
             sendConnectedBroadcastDelayed(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(),
                     getConnectivityChangeDelay());
         }
+        try {
+            mNetd.removeNetwork(thisNetId);
+        } catch (Exception e) {
+            loge("Exception removing network: " + e);
+        } finally {
+            mNetTrackers[prevNetType].setNetId(INVALID_NET_ID);
+        }
     }
 
     private void tryFailover(int prevNetType) {
@@ -2336,17 +2357,23 @@
         if (mNetConfigs[newNetType].isDefault()) {
             if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
                 if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
-                    // tear down the other
-                    NetworkStateTracker otherNet =
-                            mNetTrackers[mActiveDefaultNetwork];
-                    if (DBG) {
-                        log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
-                            " teardown");
-                    }
-                    if (!teardown(otherNet)) {
-                        loge("Network declined teardown request");
-                        teardown(thisNet);
-                        return;
+                   String teardownPolicy = SystemProperties.get("net.teardownPolicy");
+                   if (TextUtils.equals(teardownPolicy, "keep") == false) {
+                        // tear down the other
+                        NetworkStateTracker otherNet =
+                                mNetTrackers[mActiveDefaultNetwork];
+                        if (DBG) {
+                            log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
+                                " teardown");
+                        }
+                        if (!teardown(otherNet)) {
+                            loge("Network declined teardown request");
+                            teardown(thisNet);
+                            return;
+                        }
+                    } else {
+                        //TODO - remove
+                        loge("network teardown skipped due to net.teardownPolicy setting");
                     }
                 } else {
                        // don't accept this one
@@ -2358,6 +2385,15 @@
                         return;
                 }
             }
+            int thisNetId = nextNetId();
+            thisNet.setNetId(thisNetId);
+            try {
+                mNetd.createNetwork(thisNetId, thisIface);
+            } catch (Exception e) {
+                loge("Exception creating network :" + e);
+                teardown(thisNet);
+                return;
+            }
             setupDataActivityTracking(newNetType);
             synchronized (ConnectivityService.this) {
                 // have a new default network, release the transition wakelock in a second
@@ -2380,6 +2416,16 @@
             // Don't do this - if we never sign in stay, grey
             //reportNetworkCondition(mActiveDefaultNetwork, 100);
             updateNetworkSettings(thisNet);
+        } else {
+            int thisNetId = nextNetId();
+            thisNet.setNetId(thisNetId);
+            try {
+                mNetd.createNetwork(thisNetId, thisIface);
+            } catch (Exception e) {
+                loge("Exception creating network :" + e);
+                teardown(thisNet);
+                return;
+            }
         }
         thisNet.setTeardownRequested(false);
         updateMtuSizeSettings(thisNet);
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index 0d1e122..96f9ab0 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -50,6 +50,8 @@
 final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
     private static final boolean LOGD = false;
 
+    private final static boolean VDBG = false;
+
     private final String TAG;
 
     private String mSocket;
@@ -409,7 +411,7 @@
                 loge("timed-out waiting for response to " + logCmd);
                 throw new NativeDaemonFailureException(logCmd, event);
             }
-            log("RMV <- {" + event + "}");
+            if (VDBG) log("RMV <- {" + event + "}");
             events.add(event);
         } while (event.isClassContinue());
 
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 7ce45f7..b9c86dc 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -2029,4 +2029,24 @@
 
         pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
     }
+
+    public void createNetwork(int netId, String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("network", "create", netId, iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    public void removeNetwork(int netId) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("network", "destroy", netId);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 4f0c9b5..512ebc6 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -171,6 +171,7 @@
             return;
         }
         writer.println("Current scorer: " + currentScorer);
+        writer.flush();
 
         for (INetworkScoreCache scoreCache : getScoreCaches()) {
             try {
diff --git a/tests/Split/Android.mk b/tests/Split/Android.mk
new file mode 100644
index 0000000..7884d4d
--- /dev/null
+++ b/tests/Split/Android.mk
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 2014 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_PACKAGE_NAME := Split
+
+LOCAL_AAPT_FLAGS := --split fr,de
+LOCAL_AAPT_FLAGS += -v
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/Split/AndroidManifest.xml b/tests/Split/AndroidManifest.xml
new file mode 100644
index 0000000..a4956a7
--- /dev/null
+++ b/tests/Split/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.example.split">
+    <application android:label="@string/app_title">
+        <activity android:name="ActivityMain">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/Split/assets/blah.txt b/tests/Split/assets/blah.txt
new file mode 100644
index 0000000..1b37e40
--- /dev/null
+++ b/tests/Split/assets/blah.txt
@@ -0,0 +1 @@
+This is some useful info.
diff --git a/tests/Split/assets/statement.xml b/tests/Split/assets/statement.xml
new file mode 100644
index 0000000..91750d1
--- /dev/null
+++ b/tests/Split/assets/statement.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<statement>
+    <value>Hello</value>
+</statement>
diff --git a/tests/Split/res/layout-fr-sw600dp/main.xml b/tests/Split/res/layout-fr-sw600dp/main.xml
new file mode 100644
index 0000000..2461c8c
--- /dev/null
+++ b/tests/Split/res/layout-fr-sw600dp/main.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</FrameLayout>
diff --git a/tests/Split/res/layout/main.xml b/tests/Split/res/layout/main.xml
new file mode 100644
index 0000000..36992a2
--- /dev/null
+++ b/tests/Split/res/layout/main.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
diff --git a/tests/Split/res/values-de/values.xml b/tests/Split/res/values-de/values.xml
new file mode 100644
index 0000000..26d0507
--- /dev/null
+++ b/tests/Split/res/values-de/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <string name="test">Achtung!</string>
+</resources>
diff --git a/tests/Split/res/values-fr/values.xml b/tests/Split/res/values-fr/values.xml
new file mode 100644
index 0000000..16532da
--- /dev/null
+++ b/tests/Split/res/values-fr/values.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <string name="app_title">APK Divisé</string>
+    <string name="test">Bonjour, Monde!</string>
+    <string name="blah">Bleh..</string>
+    <string-array name="lotsofstrings">
+        <item>Hé là</item>
+        <item>Au revoir</item>
+    </string-array>
+</resources>
diff --git a/tests/Split/res/values-sw600dp/values.xml b/tests/Split/res/values-sw600dp/values.xml
new file mode 100644
index 0000000..a8329bb
--- /dev/null
+++ b/tests/Split/res/values-sw600dp/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <dimen name="width">230dp</dimen>
+</resources>
diff --git a/tests/Split/res/values/values.xml b/tests/Split/res/values/values.xml
new file mode 100644
index 0000000..68edc77
--- /dev/null
+++ b/tests/Split/res/values/values.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <string name="app_title">Split APK</string>
+    <string name="test">Hello, World!</string>
+    <string name="boom">Boom!</string>
+    <string name="blah">Blah...</string>
+    <string-array name="lotsofstrings">
+        <item>Hello there</item>
+        <item>Good bye</item>
+    </string-array>
+
+    <plurals name="plur">
+        <item quantity="zero">I no haz :(</item>
+        <item quantity="one">I haz 1!1! :)</item>
+        <item quantity="many">I haz ALL!</item>
+    </plurals>
+
+    <bool name="que">true</bool>
+    <color name="green">#00FF00</color>
+    <dimen name="width">23dp</dimen>
+    <item type="id" name="identifier" />
+    <integer name="number">123</integer>
+    <integer-array name="numList">
+        <item>1234</item>
+    </integer-array>
+
+    <array name="ary">
+        <item>@string/test</item>
+        <item>@string/boom</item>
+        <item>25dp</item>
+    </array>
+</resources>
diff --git a/tests/Split/src/java/com/android/example/split/ActivityMain.java b/tests/Split/src/java/com/android/example/split/ActivityMain.java
new file mode 100644
index 0000000..a15fb3c
--- /dev/null
+++ b/tests/Split/src/java/com/android/example/split/ActivityMain.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.example.split;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class ActivityMain extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TextView text = new TextView(this);
+        text.setText(R.string.test);
+        setContentView(text);
+    }
+}
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index e0dab78..12d5389 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -3,8 +3,10 @@
 //
 
 #include "AaptAssets.h"
-#include "ResourceFilter.h"
+#include "AaptConfig.h"
+#include "AaptUtil.h"
 #include "Main.h"
+#include "ResourceFilter.h"
 
 #include <utils/misc.h>
 #include <utils/SortedVector.h>
@@ -14,7 +16,6 @@
 #include <errno.h>
 
 static const char* kDefaultLocale = "default";
-static const char* kWildcardName = "any";
 static const char* kAssetDir = "assets";
 static const char* kResourceDir = "res";
 static const char* kValuesDir = "values";
@@ -149,24 +150,6 @@
 // =========================================================================
 // =========================================================================
 
-/* static */ void AaptLocaleValue::splitAndLowerCase(const char* const chars,
-        Vector<String8>* parts, const char separator) {
-    const char *p = chars;
-    const char *q;
-    while (NULL != (q = strchr(p, separator))) {
-         String8 val(p, q - p);
-         val.toLower();
-         parts->add(val);
-         p = q+1;
-    }
-
-    if (p < chars + strlen(chars)) {
-        String8 val(p);
-        val.toLower();
-        parts->add(val);
-    }
-}
-
 /* static */
 inline bool isAlpha(const String8& string) {
      const size_t length = string.length();
@@ -230,8 +213,7 @@
 bool AaptLocaleValue::initFromFilterString(const String8& str) {
      // A locale (as specified in the filter) is an underscore separated name such
      // as "en_US", "en_Latn_US", or "en_US_POSIX".
-     Vector<String8> parts;
-     splitAndLowerCase(str.string(), &parts, '_');
+     Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '_');
 
      const int numTags = parts.size();
      bool valid = false;
@@ -301,8 +283,7 @@
     if (part[0] == 'b' && part[1] == '+') {
         // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
         // except that the separator is "+" and not "-".
-        Vector<String8> subtags;
-        AaptLocaleValue::splitAndLowerCase(part.string(), &subtags, '+');
+        Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+');
         subtags.removeItemsAt(0);
         if (subtags.size() == 1) {
             setLanguage(subtags[0]);
@@ -438,1349 +419,46 @@
     }
 }
 
-
-/* static */ bool
-AaptGroupEntry::parseFilterNamePart(const String8& part, int* axis, AxisValue* value)
-{
-    ResTable_config config;
-    memset(&config, 0, sizeof(ResTable_config));
-
-    // IMSI - MCC
-    if (getMccName(part.string(), &config)) {
-        *axis = AXIS_MCC;
-        value->intValue = config.mcc;
-        return true;
-    }
-
-    // IMSI - MNC
-    if (getMncName(part.string(), &config)) {
-        *axis = AXIS_MNC;
-        value->intValue = config.mnc;
-        return true;
-    }
-
-    // locale - language
-    if (value->localeValue.initFromFilterString(part)) {
-        *axis = AXIS_LOCALE;
-        return true;
-    }
-
-    // layout direction
-    if (getLayoutDirectionName(part.string(), &config)) {
-        *axis = AXIS_LAYOUTDIR;
-        value->intValue = (config.screenLayout&ResTable_config::MASK_LAYOUTDIR);
-        return true;
-    }
-
-    // smallest screen dp width
-    if (getSmallestScreenWidthDpName(part.string(), &config)) {
-        *axis = AXIS_SMALLESTSCREENWIDTHDP;
-        value->intValue = config.smallestScreenWidthDp;
-        return true;
-    }
-
-    // screen dp width
-    if (getScreenWidthDpName(part.string(), &config)) {
-        *axis = AXIS_SCREENWIDTHDP;
-        value->intValue = config.screenWidthDp;
-        return true;
-    }
-
-    // screen dp height
-    if (getScreenHeightDpName(part.string(), &config)) {
-        *axis = AXIS_SCREENHEIGHTDP;
-        value->intValue = config.screenHeightDp;
-        return true;
-    }
-
-    // screen layout size
-    if (getScreenLayoutSizeName(part.string(), &config)) {
-        *axis = AXIS_SCREENLAYOUTSIZE;
-        value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENSIZE);
-        return true;
-    }
-
-    // screen layout long
-    if (getScreenLayoutLongName(part.string(), &config)) {
-        *axis = AXIS_SCREENLAYOUTLONG;
-        value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENLONG);
-        return true;
-    }
-
-    // orientation
-    if (getOrientationName(part.string(), &config)) {
-        *axis = AXIS_ORIENTATION;
-        value->intValue = config.orientation;
-        return true;
-    }
-
-    // ui mode type
-    if (getUiModeTypeName(part.string(), &config)) {
-        *axis = AXIS_UIMODETYPE;
-        value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
-        return true;
-    }
-
-    // ui mode night
-    if (getUiModeNightName(part.string(), &config)) {
-        *axis = AXIS_UIMODENIGHT;
-        value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
-        return true;
-    }
-
-    // density
-    if (getDensityName(part.string(), &config)) {
-        *axis = AXIS_DENSITY;
-        value->intValue = config.density;
-        return true;
-    }
-
-    // touchscreen
-    if (getTouchscreenName(part.string(), &config)) {
-        *axis = AXIS_TOUCHSCREEN;
-        value->intValue = config.touchscreen;
-        return true;
-    }
-
-    // keyboard hidden
-    if (getKeysHiddenName(part.string(), &config)) {
-        *axis = AXIS_KEYSHIDDEN;
-        value->intValue = config.inputFlags;
-        return true;
-    }
-
-    // keyboard
-    if (getKeyboardName(part.string(), &config)) {
-        *axis = AXIS_KEYBOARD;
-        value->intValue = config.keyboard;
-        return true;
-    }
-
-    // navigation hidden
-    if (getNavHiddenName(part.string(), &config)) {
-        *axis = AXIS_NAVHIDDEN;
-        value->intValue = config.inputFlags;
-        return 0;
-    }
-
-    // navigation
-    if (getNavigationName(part.string(), &config)) {
-        *axis = AXIS_NAVIGATION;
-        value->intValue = config.navigation;
-        return true;
-    }
-
-    // screen size
-    if (getScreenSizeName(part.string(), &config)) {
-        *axis = AXIS_SCREENSIZE;
-        value->intValue = config.screenSize;
-        return true;
-    }
-
-    // version
-    if (getVersionName(part.string(), &config)) {
-        *axis = AXIS_VERSION;
-        value->intValue = config.version;
-        return true;
-    }
-
-    return false;
-}
-
-AxisValue
-AaptGroupEntry::getConfigValueForAxis(const ResTable_config& config, int axis)
-{
-    AxisValue value;
-    switch (axis) {
-        case AXIS_MCC:
-            value.intValue = config.mcc;
-            break;
-        case AXIS_MNC:
-            value.intValue = config.mnc;
-            break;
-        case AXIS_LOCALE:
-            value.localeValue.initFromResTable(config);
-            break;
-        case AXIS_LAYOUTDIR:
-            value.intValue = config.screenLayout&ResTable_config::MASK_LAYOUTDIR;
-            break;
-        case AXIS_SCREENLAYOUTSIZE:
-            value.intValue = config.screenLayout&ResTable_config::MASK_SCREENSIZE;
-            break;
-        case AXIS_ORIENTATION:
-            value.intValue = config.orientation;
-            break;
-        case AXIS_UIMODETYPE:
-            value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
-            break;
-        case AXIS_UIMODENIGHT:
-            value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
-            break;
-        case AXIS_DENSITY:
-            value.intValue = config.density;
-            break;
-        case AXIS_TOUCHSCREEN:
-            value.intValue = config.touchscreen;
-            break;
-        case AXIS_KEYSHIDDEN:
-            value.intValue = config.inputFlags;
-            break;
-        case AXIS_KEYBOARD:
-            value.intValue = config.keyboard;
-            break;
-        case AXIS_NAVIGATION:
-            value.intValue = config.navigation;
-            break;
-        case AXIS_SCREENSIZE:
-            value.intValue = config.screenSize;
-            break;
-        case AXIS_SMALLESTSCREENWIDTHDP:
-            value.intValue = config.smallestScreenWidthDp;
-            break;
-        case AXIS_SCREENWIDTHDP:
-            value.intValue = config.screenWidthDp;
-            break;
-        case AXIS_SCREENHEIGHTDP:
-            value.intValue = config.screenHeightDp;
-            break;
-        case AXIS_VERSION:
-            value.intValue = config.version;
-            break;
-    }
-
-    return value;
-}
-
-bool
-AaptGroupEntry::configSameExcept(const ResTable_config& config,
-        const ResTable_config& otherConfig, int axis)
-{
-    for (int i=AXIS_START; i<=AXIS_END; i++) {
-        if (i == axis) {
-            continue;
-        }
-        if (getConfigValueForAxis(config, i) != getConfigValueForAxis(otherConfig, i)) {
-            return false;
-        }
-    }
-    return true;
-}
-
 bool
 AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
 {
-    mParamsChanged = true;
+    const char* q = strchr(dir, '-');
+    size_t typeLen;
+    if (q != NULL) {
+        typeLen = q - dir;
+    } else {
+        typeLen = strlen(dir);
+    }
 
-    Vector<String8> parts;
-    AaptLocaleValue::splitAndLowerCase(dir, &parts, '-');
-
-    String8 mcc, mnc, layoutsize, layoutlong, orient, den;
-    String8 touch, key, keysHidden, nav, navHidden, size, layoutDir, vers;
-    String8 uiModeType, uiModeNight, smallestwidthdp, widthdp, heightdp;
-
-    AaptLocaleValue locale;
-    int numLocaleComponents = 0;
-
-    const int N = parts.size();
-    int index = 0;
-    String8 part = parts[index];
-
-    // resource type
-    if (!isValidResourceType(part)) {
+    String8 type(dir, typeLen);
+    if (!isValidResourceType(type)) {
         return false;
     }
-    *resType = part;
 
-    index++;
-    if (index == N) {
-        goto success;
-    }
-    part = parts[index];
-
-    // imsi - mcc
-    if (getMccName(part.string())) {
-        mcc = part;
-
-        index++;
-        if (index == N) {
-            goto success;
+    if (q != NULL) {
+        if (!AaptConfig::parse(String8(q + 1), &mParams)) {
+            return false;
         }
-        part = parts[index];
-    } else {
-        //printf("not mcc: %s\n", part.string());
     }
 
-    // imsi - mnc
-    if (getMncName(part.string())) {
-        mnc = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not mnc: %s\n", part.string());
-    }
-
-    index = locale.initFromDirName(parts, index);
-    if (index == -1) {
-        return false;
-    }
-    if (index >= N){
-        goto success;
-    }
-
-    part = parts[index];
-    if (getLayoutDirectionName(part.string())) {
-        layoutDir = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not layout direction: %s\n", part.string());
-    }
-
-    if (getSmallestScreenWidthDpName(part.string())) {
-        smallestwidthdp = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not smallest screen width dp: %s\n", part.string());
-    }
-
-    if (getScreenWidthDpName(part.string())) {
-        widthdp = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not screen width dp: %s\n", part.string());
-    }
-
-    if (getScreenHeightDpName(part.string())) {
-        heightdp = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not screen height dp: %s\n", part.string());
-    }
-
-    if (getScreenLayoutSizeName(part.string())) {
-        layoutsize = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not screen layout size: %s\n", part.string());
-    }
-
-    if (getScreenLayoutLongName(part.string())) {
-        layoutlong = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not screen layout long: %s\n", part.string());
-    }
-
-    // orientation
-    if (getOrientationName(part.string())) {
-        orient = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not orientation: %s\n", part.string());
-    }
-
-    // ui mode type
-    if (getUiModeTypeName(part.string())) {
-        uiModeType = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not ui mode type: %s\n", part.string());
-    }
-
-    // ui mode night
-    if (getUiModeNightName(part.string())) {
-        uiModeNight = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not ui mode night: %s\n", part.string());
-    }
-
-    // density
-    if (getDensityName(part.string())) {
-        den = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not density: %s\n", part.string());
-    }
-
-    // touchscreen
-    if (getTouchscreenName(part.string())) {
-        touch = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not touchscreen: %s\n", part.string());
-    }
-
-    // keyboard hidden
-    if (getKeysHiddenName(part.string())) {
-        keysHidden = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not keysHidden: %s\n", part.string());
-    }
-
-    // keyboard
-    if (getKeyboardName(part.string())) {
-        key = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not keyboard: %s\n", part.string());
-    }
-
-    // navigation hidden
-    if (getNavHiddenName(part.string())) {
-        navHidden = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not navHidden: %s\n", part.string());
-    }
-
-    if (getNavigationName(part.string())) {
-        nav = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not navigation: %s\n", part.string());
-    }
-
-    if (getScreenSizeName(part.string())) {
-        size = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not screen size: %s\n", part.string());
-    }
-
-    if (getVersionName(part.string())) {
-        vers = part;
-
-        index++;
-        if (index == N) {
-            goto success;
-        }
-        part = parts[index];
-    } else {
-        //printf("not version: %s\n", part.string());
-    }
-
-    // if there are extra parts, it doesn't match
-    return false;
-
-success:
-    this->mcc = mcc;
-    this->mnc = mnc;
-    this->locale = locale;
-    this->screenLayoutSize = layoutsize;
-    this->screenLayoutLong = layoutlong;
-    this->smallestScreenWidthDp = smallestwidthdp;
-    this->screenWidthDp = widthdp;
-    this->screenHeightDp = heightdp;
-    this->orientation = orient;
-    this->uiModeType = uiModeType;
-    this->uiModeNight = uiModeNight;
-    this->density = den;
-    this->touchscreen = touch;
-    this->keysHidden = keysHidden;
-    this->keyboard = key;
-    this->navHidden = navHidden;
-    this->navigation = nav;
-    this->screenSize = size;
-    this->layoutDirection = layoutDir;
-    this->version = vers;
-
-    // what is this anyway?
-    this->vendor = "";
-
+    *resType = type;
     return true;
 }
 
 String8
-AaptGroupEntry::toString() const
-{
-    String8 s = this->mcc;
-    s += ",";
-    s += this->mnc;
-    s += ",";
-    s += locale.toDirName();
-    s += ",";
-    s += layoutDirection;
-    s += ",";
-    s += smallestScreenWidthDp;
-    s += ",";
-    s += screenWidthDp;
-    s += ",";
-    s += screenHeightDp;
-    s += ",";
-    s += screenLayoutSize;
-    s += ",";
-    s += screenLayoutLong;
-    s += ",";
-    s += this->orientation;
-    s += ",";
-    s += uiModeType;
-    s += ",";
-    s += uiModeNight;
-    s += ",";
-    s += density;
-    s += ",";
-    s += touchscreen;
-    s += ",";
-    s += keysHidden;
-    s += ",";
-    s += keyboard;
-    s += ",";
-    s += navHidden;
-    s += ",";
-    s += navigation;
-    s += ",";
-    s += screenSize;
-    s += ",";
-    s += version;
-    return s;
-}
-
-String8
 AaptGroupEntry::toDirName(const String8& resType) const
 {
     String8 s = resType;
-    if (this->mcc != "") {
+    String8 params = mParams.toString();
+    if (params.length() > 0) {
         if (s.length() > 0) {
             s += "-";
         }
-        s += mcc;
+        s += params;
     }
-    if (this->mnc != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += mnc;
-    }
-
-    const String8 localeComponent = locale.toDirName();
-    if (localeComponent != "") {
-         if (s.length() > 0) {
-             s += "-";
-         }
-         s += localeComponent;
-    }
-
-    if (this->layoutDirection != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += layoutDirection;
-    }
-    if (this->smallestScreenWidthDp != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += smallestScreenWidthDp;
-    }
-    if (this->screenWidthDp != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += screenWidthDp;
-    }
-    if (this->screenHeightDp != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += screenHeightDp;
-    }
-    if (this->screenLayoutSize != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += screenLayoutSize;
-    }
-    if (this->screenLayoutLong != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += screenLayoutLong;
-    }
-    if (this->orientation != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += orientation;
-    }
-    if (this->uiModeType != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += uiModeType;
-    }
-    if (this->uiModeNight != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += uiModeNight;
-    }
-    if (this->density != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += density;
-    }
-    if (this->touchscreen != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += touchscreen;
-    }
-    if (this->keysHidden != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += keysHidden;
-    }
-    if (this->keyboard != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += keyboard;
-    }
-    if (this->navHidden != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += navHidden;
-    }
-    if (this->navigation != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += navigation;
-    }
-    if (this->screenSize != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += screenSize;
-    }
-    if (this->version != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += version;
-    }
-
     return s;
 }
 
-bool AaptGroupEntry::getMccName(const char* name,
-                                    ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->mcc = 0;
-        return true;
-    }
-    const char* c = name;
-    if (tolower(*c) != 'm') return false;
-    c++;
-    if (tolower(*c) != 'c') return false;
-    c++;
-    if (tolower(*c) != 'c') return false;
-    c++;
-
-    const char* val = c;
-
-    while (*c >= '0' && *c <= '9') {
-        c++;
-    }
-    if (*c != 0) return false;
-    if (c-val != 3) return false;
-
-    int d = atoi(val);
-    if (d != 0) {
-        if (out) out->mcc = d;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getMncName(const char* name,
-                                    ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->mcc = 0;
-        return true;
-    }
-    const char* c = name;
-    if (tolower(*c) != 'm') return false;
-    c++;
-    if (tolower(*c) != 'n') return false;
-    c++;
-    if (tolower(*c) != 'c') return false;
-    c++;
-
-    const char* val = c;
-
-    while (*c >= '0' && *c <= '9') {
-        c++;
-    }
-    if (*c != 0) return false;
-    if (c-val == 0 || c-val > 3) return false;
-
-    if (out) {
-        out->mnc = atoi(val);
-        if (out->mnc == 0) {
-            out->mnc = ACONFIGURATION_MNC_ZERO;
-        }
-    }
-
-    return true;
-}
-
-bool AaptGroupEntry::getLayoutDirectionName(const char* name, ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
-                | ResTable_config::LAYOUTDIR_ANY;
-        return true;
-    } else if (strcmp(name, "ldltr") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
-                | ResTable_config::LAYOUTDIR_LTR;
-        return true;
-    } else if (strcmp(name, "ldrtl") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
-                | ResTable_config::LAYOUTDIR_RTL;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getScreenLayoutSizeName(const char* name,
-                                     ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
-                | ResTable_config::SCREENSIZE_ANY;
-        return true;
-    } else if (strcmp(name, "small") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
-                | ResTable_config::SCREENSIZE_SMALL;
-        return true;
-    } else if (strcmp(name, "normal") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
-                | ResTable_config::SCREENSIZE_NORMAL;
-        return true;
-    } else if (strcmp(name, "large") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
-                | ResTable_config::SCREENSIZE_LARGE;
-        return true;
-    } else if (strcmp(name, "xlarge") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
-                | ResTable_config::SCREENSIZE_XLARGE;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getScreenLayoutLongName(const char* name,
-                                     ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
-                | ResTable_config::SCREENLONG_ANY;
-        return true;
-    } else if (strcmp(name, "long") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
-                | ResTable_config::SCREENLONG_YES;
-        return true;
-    } else if (strcmp(name, "notlong") == 0) {
-        if (out) out->screenLayout =
-                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
-                | ResTable_config::SCREENLONG_NO;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getOrientationName(const char* name,
-                                        ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->orientation = out->ORIENTATION_ANY;
-        return true;
-    } else if (strcmp(name, "port") == 0) {
-        if (out) out->orientation = out->ORIENTATION_PORT;
-        return true;
-    } else if (strcmp(name, "land") == 0) {
-        if (out) out->orientation = out->ORIENTATION_LAND;
-        return true;
-    } else if (strcmp(name, "square") == 0) {
-        if (out) out->orientation = out->ORIENTATION_SQUARE;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getUiModeTypeName(const char* name,
-                                       ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->uiMode =
-                (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
-                | ResTable_config::UI_MODE_TYPE_ANY;
-        return true;
-    } else if (strcmp(name, "desk") == 0) {
-      if (out) out->uiMode =
-              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
-              | ResTable_config::UI_MODE_TYPE_DESK;
-        return true;
-    } else if (strcmp(name, "car") == 0) {
-      if (out) out->uiMode =
-              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
-              | ResTable_config::UI_MODE_TYPE_CAR;
-        return true;
-    } else if (strcmp(name, "television") == 0) {
-      if (out) out->uiMode =
-              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
-              | ResTable_config::UI_MODE_TYPE_TELEVISION;
-        return true;
-    } else if (strcmp(name, "appliance") == 0) {
-      if (out) out->uiMode =
-              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
-              | ResTable_config::UI_MODE_TYPE_APPLIANCE;
-        return true;
-    } else if (strcmp(name, "watch") == 0) {
-      if (out) out->uiMode =
-              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
-              | ResTable_config::UI_MODE_TYPE_WATCH;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getUiModeNightName(const char* name,
-                                          ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->uiMode =
-                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
-                | ResTable_config::UI_MODE_NIGHT_ANY;
-        return true;
-    } else if (strcmp(name, "night") == 0) {
-        if (out) out->uiMode =
-                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
-                | ResTable_config::UI_MODE_NIGHT_YES;
-        return true;
-    } else if (strcmp(name, "notnight") == 0) {
-      if (out) out->uiMode =
-              (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
-              | ResTable_config::UI_MODE_NIGHT_NO;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getDensityName(const char* name,
-                                    ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->density = ResTable_config::DENSITY_DEFAULT;
-        return true;
-    }
-    
-    if (strcmp(name, "nodpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_NONE;
-        return true;
-    }
-    
-    if (strcmp(name, "ldpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_LOW;
-        return true;
-    }
-    
-    if (strcmp(name, "mdpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_MEDIUM;
-        return true;
-    }
-    
-    if (strcmp(name, "tvdpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_TV;
-        return true;
-    }
-    
-    if (strcmp(name, "hdpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_HIGH;
-        return true;
-    }
-
-    if (strcmp(name, "xhdpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_XHIGH;
-        return true;
-    }
-
-    if (strcmp(name, "xxhdpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_XXHIGH;
-        return true;
-    }
-
-    if (strcmp(name, "xxxhdpi") == 0) {
-        if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
-        return true;
-    }
-
-    char* c = (char*)name;
-    while (*c >= '0' && *c <= '9') {
-        c++;
-    }
-
-    // check that we have 'dpi' after the last digit.
-    if (toupper(c[0]) != 'D' ||
-            toupper(c[1]) != 'P' ||
-            toupper(c[2]) != 'I' ||
-            c[3] != 0) {
-        return false;
-    }
-
-    // temporarily replace the first letter with \0 to
-    // use atoi.
-    char tmp = c[0];
-    c[0] = '\0';
-
-    int d = atoi(name);
-    c[0] = tmp;
-
-    if (d != 0) {
-        if (out) out->density = d;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getTouchscreenName(const char* name,
-                                        ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
-        return true;
-    } else if (strcmp(name, "notouch") == 0) {
-        if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
-        return true;
-    } else if (strcmp(name, "stylus") == 0) {
-        if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
-        return true;
-    } else if (strcmp(name, "finger") == 0) {
-        if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getKeysHiddenName(const char* name,
-                                       ResTable_config* out)
-{
-    uint8_t mask = 0;
-    uint8_t value = 0;
-    if (strcmp(name, kWildcardName) == 0) {
-        mask = ResTable_config::MASK_KEYSHIDDEN;
-        value = ResTable_config::KEYSHIDDEN_ANY;
-    } else if (strcmp(name, "keysexposed") == 0) {
-        mask = ResTable_config::MASK_KEYSHIDDEN;
-        value = ResTable_config::KEYSHIDDEN_NO;
-    } else if (strcmp(name, "keyshidden") == 0) {
-        mask = ResTable_config::MASK_KEYSHIDDEN;
-        value = ResTable_config::KEYSHIDDEN_YES;
-    } else if (strcmp(name, "keyssoft") == 0) {
-        mask = ResTable_config::MASK_KEYSHIDDEN;
-        value = ResTable_config::KEYSHIDDEN_SOFT;
-    }
-
-    if (mask != 0) {
-        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getKeyboardName(const char* name,
-                                        ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->keyboard = out->KEYBOARD_ANY;
-        return true;
-    } else if (strcmp(name, "nokeys") == 0) {
-        if (out) out->keyboard = out->KEYBOARD_NOKEYS;
-        return true;
-    } else if (strcmp(name, "qwerty") == 0) {
-        if (out) out->keyboard = out->KEYBOARD_QWERTY;
-        return true;
-    } else if (strcmp(name, "12key") == 0) {
-        if (out) out->keyboard = out->KEYBOARD_12KEY;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getNavHiddenName(const char* name,
-                                       ResTable_config* out)
-{
-    uint8_t mask = 0;
-    uint8_t value = 0;
-    if (strcmp(name, kWildcardName) == 0) {
-        mask = ResTable_config::MASK_NAVHIDDEN;
-        value = ResTable_config::NAVHIDDEN_ANY;
-    } else if (strcmp(name, "navexposed") == 0) {
-        mask = ResTable_config::MASK_NAVHIDDEN;
-        value = ResTable_config::NAVHIDDEN_NO;
-    } else if (strcmp(name, "navhidden") == 0) {
-        mask = ResTable_config::MASK_NAVHIDDEN;
-        value = ResTable_config::NAVHIDDEN_YES;
-    }
-
-    if (mask != 0) {
-        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getNavigationName(const char* name,
-                                     ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->navigation = out->NAVIGATION_ANY;
-        return true;
-    } else if (strcmp(name, "nonav") == 0) {
-        if (out) out->navigation = out->NAVIGATION_NONAV;
-        return true;
-    } else if (strcmp(name, "dpad") == 0) {
-        if (out) out->navigation = out->NAVIGATION_DPAD;
-        return true;
-    } else if (strcmp(name, "trackball") == 0) {
-        if (out) out->navigation = out->NAVIGATION_TRACKBALL;
-        return true;
-    } else if (strcmp(name, "wheel") == 0) {
-        if (out) out->navigation = out->NAVIGATION_WHEEL;
-        return true;
-    }
-
-    return false;
-}
-
-bool AaptGroupEntry::getScreenSizeName(const char* name, ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) {
-            out->screenWidth = out->SCREENWIDTH_ANY;
-            out->screenHeight = out->SCREENHEIGHT_ANY;
-        }
-        return true;
-    }
-
-    const char* x = name;
-    while (*x >= '0' && *x <= '9') x++;
-    if (x == name || *x != 'x') return false;
-    String8 xName(name, x-name);
-    x++;
-
-    const char* y = x;
-    while (*y >= '0' && *y <= '9') y++;
-    if (y == name || *y != 0) return false;
-    String8 yName(x, y-x);
-
-    uint16_t w = (uint16_t)atoi(xName.string());
-    uint16_t h = (uint16_t)atoi(yName.string());
-    if (w < h) {
-        return false;
-    }
-
-    if (out) {
-        out->screenWidth = w;
-        out->screenHeight = h;
-    }
-
-    return true;
-}
-
-bool AaptGroupEntry::getSmallestScreenWidthDpName(const char* name, ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) {
-            out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
-        }
-        return true;
-    }
-
-    if (*name != 's') return false;
-    name++;
-    if (*name != 'w') return false;
-    name++;
-    const char* x = name;
-    while (*x >= '0' && *x <= '9') x++;
-    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
-    String8 xName(name, x-name);
-
-    if (out) {
-        out->smallestScreenWidthDp = (uint16_t)atoi(xName.string());
-    }
-
-    return true;
-}
-
-bool AaptGroupEntry::getScreenWidthDpName(const char* name, ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) {
-            out->screenWidthDp = out->SCREENWIDTH_ANY;
-        }
-        return true;
-    }
-
-    if (*name != 'w') return false;
-    name++;
-    const char* x = name;
-    while (*x >= '0' && *x <= '9') x++;
-    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
-    String8 xName(name, x-name);
-
-    if (out) {
-        out->screenWidthDp = (uint16_t)atoi(xName.string());
-    }
-
-    return true;
-}
-
-bool AaptGroupEntry::getScreenHeightDpName(const char* name, ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) {
-            out->screenHeightDp = out->SCREENWIDTH_ANY;
-        }
-        return true;
-    }
-
-    if (*name != 'h') return false;
-    name++;
-    const char* x = name;
-    while (*x >= '0' && *x <= '9') x++;
-    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
-    String8 xName(name, x-name);
-
-    if (out) {
-        out->screenHeightDp = (uint16_t)atoi(xName.string());
-    }
-
-    return true;
-}
-
-bool AaptGroupEntry::getVersionName(const char* name, ResTable_config* out)
-{
-    if (strcmp(name, kWildcardName) == 0) {
-        if (out) {
-            out->sdkVersion = out->SDKVERSION_ANY;
-            out->minorVersion = out->MINORVERSION_ANY;
-        }
-        return true;
-    }
-
-    if (*name != 'v') {
-        return false;
-    }
-
-    name++;
-    const char* s = name;
-    while (*s >= '0' && *s <= '9') s++;
-    if (s == name || *s != 0) return false;
-    String8 sdkName(name, s-name);
-
-    if (out) {
-        out->sdkVersion = (uint16_t)atoi(sdkName.string());
-        out->minorVersion = 0;
-    }
-
-    return true;
-}
-
-int AaptGroupEntry::compare(const AaptGroupEntry& o) const
-{
-    int v = mcc.compare(o.mcc);
-    if (v == 0) v = mnc.compare(o.mnc);
-    if (v == 0) v = locale.compare(o.locale);
-    if (v == 0) v = layoutDirection.compare(o.layoutDirection);
-    if (v == 0) v = vendor.compare(o.vendor);
-    if (v == 0) v = smallestScreenWidthDp.compare(o.smallestScreenWidthDp);
-    if (v == 0) v = screenWidthDp.compare(o.screenWidthDp);
-    if (v == 0) v = screenHeightDp.compare(o.screenHeightDp);
-    if (v == 0) v = screenLayoutSize.compare(o.screenLayoutSize);
-    if (v == 0) v = screenLayoutLong.compare(o.screenLayoutLong);
-    if (v == 0) v = orientation.compare(o.orientation);
-    if (v == 0) v = uiModeType.compare(o.uiModeType);
-    if (v == 0) v = uiModeNight.compare(o.uiModeNight);
-    if (v == 0) v = density.compare(o.density);
-    if (v == 0) v = touchscreen.compare(o.touchscreen);
-    if (v == 0) v = keysHidden.compare(o.keysHidden);
-    if (v == 0) v = keyboard.compare(o.keyboard);
-    if (v == 0) v = navHidden.compare(o.navHidden);
-    if (v == 0) v = navigation.compare(o.navigation);
-    if (v == 0) v = screenSize.compare(o.screenSize);
-    if (v == 0) v = version.compare(o.version);
-    return v;
-}
-
-const ResTable_config AaptGroupEntry::toParams() const
-{
-    if (!mParamsChanged) {
-        return mParams;
-    }
-
-    mParamsChanged = false;
-    ResTable_config& params = mParams;
-    memset(&params, 0, sizeof(ResTable_config));
-    getMccName(mcc.string(), &params);
-    getMncName(mnc.string(), &params);
-    locale.writeTo(&params);
-    getLayoutDirectionName(layoutDirection.string(), &params);
-    getSmallestScreenWidthDpName(smallestScreenWidthDp.string(), &params);
-    getScreenWidthDpName(screenWidthDp.string(), &params);
-    getScreenHeightDpName(screenHeightDp.string(), &params);
-    getScreenLayoutSizeName(screenLayoutSize.string(), &params);
-    getScreenLayoutLongName(screenLayoutLong.string(), &params);
-    getOrientationName(orientation.string(), &params);
-    getUiModeTypeName(uiModeType.string(), &params);
-    getUiModeNightName(uiModeNight.string(), &params);
-    getDensityName(density.string(), &params);
-    getTouchscreenName(touchscreen.string(), &params);
-    getKeysHiddenName(keysHidden.string(), &params);
-    getKeyboardName(keyboard.string(), &params);
-    getNavHiddenName(navHidden.string(), &params);
-    getNavigationName(navigation.string(), &params);
-    getScreenSizeName(screenSize.string(), &params);
-    getVersionName(version.string(), &params);
-    
-    // Fix up version number based on specified parameters.
-    int minSdk = 0;
-    if (params.smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
-            || params.screenWidthDp != ResTable_config::SCREENWIDTH_ANY
-            || params.screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
-        minSdk = SDK_HONEYCOMB_MR2;
-    } else if ((params.uiMode&ResTable_config::MASK_UI_MODE_TYPE)
-                != ResTable_config::UI_MODE_TYPE_ANY
-            ||  (params.uiMode&ResTable_config::MASK_UI_MODE_NIGHT)
-                != ResTable_config::UI_MODE_NIGHT_ANY) {
-        minSdk = SDK_FROYO;
-    } else if ((params.screenLayout&ResTable_config::MASK_SCREENSIZE)
-                != ResTable_config::SCREENSIZE_ANY
-            ||  (params.screenLayout&ResTable_config::MASK_SCREENLONG)
-                != ResTable_config::SCREENLONG_ANY
-            || params.density != ResTable_config::DENSITY_DEFAULT) {
-        minSdk = SDK_DONUT;
-    }
-    
-    if (minSdk > params.sdkVersion) {
-        params.sdkVersion = minSdk;
-    }
-    
-    return params;
-}
 
 // =========================================================================
 // =========================================================================
@@ -2229,9 +907,7 @@
     : AaptDir(String8(), String8()),
       mHavePrivateSymbols(false),
       mChanged(false), mHaveIncludedAssets(false),
-      mRes(NULL)
-{
-}
+      mRes(NULL) {}
 
 const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const {
     if (mChanged) {
@@ -2506,7 +1182,7 @@
         String8 resType;
         bool b = group.initFromDirName(entry->d_name, &resType);
         if (!b) {
-            fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(),
+            fprintf(stderr, "invalid resource directory name: %s %s\n", srcDir.string(),
                     entry->d_name);
             err = -1;
             continue;
@@ -2654,30 +1330,35 @@
 
 status_t AaptAssets::filter(Bundle* bundle)
 {
-    ResourceFilter reqFilter;
+    WeakResourceFilter reqFilter;
     status_t err = reqFilter.parse(bundle->getConfigurations());
     if (err != NO_ERROR) {
         return err;
     }
 
-    ResourceFilter prefFilter;
-    err = prefFilter.parse(bundle->getPreferredConfigurations());
-    if (err != NO_ERROR) {
-        return err;
+    uint32_t preferredDensity = 0;
+    if (bundle->getPreferredDensity().size() > 0) {
+        ResTable_config preferredConfig;
+        if (!AaptConfig::parseDensity(bundle->getPreferredDensity().string(), &preferredConfig)) {
+            fprintf(stderr, "Error parsing preferred density: %s\n",
+                    bundle->getPreferredDensity().string());
+            return UNKNOWN_ERROR;
+        }
+        preferredDensity = preferredConfig.density;
     }
 
-    if (reqFilter.isEmpty() && prefFilter.isEmpty()) {
+    if (reqFilter.isEmpty() && preferredDensity == 0) {
         return NO_ERROR;
     }
 
     if (bundle->getVerbose()) {
         if (!reqFilter.isEmpty()) {
             printf("Applying required filter: %s\n",
-                    bundle->getConfigurations());
+                    bundle->getConfigurations().string());
         }
-        if (!prefFilter.isEmpty()) {
-            printf("Applying preferred filter: %s\n",
-                    bundle->getPreferredConfigurations());
+        if (preferredDensity > 0) {
+            printf("Applying preferred density filter: %s\n",
+                    bundle->getPreferredDensity().string());
         }
     }
 
@@ -2734,89 +1415,71 @@
             }
 
             // Quick check: no preferred filters, nothing more to do.
-            if (prefFilter.isEmpty()) {
+            if (preferredDensity == 0) {
                 continue;
             }
 
             // Get the preferred density if there is one. We do not match exactly for density.
             // If our preferred density is hdpi but we only have mdpi and xhdpi resources, we
             // pick xhdpi.
-            uint32_t preferredDensity = 0;
-            const SortedVector<AxisValue>* preferredConfigs = prefFilter.configsForAxis(AXIS_DENSITY);
-            if (preferredConfigs != NULL && preferredConfigs->size() > 0) {
-                preferredDensity = (*preferredConfigs)[0].intValue;
-            }
+            for (size_t k=0; k<grp->getFiles().size(); k++) {
+                sp<AaptFile> file = grp->getFiles().valueAt(k);
+                if (k == 0 && grp->getFiles().size() == 1) {
+                    // If this is the only file left, we need to keep it.
+                    // Otherwise the resource IDs we are using will be inconsistent
+                    // with what we get when not stripping.  Sucky, but at least
+                    // for now we can rely on the back-end doing another filtering
+                    // pass to take this out and leave us with this resource name
+                    // containing no entries.
+                    continue;
+                }
+                if (file->getPath().getPathExtension() == ".xml") {
+                    // We can't remove .xml files at this point, because when
+                    // we parse them they may add identifier resources, so
+                    // removing them can cause our resource identifiers to
+                    // become inconsistent.
+                    continue;
+                }
+                const ResTable_config& config(file->getGroupEntry().toParams());
+                if (config.density != 0 && config.density != preferredDensity) {
+                    // This is a resource we would prefer not to have.  Check
+                    // to see if have a similar variation that we would like
+                    // to have and, if so, we can drop it.
+                    uint32_t bestDensity = config.density;
 
-            // Now deal with preferred configurations.
-            for (int axis=AXIS_START; axis<=AXIS_END; axis++) {
-                for (size_t k=0; k<grp->getFiles().size(); k++) {
-                    sp<AaptFile> file = grp->getFiles().valueAt(k);
-                    if (k == 0 && grp->getFiles().size() == 1) {
-                        // If this is the only file left, we need to keep it.
-                        // Otherwise the resource IDs we are using will be inconsistent
-                        // with what we get when not stripping.  Sucky, but at least
-                        // for now we can rely on the back-end doing another filtering
-                        // pass to take this out and leave us with this resource name
-                        // containing no entries.
-                        continue;
-                    }
-                    if (file->getPath().getPathExtension() == ".xml") {
-                        // We can't remove .xml files at this point, because when
-                        // we parse them they may add identifier resources, so
-                        // removing them can cause our resource identifiers to
-                        // become inconsistent.
-                        continue;
-                    }
-                    const ResTable_config& config(file->getGroupEntry().toParams());
-                    if (!prefFilter.match(axis, config)) {
-                        // This is a resource we would prefer not to have.  Check
-                        // to see if have a similar variation that we would like
-                        // to have and, if so, we can drop it.
-
-                        uint32_t bestDensity = config.density;
-
-                        for (size_t m=0; m<grp->getFiles().size(); m++) {
-                            if (m == k) continue;
-                            sp<AaptFile> mfile = grp->getFiles().valueAt(m);
-                            const ResTable_config& mconfig(mfile->getGroupEntry().toParams());
-                            if (AaptGroupEntry::configSameExcept(config, mconfig, axis)) {
-                                if (axis == AXIS_DENSITY && preferredDensity > 0) {
-                                    // See if there is a better density resource
-                                    if (mconfig.density < bestDensity &&
-                                            mconfig.density > preferredDensity &&
-                                            bestDensity > preferredDensity) {
-                                        // This density is between our best density and
-                                        // the preferred density, therefore it is better.
-                                        bestDensity = mconfig.density;
-                                    } else if (mconfig.density > bestDensity &&
-                                            bestDensity < preferredDensity) {
-                                        // This density is better than our best density and
-                                        // our best density was smaller than our preferred
-                                        // density, so it is better.
-                                        bestDensity = mconfig.density;
-                                    }
-                                } else if (prefFilter.match(axis, mconfig)) {
-                                    if (bundle->getVerbose()) {
-                                        printf("Pruning unneeded resource: %s\n",
-                                                file->getPrintableSource().string());
-                                    }
-                                    grp->removeFile(k);
-                                    k--;
-                                    break;
-                                }
-                            }
+                    for (size_t m=0; m<grp->getFiles().size(); m++) {
+                        if (m == k) {
+                            continue;
                         }
 
-                        if (axis == AXIS_DENSITY && preferredDensity > 0 &&
-                                bestDensity != config.density) {
-                            if (bundle->getVerbose()) {
-                                printf("Pruning unneeded resource: %s\n",
-                                        file->getPrintableSource().string());
+                        sp<AaptFile> mfile = grp->getFiles().valueAt(m);
+                        const ResTable_config& mconfig(mfile->getGroupEntry().toParams());
+                        if (AaptConfig::isSameExcept(config, mconfig, ResTable_config::CONFIG_DENSITY)) {
+                            // See if there is a better density resource
+                            if (mconfig.density < bestDensity &&
+                                    mconfig.density > preferredDensity &&
+                                    bestDensity > preferredDensity) {
+                                // This density is between our best density and
+                                // the preferred density, therefore it is better.
+                                bestDensity = mconfig.density;
+                            } else if (mconfig.density > bestDensity &&
+                                    bestDensity < preferredDensity) {
+                                // This density is better than our best density and
+                                // our best density was smaller than our preferred
+                                // density, so it is better.
+                                bestDensity = mconfig.density;
                             }
-                            grp->removeFile(k);
-                            k--;
                         }
                     }
+
+                    if (bestDensity != config.density) {
+                        if (bundle->getVerbose()) {
+                            printf("Pruning unneeded resource: %s\n",
+                                    file->getPrintableSource().string());
+                        }
+                        grp->removeFile(k);
+                        k--;
+                    }
                 }
             }
         }
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index 82dda5f..0c2576a 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -6,22 +6,24 @@
 #ifndef __AAPT_ASSETS_H
 #define __AAPT_ASSETS_H
 
-#include <stdlib.h>
 #include <androidfw/AssetManager.h>
 #include <androidfw/ResourceTypes.h>
+#include <stdlib.h>
+#include <set>
 #include <utils/KeyedVector.h>
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
+
+#include "AaptConfig.h"
+#include "Bundle.h"
+#include "ConfigDescription.h"
+#include "SourcePos.h"
 #include "ZipFile.h"
 
-#include "Bundle.h"
-#include "SourcePos.h"
-
 using namespace android;
 
-
 extern const char * const gDefaultIgnoreAssets;
 extern const char * gUserIgnoreAssets;
 
@@ -82,9 +84,6 @@
          return memcmp(this, &other, sizeof(AaptLocaleValue));
      }
 
-     static void splitAndLowerCase(const char* const chars, Vector<String8>* parts,
-             const char separator);
-
      inline bool operator<(const AaptLocaleValue& o) const { return compare(o) < 0; }
      inline bool operator<=(const AaptLocaleValue& o) const { return compare(o) <= 0; }
      inline bool operator==(const AaptLocaleValue& o) const { return compare(o) == 0; }
@@ -98,31 +97,6 @@
      void setVariant(const char* variant);
 };
 
-struct AxisValue {
-    // Used for all axes except AXIS_LOCALE, which is represented
-    // as a AaptLocaleValue value.
-    int intValue;
-    AaptLocaleValue localeValue;
-
-    AxisValue() : intValue(0) {
-    }
-
-    inline int compare(const AxisValue &other) const  {
-        if (intValue != other.intValue) {
-            return intValue - other.intValue;
-        }
-
-        return localeValue.compare(other.localeValue);
-    }
-
-    inline bool operator<(const AxisValue& o) const { return compare(o) < 0; }
-    inline bool operator<=(const AxisValue& o) const { return compare(o) <= 0; }
-    inline bool operator==(const AxisValue& o) const { return compare(o) == 0; }
-    inline bool operator!=(const AxisValue& o) const { return compare(o) != 0; }
-    inline bool operator>=(const AxisValue& o) const { return compare(o) >= 0; }
-    inline bool operator>(const AxisValue& o) const { return compare(o) > 0; }
-};
-
 /**
  * This structure contains a specific variation of a single file out
  * of all the variations it can have that we can have.
@@ -130,23 +104,11 @@
 struct AaptGroupEntry
 {
 public:
-    AaptGroupEntry() : mParamsChanged(true) {
-        memset(&mParams, 0, sizeof(ResTable_config));
-    }
-
     bool initFromDirName(const char* dir, String8* resType);
 
-    static bool parseFilterNamePart(const String8& part, int* axis, AxisValue* value);
+    inline const ConfigDescription& toParams() const { return mParams; }
 
-    static AxisValue getConfigValueForAxis(const ResTable_config& config, int axis);
-
-    static bool configSameExcept(const ResTable_config& config,
-            const ResTable_config& otherConfig, int axis);
-
-    int compare(const AaptGroupEntry& o) const;
-
-    const ResTable_config toParams() const;
-
+    inline int compare(const AaptGroupEntry& o) const { return mParams.compareLogical(o.mParams); }
     inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; }
     inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; }
     inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; }
@@ -154,56 +116,13 @@
     inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; }
     inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; }
 
-    String8 toString() const;
+    String8 toString() const { return mParams.toString(); }
     String8 toDirName(const String8& resType) const;
 
-    const String8& getVersionString() const { return version; }
+    const String8 getVersionString() const { return AaptConfig::getVersion(mParams); }
 
 private:
-    static bool getMccName(const char* name, ResTable_config* out = NULL);
-    static bool getMncName(const char* name, ResTable_config* out = NULL);
-    static bool getScreenLayoutSizeName(const char* name, ResTable_config* out = NULL);
-    static bool getScreenLayoutLongName(const char* name, ResTable_config* out = NULL);
-    static bool getOrientationName(const char* name, ResTable_config* out = NULL);
-    static bool getUiModeTypeName(const char* name, ResTable_config* out = NULL);
-    static bool getUiModeNightName(const char* name, ResTable_config* out = NULL);
-    static bool getDensityName(const char* name, ResTable_config* out = NULL);
-    static bool getTouchscreenName(const char* name, ResTable_config* out = NULL);
-    static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL);
-    static bool getKeyboardName(const char* name, ResTable_config* out = NULL);
-    static bool getNavigationName(const char* name, ResTable_config* out = NULL);
-    static bool getNavHiddenName(const char* name, ResTable_config* out = NULL);
-    static bool getScreenSizeName(const char* name, ResTable_config* out = NULL);
-    static bool getSmallestScreenWidthDpName(const char* name, ResTable_config* out = NULL);
-    static bool getScreenWidthDpName(const char* name, ResTable_config* out = NULL);
-    static bool getScreenHeightDpName(const char* name, ResTable_config* out = NULL);
-    static bool getLayoutDirectionName(const char* name, ResTable_config* out = NULL);
-    static bool getVersionName(const char* name, ResTable_config* out = NULL);
-
-    String8 mcc;
-    String8 mnc;
-    AaptLocaleValue locale;
-    String8 vendor;
-    String8 smallestScreenWidthDp;
-    String8 screenWidthDp;
-    String8 screenHeightDp;
-    String8 screenLayoutSize;
-    String8 screenLayoutLong;
-    String8 orientation;
-    String8 uiModeType;
-    String8 uiModeNight;
-    String8 density;
-    String8 touchscreen;
-    String8 keysHidden;
-    String8 keyboard;
-    String8 navHidden;
-    String8 navigation;
-    String8 screenSize;
-    String8 layoutDirection;
-    String8 version;
-
-    mutable bool mParamsChanged;
-    mutable ResTable_config mParams;
+    ConfigDescription mParams;
 };
 
 inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
new file mode 100644
index 0000000..69a9c7f
--- /dev/null
+++ b/tools/aapt/AaptConfig.cpp
@@ -0,0 +1,790 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include <androidfw/ResourceTypes.h>
+#include <ctype.h>
+
+#include "AaptConfig.h"
+#include "AaptAssets.h"
+#include "AaptUtil.h"
+#include "ResourceFilter.h"
+
+using android::String8;
+using android::Vector;
+using android::ResTable_config;
+
+namespace AaptConfig {
+
+static const char* kWildcardName = "any";
+
+bool parse(const String8& str, ConfigDescription* out) {
+    Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '-');
+
+    ConfigDescription config;
+    AaptLocaleValue locale;
+    ssize_t index = 0;
+    ssize_t localeIndex = 0;
+    const ssize_t N = parts.size();
+    const char* part = parts[index].string();
+
+    if (str.length() == 0) {
+        goto success;
+    }
+
+    if (parseMcc(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseMnc(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    // Locale spans a few '-' separators, so we let it
+    // control the index.
+    localeIndex = locale.initFromDirName(parts, index);
+    if (localeIndex < 0) {
+        return false;
+    } else if (localeIndex > index) {
+        locale.writeTo(&config);
+        index = localeIndex;
+        if (index >= N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseLayoutDirection(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseSmallestScreenWidthDp(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenWidthDp(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenHeightDp(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenLayoutSize(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenLayoutLong(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseOrientation(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseUiModeType(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseUiModeNight(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseDensity(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseTouchscreen(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseKeysHidden(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseKeyboard(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseNavHidden(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseNavigation(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenSize(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseVersion(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    // Unrecognized.
+    return false;
+
+success:
+    if (out != NULL) {
+        applyVersionForCompatibility(&config);
+        *out = config;
+    }
+    return true;
+}
+
+bool parseCommaSeparatedList(const String8& str, std::set<ConfigDescription>* outSet) {
+    Vector<String8> parts = AaptUtil::splitAndLowerCase(str, ',');
+    const size_t N = parts.size();
+    for (size_t i = 0; i < N; i++) {
+        ConfigDescription config;
+        if (!parse(parts[i], &config)) {
+            return false;
+        }
+        outSet->insert(config);
+    }
+    return true;
+}
+
+void applyVersionForCompatibility(ConfigDescription* config) {
+    if (config == NULL) {
+        return;
+    }
+
+    uint16_t minSdk = 0;
+    if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+        minSdk = SDK_HONEYCOMB_MR2;
+    } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
+                != ResTable_config::UI_MODE_TYPE_ANY
+            ||  (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT)
+                != ResTable_config::UI_MODE_NIGHT_ANY) {
+        minSdk = SDK_FROYO;
+    } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE)
+                != ResTable_config::SCREENSIZE_ANY
+            ||  (config->screenLayout & ResTable_config::MASK_SCREENLONG)
+                != ResTable_config::SCREENLONG_ANY
+            || config->density != ResTable_config::DENSITY_DEFAULT) {
+        minSdk = SDK_DONUT;
+    }
+
+    if (minSdk > config->sdkVersion) {
+        config->sdkVersion = minSdk;
+    }
+}
+
+bool parseMcc(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->mcc = 0;
+        return true;
+    }
+    const char* c = name;
+    if (tolower(*c) != 'm') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+
+    const char* val = c;
+
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+    if (*c != 0) return false;
+    if (c-val != 3) return false;
+
+    int d = atoi(val);
+    if (d != 0) {
+        if (out) out->mcc = d;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseMnc(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->mcc = 0;
+        return true;
+    }
+    const char* c = name;
+    if (tolower(*c) != 'm') return false;
+    c++;
+    if (tolower(*c) != 'n') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+
+    const char* val = c;
+
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+    if (*c != 0) return false;
+    if (c-val == 0 || c-val > 3) return false;
+
+    if (out) {
+        out->mnc = atoi(val);
+        if (out->mnc == 0) {
+            out->mnc = ACONFIGURATION_MNC_ZERO;
+        }
+    }
+
+    return true;
+}
+
+bool parseLayoutDirection(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_ANY;
+        return true;
+    } else if (strcmp(name, "ldltr") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_LTR;
+        return true;
+    } else if (strcmp(name, "ldrtl") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_RTL;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseScreenLayoutSize(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_ANY;
+        return true;
+    } else if (strcmp(name, "small") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_SMALL;
+        return true;
+    } else if (strcmp(name, "normal") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_NORMAL;
+        return true;
+    } else if (strcmp(name, "large") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_LARGE;
+        return true;
+    } else if (strcmp(name, "xlarge") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_XLARGE;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_ANY;
+        return true;
+    } else if (strcmp(name, "long") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_YES;
+        return true;
+    } else if (strcmp(name, "notlong") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_NO;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseOrientation(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->orientation = out->ORIENTATION_ANY;
+        return true;
+    } else if (strcmp(name, "port") == 0) {
+        if (out) out->orientation = out->ORIENTATION_PORT;
+        return true;
+    } else if (strcmp(name, "land") == 0) {
+        if (out) out->orientation = out->ORIENTATION_LAND;
+        return true;
+    } else if (strcmp(name, "square") == 0) {
+        if (out) out->orientation = out->ORIENTATION_SQUARE;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseUiModeType(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+                | ResTable_config::UI_MODE_TYPE_ANY;
+        return true;
+    } else if (strcmp(name, "desk") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_DESK;
+        return true;
+    } else if (strcmp(name, "car") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_CAR;
+        return true;
+    } else if (strcmp(name, "television") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_TELEVISION;
+        return true;
+    } else if (strcmp(name, "appliance") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_APPLIANCE;
+        return true;
+    } else if (strcmp(name, "watch") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_WATCH;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseUiModeNight(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+                | ResTable_config::UI_MODE_NIGHT_ANY;
+        return true;
+    } else if (strcmp(name, "night") == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+                | ResTable_config::UI_MODE_NIGHT_YES;
+        return true;
+    } else if (strcmp(name, "notnight") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+              | ResTable_config::UI_MODE_NIGHT_NO;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseDensity(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->density = ResTable_config::DENSITY_DEFAULT;
+        return true;
+    }
+
+    if (strcmp(name, "nodpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_NONE;
+        return true;
+    }
+
+    if (strcmp(name, "ldpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_LOW;
+        return true;
+    }
+
+    if (strcmp(name, "mdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_MEDIUM;
+        return true;
+    }
+
+    if (strcmp(name, "tvdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_TV;
+        return true;
+    }
+
+    if (strcmp(name, "hdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_HIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XHIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xxhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xxxhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+        return true;
+    }
+
+    char* c = (char*)name;
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+
+    // check that we have 'dpi' after the last digit.
+    if (toupper(c[0]) != 'D' ||
+            toupper(c[1]) != 'P' ||
+            toupper(c[2]) != 'I' ||
+            c[3] != 0) {
+        return false;
+    }
+
+    // temporarily replace the first letter with \0 to
+    // use atoi.
+    char tmp = c[0];
+    c[0] = '\0';
+
+    int d = atoi(name);
+    c[0] = tmp;
+
+    if (d != 0) {
+        if (out) out->density = d;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseTouchscreen(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+        return true;
+    } else if (strcmp(name, "notouch") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+        return true;
+    } else if (strcmp(name, "stylus") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+        return true;
+    } else if (strcmp(name, "finger") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseKeysHidden(const char* name, ResTable_config* out) {
+    uint8_t mask = 0;
+    uint8_t value = 0;
+    if (strcmp(name, kWildcardName) == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_ANY;
+    } else if (strcmp(name, "keysexposed") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_NO;
+    } else if (strcmp(name, "keyshidden") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_YES;
+    } else if (strcmp(name, "keyssoft") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_SOFT;
+    }
+
+    if (mask != 0) {
+        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseKeyboard(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->keyboard = out->KEYBOARD_ANY;
+        return true;
+    } else if (strcmp(name, "nokeys") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+        return true;
+    } else if (strcmp(name, "qwerty") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_QWERTY;
+        return true;
+    } else if (strcmp(name, "12key") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_12KEY;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseNavHidden(const char* name, ResTable_config* out) {
+    uint8_t mask = 0;
+    uint8_t value = 0;
+    if (strcmp(name, kWildcardName) == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_ANY;
+    } else if (strcmp(name, "navexposed") == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_NO;
+    } else if (strcmp(name, "navhidden") == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_YES;
+    }
+
+    if (mask != 0) {
+        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseNavigation(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->navigation = out->NAVIGATION_ANY;
+        return true;
+    } else if (strcmp(name, "nonav") == 0) {
+        if (out) out->navigation = out->NAVIGATION_NONAV;
+        return true;
+    } else if (strcmp(name, "dpad") == 0) {
+        if (out) out->navigation = out->NAVIGATION_DPAD;
+        return true;
+    } else if (strcmp(name, "trackball") == 0) {
+        if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+        return true;
+    } else if (strcmp(name, "wheel") == 0) {
+        if (out) out->navigation = out->NAVIGATION_WHEEL;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseScreenSize(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenWidth = out->SCREENWIDTH_ANY;
+            out->screenHeight = out->SCREENHEIGHT_ANY;
+        }
+        return true;
+    }
+
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || *x != 'x') return false;
+    String8 xName(name, x-name);
+    x++;
+
+    const char* y = x;
+    while (*y >= '0' && *y <= '9') y++;
+    if (y == name || *y != 0) return false;
+    String8 yName(x, y-x);
+
+    uint16_t w = (uint16_t)atoi(xName.string());
+    uint16_t h = (uint16_t)atoi(yName.string());
+    if (w < h) {
+        return false;
+    }
+
+    if (out) {
+        out->screenWidth = w;
+        out->screenHeight = h;
+    }
+
+    return true;
+}
+
+bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 's') return false;
+    name++;
+    if (*name != 'w') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    String8 xName(name, x-name);
+
+    if (out) {
+        out->smallestScreenWidthDp = (uint16_t)atoi(xName.string());
+    }
+
+    return true;
+}
+
+bool parseScreenWidthDp(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenWidthDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'w') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    String8 xName(name, x-name);
+
+    if (out) {
+        out->screenWidthDp = (uint16_t)atoi(xName.string());
+    }
+
+    return true;
+}
+
+bool parseScreenHeightDp(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenHeightDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'h') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    String8 xName(name, x-name);
+
+    if (out) {
+        out->screenHeightDp = (uint16_t)atoi(xName.string());
+    }
+
+    return true;
+}
+
+bool parseVersion(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->sdkVersion = out->SDKVERSION_ANY;
+            out->minorVersion = out->MINORVERSION_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'v') {
+        return false;
+    }
+
+    name++;
+    const char* s = name;
+    while (*s >= '0' && *s <= '9') s++;
+    if (s == name || *s != 0) return false;
+    String8 sdkName(name, s-name);
+
+    if (out) {
+        out->sdkVersion = (uint16_t)atoi(sdkName.string());
+        out->minorVersion = 0;
+    }
+
+    return true;
+}
+
+String8 getVersion(const ResTable_config& config) {
+    return String8::format("v%u", config.sdkVersion);
+}
+
+bool isSameExcept(const ResTable_config& a, const ResTable_config& b, int axisMask) {
+    return a.diff(b) == axisMask;
+}
+
+} // namespace AaptConfig
diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h
new file mode 100644
index 0000000..2963539
--- /dev/null
+++ b/tools/aapt/AaptConfig.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 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 __AAPT_CONFIG_H
+#define __AAPT_CONFIG_H
+
+#include <set>
+#include <utils/String8.h>
+
+#include "ConfigDescription.h"
+
+/**
+ * Utility methods for dealing with configurations.
+ */
+namespace AaptConfig {
+
+/**
+ * Parse a string of the form 'fr-sw600dp-land' and fill in the
+ * given ResTable_config with resulting configuration parameters.
+ *
+ * The resulting configuration has the appropriate sdkVersion defined
+ * for backwards compatibility.
+ */
+bool parse(const android::String8& str, ConfigDescription* out = NULL);
+
+/**
+ * Parse a comma separated list of configuration strings. Duplicate configurations
+ * will be removed.
+ *
+ * Example input: "fr,de-land,fr-sw600dp-land"
+ */
+bool parseCommaSeparatedList(const android::String8& str, std::set<ConfigDescription>* outSet);
+
+/**
+ * If the configuration uses an axis that was added after
+ * the original Android release, make sure the SDK version
+ * is set accordingly.
+ */
+void applyVersionForCompatibility(ConfigDescription* config);
+
+// Individual axis
+bool parseMcc(const char* str, android::ResTable_config* out = NULL);
+bool parseMnc(const char* str, android::ResTable_config* out = NULL);
+bool parseLayoutDirection(const char* str, android::ResTable_config* out = NULL);
+bool parseSmallestScreenWidthDp(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenWidthDp(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenHeightDp(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenLayoutSize(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenLayoutLong(const char* str, android::ResTable_config* out = NULL);
+bool parseOrientation(const char* str, android::ResTable_config* out = NULL);
+bool parseUiModeType(const char* str, android::ResTable_config* out = NULL);
+bool parseUiModeNight(const char* str, android::ResTable_config* out = NULL);
+bool parseDensity(const char* str, android::ResTable_config* out = NULL);
+bool parseTouchscreen(const char* str, android::ResTable_config* out = NULL);
+bool parseKeysHidden(const char* str, android::ResTable_config* out = NULL);
+bool parseKeyboard(const char* str, android::ResTable_config* out = NULL);
+bool parseNavHidden(const char* str, android::ResTable_config* out = NULL);
+bool parseNavigation(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenSize(const char* str, android::ResTable_config* out = NULL);
+bool parseVersion(const char* str, android::ResTable_config* out = NULL);
+
+android::String8 getVersion(const android::ResTable_config& config);
+
+/**
+ * Returns true if the two configurations only differ by the specified axis.
+ * The axis mask is a bitmask of CONFIG_* constants.
+ */
+bool isSameExcept(const android::ResTable_config& a, const android::ResTable_config& b, int configMask);
+
+} // namespace AaptConfig
+
+#endif // __AAPT_CONFIG_H
diff --git a/tools/aapt/AaptUtil.cpp b/tools/aapt/AaptUtil.cpp
new file mode 100644
index 0000000..293e144
--- /dev/null
+++ b/tools/aapt/AaptUtil.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include "AaptUtil.h"
+
+using android::Vector;
+using android::String8;
+
+namespace AaptUtil {
+
+Vector<String8> split(const String8& str, const char sep) {
+    Vector<String8> parts;
+    const char* p = str.string();
+    const char* q;
+
+    while (true) {
+        q = strchr(p, sep);
+        if (q == NULL) {
+            parts.add(String8(p, strlen(p)));
+            return parts;
+        }
+
+        parts.add(String8(p, q-p));
+        p = q + 1;
+    }
+    return parts;
+}
+
+Vector<String8> splitAndLowerCase(const String8& str, const char sep) {
+    Vector<String8> parts;
+    const char* p = str.string();
+    const char* q;
+
+    while (true) {
+        q = strchr(p, sep);
+        if (q == NULL) {
+            String8 val(p, strlen(p));
+            val.toLower();
+            parts.add(val);
+            return parts;
+        }
+
+        String8 val(p, q-p);
+        val.toLower();
+        parts.add(val);
+        p = q + 1;
+    }
+    return parts;
+}
+
+} // namespace AaptUtil
diff --git a/tools/aapt/AaptUtil.h b/tools/aapt/AaptUtil.h
new file mode 100644
index 0000000..47a704a
--- /dev/null
+++ b/tools/aapt/AaptUtil.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 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 __AAPT_UTIL_H
+#define __AAPT_UTIL_H
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace AaptUtil {
+
+android::Vector<android::String8> split(const android::String8& str, const char sep);
+android::Vector<android::String8> splitAndLowerCase(const android::String8& str, const char sep);
+
+} // namespace AaptUtil
+
+#endif // __AAPT_UTIL_H
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index 806f8ff..700afa1 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -1,104 +1,168 @@
-# 
-# Copyright 2006 The Android Open Source Project
 #
-# Android Asset Packaging Tool
+# Copyright (C) 2014 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.
 #
 
 # This tool is prebuilt if we're doing an app-only build.
 ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),)
 
+# ==========================================================
+# Setup some common variables for the different build
+# targets here.
+# ==========================================================
+LOCAL_PATH:= $(call my-dir)
 
-aapt_src_files := \
-	AaptAssets.cpp \
-	Command.cpp \
-	CrunchCache.cpp \
-	FileFinder.cpp \
-	Main.cpp \
-	Package.cpp \
-	StringPool.cpp \
-	XMLNode.cpp \
-	ResourceFilter.cpp \
-	ResourceIdCache.cpp \
-	ResourceTable.cpp \
-	Images.cpp \
-	Resource.cpp \
+aaptMain := Main.cpp
+aaptSources := \
+    AaptAssets.cpp \
+    AaptConfig.cpp \
+    AaptUtil.cpp \
+    ApkBuilder.cpp \
+    Command.cpp \
+    CrunchCache.cpp \
+    FileFinder.cpp \
+    Package.cpp \
+    StringPool.cpp \
+    XMLNode.cpp \
+    ResourceFilter.cpp \
+    ResourceIdCache.cpp \
+    ResourceTable.cpp \
+    Images.cpp \
+    Resource.cpp \
     pseudolocalize.cpp \
     SourcePos.cpp \
-	WorkQueue.cpp \
+    WorkQueue.cpp \
     ZipEntry.cpp \
     ZipFile.cpp \
-	qsort_r_compat.c
+    qsort_r_compat.c
 
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
+aaptTests := \
+    tests/AaptConfig_test.cpp \
+    tests/AaptGroupEntry_test.cpp \
+    tests/ResourceFilter_test.cpp
 
-LOCAL_SRC_FILES := $(aapt_src_files)
+aaptCIncludes := \
+    external/libpng \
+    external/zlib
 
-LOCAL_CFLAGS += -Wno-format-y2k
-ifeq (darwin,$(HOST_OS))
-LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS
-endif
-
-LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
-
-LOCAL_C_INCLUDES += external/libpng
-LOCAL_C_INCLUDES += external/zlib
-
-LOCAL_STATIC_LIBRARIES := \
-	libandroidfw \
-	libutils \
-	libcutils \
-	libexpat \
-	libpng \
-	liblog \
-	libziparchive-host
+aaptHostLdLibs :=
+aaptHostStaticLibs := \
+    libandroidfw \
+    libpng \
+    liblog \
+    libutils \
+    libcutils \
+    libexpat \
+    libziparchive-host
 
 ifeq ($(HOST_OS),linux)
-LOCAL_LDLIBS += -lrt -ldl -lpthread
+    aaptHostLdLibs += -lrt -ldl -lpthread
 endif
 
 # Statically link libz for MinGW (Win SDK under Linux),
 # and dynamically link for all others.
 ifneq ($(strip $(USE_MINGW)),)
-  LOCAL_STATIC_LIBRARIES += libz
+    aaptHostStaticLibs += libz
 else
-  LOCAL_LDLIBS += -lz
+    aaptHostLdLibs += -lz
 endif
 
+
+# ==========================================================
+# Build the host static library: libaapt
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaapt
+
+LOCAL_SRC_FILES := $(aaptSources)
+LOCAL_C_INCLUDES += $(aaptCIncludes)
+
+LOCAL_CFLAGS += -Wno-format-y2k
+LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
+ifeq (darwin,$(HOST_OS))
+LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS
+endif
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# ==========================================================
+# Build the host executable: aapt
+# ==========================================================
+include $(CLEAR_VARS)
+
 LOCAL_MODULE := aapt
 
+LOCAL_SRC_FILES := $(aaptMain)
+
+LOCAL_STATIC_LIBRARIES += \
+    libaapt \
+    $(aaptHostStaticLibs)
+LOCAL_LDLIBS += $(aaptHostLdLibs)
+
 include $(BUILD_HOST_EXECUTABLE)
 
-# aapt for running on the device
-# =========================================================
+
+# ==========================================================
+# Build the host tests: libaapt_tests
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaapt_tests
+
+LOCAL_SRC_FILES += $(aaptTests)
+LOCAL_C_INCLUDES += $(LOCAL_PATH)
+
+LOCAL_STATIC_LIBRARIES += \
+    libaapt \
+    $(aaptHostStaticLibs)
+LOCAL_LDLIBS += $(aaptHostLdLibs)
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+
+# ==========================================================
+# Build the device executable: aapt
+# ==========================================================
 ifneq ($(SDK_ONLY),true)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(aapt_src_files)
-
 LOCAL_MODULE := aapt
 
-LOCAL_C_INCLUDES += bionic
-LOCAL_C_INCLUDES += bionic/libstdc++/include
-LOCAL_C_INCLUDES += external/stlport/stlport
-LOCAL_C_INCLUDES += external/libpng
-LOCAL_C_INCLUDES += external/zlib
-
-LOCAL_CFLAGS += -Wno-non-virtual-dtor
+LOCAL_SRC_FILES := $(aaptSources) $(aaptMain)
+LOCAL_C_INCLUDES += \
+    $(aaptCIncludes) \
+    bionic \
+    external/stlport/stlport
 
 LOCAL_SHARED_LIBRARIES := \
-        libandroidfw \
-        libutils \
-        libcutils \
-        libpng \
-        liblog \
-        libz
+    libandroidfw \
+    libutils \
+    libcutils \
+    libpng \
+    liblog \
+    libz
 
 LOCAL_STATIC_LIBRARIES := \
-        libstlport_static \
-        libexpat_static
+    libstlport_static \
+    libexpat_static
+
+LOCAL_CPPFLAGS += -Wno-non-virtual-dtor
 
 include $(BUILD_EXECUTABLE)
-endif
+
+endif # Not SDK_ONLY
 
 endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
diff --git a/tools/aapt/ApkBuilder.cpp b/tools/aapt/ApkBuilder.cpp
new file mode 100644
index 0000000..12f6040
--- /dev/null
+++ b/tools/aapt/ApkBuilder.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include "AaptAssets.h"
+#include "ApkBuilder.h"
+
+using namespace android;
+
+ApkBuilder::ApkBuilder(const sp<WeakResourceFilter>& configFilter)
+    : mConfigFilter(configFilter)
+    , mDefaultFilter(new AndResourceFilter()) {
+    // Add the default split, which is present for all APKs.
+    mDefaultFilter->addFilter(mConfigFilter);
+    mSplits.add(new ApkSplit(std::set<ConfigDescription>(), mDefaultFilter, true));
+}
+
+status_t ApkBuilder::createSplitForConfigs(const std::set<ConfigDescription>& configs) {
+    const size_t N = mSplits.size();
+    for (size_t i = 0; i < N; i++) {
+        const std::set<ConfigDescription>& splitConfigs = mSplits[i]->getConfigs();
+        std::set<ConfigDescription>::const_iterator iter = configs.begin();
+        for (; iter != configs.end(); iter++) {
+            if (splitConfigs.count(*iter) > 0) {
+                // Can't have overlapping configurations.
+                fprintf(stderr, "ERROR: Split configuration '%s' is already defined "
+                        "in another split.\n", iter->toString().string());
+                return ALREADY_EXISTS;
+            }
+        }
+    }
+
+    sp<StrongResourceFilter> splitFilter = new StrongResourceFilter(configs);
+
+    // Add the inverse filter of this split filter to the base apk filter so it will
+    // omit resources that belong in this split.
+    mDefaultFilter->addFilter(new InverseResourceFilter(splitFilter));
+
+    // Now add the apk-wide config filter to our split filter.
+    sp<AndResourceFilter> filter = new AndResourceFilter();
+    filter->addFilter(splitFilter);
+    filter->addFilter(mConfigFilter);
+    mSplits.add(new ApkSplit(configs, filter));
+    return NO_ERROR;
+}
+
+status_t ApkBuilder::addEntry(const String8& path, const sp<AaptFile>& file) {
+    const size_t N = mSplits.size();
+    for (size_t i = 0; i < N; i++) {
+        if (mSplits[i]->matches(file)) {
+            return mSplits.editItemAt(i)->addEntry(path, file);
+        }
+    }
+    // Entry can be dropped if it doesn't match any split. This will only happen
+    // if the enry doesn't mConfigFilter.
+    return NO_ERROR;
+}
+
+void ApkBuilder::print() const {
+    fprintf(stderr, "APK Builder\n");
+    fprintf(stderr, "-----------\n");
+    const size_t N = mSplits.size();
+    for (size_t i = 0; i < N; i++) {
+        mSplits[i]->print();
+        fprintf(stderr, "\n");
+    }
+}
+
+ApkSplit::ApkSplit(const std::set<ConfigDescription>& configs, const sp<ResourceFilter>& filter, bool isBase)
+    : mConfigs(configs), mFilter(filter), mIsBase(isBase) {
+    std::set<ConfigDescription>::const_iterator iter = configs.begin();
+    for (; iter != configs.end(); iter++) {
+        if (mName.size() > 0) {
+            mName.append(",");
+            mDirName.append("_");
+        }
+
+        String8 configStr = iter->toString();
+        mName.append(configStr);
+        mDirName.append(configStr);
+    }
+}
+
+status_t ApkSplit::addEntry(const String8& path, const sp<AaptFile>& file) {
+    if (!mFiles.insert(OutputEntry(path, file)).second) {
+        // Duplicate file.
+        return ALREADY_EXISTS;
+    }
+    return NO_ERROR;
+}
+
+void ApkSplit::print() const {
+    fprintf(stderr, "APK Split '%s'\n", mName.string());
+
+    std::set<OutputEntry>::const_iterator iter = mFiles.begin();
+    for (; iter != mFiles.end(); iter++) {
+        fprintf(stderr, "  %s (%s)\n", iter->getPath().string(), iter->getFile()->getSourceFile().string());
+    }
+}
diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h
new file mode 100644
index 0000000..a4b7d4a
--- /dev/null
+++ b/tools/aapt/ApkBuilder.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2014 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 __APK_BUILDER_H
+#define __APK_BUILDER_H
+
+#include <set>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+#include "ConfigDescription.h"
+#include "OutputSet.h"
+#include "ResourceFilter.h"
+
+class ApkSplit;
+class AaptFile;
+
+class ApkBuilder : public android::RefBase {
+public:
+    ApkBuilder(const sp<WeakResourceFilter>& configFilter);
+
+    /**
+     * Tells the builder to generate a separate APK for resources that
+     * match the configurations specified. Split APKs can not have
+     * overlapping resources.
+     *
+     * NOTE: All splits should be set up before any files are added.
+     */
+    android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs);
+
+    /**
+     * Adds a file to be written to the final APK. It's name must not collide
+     * with that of any files previously added. When a Split APK is being
+     * generated, duplicates can exist as long as they are in different splits
+     * (resources.arsc, AndroidManifest.xml).
+     */
+    android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file);
+
+    android::Vector<sp<ApkSplit> >& getSplits() {
+        return mSplits;
+    }
+
+    void print() const;
+
+private:
+    android::sp<ResourceFilter> mConfigFilter;
+    android::sp<AndResourceFilter> mDefaultFilter;
+    android::Vector<sp<ApkSplit> > mSplits;
+};
+
+class ApkSplit : public OutputSet {
+public:
+    android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file);
+
+    const std::set<OutputEntry>& getEntries() const {
+        return mFiles;
+    }
+
+    const std::set<ConfigDescription>& getConfigs() const {
+        return mConfigs;
+    }
+
+    bool matches(const sp<AaptFile>& file) const {
+        return mFilter->match(file->getGroupEntry().toParams());
+    }
+
+    sp<ResourceFilter> getResourceFilter() const {
+        return mFilter;
+    }
+
+    const android::String8& getPrintableName() const {
+        return mName;
+    }
+
+    const android::String8& getDirectorySafeName() const {
+        return mDirName;
+    }
+
+    bool isBase() const {
+        return mIsBase;
+    }
+
+    void print() const;
+
+private:
+    friend class ApkBuilder;
+
+    ApkSplit(const std::set<ConfigDescription>& configs, const android::sp<ResourceFilter>& filter, bool isBase=false);
+
+    std::set<ConfigDescription> mConfigs;
+    const sp<ResourceFilter> mFilter;
+    const bool mIsBase;
+    String8 mName;
+    String8 mDirName;
+    std::set<OutputEntry> mFiles;
+};
+
+#endif // __APK_BUILDER_H
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index ebe1bed..ceb52a0 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -151,10 +151,12 @@
     void setPublicOutputFile(const char* file) { mPublicOutputFile = file; }
     const char* getRClassDir() const { return mRClassDir; }
     void setRClassDir(const char* dir) { mRClassDir = dir; }
-    const char* getConfigurations() const { return mConfigurations.size() > 0 ? mConfigurations.string() : NULL; }
+    const android::String8& getConfigurations() const { return mConfigurations; }
     void addConfigurations(const char* val) { if (mConfigurations.size() > 0) { mConfigurations.append(","); mConfigurations.append(val); } else { mConfigurations = val; } }
-    const char* getPreferredConfigurations() const { return mPreferredConfigurations.size() > 0 ? mPreferredConfigurations.string() : NULL; }
-    void addPreferredConfigurations(const char* val) { if (mPreferredConfigurations.size() > 0) { mPreferredConfigurations.append(","); mPreferredConfigurations.append(val); } else { mPreferredConfigurations = val; } }
+    const android::String8& getPreferredDensity() const { return mPreferredDensity; }
+    void setPreferredDensity(const char* val) { mPreferredDensity = val; }
+    void addSplitConfigurations(const char* val) { mPartialConfigurations.add(android::String8(val)); }
+    const android::Vector<android::String8>& getSplitConfigurations() const { return mPartialConfigurations; }
     const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; }
     void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; }
     const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; }
@@ -286,7 +288,8 @@
     const char* mRClassDir;
     const char* mResourceIntermediatesDir;
     android::String8 mConfigurations;
-    android::String8 mPreferredConfigurations;
+    android::String8 mPreferredDensity;
+    android::Vector<android::String8> mPartialConfigurations;
     android::Vector<const char*> mPackageIncludes;
     android::Vector<const char*> mJarFiles;
     android::Vector<const char*> mNoCompressExtensions;
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 0af1ce1..0360200 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -3,6 +3,7 @@
 //
 // Android Asset Packaging Tool main entry point.
 //
+#include "ApkBuilder.h"
 #include "Main.h"
 #include "Bundle.h"
 #include "ResourceFilter.h"
@@ -2034,6 +2035,47 @@
     return (result != NO_ERROR);
 }
 
+static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder) {
+    const size_t numDirs = dir->getDirs().size();
+    for (size_t i = 0; i < numDirs; i++) {
+        status_t err = addResourcesToBuilder(dir->getDirs().valueAt(i), builder);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    const size_t numFiles = dir->getFiles().size();
+    for (size_t i = 0; i < numFiles; i++) {
+        sp<AaptGroup> gp = dir->getFiles().valueAt(i);
+        const size_t numConfigs = gp->getFiles().size();
+        for (size_t j = 0; j < numConfigs; j++) {
+            status_t err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
+            if (err != NO_ERROR) {
+                fprintf(stderr, "Failed to add %s (%s) to builder.\n",
+                        gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
+                return err;
+            }
+        }
+    }
+    return NO_ERROR;
+}
+
+static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
+    if (split->isBase()) {
+        return original;
+    }
+
+    String8 ext(original.getPathExtension());
+    if (ext == String8(".apk")) {
+        return String8::format("%s_%s%s",
+                original.getBasePath().string(),
+                split->getDirectorySafeName().string(),
+                ext.string());
+    }
+
+    return String8::format("%s_%s", original.string(),
+            split->getDirectorySafeName().string());
+}
 
 /*
  * Package up an asset directory and associated application files.
@@ -2047,17 +2089,18 @@
     int N;
     FILE* fp;
     String8 dependencyFile;
+    sp<ApkBuilder> builder;
 
     // -c en_XA or/and ar_XB means do pseudolocalization
-    ResourceFilter filter;
-    err = filter.parse(bundle->getConfigurations());
+    sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
+    err = configFilter->parse(bundle->getConfigurations());
     if (err != NO_ERROR) {
         goto bail;
     }
-    if (filter.containsPseudo()) {
+    if (configFilter->containsPseudo()) {
         bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
     }
-    if (filter.containsPseudoBidi()) {
+    if (configFilter->containsPseudoBidi()) {
         bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
     }
 
@@ -2105,9 +2148,32 @@
         assets->print(String8());
     }
 
+    // Create the ApkBuilder, which will collect the compiled files
+    // to write to the final APK (or sets of APKs if we are building
+    // a Split APK.
+    builder = new ApkBuilder(configFilter);
+
+    // If we are generating a Split APK, find out which configurations to split on.
+    if (bundle->getSplitConfigurations().size() > 0) {
+        const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
+        const size_t numSplits = splitStrs.size();
+        for (size_t i = 0; i < numSplits; i++) {
+            std::set<ConfigDescription> configs;
+            if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
+                fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
+                goto bail;
+            }
+
+            err = builder->createSplitForConfigs(configs);
+            if (err != NO_ERROR) {
+                goto bail;
+            }
+        }
+    }
+
     // If they asked for any fileAs that need to be compiled, do so.
     if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
-        err = buildResources(bundle, assets);
+        err = buildResources(bundle, assets, builder);
         if (err != 0) {
             goto bail;
         }
@@ -2194,11 +2260,24 @@
 
     // Write the apk
     if (outputAPKFile) {
-        err = writeAPK(bundle, assets, String8(outputAPKFile));
+        // Gather all resources and add them to the APK Builder. The builder will then
+        // figure out which Split they belong in.
+        err = addResourcesToBuilder(assets, builder);
         if (err != NO_ERROR) {
-            fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
             goto bail;
         }
+
+        const Vector<sp<ApkSplit> >& splits = builder->getSplits();
+        const size_t numSplits = splits.size();
+        for (size_t i = 0; i < numSplits; i++) {
+            const sp<ApkSplit>& split = splits[i];
+            String8 outputPath = buildApkName(String8(outputAPKFile), split);
+            err = writeAPK(bundle, outputPath, split);
+            if (err != NO_ERROR) {
+                fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
+                goto bail;
+            }
+        }
     }
 
     // If we've been asked to generate a dependency file, we need to finish up here.
diff --git a/tools/aapt/ConfigDescription.h b/tools/aapt/ConfigDescription.h
new file mode 100644
index 0000000..779c423
--- /dev/null
+++ b/tools/aapt/ConfigDescription.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 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 __CONFIG_DESCRIPTION_H
+#define __CONFIG_DESCRIPTION_H
+
+#include <androidfw/ResourceTypes.h>
+
+/**
+ * Subclass of ResTable_config that adds convenient
+ * initialization and comparison methods.
+ */
+struct ConfigDescription : public android::ResTable_config {
+    ConfigDescription() {
+        memset(this, 0, sizeof(*this));
+        size = sizeof(android::ResTable_config);
+    }
+    ConfigDescription(const android::ResTable_config&o) {
+        *static_cast<android::ResTable_config*>(this) = o;
+        size = sizeof(android::ResTable_config);
+    }
+    ConfigDescription(const ConfigDescription&o) {
+        *static_cast<android::ResTable_config*>(this) = o;
+    }
+
+    ConfigDescription& operator=(const android::ResTable_config& o) {
+        *static_cast<android::ResTable_config*>(this) = o;
+        size = sizeof(android::ResTable_config);
+        return *this;
+    }
+    ConfigDescription& operator=(const ConfigDescription& o) {
+        *static_cast<android::ResTable_config*>(this) = o;
+        return *this;
+    }
+
+    inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; }
+    inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; }
+    inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; }
+    inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; }
+    inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; }
+    inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; }
+};
+
+#endif // __CONFIG_DESCRIPTION_H
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 1cf4783..5a60014 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -70,6 +70,7 @@
         "        [-F apk-file] [-J R-file-dir] \\\n"
         "        [--product product1,product2,...] \\\n"
         "        [-c CONFIGS] [--preferred-configurations CONFIGS] \\\n"
+        "        [--split CONFIGS [--split CONFIGS]] \\\n"
         "        [raw-files-dir [raw-files-dir] ...] \\\n"
         "        [--output-text-symbols DIR]\n"
         "\n"
@@ -166,10 +167,12 @@
         "       generate dependency files in the same directories for R.java and resource package\n"
         "   --auto-add-overlay\n"
         "       Automatically add resources that are only in overlays.\n"
-        "   --preferred-configurations\n"
-        "       Like the -c option for filtering out unneeded configurations, but\n"
-        "       only expresses a preference.  If there is no resource available with\n"
-        "       the preferred configuration then it will not be stripped.\n"
+        "   --preferred-density\n"
+        "       Specifies a preference for a particular density. Resources that do not\n"
+        "       match this density and have variants that are a closer match are removed.\n"
+        "   --split\n"
+        "       Builds a separate split APK for the configurations listed. This can\n"
+        "       be loaded alongside the base APK at runtime.\n"
         "   --rename-manifest-package\n"
         "       Rewrite the manifest so that its package name is the package name\n"
         "       given here.  Relative class names (for example .Foo) will be\n"
@@ -568,15 +571,24 @@
                     bundle.setGenDependencies(true);
                 } else if (strcmp(cp, "-utf16") == 0) {
                     bundle.setWantUTF16(true);
-                } else if (strcmp(cp, "-preferred-configurations") == 0) {
+                } else if (strcmp(cp, "-preferred-density") == 0) {
                     argc--;
                     argv++;
                     if (!argc) {
-                        fprintf(stderr, "ERROR: No argument supplied for '--preferred-configurations' option\n");
+                        fprintf(stderr, "ERROR: No argument supplied for '--preferred-density' option\n");
                         wantUsage = true;
                         goto bail;
                     }
-                    bundle.addPreferredConfigurations(argv[0]);
+                    bundle.setPreferredDensity(argv[0]);
+                } else if (strcmp(cp, "-split") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--split' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.addSplitConfigurations(argv[0]);
                 } else if (strcmp(cp, "-rename-manifest-package") == 0) {
                     argc--;
                     argv++;
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
index a6b39ac..34c4496 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -10,8 +10,12 @@
 #include <utils/threads.h>
 #include <utils/List.h>
 #include <utils/Errors.h>
-#include "Bundle.h"
+#include <utils/StrongPointer.h>
+
 #include "AaptAssets.h"
+#include "ApkBuilder.h"
+#include "Bundle.h"
+#include "ResourceFilter.h"
 #include "ZipFile.h"
 
 
@@ -22,6 +26,8 @@
     #include <time.h>
 #endif /* BENCHMARK */
 
+class OutputSet;
+
 extern int doVersion(Bundle* bundle);
 extern int doList(Bundle* bundle);
 extern int doDump(Bundle* bundle);
@@ -34,13 +40,13 @@
 extern int calcPercent(long uncompressedLen, long compressedLen);
 
 extern android::status_t writeAPK(Bundle* bundle,
-    const sp<AaptAssets>& assets,
-    const android::String8& outputFile);
+    const android::String8& outputFile,
+    const android::sp<OutputSet>& outputSet);
 
 extern android::status_t updatePreProcessedCache(Bundle* bundle);
 
 extern android::status_t buildResources(Bundle* bundle,
-    const sp<AaptAssets>& assets);
+    const sp<AaptAssets>& assets, sp<ApkBuilder>& builder);
 
 extern android::status_t writeResourceSymbols(Bundle* bundle,
     const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate);
@@ -49,8 +55,6 @@
 
 extern bool isValidResourceType(const String8& type);
 
-ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
-
 extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets);
 
 int dumpResources(Bundle* bundle);
diff --git a/tools/aapt/OutputSet.h b/tools/aapt/OutputSet.h
new file mode 100644
index 0000000..ea9ef70
--- /dev/null
+++ b/tools/aapt/OutputSet.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 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 __OUTPUT_SET_H
+#define __OUTPUT_SET_H
+
+#include <set>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+
+class AaptFile;
+
+class OutputEntry {
+public:
+    OutputEntry() {}
+    OutputEntry(const android::String8& path, const android::sp<const AaptFile>& file)
+        : mPath(path), mFile(file) {}
+
+    inline const android::sp<const AaptFile>& getFile() const {
+        return mFile;
+    }
+
+    inline const android::String8& getPath() const {
+        return mPath;
+    }
+
+    bool operator<(const OutputEntry& o) const { return getPath() < o.mPath; }
+    bool operator==(const OutputEntry& o) const { return getPath() == o.mPath; }
+
+private:
+    android::String8 mPath;
+    android::sp<const AaptFile> mFile;
+};
+
+class OutputSet : public virtual android::RefBase {
+public:
+    virtual const std::set<OutputEntry>& getEntries() const = 0;
+
+    virtual ~OutputSet() {}
+};
+
+#endif // __OUTPUT_SET_H
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 872d95c..dc16e35 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -5,6 +5,7 @@
 //
 #include "Main.h"
 #include "AaptAssets.h"
+#include "OutputSet.h"
 #include "ResourceTable.h"
 #include "ResourceFilter.h"
 
@@ -36,11 +37,8 @@
 };
 
 /* fwd decls, so I can write this downward */
-ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
-ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
-                        const AaptGroupEntry& ge, const ResourceFilter* filter);
-bool processFile(Bundle* bundle, ZipFile* zip,
-                        const sp<AaptGroup>& group, const sp<AaptFile>& file);
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet);
+bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file);
 bool okayToCompress(Bundle* bundle, const String8& pathName);
 ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
 
@@ -51,8 +49,7 @@
  * On success, "bundle->numPackages" will be the number of Zip packages
  * we created.
  */
-status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets,
-                       const String8& outputFile)
+status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet)
 {
     #if BENCHMARK
     fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
@@ -112,7 +109,7 @@
         printf("Writing all files...\n");
     }
 
-    count = processAssets(bundle, zip, assets);
+    count = processAssets(bundle, zip, outputSet);
     if (count < 0) {
         fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
                 outputFile.string());
@@ -218,72 +215,24 @@
     return result;
 }
 
-ssize_t processAssets(Bundle* bundle, ZipFile* zip,
-                      const sp<AaptAssets>& assets)
-{
-    ResourceFilter filter;
-    status_t status = filter.parse(bundle->getConfigurations());
-    if (status != NO_ERROR) {
-        return -1;
-    }
-
-    ssize_t count = 0;
-
-    const size_t N = assets->getGroupEntries().size();
-    for (size_t i=0; i<N; i++) {
-        const AaptGroupEntry& ge = assets->getGroupEntries()[i];
-
-        ssize_t res = processAssets(bundle, zip, assets, ge, &filter);
-        if (res < 0) {
-            return res;
-        }
-
-        count += res;
-    }
-
-    return count;
-}
-
-ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
-        const AaptGroupEntry& ge, const ResourceFilter* filter)
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet)
 {
     ssize_t count = 0;
-
-    const size_t ND = dir->getDirs().size();
-    size_t i;
-    for (i=0; i<ND; i++) {
-        const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
-
-        const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0;
-
-        if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) {
-            continue;
-        }
-
-        ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL);
-        if (res < 0) {
-            return res;
-        }
-        count += res;
-    }
-
-    if (filter != NULL && !filter->match(ge.toParams())) {
-        return count;
-    }
-
-    const size_t NF = dir->getFiles().size();
-    for (i=0; i<NF; i++) {
-        sp<AaptGroup> gp = dir->getFiles().valueAt(i);
-        ssize_t fi = gp->getFiles().indexOfKey(ge);
-        if (fi >= 0) {
-            sp<AaptFile> fl = gp->getFiles().valueAt(fi);
-            if (!processFile(bundle, zip, gp, fl)) {
+    const std::set<OutputEntry>& entries = outputSet->getEntries();
+    std::set<OutputEntry>::const_iterator iter = entries.begin();
+    for (; iter != entries.end(); iter++) {
+        const OutputEntry& entry = *iter;
+        if (entry.getFile() == NULL) {
+            fprintf(stderr, "warning: null file being processed.\n");
+        } else {
+            String8 storagePath(entry.getPath());
+            storagePath.convertToResPath();
+            if (!processFile(bundle, zip, storagePath, entry.getFile())) {
                 return UNKNOWN_ERROR;
             }
             count++;
         }
     }
-
     return count;
 }
 
@@ -294,12 +243,10 @@
  * delete the existing entry before adding the new one.
  */
 bool processFile(Bundle* bundle, ZipFile* zip,
-                 const sp<AaptGroup>& group, const sp<AaptFile>& file)
+                 String8 storageName, const sp<const AaptFile>& file)
 {
     const bool hasData = file->hasData();
 
-    String8 storageName(group->getPath());
-    storageName.convertToResPath();
     ZipEntry* entry;
     bool fromGzip = false;
     status_t result;
@@ -403,8 +350,8 @@
             fprintf(stderr, "      Unable to add '%s': file already in archive (try '-u'?)\n",
                     file->getPrintableSource().string());
         } else {
-            fprintf(stderr, "      Unable to add '%s': Zip add failed\n", 
-                    file->getPrintableSource().string());
+            fprintf(stderr, "      Unable to add '%s': Zip add failed (%d)\n",
+                    file->getPrintableSource().string(), result);
         }
         return false;
     }
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 1348be3..e599643 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -179,24 +179,6 @@
         || type == "color" || type == "menu" || type == "mipmap";
 }
 
-static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true)
-{
-    sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc"));
-    sp<AaptFile> file;
-    if (group != NULL) {
-        file = group->getFiles().valueFor(AaptGroupEntry());
-        if (file != NULL) {
-            return file;
-        }
-    }
-
-    if (!makeIfNecessary) {
-        return NULL;
-    }
-    return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(),
-                            NULL, String8());
-}
-
 static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
     const sp<AaptGroup>& grp)
 {
@@ -359,23 +341,6 @@
     return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
 }
 
-status_t postProcessImages(const sp<AaptAssets>& assets,
-                           ResourceTable* table,
-                           const sp<ResourceTypeSet>& set)
-{
-    ResourceDirIterator it(set, String8("drawable"));
-    bool hasErrors = false;
-    ssize_t res;
-    while ((res=it.next()) == NO_ERROR) {
-        res = postProcessImage(assets, table, it.getFile());
-        if (res < NO_ERROR) {
-            hasErrors = true;
-        }
-    }
-
-    return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
-}
-
 static void collect_files(const sp<AaptDir>& dir,
         KeyedVector<String8, sp<ResourceTypeSet> >* resources)
 {
@@ -906,7 +871,38 @@
     return 0;
 }
 
-status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
+status_t generateAndroidManifestForSplit(const String16& package, const sp<ApkSplit>& split,
+        sp<AaptFile>& outFile) {
+    const String8 filename("AndroidManifest.xml");
+    const String16 androidPrefix("android");
+    const String16 androidNSUri("http://schemas.android.com/apk/res/android");
+    sp<XMLNode> root = XMLNode::newNamespace(filename, androidPrefix, androidNSUri);
+
+    // Build the <manifest> tag
+    sp<XMLNode> manifest = XMLNode::newElement(filename, String16(), String16("manifest"));
+
+    // Add the 'package' attribute which is set to the original package name.
+    manifest->addAttribute(String16(), String16("package"), package);
+
+    // Add the 'split' attribute which describes the configurations included.
+    String8 splitName("config_");
+    splitName.append(split->getDirectorySafeName());
+    manifest->addAttribute(String16(), String16("split"), String16(splitName));
+
+    // Build an empty <application> tag (required).
+    sp<XMLNode> app = XMLNode::newElement(filename, String16(), String16("application"));
+    manifest->addChild(app);
+    root->addChild(manifest);
+
+    status_t err = root->flatten(outFile, true, true);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    outFile->setCompressionMethod(ZipEntry::kCompressDeflated);
+    return NO_ERROR;
+}
+
+status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
 {
     // First, look for a package file to parse.  This is required to
     // be able to generate the resource information.
@@ -1122,12 +1118,6 @@
     // --------------------------------------------------------------------
 
     if (table.hasResources()) {
-        sp<AaptFile> resFile(getResourceFile(assets));
-        if (resFile == NULL) {
-            fprintf(stderr, "Error: unable to generate entry for resource data\n");
-            return UNKNOWN_ERROR;
-        }
-
         err = table.assignResourceIds();
         if (err < NO_ERROR) {
             return err;
@@ -1235,16 +1225,24 @@
     }
 
     if (drawables != NULL) {
-        err = postProcessImages(assets, &table, drawables);
-        if (err != NO_ERROR) {
+        ResourceDirIterator it(drawables, String8("drawable"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = postProcessImage(assets, &table, it.getFile());
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
             hasErrors = true;
         }
+        err = NO_ERROR;
     }
 
     if (colors != NULL) {
         ResourceDirIterator it(colors, String8("color"));
         while ((err=it.next()) == NO_ERROR) {
-          err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1320,15 +1318,35 @@
             return err;
         }
 
-        resFile = getResourceFile(assets);
-        if (resFile == NULL) {
-            fprintf(stderr, "Error: unable to generate entry for resource data\n");
-            return UNKNOWN_ERROR;
-        }
+        Vector<sp<ApkSplit> >& splits = builder->getSplits();
+        const size_t numSplits = splits.size();
+        for (size_t i = 0; i < numSplits; i++) {
+            sp<ApkSplit>& split = splits.editItemAt(i);
+            sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"),
+                    AaptGroupEntry(), String8());
+            err = table.flatten(bundle, split->getResourceFilter(), flattenedTable);
+            if (err != NO_ERROR) {
+                fprintf(stderr, "Failed to generate resource table for split '%s'\n",
+                        split->getPrintableName().string());
+                return err;
+            }
+            split->addEntry(String8("resources.arsc"), flattenedTable);
 
-        err = table.flatten(bundle, resFile);
-        if (err < NO_ERROR) {
-            return err;
+            if (split->isBase()) {
+                resFile = flattenedTable;
+                finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
+            } else {
+                sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"),
+                        AaptGroupEntry(), String8());
+                err = generateAndroidManifestForSplit(String16(assets->getPackage()), split,
+                        generatedManifest);
+                if (err != NO_ERROR) {
+                    fprintf(stderr, "Failed to generate AndroidManifest.xml for split '%s'\n",
+                            split->getPrintableName().string());
+                    return err;
+                }
+                split->addEntry(String8("AndroidManifest.xml"), generatedManifest);
+            }
         }
 
         if (bundle->getPublicOutputFile()) {
@@ -1344,18 +1362,13 @@
             table.writePublicDefinitions(String16(assets->getPackage()), fp);
             fclose(fp);
         }
-        
-        // Read resources back in,
-        finalResTable.add(resFile->getData(), resFile->getSize());
-        
-#if 0
-        NOISY(
-              printf("Generated resources:\n");
-              finalResTable.print();
-        )
-#endif
+
+        if (finalResTable.getTableCount() == 0 || resFile == NULL) {
+            fprintf(stderr, "No resource table was generated.\n");
+            return UNKNOWN_ERROR;
+        }
     }
-    
+
     // Perform a basic validation of the manifest file.  This time we
     // parse it with the comments intact, so that we can use them to
     // generate java docs...  so we are not going to write this one
diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp
index 8ca852e..de8b4fc 100644
--- a/tools/aapt/ResourceFilter.cpp
+++ b/tools/aapt/ResourceFilter.cpp
@@ -1,119 +1,92 @@
 //
-// Copyright 2011 The Android Open Source Project
+// Copyright 2014 The Android Open Source Project
 //
 // Build resource files from raw assets.
 //
 
 #include "ResourceFilter.h"
+#include "AaptUtil.h"
+#include "AaptConfig.h"
 
 status_t
-ResourceFilter::parse(const char* arg)
+WeakResourceFilter::parse(const String8& str)
 {
-    if (arg == NULL) {
-        return 0;
-    }
-
-    const char* p = arg;
-    const char* q;
-
-    while (true) {
-        q = strchr(p, ',');
-        if (q == NULL) {
-            q = p + strlen(p);
-        }
-
-        String8 part(p, q-p);
-
+    Vector<String8> configStrs = AaptUtil::split(str, ',');
+    const size_t N = configStrs.size();
+    mConfigs.clear();
+    mConfigMask = 0;
+    mConfigs.resize(N);
+    for (size_t i = 0; i < N; i++) {
+        const String8& part = configStrs[i];
         if (part == "en_XA") {
             mContainsPseudoAccented = true;
         } else if (part == "ar_XB") {
             mContainsPseudoBidi = true;
         }
-        int axis;
-        AxisValue value;
-        if (!AaptGroupEntry::parseFilterNamePart(part, &axis, &value)) {
-            fprintf(stderr, "Invalid configuration: %s\n", arg);
-            fprintf(stderr, "                       ");
-            for (int i=0; i<p-arg; i++) {
-                fprintf(stderr, " ");
-            }
-            for (int i=0; i<q-p; i++) {
-                fprintf(stderr, "^");
-            }
-            fprintf(stderr, "\n");
-            return 1;
+
+        std::pair<ConfigDescription, uint32_t>& entry = mConfigs.editItemAt(i);
+
+        AaptLocaleValue val;
+        if (val.initFromFilterString(part)) {
+            // For backwards compatibility, we accept configurations that
+            // only specify locale in the standard 'en_US' format.
+            val.writeTo(&entry.first);
+        } else if (!AaptConfig::parse(part, &entry.first)) {
+            fprintf(stderr, "Invalid configuration: %s\n", part.string());
+            return UNKNOWN_ERROR;
         }
 
-        ssize_t index = mData.indexOfKey(axis);
-        if (index < 0) {
-            mData.add(axis, SortedVector<AxisValue>());
-        }
-        SortedVector<AxisValue>& sv = mData.editValueFor(axis);
-        sv.add(value);
+        entry.second = mDefault.diff(entry.first);
 
-        // If it's a locale with a region, script or variant, we should also match an
-        // unmodified locale of the same language
-        if (axis == AXIS_LOCALE) {
-            if (value.localeValue.region[0] || value.localeValue.script[0] ||
-                value.localeValue.variant[0]) {
-                AxisValue copy;
-                memcpy(copy.localeValue.language, value.localeValue.language,
-                       sizeof(value.localeValue.language));
-                sv.add(copy);
-            }
-        }
-        p = q;
-        if (!*p) break;
-        p++;
+        // Ignore the version
+        entry.second &= ~ResTable_config::CONFIG_VERSION;
+
+        mConfigMask |= entry.second;
     }
 
     return NO_ERROR;
 }
 
 bool
-ResourceFilter::isEmpty() const
+WeakResourceFilter::match(const ResTable_config& config) const
 {
-    return mData.size() == 0;
-}
-
-bool
-ResourceFilter::match(int axis, const AxisValue& value) const
-{
-    if (value.intValue == 0 && (value.localeValue.language[0] == 0)) {
-        // they didn't specify anything so take everything
+    uint32_t mask = mDefault.diff(config);
+    if ((mConfigMask & mask) == 0) {
+        // The two configurations don't have any common axis.
         return true;
     }
-    ssize_t index = mData.indexOfKey(axis);
-    if (index < 0) {
-        // we didn't request anything on this axis so take everything
-        return true;
-    }
-    const SortedVector<AxisValue>& sv = mData.valueAt(index);
-    return sv.indexOf(value) >= 0;
-}
 
-bool
-ResourceFilter::match(int axis, const ResTable_config& config) const
-{
-    return match(axis, AaptGroupEntry::getConfigValueForAxis(config, axis));
-}
-
-bool
-ResourceFilter::match(const ResTable_config& config) const
-{
-    for (int i=AXIS_START; i<=AXIS_END; i++) {
-        if (!match(i, AaptGroupEntry::getConfigValueForAxis(config, i))) {
-            return false;
+    const size_t N = mConfigs.size();
+    for (size_t i = 0; i < N; i++) {
+        const std::pair<ConfigDescription, uint32_t>& entry = mConfigs[i];
+        uint32_t diff = entry.first.diff(config);
+        if ((diff & entry.second) == 0) {
+            return true;
+        } else if ((diff & entry.second) == ResTable_config::CONFIG_LOCALE) {
+            // If the locales differ, but the languages are the same and
+            // the locale we are matching only has a language specified,
+            // we match.
+            if (config.language[0] && memcmp(config.language, entry.first.language, sizeof(config.language)) == 0) {
+                if (config.country[0] == 0) {
+                    return true;
+                }
+            }
         }
     }
-    return true;
+    return false;
 }
 
-const SortedVector<AxisValue>* ResourceFilter::configsForAxis(int axis) const
-{
-    ssize_t index = mData.indexOfKey(axis);
-    if (index < 0) {
-        return NULL;
+status_t
+StrongResourceFilter::parse(const String8& str) {
+    Vector<String8> configStrs = AaptUtil::split(str, ',');
+    ConfigDescription config;
+    mConfigs.clear();
+    for (size_t i = 0; i < configStrs.size(); i++) {
+        if (!AaptConfig::parse(configStrs[i], &config)) {
+            fprintf(stderr, "Invalid configuration: %s\n", configStrs[i].string());
+            return UNKNOWN_ERROR;
+        }
+        mConfigs.insert(config);
     }
-    return &mData.valueAt(index);
+    return NO_ERROR;
 }
diff --git a/tools/aapt/ResourceFilter.h b/tools/aapt/ResourceFilter.h
index c57770e..f459584 100644
--- a/tools/aapt/ResourceFilter.h
+++ b/tools/aapt/ResourceFilter.h
@@ -7,31 +7,137 @@
 #ifndef RESOURCE_FILTER_H
 #define RESOURCE_FILTER_H
 
+#include <androidfw/ResourceTypes.h>
+#include <set>
+#include <utility>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
 #include "AaptAssets.h"
+#include "ConfigDescription.h"
+
+class ResourceFilter : public virtual android::RefBase {
+public:
+    virtual bool match(const android::ResTable_config& config) const = 0;
+};
 
 /**
  * Implements logic for parsing and handling "-c" and "--preferred-configurations"
  * options.
  */
-class ResourceFilter
-{
+class WeakResourceFilter : public ResourceFilter {
 public:
-    ResourceFilter() : mData(), mContainsPseudoAccented(false),
-        mContainsPseudoBidi(false) {}
-    status_t parse(const char* arg);
-    bool isEmpty() const;
-    bool match(int axis, const ResTable_config& config) const;
-    bool match(const ResTable_config& config) const;
-    const SortedVector<AxisValue>* configsForAxis(int axis) const;
-    inline bool containsPseudo() const { return mContainsPseudoAccented; }
-    inline bool containsPseudoBidi() const { return mContainsPseudoBidi; }
+    WeakResourceFilter()
+        : mContainsPseudoAccented(false)
+        , mContainsPseudoBidi(false) {}
+
+    android::status_t parse(const android::String8& str);
+
+    bool match(const android::ResTable_config& config) const;
+
+    inline bool isEmpty() const {
+        return mConfigMask == 0;
+    }
+
+    inline bool containsPseudo() const {
+        return mContainsPseudoAccented;
+    }
+
+    inline bool containsPseudoBidi() const {
+        return mContainsPseudoBidi;
+    }
 
 private:
-    bool match(int axis, const AxisValue& value) const;
+    ConfigDescription mDefault;
+    uint32_t mConfigMask;
+    android::Vector<std::pair<ConfigDescription, uint32_t> > mConfigs;
 
-    KeyedVector<int,SortedVector<AxisValue> > mData;
     bool mContainsPseudoAccented;
     bool mContainsPseudoBidi;
 };
 
+/**
+ * Matches resources that have at least one of the configurations
+ * that this filter is looking for. In order to match a configuration,
+ * the resource must have the exact same configuration.
+ *
+ * This filter acts as a logical OR when matching resources.
+ *
+ * For example, if the filter is looking for resources with
+ * fr-land, de-land, or sw600dp:
+ *
+ * (PASS) fr-land
+ * (FAIL) fr
+ * (PASS) de-land
+ * (FAIL) de
+ * (FAIL) de-sw600dp
+ * (PASS) sw600dp
+ * (FAIL) sw600dp-land
+ */
+class StrongResourceFilter : public ResourceFilter {
+public:
+    StrongResourceFilter() {}
+    StrongResourceFilter(const std::set<ConfigDescription>& configs)
+        : mConfigs(configs) {}
+
+    android::status_t parse(const android::String8& str);
+
+    bool match(const android::ResTable_config& config) const {
+        std::set<ConfigDescription>::const_iterator iter = mConfigs.begin();
+        for (; iter != mConfigs.end(); iter++) {
+            if (iter->compare(config) == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    inline const std::set<ConfigDescription>& getConfigs() const {
+        return mConfigs;
+    }
+
+private:
+    std::set<ConfigDescription> mConfigs;
+};
+
+/**
+ * Negates the response of the target filter.
+ */
+class InverseResourceFilter : public ResourceFilter {
+public:
+    InverseResourceFilter(const android::sp<ResourceFilter>& filter)
+        : mFilter(filter) {}
+
+    bool match(const android::ResTable_config& config) const {
+        return !mFilter->match(config);
+    }
+
+private:
+    const android::sp<ResourceFilter> mFilter;
+};
+
+/**
+ * A logical AND of all the added filters.
+ */
+class AndResourceFilter : public ResourceFilter {
+public:
+    void addFilter(const android::sp<ResourceFilter>& filter) {
+        mFilters.add(filter);
+    }
+
+    bool match(const android::ResTable_config& config) const {
+        const size_t N = mFilters.size();
+        for (size_t i = 0; i < N; i++) {
+            if (!mFilters[i]->match(config)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+private:
+    android::Vector<android::sp<ResourceFilter> > mFilters;
+};
 #endif
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 6eab65b..efbba40 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2095,10 +2095,10 @@
     return mNumLocal > 0;
 }
 
-sp<AaptFile> ResourceTable::flatten(Bundle* bundle)
+sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter)
 {
     sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
-    status_t err = flatten(bundle, data);
+    status_t err = flatten(bundle, filter, data);
     return err == NO_ERROR ? data : NULL;
 }
 
@@ -2658,8 +2658,8 @@
         }
 
         // Check that all requested localizations are present for this string
-        if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) {
-            const char* allConfigs = mBundle->getConfigurations();
+        if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) {
+            const char* allConfigs = mBundle->getConfigurations().string();
             const char* start = allConfigs;
             const char* comma;
             
@@ -2713,14 +2713,8 @@
     return err;
 }
 
-status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest)
+status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, const sp<AaptFile>& dest)
 {
-    ResourceFilter filter;
-    status_t err = filter.parse(bundle->getConfigurations());
-    if (err != NO_ERROR) {
-        return err;
-    }
-
     const ConfigDescription nullConfig;
 
     const size_t N = mOrderedPackages.size();
@@ -2795,7 +2789,7 @@
                 const size_t N = c->getEntries().size();
                 for (size_t ei=0; ei<N; ei++) {
                     ConfigDescription config = c->getEntries().keyAt(ei);
-                    if (filterable && !filter.match(config)) {
+                    if (filterable && !filter->match(config)) {
                         continue;
                     }
                     sp<Entry> e = c->getEntries().valueAt(ei);
@@ -2887,7 +2881,7 @@
             return amt;
         }
 
-        err = flattenLibraryTable(data, libraryPackages);
+        status_t err = flattenLibraryTable(data, libraryPackages);
         if (err != NO_ERROR) {
             fprintf(stderr, "ERROR: failed to write library table\n");
             return err;
@@ -2943,11 +2937,11 @@
                     }
                     const size_t CN = cl->getEntries().size();
                     for (size_t ci=0; ci<CN; ci++) {
-                        if (filterable && !filter.match(cl->getEntries().keyAt(ci))) {
+                        if (filterable && !filter->match(cl->getEntries().keyAt(ci))) {
                             continue;
                         }
                         for (size_t cj=ci+1; cj<CN; cj++) {
-                            if (filterable && !filter.match(cl->getEntries().keyAt(cj))) {
+                            if (filterable && !filter->match(cl->getEntries().keyAt(cj))) {
                                 continue;
                             }
                             typeSpecFlags[ei] |= htodl(
@@ -2989,7 +2983,7 @@
                       config.screenHeightDp,
                       config.layoutDirection));
                       
-                if (filterable && !filter.match(config)) {
+                if (filterable && !filter->match(config)) {
                     continue;
                 }
                 
@@ -3108,7 +3102,7 @@
     }
     
     ssize_t strStart = dest->getSize();
-    err = valueStrings.writeStringBlock(dest);
+    status_t err = valueStrings.writeStringBlock(dest);
     if (err != NO_ERROR) {
         return err;
     }
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index ec8fd175e..a73993c 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -7,8 +7,10 @@
 #ifndef RESOURCE_TABLE_H
 #define RESOURCE_TABLE_H
 
+#include "ConfigDescription.h"
 #include "StringPool.h"
 #include "SourcePos.h"
+#include "ResourceFilter.h"
 
 #include <set>
 #include <map>
@@ -76,37 +78,6 @@
     class Type;
     class Entry;
 
-    struct ConfigDescription : public ResTable_config {
-        ConfigDescription() {
-            memset(this, 0, sizeof(*this));
-            size = sizeof(ResTable_config);
-        }
-        ConfigDescription(const ResTable_config&o) {
-            *static_cast<ResTable_config*>(this) = o;
-            size = sizeof(ResTable_config);
-        }
-        ConfigDescription(const ConfigDescription&o) {
-            *static_cast<ResTable_config*>(this) = o;
-        }
-
-        ConfigDescription& operator=(const ResTable_config& o) {
-            *static_cast<ResTable_config*>(this) = o;
-            size = sizeof(ResTable_config);
-            return *this;
-        }
-        ConfigDescription& operator=(const ConfigDescription& o) {
-            *static_cast<ResTable_config*>(this) = o;
-            return *this;
-        }
-
-        inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; }
-        inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; }
-        inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; }
-        inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; }
-        inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; }
-        inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; }
-    };
-
     ResourceTable(Bundle* bundle, const String16& assetsPackage);
 
     status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets);
@@ -182,7 +153,7 @@
     size_t numLocalResources() const;
     bool hasResources() const;
 
-    sp<AaptFile> flatten(Bundle*);
+    sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter);
 
     static inline uint32_t makeResId(uint32_t packageId,
                                      uint32_t typeId,
@@ -223,7 +194,7 @@
     void addLocalization(const String16& name, const String8& locale, const SourcePos& src);
     status_t validateLocalizations(void);
 
-    status_t flatten(Bundle*, const sp<AaptFile>& dest);
+    status_t flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, const sp<AaptFile>& dest);
     status_t flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs);
 
     void writePublicDefinitions(const String16& package, FILE* fp);
diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp
new file mode 100644
index 0000000..e795d818
--- /dev/null
+++ b/tools/aapt/tests/AaptConfig_test.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include <utils/String8.h>
+#include <gtest/gtest.h>
+
+#include "AaptConfig.h"
+#include "ConfigDescription.h"
+#include "TestHelper.h"
+
+using android::String8;
+
+static ::testing::AssertionResult TestParse(const String8& input, ConfigDescription* config=NULL) {
+    if (AaptConfig::parse(String8(input), config)) {
+        return ::testing::AssertionSuccess() << input << " was successfully parsed";
+    }
+    return ::testing::AssertionFailure() << input << " could not be parsed";
+}
+
+static ::testing::AssertionResult TestParse(const char* input, ConfigDescription* config=NULL) {
+    return TestParse(String8(input), config);
+}
+
+TEST(AaptConfigTest, ParseFailWhenQualifiersAreOutOfOrder) {
+    EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
+    EXPECT_FALSE(TestParse("land-en"));
+    EXPECT_FALSE(TestParse("hdpi-320dpi"));
+}
+
+TEST(AaptConfigTest, ParseFailWhenQualifiersAreNotMatched) {
+    EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
+}
+
+TEST(AaptConfigTest, ParseFailWhenQualifiersHaveTrailingDash) {
+    EXPECT_FALSE(TestParse("en-sw600dp-land-"));
+}
+
+TEST(AaptConfigTest, ParseBasicQualifiers) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("", &config));
+    EXPECT_EQ(String8(""), config.toString());
+
+    EXPECT_TRUE(TestParse("fr-land", &config));
+    EXPECT_EQ(String8("fr-land"), config.toString());
+
+    EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
+                "xhdpi-keyssoft-qwerty-navexposed-nonav", &config));
+    EXPECT_EQ(String8("mcc310-pl-sw720dp-normal-long-port-night-"
+                "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString());
+}
+
+TEST(AaptConfigTest, ParseLocales) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("en-rUS", &config));
+    EXPECT_EQ(String8("en-US"), config.toString());
+}
+
+TEST(AaptConfigTest, ParseQualifierAddedInApi13) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("sw600dp", &config));
+    EXPECT_EQ(String8("sw600dp-v13"), config.toString());
+
+    EXPECT_TRUE(TestParse("sw600dp-v8", &config));
+    EXPECT_EQ(String8("sw600dp-v13"), config.toString());
+}
diff --git a/tools/aapt/tests/AaptGroupEntry_test.cpp b/tools/aapt/tests/AaptGroupEntry_test.cpp
new file mode 100644
index 0000000..7348a08
--- /dev/null
+++ b/tools/aapt/tests/AaptGroupEntry_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include <utils/String8.h>
+#include <gtest/gtest.h>
+
+#include "AaptAssets.h"
+#include "ResourceFilter.h"
+#include "TestHelper.h"
+
+using android::String8;
+
+static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const String8& dirName,
+        String8* outType) {
+    if (entry.initFromDirName(dirName, outType)) {
+        return ::testing::AssertionSuccess() << dirName << " was successfully parsed";
+    }
+    return ::testing::AssertionFailure() << dirName << " could not be parsed";
+}
+
+static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const char* input,
+        String8* outType) {
+    return TestParse(entry, String8(input), outType);
+}
+
+TEST(AaptGroupEntryTest, ParseNoQualifier) {
+    AaptGroupEntry entry;
+    String8 type;
+    EXPECT_TRUE(TestParse(entry, "menu", &type));
+    EXPECT_EQ(String8("menu"), type);
+}
+
+TEST(AaptGroupEntryTest, ParseCorrectType) {
+    AaptGroupEntry entry;
+    String8 type;
+    EXPECT_TRUE(TestParse(entry, "anim", &type));
+    EXPECT_EQ(String8("anim"), type);
+
+    EXPECT_TRUE(TestParse(entry, "animator", &type));
+    EXPECT_EQ(String8("animator"), type);
+}
diff --git a/tools/aapt/tests/ResourceFilter_test.cpp b/tools/aapt/tests/ResourceFilter_test.cpp
new file mode 100644
index 0000000..30697bb
--- /dev/null
+++ b/tools/aapt/tests/ResourceFilter_test.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/String8.h>
+#include <gtest/gtest.h>
+
+#include "AaptConfig.h"
+#include "ResourceFilter.h"
+#include "ConfigDescription.h"
+
+using android::String8;
+
+// In this context, 'Axis' represents a particular field in the configuration,
+// such as language or density.
+
+TEST(WeakResourceFilterTest, EmptyFilterMatchesAnything) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("")));
+
+    ConfigDescription config;
+    config.density = 320;
+
+    EXPECT_TRUE(filter.match(config));
+
+    config.language[0] = 'f';
+    config.language[1] = 'r';
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithUnrelatedAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.density = 320;
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.language[0] = 'f';
+    config.language[1] = 'r';
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.language[0] = 'f';
+    config.language[1] = 'r';
+    config.density = 320;
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.language[0] = 'd';
+    config.language[1] = 'e';
+
+    EXPECT_FALSE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("de-rDE")));
+
+    ConfigDescription config;
+    config.language[0] = 'd';
+    config.language[1] = 'e';
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, ParsesStandardLocaleOnlyString) {
+    WeakResourceFilter filter;
+    EXPECT_EQ(NO_ERROR, filter.parse(String8("de_DE")));
+}
+
+TEST(WeakResourceFilterTest, IgnoresVersion) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("normal-v4")));
+
+    ConfigDescription config;
+    config.smallestScreenWidthDp = 600;
+    config.version = 13;
+
+    // The configs don't match on any axis besides version, which should be ignored.
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithRegion) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("kok,kok_IN,kok_419")));
+
+    ConfigDescription config;
+    AaptLocaleValue val;
+    ASSERT_TRUE(val.initFromFilterString(String8("kok_IN")));
+    val.writeTo(&config);
+
+    EXPECT_TRUE(filter.match(config));
+}
+
diff --git a/tools/aapt/tests/TestHelper.h b/tools/aapt/tests/TestHelper.h
new file mode 100644
index 0000000..79174832
--- /dev/null
+++ b/tools/aapt/tests/TestHelper.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 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 __TEST_HELPER_H
+#define __TEST_HELPER_H
+
+#include <utils/String8.h>
+
+namespace android {
+
+/**
+ * Stream operator for nicely printing String8's in gtest output.
+ */
+inline std::ostream& operator<<(std::ostream& stream, const String8& str) {
+    return stream << str.string();
+}
+
+}
+
+#endif