Merge "Final polish of the interrogation feature."
diff --git a/api/current.txt b/api/current.txt
index 82706f4..f641c96 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11180,6 +11180,8 @@
     method public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(int, android.net.SSLSessionCache);
     method public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
     method public java.lang.String[] getSupportedCipherSuites();
+    method public void setKeyManagers(javax.net.ssl.KeyManager[]);
+    method public void setTrustManagers(javax.net.ssl.TrustManager[]);
   }
 
   public final class SSLSessionCache {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3626bf5..7fd5a7d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -676,7 +676,8 @@
 
         bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
 
-        if (bm.getWidth() == width && bm.getHeight() == height) {
+        if (width <= 0 || height <= 0
+                || (bm.getWidth() == width && bm.getHeight() == height)) {
             return bm;
         }
 
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 906a564..9bd45d3 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -277,24 +277,24 @@
     public int compatSmallestScreenWidthDp;
 
     /**
-     * @hide
+     * @hide Do not use. Implementation not finished.
      */
-    public static final int LAYOUT_DIRECTION_UNDEFINED = -1;
+    public static final int TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE = -1;
 
     /**
-     * @hide
+     * @hide Do not use. Implementation not finished.
      */
-    public static final int LAYOUT_DIRECTION_LTR = 0;
+    public static final int TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE = 0;
 
     /**
-     * @hide
+     * @hide Do not use. Implementation not finished.
      */
-    public static final int LAYOUT_DIRECTION_RTL = 1;
+    public static final int TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE = 1;
 
     /**
-     * @hide The layout direction associated to the current Locale
+     * @hide The text layout direction associated to the current Locale
      */
-    public int layoutDirection;
+    public int textLayoutDirection;
 
     /**
      * @hide Internal book-keeping.
@@ -322,7 +322,7 @@
         mnc = o.mnc;
         if (o.locale != null) {
             locale = (Locale) o.locale.clone();
-            layoutDirection = o.layoutDirection;
+            textLayoutDirection = o.textLayoutDirection;
         }
         userSetLocale = o.userSetLocale;
         touchscreen = o.touchscreen;
@@ -358,6 +358,11 @@
         } else {
             sb.append(" (no locale)");
         }
+        switch (textLayoutDirection) {
+            case TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE: sb.append(" ?layoutdir"); break;
+            case TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE: sb.append(" rtl"); break;
+            default: sb.append(" layoutdir="); sb.append(textLayoutDirection); break;
+        }
         if (smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
             sb.append(" sw"); sb.append(smallestScreenWidthDp); sb.append("dp");
         } else {
@@ -450,11 +455,6 @@
             case NAVIGATIONHIDDEN_YES: sb.append("/h"); break;
             default: sb.append("/"); sb.append(navigationHidden); break;
         }
-        switch (layoutDirection) {
-            case LAYOUT_DIRECTION_UNDEFINED: sb.append(" ?layoutdir"); break;
-            case LAYOUT_DIRECTION_LTR: sb.append(" ltr"); break;
-            case LAYOUT_DIRECTION_RTL: sb.append(" rtl"); break;
-        }
         if (seq != 0) {
             sb.append(" s.");
             sb.append(seq);
@@ -483,8 +483,8 @@
         screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
         screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
         smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+        textLayoutDirection = TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE;
         seq = 0;
-        layoutDirection = LAYOUT_DIRECTION_LTR;
     }
 
     /** {@hide} */
@@ -519,7 +519,7 @@
             changed |= ActivityInfo.CONFIG_LOCALE;
             locale = delta.locale != null
                     ? (Locale) delta.locale.clone() : null;
-            layoutDirection = getLayoutDirectionFromLocale(locale);
+            textLayoutDirection = getLayoutDirectionFromLocale(locale);
         }
         if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
         {
@@ -611,23 +611,25 @@
     /**
      * Return the layout direction for a given Locale
      * @param locale the Locale for which we want the layout direction. Can be null.
-     * @return the layout direction. This may be one of {@link #LAYOUT_DIRECTION_UNDEFINED},
-     * {@link #LAYOUT_DIRECTION_LTR} or {@link #LAYOUT_DIRECTION_RTL}.
+     * @return the layout direction. This may be one of:
+     * {@link #TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE} or
+     * {@link #TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE} or
+     * {@link #TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE}.
      *
      * @hide
      */
     public static int getLayoutDirectionFromLocale(Locale locale) {
-        if (locale == null || locale.equals(Locale.ROOT)) return LAYOUT_DIRECTION_UNDEFINED;
+        if (locale == null || locale.equals(Locale.ROOT)) return TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE;
         // Be careful: this code will need to be changed when vertical scripts will be supported
         // OR if ICU4C is updated to have the "likelySubtags" file
         switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
             case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
-                return LAYOUT_DIRECTION_LTR;
+                return TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE;
             case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
             case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
-                return LAYOUT_DIRECTION_RTL;
+                return TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE;
             default:
-                return LAYOUT_DIRECTION_UNDEFINED;
+                return TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE;
         }
     }
 
@@ -810,7 +812,7 @@
         dest.writeInt(compatScreenWidthDp);
         dest.writeInt(compatScreenHeightDp);
         dest.writeInt(compatSmallestScreenWidthDp);
-        dest.writeInt(layoutDirection);
+        dest.writeInt(textLayoutDirection);
         dest.writeInt(seq);
     }
 
@@ -838,7 +840,7 @@
         compatScreenWidthDp = source.readInt();
         compatScreenHeightDp = source.readInt();
         compatSmallestScreenWidthDp = source.readInt();
-        layoutDirection = source.readInt();
+        textLayoutDirection = source.readInt();
         seq = source.readInt();
     }
     
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index c9238eb..82495e3 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -34,7 +34,7 @@
     void registerListener(INetworkPolicyListener listener);
     void unregisterListener(INetworkPolicyListener listener);
 
-    void setNetworkPolicy(int networkType, String subscriberId, in NetworkPolicy policy);
-    NetworkPolicy getNetworkPolicy(int networkType, String subscriberId);
+    void setNetworkPolicies(in NetworkPolicy[] policies);
+    NetworkPolicy[] getNetworkPolicies();
 
 }
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index b9909c3..1899281 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -26,17 +26,27 @@
  * @hide
  */
 public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
-    public final int cycleDay;
-    public final long warningBytes;
-    public final long limitBytes;
+    public final int networkTemplate;
+    public final String subscriberId;
+    public int cycleDay;
+    public long warningBytes;
+    public long limitBytes;
 
-    public NetworkPolicy(int cycleDay, long warningBytes, long limitBytes) {
+    public static final long WARNING_DISABLED = -1;
+    public static final long LIMIT_DISABLED = -1;
+
+    public NetworkPolicy(int networkTemplate, String subscriberId, int cycleDay, long warningBytes,
+            long limitBytes) {
+        this.networkTemplate = networkTemplate;
+        this.subscriberId = subscriberId;
         this.cycleDay = cycleDay;
         this.warningBytes = warningBytes;
         this.limitBytes = limitBytes;
     }
 
     public NetworkPolicy(Parcel in) {
+        networkTemplate = in.readInt();
+        subscriberId = in.readString();
         cycleDay = in.readInt();
         warningBytes = in.readLong();
         limitBytes = in.readLong();
@@ -44,6 +54,8 @@
 
     /** {@inheritDoc} */
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(networkTemplate);
+        dest.writeString(subscriberId);
         dest.writeInt(cycleDay);
         dest.writeLong(warningBytes);
         dest.writeLong(limitBytes);
@@ -56,17 +68,21 @@
 
     /** {@inheritDoc} */
     public int compareTo(NetworkPolicy another) {
-        if (another == null || limitBytes < another.limitBytes) {
+        if (another == null || another.limitBytes == LIMIT_DISABLED) {
+            // other value is missing or disabled; we win
             return -1;
-        } else {
+        }
+        if (limitBytes == LIMIT_DISABLED || another.limitBytes < limitBytes) {
+            // we're disabled or other limit is smaller; they win
             return 1;
         }
+        return 0;
     }
 
     @Override
     public String toString() {
-        return "NetworkPolicy: cycleDay=" + cycleDay + ", warningBytes=" + warningBytes
-                + ", limitBytes=" + limitBytes;
+        return "NetworkPolicy: networkTemplate=" + networkTemplate + ", cycleDay=" + cycleDay
+                + ", warningBytes=" + warningBytes + ", limitBytes=" + limitBytes;
     }
 
     public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 08b1a81..13ece40 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -55,17 +55,17 @@
     }
 
     /** {@hide} */
-    public void setNetworkPolicy(int networkType, String subscriberId, NetworkPolicy policy) {
+    public void setNetworkPolicies(NetworkPolicy[] policies) {
         try {
-            mService.setNetworkPolicy(networkType, subscriberId, policy);
+            mService.setNetworkPolicies(policies);
         } catch (RemoteException e) {
         }
     }
 
     /** {@hide} */
-    public NetworkPolicy getNetworkPolicy(int networkType, String subscriberId) {
+    public NetworkPolicy[] getNetworkPolicies() {
         try {
-            return mService.getNetworkPolicy(networkType, subscriberId);
+            return mService.getNetworkPolicies();
         } catch (RemoteException e) {
             return null;
         }
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 6354e9a..60f740e 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -23,6 +23,7 @@
 
 import java.io.CharArrayWriter;
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.HashSet;
 
 /**
@@ -48,74 +49,60 @@
      * generated.
      */
     public final long elapsedRealtime;
-    public final String[] iface;
-    public final int[] uid;
-    public final long[] rx;
-    public final long[] tx;
+    public int size;
+    public String[] iface;
+    public int[] uid;
+    public long[] rx;
+    public long[] tx;
 
     // TODO: add fg/bg stats once reported by kernel
 
-    private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) {
+    public NetworkStats(long elapsedRealtime, int initialSize) {
         this.elapsedRealtime = elapsedRealtime;
-        this.iface = iface;
-        this.uid = uid;
-        this.rx = rx;
-        this.tx = tx;
+        this.size = 0;
+        this.iface = new String[initialSize];
+        this.uid = new int[initialSize];
+        this.rx = new long[initialSize];
+        this.tx = new long[initialSize];
     }
 
     public NetworkStats(Parcel parcel) {
         elapsedRealtime = parcel.readLong();
+        size = parcel.readInt();
         iface = parcel.createStringArray();
         uid = parcel.createIntArray();
         rx = parcel.createLongArray();
         tx = parcel.createLongArray();
     }
 
-    public static class Builder {
-        private long mElapsedRealtime;
-        private final String[] mIface;
-        private final int[] mUid;
-        private final long[] mRx;
-        private final long[] mTx;
-
-        private int mIndex = 0;
-
-        public Builder(long elapsedRealtime, int size) {
-            mElapsedRealtime = elapsedRealtime;
-            mIface = new String[size];
-            mUid = new int[size];
-            mRx = new long[size];
-            mTx = new long[size];
+    public NetworkStats addEntry(String iface, int uid, long rx, long tx) {
+        if (size >= this.iface.length) {
+            final int newLength = Math.max(this.iface.length, 10) * 3 / 2;
+            this.iface = Arrays.copyOf(this.iface, newLength);
+            this.uid = Arrays.copyOf(this.uid, newLength);
+            this.rx = Arrays.copyOf(this.rx, newLength);
+            this.tx = Arrays.copyOf(this.tx, newLength);
         }
 
-        public Builder addEntry(String iface, int uid, long rx, long tx) {
-            mIface[mIndex] = iface;
-            mUid[mIndex] = uid;
-            mRx[mIndex] = rx;
-            mTx[mIndex] = tx;
-            mIndex++;
-            return this;
-        }
+        this.iface[size] = iface;
+        this.uid[size] = uid;
+        this.rx[size] = rx;
+        this.tx[size] = tx;
+        size++;
 
-        public NetworkStats build() {
-            if (mIndex != mIface.length) {
-                throw new IllegalArgumentException("unexpected number of entries");
-            }
-            return new NetworkStats(mElapsedRealtime, mIface, mUid, mRx, mTx);
-        }
+        return this;
     }
 
+    @Deprecated
     public int length() {
-        // length is identical for all fields
-        return iface.length;
+        return size;
     }
 
     /**
      * Find first stats index that matches the requested parameters.
      */
     public int findIndex(String iface, int uid) {
-        final int length = length();
-        for (int i = 0; i < length; i++) {
+        for (int i = 0; i < size; i++) {
             if (equal(iface, this.iface[i]) && uid == this.uid[i]) {
                 return i;
             }
@@ -195,9 +182,8 @@
         }
 
         // result will have our rows, and elapsed time between snapshots
-        final int length = length();
-        final NetworkStats.Builder result = new NetworkStats.Builder(deltaRealtime, length);
-        for (int i = 0; i < length; i++) {
+        final NetworkStats result = new NetworkStats(deltaRealtime, size);
+        for (int i = 0; i < size; i++) {
             final String iface = this.iface[i];
             final int uid = this.uid[i];
 
@@ -221,7 +207,7 @@
             }
         }
 
-        return result.build();
+        return result;
     }
 
     private static boolean equal(Object a, Object b) {
@@ -255,6 +241,7 @@
     /** {@inheritDoc} */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeLong(elapsedRealtime);
+        dest.writeInt(size);
         dest.writeStringArray(iface);
         dest.writeIntArray(uid);
         dest.writeLongArray(rx);
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index a697e96..5fa8e21 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -53,11 +53,15 @@
     public long[] tx;
 
     public NetworkStatsHistory(long bucketDuration) {
+        this(bucketDuration, 10);
+    }
+
+    public NetworkStatsHistory(long bucketDuration, int initialSize) {
         this.bucketDuration = bucketDuration;
-        bucketStart = new long[0];
-        rx = new long[0];
-        tx = new long[0];
-        bucketCount = bucketStart.length;
+        bucketStart = new long[initialSize];
+        rx = new long[initialSize];
+        tx = new long[initialSize];
+        bucketCount = 0;
     }
 
     public NetworkStatsHistory(Parcel in) {
@@ -168,8 +172,8 @@
      */
     private void insertBucket(int index, long start) {
         // create more buckets when needed
-        if (bucketCount + 1 > bucketStart.length) {
-            final int newLength = bucketStart.length + 10;
+        if (bucketCount >= bucketStart.length) {
+            final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
             bucketStart = Arrays.copyOf(bucketStart, newLength);
             rx = Arrays.copyOf(rx, newLength);
             tx = Arrays.copyOf(tx, newLength);
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 8e50cd5..5c4b258 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -240,7 +240,6 @@
 
     /**
      * Sets the {@link TrustManager}s to be used for connections made by this factory.
-     * @hide
      */
     public void setTrustManagers(TrustManager[] trustManager) {
         mTrustManagers = trustManager;
@@ -253,7 +252,6 @@
 
     /**
      * Sets the {@link KeyManager}s to be used for connections made by this factory.
-     * @hide
      */
     public void setKeyManagers(KeyManager[] keyManagers) {
         mKeyManagers = keyManagers;
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 8a688d5..3725fa6 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -41,11 +41,9 @@
      */
     public final static int UNSUPPORTED = -1;
 
-    // TODO: find better home for these template constants
-
     /**
      * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
-     * networks together. Only uses statistics for currently active IMSI.
+     * networks together. Only uses statistics for requested IMSI.
      *
      * @hide
      */
@@ -54,7 +52,7 @@
     /**
      * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
      * networks together that roughly meet a "3G" definition, or lower. Only
-     * uses statistics for currently active IMSI.
+     * uses statistics for requested IMSI.
      *
      * @hide
      */
@@ -63,7 +61,7 @@
     /**
      * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
      * networks together that meet a "4G" definition. Only uses statistics for
-     * currently active IMSI.
+     * requested IMSI.
      *
      * @hide
      */
@@ -184,6 +182,17 @@
         }
     }
 
+    /** {@hide} */
+    public static boolean isNetworkTemplateMobile(int networkTemplate) {
+        switch (networkTemplate) {
+            case TEMPLATE_MOBILE_3G_LOWER:
+            case TEMPLATE_MOBILE_4G:
+            case TEMPLATE_MOBILE_ALL:
+                return true;
+        }
+        return false;
+    }
+
     /**
      * Get the total number of packets transmitted through the mobile interface.
      *
diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl
index b66035f..aa5937e 100644
--- a/core/java/android/nfc/INfcTag.aidl
+++ b/core/java/android/nfc/INfcTag.aidl
@@ -43,7 +43,6 @@
     int formatNdef(int nativeHandle, in byte[] key);
     Tag rediscover(int nativehandle);
 
-    void setIsoDepTimeout(int timeout);
-    void setFelicaTimeout(int timeout);
+    int setTimeout(int technology, int timeout);
     void resetTimeouts();
 }
diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java
index 38b2bbd..d02086f 100644
--- a/core/java/android/nfc/tech/IsoDep.java
+++ b/core/java/android/nfc/tech/IsoDep.java
@@ -16,6 +16,7 @@
 
 package android.nfc.tech;
 
+import android.nfc.ErrorCodes;
 import android.nfc.Tag;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -90,7 +91,10 @@
      */
     public void setTimeout(int timeout) {
         try {
-            mTag.getTagService().setIsoDepTimeout(timeout);
+            int err = mTag.getTagService().setTimeout(TagTechnology.ISO_DEP, timeout);
+            if (err != ErrorCodes.SUCCESS) {
+                throw new IllegalArgumentException("The supplied timeout is not valid");
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "NFC service dead", e);
         }
diff --git a/core/java/android/nfc/tech/MifareClassic.java b/core/java/android/nfc/tech/MifareClassic.java
index c4b7718..5cafe5b 100644
--- a/core/java/android/nfc/tech/MifareClassic.java
+++ b/core/java/android/nfc/tech/MifareClassic.java
@@ -16,9 +16,11 @@
 
 package android.nfc.tech;
 
+import android.nfc.ErrorCodes;
 import android.nfc.Tag;
 import android.nfc.TagLostException;
 import android.os.RemoteException;
+import android.util.Log;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -69,6 +71,8 @@
  * require the {@link android.Manifest.permission#NFC} permission.
  */
 public final class MifareClassic extends BasicTagTechnology {
+    private static final String TAG = "NFC";
+
     /**
      * The default factory key.
      */
@@ -568,6 +572,31 @@
         return transceive(data, true);
     }
 
+    /**
+     * Set the timeout of {@link #transceive} in milliseconds.
+     * <p>The timeout only applies to MifareUltralight {@link #transceive},
+     * and is reset to a default value when {@link #close} is called.
+     * <p>Setting a longer timeout may be useful when performing
+     * transactions that require a long processing time on the tag
+     * such as key generation.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param timeout timeout value in milliseconds
+     * @hide
+     */
+    // TODO Unhide for ICS
+    public void setTimeout(int timeout) {
+        try {
+            int err = mTag.getTagService().setTimeout(TagTechnology.MIFARE_CLASSIC, timeout);
+            if (err != ErrorCodes.SUCCESS) {
+                throw new IllegalArgumentException("The supplied timeout is not valid");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "NFC service dead", e);
+        }
+    }
+
     private static void validateSector(int sector) {
         // Do not be too strict on upper bounds checking, since some cards
         // have more addressable memory than they report. For example,
diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/core/java/android/nfc/tech/MifareUltralight.java
index 6c2754b..3d4cdd1 100644
--- a/core/java/android/nfc/tech/MifareUltralight.java
+++ b/core/java/android/nfc/tech/MifareUltralight.java
@@ -16,10 +16,12 @@
 
 package android.nfc.tech;
 
+import android.nfc.ErrorCodes;
 import android.nfc.Tag;
 import android.nfc.TagLostException;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.util.Log;
 
 import java.io.IOException;
 
@@ -57,6 +59,8 @@
  * require the {@link android.Manifest.permission#NFC} permission.
  */
 public final class MifareUltralight extends BasicTagTechnology {
+    private static final String TAG = "NFC";
+
     /** A MIFARE Ultralight compatible tag of unknown type */
     public static final int TYPE_UNKNOWN = -1;
     /** A MIFARE Ultralight tag */
@@ -208,6 +212,32 @@
         return transceive(data, true);
     }
 
+    /**
+     * Set the timeout of {@link #transceive} in milliseconds.
+     * <p>The timeout only applies to MifareUltralight {@link #transceive},
+     * and is reset to a default value when {@link #close} is called.
+     * <p>Setting a longer timeout may be useful when performing
+     * transactions that require a long processing time on the tag
+     * such as key generation.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param timeout timeout value in milliseconds
+     * @hide
+     */
+    // TODO Unhide for ICS
+    public void setTimeout(int timeout) {
+        try {
+            int err = mTag.getTagService().setTimeout(
+                    TagTechnology.MIFARE_ULTRALIGHT, timeout);
+            if (err != ErrorCodes.SUCCESS) {
+                throw new IllegalArgumentException("The supplied timeout is not valid");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "NFC service dead", e);
+        }
+    }
+
     private static void validatePageIndex(int pageIndex) {
         // Do not be too strict on upper bounds checking, since some cards
         // may have more addressable memory than they report.
diff --git a/core/java/android/nfc/tech/NfcA.java b/core/java/android/nfc/tech/NfcA.java
index 1843eae..08095e6 100644
--- a/core/java/android/nfc/tech/NfcA.java
+++ b/core/java/android/nfc/tech/NfcA.java
@@ -16,9 +16,11 @@
 
 package android.nfc.tech;
 
+import android.nfc.ErrorCodes;
 import android.nfc.Tag;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.util.Log;
 
 import java.io.IOException;
 
@@ -33,6 +35,8 @@
  * require the {@link android.Manifest.permission#NFC} permission.
  */
 public final class NfcA extends BasicTagTechnology {
+    private static final String TAG = "NFC";
+
     /** @hide */
     public static final String EXTRA_SAK = "sak";
     /** @hide */
@@ -112,4 +116,29 @@
     public byte[] transceive(byte[] data) throws IOException {
         return transceive(data, true);
     }
+
+    /**
+     * Set the timeout of {@link #transceive} in milliseconds.
+     * <p>The timeout only applies to NfcA {@link #transceive}, and is
+     * reset to a default value when {@link #close} is called.
+     * <p>Setting a longer timeout may be useful when performing
+     * transactions that require a long processing time on the tag
+     * such as key generation.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param timeout timeout value in milliseconds
+     * @hide
+     */
+    // TODO Unhide for ICS
+    public void setTimeout(int timeout) {
+        try {
+            int err = mTag.getTagService().setTimeout(TagTechnology.NFC_A, timeout);
+            if (err != ErrorCodes.SUCCESS) {
+                throw new IllegalArgumentException("The supplied timeout is not valid");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "NFC service dead", e);
+        }
+    }
 }
diff --git a/core/java/android/nfc/tech/NfcF.java b/core/java/android/nfc/tech/NfcF.java
index 250c9b3..85abf89 100644
--- a/core/java/android/nfc/tech/NfcF.java
+++ b/core/java/android/nfc/tech/NfcF.java
@@ -16,6 +16,7 @@
 
 package android.nfc.tech;
 
+import android.nfc.ErrorCodes;
 import android.nfc.Tag;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -131,7 +132,10 @@
     // TODO Unhide for ICS
     public void setTimeout(int timeout) {
         try {
-            mTag.getTagService().setFelicaTimeout(timeout);
+            int err = mTag.getTagService().setTimeout(TagTechnology.NFC_F, timeout);
+            if (err != ErrorCodes.SUCCESS) {
+                throw new IllegalArgumentException("The supplied timeout is not valid");
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "NFC service dead", e);
         }
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index bb6ed9c..3b2e6b6 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -178,9 +178,8 @@
      * have specific uses which are expected to be consistent by the app and
      * sync adapter.
      *
-     * @hide
      */
-    public interface SyncColumns extends CalendarSyncColumns {
+    protected interface SyncColumns extends CalendarSyncColumns {
         /**
          * The account that was used to sync the entry to the device. If the
          * account_type is not {@link #ACCOUNT_TYPE_LOCAL} then the name and
@@ -223,7 +222,7 @@
 
         /**
          * If set to 1 this causes events on this calendar to be duplicated with
-         * {@link EventsColumns#LAST_SYNCED} set to 1 whenever the event
+         * {@link Events#LAST_SYNCED} set to 1 whenever the event
          * transitions from non-dirty to dirty. The duplicated event will not be
          * expanded in the instances table and will only show up in sync adapter
          * queries of the events table. It will also be deleted when the
@@ -236,7 +235,7 @@
     /**
      * Columns specific to the Calendars Uri that other Uris can query.
      */
-    private interface CalendarsColumns {
+    protected interface CalendarsColumns {
         /**
          * The color of the calendar
          * <P>Type: INTEGER (color value)</P>
@@ -549,7 +548,7 @@
     /**
      * Columns from the Attendees table that other tables join into themselves.
      */
-    private interface AttendeesColumns {
+    protected interface AttendeesColumns {
 
         /**
          * The id of the event. Column name.
@@ -639,7 +638,7 @@
     /**
      * Columns from the Events table that other tables join into themselves.
      */
-    private interface EventsColumns {
+    protected interface EventsColumns {
 
         /**
          * The {@link Calendars#_ID} of the calendar the event belongs to.
@@ -1525,7 +1524,7 @@
      * time zone for the instaces. These settings are stored using a key/value
      * scheme.
      */
-    private interface CalendarCacheColumns {
+    protected interface CalendarCacheColumns {
         /**
          * The key for the setting. Keys are defined in {@link CalendarCache}.
          */
@@ -1597,7 +1596,7 @@
      * the Instances table and these are all stored in the first (and only)
      * row of the CalendarMetaData table.
      */
-    private interface CalendarMetaDataColumns {
+    protected interface CalendarMetaDataColumns {
         /**
          * The local timezone that was used for precomputing the fields
          * in the Instances table.
@@ -1637,7 +1636,7 @@
     public static final class CalendarMetaData implements CalendarMetaDataColumns, BaseColumns {
     }
 
-    private interface EventDaysColumns {
+    protected interface EventDaysColumns {
         /**
          * The Julian starting day number. Column name.
          * <P>Type: INTEGER (int)</P>
@@ -1655,7 +1654,7 @@
      * Fields and helpers for querying for a list of days that contain events.
      */
     public static final class EventDays implements EventDaysColumns {
-        private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
                 + "/instances/groupbyday");
 
         /**
@@ -1690,7 +1689,7 @@
         }
     }
 
-    private interface RemindersColumns {
+    protected interface RemindersColumns {
         /**
          * The event the reminder belongs to. Column name.
          * <P>Type: INTEGER (foreign key to the Events table)</P>
@@ -1755,7 +1754,7 @@
         }
     }
 
-    private interface CalendarAlertsColumns {
+    protected interface CalendarAlertsColumns {
         /**
          * The event that the alert belongs to. Column name.
          * <P>Type: INTEGER (foreign key to the Events table)</P>
@@ -2069,7 +2068,7 @@
         }
     }
 
-    private interface ExtendedPropertiesColumns {
+    protected interface ExtendedPropertiesColumns {
         /**
          * The event the extended property belongs to. Column name.
          * <P>Type: INTEGER (foreign key to the Events table)</P>
@@ -2128,7 +2127,7 @@
     /**
      * Columns from the EventsRawTimes table
      */
-    private interface EventsRawTimesColumns {
+    protected interface EventsRawTimesColumns {
         /**
          * The corresponding event id. Column name.
          * <P>Type: INTEGER (long)</P>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6ab7738..1025b20 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3800,13 +3800,13 @@
         /** {@hide} */
         public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold";
         /** {@hide} */
-        public static final String NETSTATS_SUMMARY_BUCKET_DURATION = "netstats_summary_bucket_duration";
+        public static final String NETSTATS_NETWORK_BUCKET_DURATION = "netstats_network_bucket_duration";
         /** {@hide} */
-        public static final String NETSTATS_SUMMARY_MAX_HISTORY = "netstats_summary_max_history";
+        public static final String NETSTATS_NETWORK_MAX_HISTORY = "netstats_network_max_history";
         /** {@hide} */
-        public static final String NETSTATS_DETAIL_BUCKET_DURATION = "netstats_detail_bucket_duration";
+        public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
         /** {@hide} */
-        public static final String NETSTATS_DETAIL_MAX_HISTORY = "netstats_detail_max_history";
+        public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
 
         /**
          * @hide
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index c397af9..376e0bb 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -19,6 +19,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.provider.CallLog.Calls;
+
 /**
  * The contract between the voicemail provider and applications. Contains
  * definitions for the supported URIs and columns.
@@ -45,13 +46,17 @@
  */
 // TODO: unhide when the API is approved by android-api-council
 public class VoicemailContract {
+    /** Not instantiable. */
+    private VoicemailContract() {
+    }
+
     /** The authority used by the voicemail provider. */
     public static final String AUTHORITY = "com.android.voicemail";
 
     /** URI to insert/retrieve all voicemails. */
     public static final Uri CONTENT_URI =
             Uri.parse("content://" + AUTHORITY + "/voicemail");
-    /** URI to insert/retrieve voicemails by a given voicemai source. */
+    /** URI to insert/retrieve voicemails by a given voicemail source. */
     public static final Uri CONTENT_URI_SOURCE =
             Uri.parse("content://" + AUTHORITY + "/voicemail/source/");
 
@@ -72,6 +77,10 @@
             "vnd.android.cursor.dir/voicemails";
 
     public static final class Voicemails implements BaseColumns {
+        /** Not instantiable. */
+        private Voicemails() {
+        }
+
         /**
          * Phone number of the voicemail sender.
          * <P>Type: TEXT</P>
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 44229a4..4cae9d8 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -253,18 +253,19 @@
         mLayout.setVisibility(View.VISIBLE);
 
         WebChromeClient client = webView.getWebChromeClient();
-        client.onShowCustomView(mLayout, mCallback);
-        // Plugins like Flash will draw over the video so hide
-        // them while we're playing.
-        if (webView.getViewManager() != null)
-            webView.getViewManager().hideAll();
+        if (client != null) {
+            client.onShowCustomView(mLayout, mCallback);
+            // Plugins like Flash will draw over the video so hide
+            // them while we're playing.
+            if (webView.getViewManager() != null)
+                webView.getViewManager().hideAll();
 
-        mProgressView = client.getVideoLoadingProgressView();
-        if (mProgressView != null) {
-            mLayout.addView(mProgressView, layoutParams);
-            mProgressView.setVisibility(View.VISIBLE);
+            mProgressView = client.getVideoLoadingProgressView();
+            if (mProgressView != null) {
+                mLayout.addView(mProgressView, layoutParams);
+                mProgressView.setVisibility(View.VISIBLE);
+            }
         }
-
     }
 
     /**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 95e4880..10c7390 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -5605,8 +5605,8 @@
      * Adjustable parameters. Angle is the radians on a unit circle, limited
      * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical)
      */
-    private static final float HSLOPE_TO_START_SNAP = .1f;
-    private static final float HSLOPE_TO_BREAK_SNAP = .2f;
+    private static final float HSLOPE_TO_START_SNAP = .25f;
+    private static final float HSLOPE_TO_BREAK_SNAP = .4f;
     private static final float VSLOPE_TO_START_SNAP = 1.25f;
     private static final float VSLOPE_TO_BREAK_SNAP = .95f;
     /*
@@ -5617,6 +5617,11 @@
      */
     private static final float ANGLE_VERT = 2f;
     private static final float ANGLE_HORIZ = 0f;
+    /*
+     *  The modified moving average weight.
+     *  Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n
+     */
+    private static final float MMA_WEIGHT_N = 5;
 
     private boolean hitFocusedPlugin(int contentX, int contentY) {
         if (DebugFlags.WEB_VIEW) {
@@ -6003,8 +6008,9 @@
                 if (deltaX == 0 && deltaY == 0) {
                     keepScrollBarsVisible = done = true;
                 } else {
-                    mAverageAngle = (mAverageAngle +
-                            calculateDragAngle(deltaX, deltaY)) / 2;
+                    mAverageAngle +=
+                        (calculateDragAngle(deltaX, deltaY) - mAverageAngle)
+                        / MMA_WEIGHT_N;
                     if (mSnapScrollMode != SNAP_NONE) {
                         if (mSnapScrollMode == SNAP_Y) {
                             // radical change means getting out of snap mode
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index bda82a3..092c2f7 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -494,6 +494,14 @@
         requestLayout();
     }
 
+    private static int max2(int[] a, int valueIfEmpty) {
+        int result = valueIfEmpty;
+        for (int i = 0, N = a.length; i < N; i++) {
+            result = Math.max(result, a[i]);
+        }
+        return result;
+    }
+
     private static int sum(float[] a) {
         int result = 0;
         for (int i = 0, length = a.length; i < length; i++) {
@@ -1409,7 +1417,7 @@
         // External entry points
 
         private int size(int[] locations) {
-            return locations[locations.length - 1] - locations[0];
+            return max2(locations, 0) - locations[0];
         }
 
         private int getMin() {
@@ -1878,21 +1886,13 @@
             return result;
         }
 
-        private static int max(int[] a, int valueIfEmpty) {
-            int result = valueIfEmpty;
-            for (int i = 0, length = a.length; i < length; i++) {
-                result = Math.max(result, a[i]);
-            }
-            return result;
-        }
-
         /*
         Create a compact array of keys or values using the supplied index.
          */
         private static <K> K[] compact(K[] a, int[] index) {
             int size = a.length;
             Class<?> componentType = a.getClass().getComponentType();
-            K[] result = (K[]) Array.newInstance(componentType, max(index, -1) + 1);
+            K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1);
 
             // this overwrite duplicates, retaining the last equivalent entry
             for (int i = 0; i < size; i++) {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 40b0a9c..4c47d37 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -711,6 +711,17 @@
         requestBindService();
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mWorkerThread != null) {
+                mWorkerThread.quit();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
     private void loadNextIndexInBackground() {
         mWorkerQueue.post(new Runnable() {
             @Override
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
index c83f910..37bb522 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
@@ -50,8 +50,8 @@
         android:layout_width="0dip"
         android:gravity="center_horizontal|center_vertical">
 
-        <com.android.internal.widget.WaveView
-            android:id="@+id/unlock_widget"
+        <TextView
+            android:id="@+id/screenLocked"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textAppearance="?android:attr/textAppearanceMedium"
@@ -75,7 +75,7 @@
 
 
         <com.android.internal.widget.WaveView
-            android:id="@+id/wave_view"
+            android:id="@+id/unlock_widget"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_gravity="center_vertical|center_horizontal"
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
index f1f745e..54a5e4e 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
@@ -30,169 +30,169 @@
         args = {Locale.class}
     )
     public void testGetLayoutDirectionFromLocale() {
-        assertEquals(Configuration.LAYOUT_DIRECTION_UNDEFINED,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(null));
 
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.ENGLISH));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.CANADA));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.CANADA_FRENCH));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.FRANCE));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.FRENCH));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.GERMAN));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.GERMANY));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.ITALIAN));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.ITALY));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.UK));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.US));
 
-        assertEquals(Configuration.LAYOUT_DIRECTION_UNDEFINED,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.ROOT));
 
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.CHINA));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.CHINESE));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.JAPAN));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.JAPANESE));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.KOREA));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.KOREAN));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.PRC));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.SIMPLIFIED_CHINESE));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.TAIWAN));
-        assertEquals(Configuration.LAYOUT_DIRECTION_LTR,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(Locale.TRADITIONAL_CHINESE));
 
         Locale locale = new Locale("ar");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "AE");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "BH");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "DZ");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "EG");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "IQ");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "JO");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "KW");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "LB");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "LY");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "MA");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "OM");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "QA");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "SA");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "SD");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "SY");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "TN");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ar", "YE");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
 
         locale = new Locale("fa");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("fa", "AF");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("fa", "IR");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
 
         locale = new Locale("iw");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("iw", "IL");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("he");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("he", "IL");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
 
         // The following test will not pass until we are able to take care about the scrip subtag
         // thru having the "likelySubTags" file into ICU4C
 //        locale = new Locale("pa_Arab");
-//        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+//        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
 //            Configuration.getLayoutDirectionFromLocale(locale));
 //        locale = new Locale("pa_Arab", "PK");
-//        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+//        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
 //            Configuration.getLayoutDirectionFromLocale(locale));
 
         locale = new Locale("ps");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
         locale = new Locale("ps", "AF");
-        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
             Configuration.getLayoutDirectionFromLocale(locale));
 
         // The following test will not work as the localized display name would be "Urdu" with ICU 4.4
         // We will need ICU 4.6 to get the correct localized display name
 //        locale = new Locale("ur");
-//        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+//        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
 //            Configuration.getLayoutDirectionFromLocale(locale));
 //        locale = new Locale("ur", "IN");
-//        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+//        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
 //            Configuration.getLayoutDirectionFromLocale(locale));
 //        locale = new Locale("ur", "PK");
-//        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+//        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
 //            Configuration.getLayoutDirectionFromLocale(locale));
 
         // The following test will not pass until we are able to take care about the scrip subtag
         // thru having the "likelySubTags" file into ICU4C
 //        locale = new Locale("uz_Arab");
-//        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+//        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
 //            Configuration.getLayoutDirectionFromLocale(locale));
 //        locale = new Locale("uz_Arab", "AF");
-//        assertEquals(Configuration.LAYOUT_DIRECTION_RTL,
+//        assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE,
 //            Configuration.getLayoutDirectionFromLocale(locale));
     }
 }
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 8a3e871..5250a7c 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -16,7 +16,6 @@
 
 package android.net;
 
-import android.os.SystemClock;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import junit.framework.TestCase;
@@ -25,12 +24,14 @@
 public class NetworkStatsTest extends TestCase {
 
     private static final String TEST_IFACE = "test0";
+    private static final int TEST_UID = 1001;
+    private static final long TEST_START = 1194220800000L;
 
     public void testFindIndex() throws Exception {
-        final NetworkStats stats = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 3)
+        final NetworkStats stats = new NetworkStats(TEST_START, 3)
                 .addEntry(TEST_IFACE, 100, 1024, 0)
                 .addEntry(TEST_IFACE, 101, 0, 1024)
-                .addEntry(TEST_IFACE, 102, 1024, 1024).build();
+                .addEntry(TEST_IFACE, 102, 1024, 1024);
 
         assertEquals(2, stats.findIndex(TEST_IFACE, 102));
         assertEquals(2, stats.findIndex(TEST_IFACE, 102));
@@ -38,14 +39,40 @@
         assertEquals(-1, stats.findIndex(TEST_IFACE, 6));
     }
 
-    public void testSubtractIdenticalData() throws Exception {
-        final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
-                .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+    public void testAddEntryGrow() throws Exception {
+        final NetworkStats stats = new NetworkStats(TEST_START, 2);
 
-        final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+        assertEquals(0, stats.size);
+        assertEquals(2, stats.iface.length);
+
+        stats.addEntry(TEST_IFACE, TEST_UID, 1L, 2L);
+        stats.addEntry(TEST_IFACE, TEST_UID, 2L, 2L);
+
+        assertEquals(2, stats.size);
+        assertEquals(2, stats.iface.length);
+
+        stats.addEntry(TEST_IFACE, TEST_UID, 3L, 4L);
+        stats.addEntry(TEST_IFACE, TEST_UID, 4L, 4L);
+        stats.addEntry(TEST_IFACE, TEST_UID, 5L, 5L);
+
+        assertEquals(5, stats.size);
+        assertTrue(stats.iface.length >= 5);
+
+        assertEquals(1L, stats.rx[0]);
+        assertEquals(2L, stats.rx[1]);
+        assertEquals(3L, stats.rx[2]);
+        assertEquals(4L, stats.rx[3]);
+        assertEquals(5L, stats.rx[4]);
+    }
+
+    public void testSubtractIdenticalData() throws Exception {
+        final NetworkStats before = new NetworkStats(TEST_START, 2)
                 .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+                .addEntry(TEST_IFACE, 101, 0, 1024);
+
+        final NetworkStats after = new NetworkStats(TEST_START, 2)
+                .addEntry(TEST_IFACE, 100, 1024, 0)
+                .addEntry(TEST_IFACE, 101, 0, 1024);
 
         final NetworkStats result = after.subtract(before);
 
@@ -57,13 +84,13 @@
     }
 
     public void testSubtractIdenticalRows() throws Exception {
-        final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+        final NetworkStats before = new NetworkStats(TEST_START, 2)
                 .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+                .addEntry(TEST_IFACE, 101, 0, 1024);
 
-        final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+        final NetworkStats after = new NetworkStats(TEST_START, 2)
                 .addEntry(TEST_IFACE, 100, 1025, 2)
-                .addEntry(TEST_IFACE, 101, 3, 1028).build();
+                .addEntry(TEST_IFACE, 101, 3, 1028);
 
         final NetworkStats result = after.subtract(before);
 
@@ -75,14 +102,14 @@
     }
 
     public void testSubtractNewRows() throws Exception {
-        final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+        final NetworkStats before = new NetworkStats(TEST_START, 2)
                 .addEntry(TEST_IFACE, 100, 1024, 0)
-                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+                .addEntry(TEST_IFACE, 101, 0, 1024);
 
-        final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 3)
+        final NetworkStats after = new NetworkStats(TEST_START, 3)
                 .addEntry(TEST_IFACE, 100, 1024, 0)
                 .addEntry(TEST_IFACE, 101, 0, 1024)
-                .addEntry(TEST_IFACE, 102, 1024, 1024).build();
+                .addEntry(TEST_IFACE, 102, 1024, 1024);
 
         final NetworkStats result = after.subtract(before);
 
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 2b31462..9294df6 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -188,6 +188,11 @@
 
     status_t setBufferCountServerLocked(int bufferCount);
 
+    // computeCurrentTransformMatrix computes the transform matrix for the
+    // current texture.  It uses mCurrentTransform and the current GraphicBuffer
+    // to compute this matrix and stores it in mCurrentTransformMatrix.
+    void computeCurrentTransformMatrix();
+
     enum { INVALID_BUFFER_SLOT = -1 };
 
     struct BufferSlot {
@@ -288,9 +293,9 @@
     // by calling setBufferCount or setBufferCountServer
     int mBufferCount;
 
-    // mRequestedBufferCount is the number of buffer slots requested by the
-    // client. The default is zero, which means the client doesn't care how
-    // many buffers there is.
+    // mClientBufferCount is the number of buffer slots requested by the client.
+    // The default is zero, which means the client doesn't care how many buffers
+    // there is.
     int mClientBufferCount;
 
     // mServerBufferCount buffer count requested by the server-side
@@ -322,6 +327,11 @@
     // gets set to mLastQueuedTransform each time updateTexImage is called.
     uint32_t mCurrentTransform;
 
+    // mCurrentTransformMatrix is the transform matrix for the current texture.
+    // It gets computed by computeTransformMatrix each time updateTexImage is
+    // called.
+    float mCurrentTransformMatrix[16];
+
     // mCurrentTimestamp is the timestamp for the current texture. It
     // gets set to mLastQueuedTimestamp each time updateTexImage is called.
     int64_t mCurrentTimestamp;
@@ -362,6 +372,7 @@
     // variables of SurfaceTexture objects. It must be locked whenever the
     // member variables are accessed.
     mutable Mutex mMutex;
+
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/utils/BlobCache.h b/include/utils/BlobCache.h
new file mode 100644
index 0000000..8f76d72
--- /dev/null
+++ b/include/utils/BlobCache.h
@@ -0,0 +1,181 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#ifndef ANDROID_BLOB_CACHE_H
+#define ANDROID_BLOB_CACHE_H
+
+#include <stddef.h>
+
+#include <utils/RefBase.h>
+#include <utils/SortedVector.h>
+#include <utils/threads.h>
+
+namespace android {
+
+// A BlobCache is an in-memory cache for binary key/value pairs. All the public
+// methods are thread-safe.
+//
+// The cache contents can be serialized to a file and reloaded in a subsequent
+// execution of the program. This serialization is non-portable and should only
+// be loaded by the device that generated it.
+class BlobCache : public RefBase {
+public:
+
+    // Create an empty blob cache. The blob cache will cache key/value pairs
+    // with key and value sizes less than or equal to maxKeySize and
+    // maxValueSize, respectively. The total combined size of ALL cache entries
+    // (key sizes plus value sizes) will not exceed maxTotalSize.
+    BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize);
+
+    // set inserts a new binary value into the cache and associates it with the
+    // given binary key.  If the key or value are too large for the cache then
+    // the cache remains unchanged.  This includes the case where a different
+    // value was previously associated with the given key - the old value will
+    // remain in the cache.  If the given key and value are small enough to be
+    // put in the cache (based on the maxKeySize, maxValueSize, and maxTotalSize
+    // values specified to the BlobCache constructor), then the key/value pair
+    // will be in the cache after set returns.  Note, however, that a subsequent
+    // call to set may evict old key/value pairs from the cache.
+    //
+    // Preconditions:
+    //   key != NULL
+    //   0 < keySize
+    //   value != NULL
+    //   0 < valueSize
+    void set(const void* key, size_t keySize, const void* value,
+            size_t valueSize);
+
+    // The get function retrieves from the cache the binary value associated
+    // with a given binary key.  If the key is present in the cache then the
+    // length of the binary value associated with that key is returned.  If the
+    // value argument is non-NULL and the size of the cached value is less than
+    // valueSize bytes then the cached value is copied into the buffer pointed
+    // to by the value argument.  If the key is not present in the cache then 0
+    // is returned and the buffer pointed to by the value argument is not
+    // modified.
+    //
+    // Note that when calling get multiple times with the same key, the later
+    // calls may fail, returning 0, even if earlier calls succeeded.  The return
+    // value must be checked for each call.
+    //
+    // Preconditions:
+    //   key != NULL
+    //   0 < keySize
+    //   0 <= valueSize
+    size_t get(const void* key, size_t keySize, void* value, size_t valueSize);
+
+private:
+    // Copying is disallowed.
+    BlobCache(const BlobCache&);
+    void operator=(const BlobCache&);
+
+    // clean evicts a randomly chosen set of entries from the cache such that
+    // the total size of all remaining entries is less than mMaxTotalSize/2.
+    void clean();
+
+    // isCleanable returns true if the cache is full enough for the clean method
+    // to have some effect, and false otherwise.
+    bool isCleanable() const;
+
+    // A Blob is an immutable sized unstructured data blob.
+    class Blob : public RefBase {
+    public:
+        Blob(const void* data, size_t size, bool copyData);
+        ~Blob();
+
+        bool operator<(const Blob& rhs) const;
+
+        const void* getData() const;
+        size_t getSize() const;
+
+    private:
+        // Copying is not allowed.
+        Blob(const Blob&);
+        void operator=(const Blob&);
+
+        // mData points to the buffer containing the blob data.
+        const void* mData;
+
+        // mSize is the size of the blob data in bytes.
+        size_t mSize;
+
+        // mOwnsData indicates whether or not this Blob object should free the
+        // memory pointed to by mData when the Blob gets destructed.
+        bool mOwnsData;
+    };
+
+    // A CacheEntry is a single key/value pair in the cache.
+    class CacheEntry {
+    public:
+        CacheEntry();
+        CacheEntry(const sp<Blob>& key, const sp<Blob>& value);
+        CacheEntry(const CacheEntry& ce);
+
+        bool operator<(const CacheEntry& rhs) const;
+        const CacheEntry& operator=(const CacheEntry&);
+
+        sp<Blob> getKey() const;
+        sp<Blob> getValue() const;
+
+        void setValue(const sp<Blob>& value);
+
+    private:
+
+        // mKey is the key that identifies the cache entry.
+        sp<Blob> mKey;
+
+        // mValue is the cached data associated with the key.
+        sp<Blob> mValue;
+    };
+
+    // mMaxKeySize is the maximum key size that will be cached. Calls to
+    // BlobCache::set with a keySize parameter larger than mMaxKeySize will
+    // simply not add the key/value pair to the cache.
+    const size_t mMaxKeySize;
+
+    // mMaxValueSize is the maximum value size that will be cached. Calls to
+    // BlobCache::set with a valueSize parameter larger than mMaxValueSize will
+    // simply not add the key/value pair to the cache.
+    const size_t mMaxValueSize;
+
+    // mMaxTotalSize is the maximum size that all cache entries can occupy. This
+    // includes space for both keys and values. When a call to BlobCache::set
+    // would otherwise cause this limit to be exceeded, either the key/value
+    // pair passed to BlobCache::set will not be cached or other cache entries
+    // will be evicted from the cache to make room for the new entry.
+    const size_t mMaxTotalSize;
+
+    // mTotalSize is the total combined size of all keys and values currently in
+    // the cache.
+    size_t mTotalSize;
+
+    // mRandState is the pseudo-random number generator state. It is passed to
+    // nrand48 to generate random numbers when needed. It must be protected by
+    // mMutex.
+    unsigned short mRandState[3];
+
+    // mCacheEntries stores all the cache entries that are resident in memory.
+    // Cache entries are added to it by the 'set' method.
+    SortedVector<CacheEntry> mCacheEntries;
+
+    // mMutex is used to synchronize access to all member variables.  It must be
+    // locked any time the member variables are written or read.
+    Mutex mMutex;
+};
+
+}
+
+#endif // ANDROID_BLOB_CACHE_H
diff --git a/include/utils/threads.h b/include/utils/threads.h
index 41e5766..0bd69cf 100644
--- a/include/utils/threads.h
+++ b/include/utils/threads.h
@@ -526,6 +526,7 @@
     Thread& operator=(const Thread&);
     static  int             _threadLoop(void* user);
     const   bool            mCanCallJava;
+    // always hold mLock when reading or writing
             thread_id_t     mThread;
     mutable Mutex           mLock;
             Condition       mThreadExitedCondition;
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index ee97dcf..2cda4c8 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -96,6 +96,7 @@
     sp<ISurfaceComposer> composer(ComposerService::getComposerService());
     mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
     mNextCrop.makeInvalid();
+    memcpy(mCurrentTransformMatrix, mtxIdentity, sizeof(mCurrentTransformMatrix));
 }
 
 SurfaceTexture::~SurfaceTexture() {
@@ -547,6 +548,7 @@
         mCurrentCrop = mSlots[buf].mCrop;
         mCurrentTransform = mSlots[buf].mTransform;
         mCurrentTimestamp = mSlots[buf].mTimestamp;
+        computeCurrentTransformMatrix();
         mDequeueCondition.signal();
     } else {
         // We always bind the texture even if we don't update its contents.
@@ -596,8 +598,12 @@
 }
 
 void SurfaceTexture::getTransformMatrix(float mtx[16]) {
-    LOGV("SurfaceTexture::getTransformMatrix");
     Mutex::Autolock lock(mMutex);
+    memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix));
+}
+
+void SurfaceTexture::computeCurrentTransformMatrix() {
+    LOGV("SurfaceTexture::computeCurrentTransformMatrix");
 
     float xform[16];
     for (int i = 0; i < 16; i++) {
@@ -684,7 +690,7 @@
     // coordinate of 0, so SurfaceTexture must behave the same way.  We don't
     // want to expose this to applications, however, so we must add an
     // additional vertical flip to the transform after all the other transforms.
-    mtxMul(mtx, mtxFlipV, mtxBeforeFlipV);
+    mtxMul(mCurrentTransformMatrix, mtxFlipV, mtxBeforeFlipV);
 }
 
 nsecs_t SurfaceTexture::getTimestamp() {
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index 2f704c8..da04b4a 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -514,4 +514,112 @@
     thread->requestExitAndWait();
 }
 
+TEST_F(SurfaceTextureClientTest, GetTransformMatrixReturnsVerticalFlip) {
+    sp<ANativeWindow> anw(mSTC);
+    sp<SurfaceTexture> st(mST);
+    android_native_buffer_t* buf[3];
+    float mtx[16] = {};
+    ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0]));
+    ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0]));
+    ASSERT_EQ(OK, st->updateTexImage());
+    st->getTransformMatrix(mtx);
+
+    EXPECT_EQ(1.f, mtx[0]);
+    EXPECT_EQ(0.f, mtx[1]);
+    EXPECT_EQ(0.f, mtx[2]);
+    EXPECT_EQ(0.f, mtx[3]);
+
+    EXPECT_EQ(0.f, mtx[4]);
+    EXPECT_EQ(-1.f, mtx[5]);
+    EXPECT_EQ(0.f, mtx[6]);
+    EXPECT_EQ(0.f, mtx[7]);
+
+    EXPECT_EQ(0.f, mtx[8]);
+    EXPECT_EQ(0.f, mtx[9]);
+    EXPECT_EQ(1.f, mtx[10]);
+    EXPECT_EQ(0.f, mtx[11]);
+
+    EXPECT_EQ(0.f, mtx[12]);
+    EXPECT_EQ(1.f, mtx[13]);
+    EXPECT_EQ(0.f, mtx[14]);
+    EXPECT_EQ(1.f, mtx[15]);
 }
+
+TEST_F(SurfaceTextureClientTest, GetTransformMatrixSucceedsAfterFreeingBuffers) {
+    sp<ANativeWindow> anw(mSTC);
+    sp<SurfaceTexture> st(mST);
+    android_native_buffer_t* buf[3];
+    float mtx[16] = {};
+    ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0]));
+    ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0]));
+    ASSERT_EQ(OK, st->updateTexImage());
+    ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 6)); // frees buffers
+    st->getTransformMatrix(mtx);
+
+    EXPECT_EQ(1.f, mtx[0]);
+    EXPECT_EQ(0.f, mtx[1]);
+    EXPECT_EQ(0.f, mtx[2]);
+    EXPECT_EQ(0.f, mtx[3]);
+
+    EXPECT_EQ(0.f, mtx[4]);
+    EXPECT_EQ(-1.f, mtx[5]);
+    EXPECT_EQ(0.f, mtx[6]);
+    EXPECT_EQ(0.f, mtx[7]);
+
+    EXPECT_EQ(0.f, mtx[8]);
+    EXPECT_EQ(0.f, mtx[9]);
+    EXPECT_EQ(1.f, mtx[10]);
+    EXPECT_EQ(0.f, mtx[11]);
+
+    EXPECT_EQ(0.f, mtx[12]);
+    EXPECT_EQ(1.f, mtx[13]);
+    EXPECT_EQ(0.f, mtx[14]);
+    EXPECT_EQ(1.f, mtx[15]);
+}
+
+TEST_F(SurfaceTextureClientTest, GetTransformMatrixSucceedsAfterFreeingBuffersWithCrop) {
+    sp<ANativeWindow> anw(mSTC);
+    sp<SurfaceTexture> st(mST);
+    android_native_buffer_t* buf[3];
+    float mtx[16] = {};
+    android_native_rect_t crop;
+    crop.left = 0;
+    crop.top = 0;
+    crop.right = 5;
+    crop.bottom = 5;
+
+    ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4));
+    ASSERT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 8, 8, 0));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0]));
+    ASSERT_EQ(OK, native_window_set_crop(anw.get(), &crop));
+    ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0]));
+    ASSERT_EQ(OK, st->updateTexImage());
+    ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 6)); // frees buffers
+    st->getTransformMatrix(mtx);
+
+    // This accounts for the 1 texel shrink for each edge that's included in the
+    // transform matrix to avoid texturing outside the crop region.
+    EXPECT_EQ(.5f, mtx[0]);
+    EXPECT_EQ(0.f, mtx[1]);
+    EXPECT_EQ(0.f, mtx[2]);
+    EXPECT_EQ(0.f, mtx[3]);
+
+    EXPECT_EQ(0.f, mtx[4]);
+    EXPECT_EQ(-.5f, mtx[5]);
+    EXPECT_EQ(0.f, mtx[6]);
+    EXPECT_EQ(0.f, mtx[7]);
+
+    EXPECT_EQ(0.f, mtx[8]);
+    EXPECT_EQ(0.f, mtx[9]);
+    EXPECT_EQ(1.f, mtx[10]);
+    EXPECT_EQ(0.f, mtx[11]);
+
+    EXPECT_EQ(0.f, mtx[12]);
+    EXPECT_EQ(.5f, mtx[13]);
+    EXPECT_EQ(0.f, mtx[14]);
+    EXPECT_EQ(1.f, mtx[15]);
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
index 8747ba5..16280d2 100644
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ b/libs/gui/tests/SurfaceTexture_test.cpp
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
+
 #include <gtest/gtest.h>
 #include <gui/SurfaceTexture.h>
 #include <gui/SurfaceTextureClient.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/String8.h>
+#include <utils/threads.h>
 
 #include <surfaceflinger/ISurfaceComposer.h>
 #include <surfaceflinger/Surface.h>
@@ -618,4 +621,269 @@
     }
 }
 
+/*
+ * This test is for testing GL -> GL texture streaming via SurfaceTexture.  It
+ * contains functionality to create a producer thread that will perform GL
+ * rendering to an ANativeWindow that feeds frames to a SurfaceTexture.
+ * Additionally it supports interlocking the producer and consumer threads so
+ * that a specific sequence of calls can be deterministically created by the
+ * test.
+ *
+ * The intended usage is as follows:
+ *
+ * TEST_F(...) {
+ *     class PT : public ProducerThread {
+ *         virtual void render() {
+ *             ...
+ *             swapBuffers();
+ *         }
+ *     };
+ *
+ *     runProducerThread(new PT());
+ *
+ *     // The order of these calls will vary from test to test and may include
+ *     // multiple frames and additional operations (e.g. GL rendering from the
+ *     // texture).
+ *     fc->waitForFrame();
+ *     mST->updateTexImage();
+ *     fc->finishFrame();
+ * }
+ *
+ */
+class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest {
+protected:
+
+    // ProducerThread is an abstract base class to simplify the creation of
+    // OpenGL ES frame producer threads.
+    class ProducerThread : public Thread {
+    public:
+        virtual ~ProducerThread() {
+        }
+
+        void setEglObjects(EGLDisplay producerEglDisplay,
+                EGLSurface producerEglSurface,
+                EGLContext producerEglContext) {
+            mProducerEglDisplay = producerEglDisplay;
+            mProducerEglSurface = producerEglSurface;
+            mProducerEglContext = producerEglContext;
+        }
+
+        virtual bool threadLoop() {
+            eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface,
+                    mProducerEglSurface, mProducerEglContext);
+            render();
+            eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                    EGL_NO_CONTEXT);
+            return false;
+        }
+
+    protected:
+        virtual void render() = 0;
+
+        void swapBuffers() {
+            eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface);
+        }
+
+        EGLDisplay mProducerEglDisplay;
+        EGLSurface mProducerEglSurface;
+        EGLContext mProducerEglContext;
+    };
+
+    // FrameCondition is a utility class for interlocking between the producer
+    // and consumer threads.  The FrameCondition object should be created and
+    // destroyed in the consumer thread only.  The consumer thread should set
+    // the FrameCondition as the FrameAvailableListener of the SurfaceTexture,
+    // and should call both waitForFrame and finishFrame once for each expected
+    // frame.
+    //
+    // This interlocking relies on the fact that onFrameAvailable gets called
+    // synchronously from SurfaceTexture::queueBuffer.
+    class FrameCondition : public SurfaceTexture::FrameAvailableListener {
+    public:
+        // waitForFrame waits for the next frame to arrive.  This should be
+        // called from the consumer thread once for every frame expected by the
+        // test.
+        void waitForFrame() {
+            LOGV("+waitForFrame");
+            Mutex::Autolock lock(mMutex);
+            status_t result = mFrameAvailableCondition.wait(mMutex);
+            LOGV("-waitForFrame");
+        }
+
+        // Allow the producer to return from its swapBuffers call and continue
+        // on to produce the next frame.  This should be called by the consumer
+        // thread once for every frame expected by the test.
+        void finishFrame() {
+            LOGV("+finishFrame");
+            Mutex::Autolock lock(mMutex);
+            mFrameFinishCondition.signal();
+            LOGV("-finishFrame");
+        }
+
+        // This should be called by SurfaceTexture on the producer thread.
+        virtual void onFrameAvailable() {
+            LOGV("+onFrameAvailable");
+            Mutex::Autolock lock(mMutex);
+            mFrameAvailableCondition.signal();
+            mFrameFinishCondition.wait(mMutex);
+            LOGV("-onFrameAvailable");
+        }
+
+    protected:
+        Mutex mMutex;
+        Condition mFrameAvailableCondition;
+        Condition mFrameFinishCondition;
+    };
+
+    SurfaceTextureGLToGLTest():
+            mProducerEglSurface(EGL_NO_SURFACE),
+            mProducerEglContext(EGL_NO_CONTEXT) {
+    }
+
+    virtual void SetUp() {
+        SurfaceTextureGLTest::SetUp();
+
+        EGLConfig myConfig = {0};
+        EGLint numConfigs = 0;
+        EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &myConfig,
+                1, &numConfigs));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+        mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, myConfig,
+                mANW.get(), NULL);
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface);
+
+        mProducerEglContext = eglCreateContext(mEglDisplay, myConfig,
+                EGL_NO_CONTEXT, getContextAttribs());
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext);
+
+        mFC = new FrameCondition();
+        mST->setFrameAvailableListener(mFC);
+    }
+
+    virtual void TearDown() {
+        if (mProducerThread != NULL) {
+            mProducerThread->requestExitAndWait();
+        }
+        if (mProducerEglContext != EGL_NO_CONTEXT) {
+            eglDestroyContext(mEglDisplay, mProducerEglContext);
+        }
+        if (mProducerEglSurface != EGL_NO_SURFACE) {
+            eglDestroySurface(mEglDisplay, mProducerEglSurface);
+        }
+        mProducerThread.clear();
+        mFC.clear();
+    }
+
+    void runProducerThread(const sp<ProducerThread> producerThread) {
+        ASSERT_TRUE(mProducerThread == NULL);
+        mProducerThread = producerThread;
+        producerThread->setEglObjects(mEglDisplay, mProducerEglSurface,
+                mProducerEglContext);
+        producerThread->run();
+    }
+
+    EGLSurface mProducerEglSurface;
+    EGLContext mProducerEglContext;
+    sp<ProducerThread> mProducerThread;
+    sp<FrameCondition> mFC;
+};
+
+// XXX: This test is disabled because it causes hangs on some devices.
+TEST_F(SurfaceTextureGLToGLTest, DISABLED_UpdateTexImageBeforeFrameFinishedWorks) {
+    class PT : public ProducerThread {
+        virtual void render() {
+            glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+            glClear(GL_COLOR_BUFFER_BIT);
+            swapBuffers();
+        }
+    };
+
+    runProducerThread(new PT());
+
+    mFC->waitForFrame();
+    mST->updateTexImage();
+    mFC->finishFrame();
+
+    // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
 }
+
+TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedWorks) {
+    class PT : public ProducerThread {
+        virtual void render() {
+            glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+            glClear(GL_COLOR_BUFFER_BIT);
+            swapBuffers();
+        }
+    };
+
+    runProducerThread(new PT());
+
+    mFC->waitForFrame();
+    mFC->finishFrame();
+    mST->updateTexImage();
+
+    // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
+}
+
+// XXX: This test is disabled because it causes hangs on some devices.
+TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageBeforeFrameFinishedWorks) {
+    enum { NUM_ITERATIONS = 1024 };
+
+    class PT : public ProducerThread {
+        virtual void render() {
+            for (int i = 0; i < NUM_ITERATIONS; i++) {
+                glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+                glClear(GL_COLOR_BUFFER_BIT);
+                LOGV("+swapBuffers");
+                swapBuffers();
+                LOGV("-swapBuffers");
+            }
+        }
+    };
+
+    runProducerThread(new PT());
+
+    for (int i = 0; i < NUM_ITERATIONS; i++) {
+        mFC->waitForFrame();
+        LOGV("+updateTexImage");
+        mST->updateTexImage();
+        LOGV("-updateTexImage");
+        mFC->finishFrame();
+
+        // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
+    }
+}
+
+// XXX: This test is disabled because it causes hangs on some devices.
+TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageAfterFrameFinishedWorks) {
+    enum { NUM_ITERATIONS = 1024 };
+
+    class PT : public ProducerThread {
+        virtual void render() {
+            for (int i = 0; i < NUM_ITERATIONS; i++) {
+                glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+                glClear(GL_COLOR_BUFFER_BIT);
+                LOGV("+swapBuffers");
+                swapBuffers();
+                LOGV("-swapBuffers");
+            }
+        }
+    };
+
+    runProducerThread(new PT());
+
+    for (int i = 0; i < NUM_ITERATIONS; i++) {
+        mFC->waitForFrame();
+        mFC->finishFrame();
+        LOGV("+updateTexImage");
+        mST->updateTexImage();
+        LOGV("-updateTexImage");
+
+        // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
+    }
+}
+
+} // namespace android
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index e8d40ba..093189c 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -21,6 +21,7 @@
 	Asset.cpp \
 	AssetDir.cpp \
 	AssetManager.cpp \
+	BlobCache.cpp \
 	BufferedTextOutput.cpp \
 	CallStack.cpp \
 	Debug.cpp \
diff --git a/libs/utils/BlobCache.cpp b/libs/utils/BlobCache.cpp
new file mode 100644
index 0000000..1298fa7
--- /dev/null
+++ b/libs/utils/BlobCache.cpp
@@ -0,0 +1,232 @@
+/*
+ ** Copyright 2011, 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.
+ */
+
+#define LOG_TAG "BlobCache"
+//#define LOG_NDEBUG 0
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <utils/BlobCache.h>
+#include <utils/Log.h>
+
+namespace android {
+
+BlobCache::BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize):
+        mMaxKeySize(maxKeySize),
+        mMaxValueSize(maxValueSize),
+        mMaxTotalSize(maxTotalSize),
+        mTotalSize(0) {
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    mRandState[0] = (now >> 0) & 0xFFFF;
+    mRandState[1] = (now >> 16) & 0xFFFF;
+    mRandState[2] = (now >> 32) & 0xFFFF;
+    LOGV("initializing random seed using %lld", now);
+}
+
+void BlobCache::set(const void* key, size_t keySize, const void* value,
+        size_t valueSize) {
+    if (mMaxKeySize < keySize) {
+        LOGV("set: not caching because the key is too large: %d (limit: %d)",
+                keySize, mMaxKeySize);
+        return;
+    }
+    if (mMaxValueSize < valueSize) {
+        LOGV("set: not caching because the value is too large: %d (limit: %d)",
+                valueSize, mMaxValueSize);
+        return;
+    }
+    if (mMaxTotalSize < keySize + valueSize) {
+        LOGV("set: not caching because the combined key/value size is too "
+                "large: %d (limit: %d)", keySize + valueSize, mMaxTotalSize);
+        return;
+    }
+    if (keySize == 0) {
+        LOGW("set: not caching because keySize is 0");
+        return;
+    }
+    if (valueSize <= 0) {
+        LOGW("set: not caching because valueSize is 0");
+        return;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    sp<Blob> dummyKey(new Blob(key, keySize, false));
+    CacheEntry dummyEntry(dummyKey, NULL);
+
+    while (true) {
+
+        ssize_t index = mCacheEntries.indexOf(dummyEntry);
+        if (index < 0) {
+            // Create a new cache entry.
+            sp<Blob> keyBlob(new Blob(key, keySize, true));
+            sp<Blob> valueBlob(new Blob(value, valueSize, true));
+            size_t newTotalSize = mTotalSize + keySize + valueSize;
+            if (mMaxTotalSize < newTotalSize) {
+                if (isCleanable()) {
+                    // Clean the cache and try again.
+                    clean();
+                    continue;
+                } else {
+                    LOGV("set: not caching new key/value pair because the "
+                            "total cache size limit would be exceeded: %d "
+                            "(limit: %d)",
+                            keySize + valueSize, mMaxTotalSize);
+                    break;
+                }
+            }
+            mCacheEntries.add(CacheEntry(keyBlob, valueBlob));
+            mTotalSize = newTotalSize;
+            LOGV("set: created new cache entry with %d byte key and %d byte value",
+                    keySize, valueSize);
+        } else {
+            // Update the existing cache entry.
+            sp<Blob> valueBlob(new Blob(value, valueSize, true));
+            sp<Blob> oldValueBlob(mCacheEntries[index].getValue());
+            size_t newTotalSize = mTotalSize + valueSize - oldValueBlob->getSize();
+            if (mMaxTotalSize < newTotalSize) {
+                if (isCleanable()) {
+                    // Clean the cache and try again.
+                    clean();
+                    continue;
+                } else {
+                    LOGV("set: not caching new value because the total cache "
+                            "size limit would be exceeded: %d (limit: %d)",
+                            keySize + valueSize, mMaxTotalSize);
+                    break;
+                }
+            }
+            mCacheEntries.editItemAt(index).setValue(valueBlob);
+            mTotalSize = newTotalSize;
+            LOGV("set: updated existing cache entry with %d byte key and %d byte "
+                    "value", keySize, valueSize);
+        }
+        break;
+    }
+}
+
+size_t BlobCache::get(const void* key, size_t keySize, void* value,
+        size_t valueSize) {
+    if (mMaxKeySize < keySize) {
+        LOGV("get: not searching because the key is too large: %d (limit %d)",
+                keySize, mMaxKeySize);
+        return 0;
+    }
+    Mutex::Autolock lock(mMutex);
+    sp<Blob> dummyKey(new Blob(key, keySize, false));
+    CacheEntry dummyEntry(dummyKey, NULL);
+    ssize_t index = mCacheEntries.indexOf(dummyEntry);
+    if (index < 0) {
+        LOGV("get: no cache entry found for key of size %d", keySize);
+        return 0;
+    }
+
+    // The key was found. Return the value if the caller's buffer is large
+    // enough.
+    sp<Blob> valueBlob(mCacheEntries[index].getValue());
+    size_t valueBlobSize = valueBlob->getSize();
+    if (valueBlobSize <= valueSize) {
+        LOGV("get: copying %d bytes to caller's buffer", valueBlobSize);
+        memcpy(value, valueBlob->getData(), valueBlobSize);
+    } else {
+        LOGV("get: caller's buffer is too small for value: %d (needs %d)",
+                valueSize, valueBlobSize);
+    }
+    return valueBlobSize;
+}
+
+void BlobCache::clean() {
+    // Remove a random cache entry until the total cache size gets below half
+    // the maximum total cache size.
+    while (mTotalSize > mMaxTotalSize / 2) {
+        size_t i = size_t(nrand48(mRandState) % (mCacheEntries.size()));
+        const CacheEntry& entry(mCacheEntries[i]);
+        mTotalSize -= entry.getKey()->getSize() + entry.getValue()->getSize();
+        mCacheEntries.removeAt(i);
+    }
+}
+
+bool BlobCache::isCleanable() const {
+    return mTotalSize > mMaxTotalSize / 2;
+}
+
+BlobCache::Blob::Blob(const void* data, size_t size, bool copyData):
+        mData(copyData ? malloc(size) : data),
+        mSize(size),
+        mOwnsData(copyData) {
+    if (copyData) {
+        memcpy(const_cast<void*>(mData), data, size);
+    }
+}
+
+BlobCache::Blob::~Blob() {
+    if (mOwnsData) {
+        free(const_cast<void*>(mData));
+    }
+}
+
+bool BlobCache::Blob::operator<(const Blob& rhs) const {
+    if (mSize == rhs.mSize) {
+        return memcmp(mData, rhs.mData, mSize) < 0;
+    } else {
+        return mSize < rhs.mSize;
+    }
+}
+
+const void* BlobCache::Blob::getData() const {
+    return mData;
+}
+
+size_t BlobCache::Blob::getSize() const {
+    return mSize;
+}
+
+BlobCache::CacheEntry::CacheEntry() {
+}
+
+BlobCache::CacheEntry::CacheEntry(const sp<Blob>& key, const sp<Blob>& value):
+        mKey(key),
+        mValue(value) {
+}
+
+BlobCache::CacheEntry::CacheEntry(const CacheEntry& ce):
+        mKey(ce.mKey),
+        mValue(ce.mValue) {
+}
+
+bool BlobCache::CacheEntry::operator<(const CacheEntry& rhs) const {
+    return *mKey < *rhs.mKey;
+}
+
+const BlobCache::CacheEntry& BlobCache::CacheEntry::operator=(const CacheEntry& rhs) {
+    mKey = rhs.mKey;
+    mValue = rhs.mValue;
+    return *this;
+}
+
+sp<BlobCache::Blob> BlobCache::CacheEntry::getKey() const {
+    return mKey;
+}
+
+sp<BlobCache::Blob> BlobCache::CacheEntry::getValue() const {
+    return mValue;
+}
+
+void BlobCache::CacheEntry::setValue(const sp<Blob>& value) {
+    mValue = value;
+}
+
+} // namespace android
diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp
index 8b5da0e..c748228 100644
--- a/libs/utils/Threads.cpp
+++ b/libs/utils/Threads.cpp
@@ -168,6 +168,9 @@
         return 0;
     }
 
+    // Note that *threadID is directly available to the parent only, as it is
+    // assigned after the child starts.  Use memory barrier / lock if the child
+    // or other threads also need access.
     if (threadId != NULL) {
         *threadId = (android_thread_id_t)thread; // XXX: this is not portable
     }
@@ -718,7 +721,6 @@
         res = androidCreateRawThreadEtc(_threadLoop,
                 this, name, priority, stack, &mThread);
     }
-    // The new thread wakes up at _threadLoop, but immediately blocks on mLock
     
     if (res == false) {
         mStatus = UNKNOWN_ERROR;   // something happened!
@@ -742,11 +744,6 @@
 {
     Thread* const self = static_cast<Thread*>(user);
 
-    // force a memory barrier before reading any fields, in particular mHoldSelf
-    {
-    Mutex::Autolock _l(self->mLock);
-    }
-
     sp<Thread> strong(self->mHoldSelf);
     wp<Thread> weak(strong);
     self->mHoldSelf.clear();
@@ -816,6 +813,7 @@
 
 status_t Thread::requestExitAndWait()
 {
+    Mutex::Autolock _l(mLock);
     if (mThread == getThreadId()) {
         LOGW(
         "Thread (this=%p): don't call waitForExit() from this "
@@ -825,9 +823,8 @@
         return WOULD_BLOCK;
     }
     
-    requestExit();
+    mExitPending = true;
 
-    Mutex::Autolock _l(mLock);
     while (mRunning == true) {
         mThreadExitedCondition.wait(mLock);
     }
diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk
index 72d4876..87ad98e 100644
--- a/libs/utils/tests/Android.mk
+++ b/libs/utils/tests/Android.mk
@@ -6,6 +6,7 @@
 
 # Build the unit tests.
 test_src_files := \
+	BlobCache_test.cpp \
 	ObbFile_test.cpp \
 	Looper_test.cpp \
 	String8_test.cpp \
diff --git a/libs/utils/tests/BlobCache_test.cpp b/libs/utils/tests/BlobCache_test.cpp
new file mode 100644
index 0000000..653ea5e
--- /dev/null
+++ b/libs/utils/tests/BlobCache_test.cpp
@@ -0,0 +1,257 @@
+/*
+ ** Copyright 2011, 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 <gtest/gtest.h>
+
+#include <utils/BlobCache.h>
+
+namespace android {
+
+class BlobCacheTest : public ::testing::Test {
+protected:
+    enum {
+        MAX_KEY_SIZE = 6,
+        MAX_VALUE_SIZE = 8,
+        MAX_TOTAL_SIZE = 13,
+    };
+
+    virtual void SetUp() {
+        mBC = new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE);
+    }
+
+    virtual void TearDown() {
+        mBC.clear();
+    }
+
+    sp<BlobCache> mBC;
+};
+
+TEST_F(BlobCacheTest, CacheSingleValueSucceeds) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+    ASSERT_EQ('g', buf[2]);
+    ASSERT_EQ('h', buf[3]);
+}
+
+TEST_F(BlobCacheTest, CacheTwoValuesSucceeds) {
+    char buf[2] = { 0xee, 0xee };
+    mBC->set("ab", 2, "cd", 2);
+    mBC->set("ef", 2, "gh", 2);
+    ASSERT_EQ(size_t(2), mBC->get("ab", 2, buf, 2));
+    ASSERT_EQ('c', buf[0]);
+    ASSERT_EQ('d', buf[1]);
+    ASSERT_EQ(size_t(2), mBC->get("ef", 2, buf, 2));
+    ASSERT_EQ('g', buf[0]);
+    ASSERT_EQ('h', buf[1]);
+}
+
+TEST_F(BlobCacheTest, GetOnlyWritesInsideBounds) {
+    char buf[6] = { 0xee, 0xee, 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf+1, 4));
+    ASSERT_EQ(0xee, buf[0]);
+    ASSERT_EQ('e', buf[1]);
+    ASSERT_EQ('f', buf[2]);
+    ASSERT_EQ('g', buf[3]);
+    ASSERT_EQ('h', buf[4]);
+    ASSERT_EQ(0xee, buf[5]);
+}
+
+TEST_F(BlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
+    char buf[3] = { 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 3));
+    ASSERT_EQ(0xee, buf[0]);
+    ASSERT_EQ(0xee, buf[1]);
+    ASSERT_EQ(0xee, buf[2]);
+}
+
+TEST_F(BlobCacheTest, GetDoesntAccessNullBuffer) {
+    mBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mBC->get("abcd", 4, NULL, 0));
+}
+
+TEST_F(BlobCacheTest, MultipleSetsCacheLatestValue) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+    mBC->set("abcd", 4, "ijkl", 4);
+    ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4));
+    ASSERT_EQ('i', buf[0]);
+    ASSERT_EQ('j', buf[1]);
+    ASSERT_EQ('k', buf[2]);
+    ASSERT_EQ('l', buf[3]);
+}
+
+TEST_F(BlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
+    char buf[MAX_VALUE_SIZE+1] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+    mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1);
+    ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+    ASSERT_EQ('g', buf[2]);
+    ASSERT_EQ('h', buf[3]);
+}
+
+TEST_F(BlobCacheTest, DoesntCacheIfKeyIsTooBig) {
+    char key[MAX_KEY_SIZE+1];
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    for (int i = 0; i < MAX_KEY_SIZE+1; i++) {
+        key[i] = 'a';
+    }
+    mBC->set(key, MAX_KEY_SIZE+1, "bbbb", 4);
+    ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE+1, buf, 4));
+    ASSERT_EQ(0xee, buf[0]);
+    ASSERT_EQ(0xee, buf[1]);
+    ASSERT_EQ(0xee, buf[2]);
+    ASSERT_EQ(0xee, buf[3]);
+}
+
+TEST_F(BlobCacheTest, DoesntCacheIfValueIsTooBig) {
+    char buf[MAX_VALUE_SIZE+1];
+    for (int i = 0; i < MAX_VALUE_SIZE+1; i++) {
+        buf[i] = 'b';
+    }
+    mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1);
+    for (int i = 0; i < MAX_VALUE_SIZE+1; i++) {
+        buf[i] = 0xee;
+    }
+    ASSERT_EQ(size_t(0), mBC->get("abcd", 4, buf, MAX_VALUE_SIZE+1));
+    for (int i = 0; i < MAX_VALUE_SIZE+1; i++) {
+        SCOPED_TRACE(i);
+        ASSERT_EQ(0xee, buf[i]);
+    }
+}
+
+TEST_F(BlobCacheTest, DoesntCacheIfKeyValuePairIsTooBig) {
+    // Check a testing assumptions
+    ASSERT_TRUE(MAX_TOTAL_SIZE < MAX_KEY_SIZE + MAX_VALUE_SIZE);
+    ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE);
+
+    enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE + 1 };
+
+    char key[MAX_KEY_SIZE];
+    char buf[bufSize];
+    for (int i = 0; i < MAX_KEY_SIZE; i++) {
+        key[i] = 'a';
+    }
+    for (int i = 0; i < bufSize; i++) {
+        buf[i] = 'b';
+    }
+
+    mBC->set(key, MAX_KEY_SIZE, buf, MAX_VALUE_SIZE);
+    ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE, NULL, 0));
+}
+
+TEST_F(BlobCacheTest, CacheMaxKeySizeSucceeds) {
+    char key[MAX_KEY_SIZE];
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    for (int i = 0; i < MAX_KEY_SIZE; i++) {
+        key[i] = 'a';
+    }
+    mBC->set(key, MAX_KEY_SIZE, "wxyz", 4);
+    ASSERT_EQ(size_t(4), mBC->get(key, MAX_KEY_SIZE, buf, 4));
+    ASSERT_EQ('w', buf[0]);
+    ASSERT_EQ('x', buf[1]);
+    ASSERT_EQ('y', buf[2]);
+    ASSERT_EQ('z', buf[3]);
+}
+
+TEST_F(BlobCacheTest, CacheMaxValueSizeSucceeds) {
+    char buf[MAX_VALUE_SIZE];
+    for (int i = 0; i < MAX_VALUE_SIZE; i++) {
+        buf[i] = 'b';
+    }
+    mBC->set("abcd", 4, buf, MAX_VALUE_SIZE);
+    for (int i = 0; i < MAX_VALUE_SIZE; i++) {
+        buf[i] = 0xee;
+    }
+    ASSERT_EQ(size_t(MAX_VALUE_SIZE), mBC->get("abcd", 4, buf,
+            MAX_VALUE_SIZE));
+    for (int i = 0; i < MAX_VALUE_SIZE; i++) {
+        SCOPED_TRACE(i);
+        ASSERT_EQ('b', buf[i]);
+    }
+}
+
+TEST_F(BlobCacheTest, CacheMaxKeyValuePairSizeSucceeds) {
+    // Check a testing assumption
+    ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE);
+
+    enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE };
+
+    char key[MAX_KEY_SIZE];
+    char buf[bufSize];
+    for (int i = 0; i < MAX_KEY_SIZE; i++) {
+        key[i] = 'a';
+    }
+    for (int i = 0; i < bufSize; i++) {
+        buf[i] = 'b';
+    }
+
+    mBC->set(key, MAX_KEY_SIZE, buf, bufSize);
+    ASSERT_EQ(size_t(bufSize), mBC->get(key, MAX_KEY_SIZE, NULL, 0));
+}
+
+TEST_F(BlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
+    char buf[1] = { 0xee };
+    mBC->set("x", 1, "y", 1);
+    ASSERT_EQ(size_t(1), mBC->get("x", 1, buf, 1));
+    ASSERT_EQ('y', buf[0]);
+}
+
+TEST_F(BlobCacheTest, CacheSizeDoesntExceedTotalLimit) {
+    for (int i = 0; i < 256; i++) {
+        uint8_t k = i;
+        mBC->set(&k, 1, "x", 1);
+    }
+    int numCached = 0;
+    for (int i = 0; i < 256; i++) {
+        uint8_t k = i;
+        if (mBC->get(&k, 1, NULL, 0) == 1) {
+            numCached++;
+        }
+    }
+    ASSERT_GE(MAX_TOTAL_SIZE / 2, numCached);
+}
+
+TEST_F(BlobCacheTest, ExceedingTotalLimitHalvesCacheSize) {
+    // Fill up the entire cache with 1 char key/value pairs.
+    const int maxEntries = MAX_TOTAL_SIZE / 2;
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        mBC->set(&k, 1, "x", 1);
+    }
+    // Insert one more entry, causing a cache overflow.
+    {
+        uint8_t k = maxEntries;
+        mBC->set(&k, 1, "x", 1);
+    }
+    // Count the number of entries in the cache.
+    int numCached = 0;
+    for (int i = 0; i < maxEntries+1; i++) {
+        uint8_t k = i;
+        if (mBC->get(&k, 1, NULL, 0) == 1) {
+            numCached++;
+        }
+    }
+    ASSERT_EQ(maxEntries/2 + 1, numCached);
+}
+
+} // namespace android
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel.xml
index ef57228..72519fb 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel.xml
@@ -57,15 +57,15 @@
                 android:layout_width="match_parent"
                 android:layout_weight="1"
                 >
-                <LinearLayout
+                <com.android.systemui.statusbar.policy.NotificationRowLayout
                     android:id="@+id/content"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:gravity="center_horizontal|bottom"
-                    android:orientation="vertical"
                     android:clickable="true"
                     android:focusable="true"
                     android:descendantFocusability="afterDescendants"
+                    systemui:rowHeight="@dimen/notification_height"
                     />
             </ScrollView>
         </LinearLayout>
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_row.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_row.xml
index 8e456b2..93085d7 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_row.xml
@@ -1,6 +1,6 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="65dp"
+    android:layout_height="@dimen/notification_height"
     >
 
     <ImageButton
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 18e8273..6670eff 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -18,7 +18,9 @@
 */
 -->
 
-<com.android.systemui.statusbar.phone.ExpandedView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.statusbar.phone.ExpandedView 
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
     android:orientation="vertical"
     android:focusable="true"
     android:descendantFocusability="afterDescendants"
@@ -97,10 +99,11 @@
                     android:textAppearance="@style/TextAppearance.StatusBar.Title"
                     android:text="@string/status_bar_ongoing_events_title"
                     />
-                <LinearLayout android:id="@+id/ongoingItems"
+                <com.android.systemui.statusbar.policy.NotificationRowLayout
+                    android:id="@+id/ongoingItems"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:orientation="vertical"
+                    systemui:rowHeight="@dimen/notification_height"
                     />
 
                 <TextView android:id="@+id/latestTitle"
@@ -111,10 +114,11 @@
                     android:textAppearance="@style/TextAppearance.StatusBar.Title"
                     android:text="@string/status_bar_latest_events_title"
                     />
-                <LinearLayout android:id="@+id/latestItems"
+                <com.android.systemui.statusbar.policy.NotificationRowLayout
+                    android:id="@+id/latestItems"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:orientation="vertical"
+                    systemui:rowHeight="@dimen/notification_height"
                     />
             </LinearLayout>
         </ScrollView>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 8e456b2..93085d7 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -1,6 +1,6 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="65dp"
+    android:layout_height="@dimen/notification_height"
     >
 
     <ImageButton
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index fb2f7d63..5291629 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -24,5 +24,8 @@
     <declare-styleable name="NotificationLinearLayout">
         <attr name="insetLeft" format="dimension" />
     </declare-styleable>
+    <declare-styleable name="NotificationRowLayout">
+        <attr name="rowHeight" format="dimension" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 657dc46..5f0fbef 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -47,5 +47,8 @@
 
     <!-- thickness (height) of the navigation bar on phones that require it -->
     <dimen name="navigation_bar_size">42dp</dimen>
+
+    <!-- thickness (height) of each notification row, including any separators or padding -->
+    <dimen name="notification_height">65dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandedView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandedView.java
index 92b8976..51fc7c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandedView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.Display;
@@ -37,6 +38,8 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+
+        setLayerType(LAYER_TYPE_HARDWARE, null);
     }
 
     /** We want to shrink down to 0, and ignore the background. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 00de920..3d15a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -130,11 +130,11 @@
     // ongoing
     NotificationData mOngoing = new NotificationData();
     TextView mOngoingTitle;
-    LinearLayout mOngoingItems;
+    ViewGroup mOngoingItems;
     // latest
     NotificationData mLatest = new NotificationData();
     TextView mLatestTitle;
-    LinearLayout mLatestItems;
+    ViewGroup mLatestItems;
     // position
     int[] mPositionTmp = new int[2];
     boolean mExpanded;
@@ -268,9 +268,9 @@
         mExpandedView = expanded;
         mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout);
         mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle);
-        mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems);
+        mOngoingItems = (ViewGroup)expanded.findViewById(R.id.ongoingItems);
         mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle);
-        mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems);
+        mLatestItems = (ViewGroup)expanded.findViewById(R.id.latestItems);
         mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle);
         mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button);
         mClearButton.setOnClickListener(mClearButtonListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
new file mode 100644
index 0000000..24eee27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2011 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.systemui.statusbar.policy;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.HashSet;
+
+import com.android.systemui.R;
+
+public class NotificationRowLayout extends ViewGroup {
+    private static final String TAG = "NotificationRowLayout";
+    private static final boolean DEBUG = false;
+
+    private static final boolean ANIMATE_LAYOUT = true;
+
+    private static final int ANIM_LEN = DEBUG ? 5000 : 250;
+
+    Rect mTmpRect = new Rect();
+    int mNumRows = 0;
+    int mRowHeight = 0;
+    int mHeight = 0;
+
+    HashSet<View> mAppearingViews = new HashSet<View>();
+    HashSet<View> mDisappearingViews = new HashSet<View>();
+
+    public NotificationRowLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NotificationRowLayout,
+                defStyle, 0);
+        mRowHeight = a.getDimensionPixelSize(R.styleable.NotificationRowLayout_rowHeight, 0);
+        a.recycle();
+
+        setLayoutTransition(null);
+
+        if (DEBUG) {
+            setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
+                @Override
+                public void onChildViewAdded(View parent, View child) {
+                    Slog.d(TAG, "view added: " + child + "; new count: " + getChildCount());
+                }
+                @Override
+                public void onChildViewRemoved(View parent, View child) {
+                    Slog.d(TAG, "view removed: " + child + "; new count: " + (getChildCount() - 1));
+                }
+            });
+
+            setBackgroundColor(0x80FF8000);
+        }
+
+    }
+
+    //**
+    @Override
+    public void addView(View child, int index, LayoutParams params) {
+        super.addView(child, index, params);
+
+        final View childF = child;
+
+        if (ANIMATE_LAYOUT) {
+            mAppearingViews.add(child);
+
+            child.setPivotY(0);
+            AnimatorSet a = new AnimatorSet();
+            a.playTogether(
+                    ObjectAnimator.ofFloat(child, "alpha", 0f, 1f)
+//                    ,ObjectAnimator.ofFloat(child, "scaleY", 0f, 1f)
+            );
+            a.setDuration(ANIM_LEN);
+            a.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAppearingViews.remove(childF);
+                }
+            });
+            a.start();
+            requestLayout(); // start the container animation
+        }
+    }
+
+    @Override
+    public void removeView(View child) {
+        final View childF = child;
+        if (ANIMATE_LAYOUT) {
+            if (mAppearingViews.contains(child)) {
+                mAppearingViews.remove(child);
+            }
+            mDisappearingViews.add(child);
+
+            child.setPivotY(0);
+            AnimatorSet a = new AnimatorSet();
+            a.playTogether(
+                    ObjectAnimator.ofFloat(child, "alpha", 0f)
+//                    ,ObjectAnimator.ofFloat(child, "scaleY", 0f)
+                    ,ObjectAnimator.ofFloat(child, "translationX", 300f)
+            );
+            a.setDuration(ANIM_LEN);
+            a.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    NotificationRowLayout.super.removeView(childF);
+                    childF.setAlpha(1f);
+                    mDisappearingViews.remove(childF);
+                }
+            });
+            a.start();
+            requestLayout(); // start the container animation
+        } else {
+            super.removeView(child);
+        }
+    }
+    //**
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        setWillNotDraw(false);
+    }
+
+    @Override
+    public void onDraw(android.graphics.Canvas c) {
+        super.onDraw(c);
+        if (DEBUG) {
+            Slog.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
+                    + getMeasuredHeight() + "px");
+            c.save();
+            c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
+                    android.graphics.Region.Op.DIFFERENCE);
+            c.drawColor(0xFFFF8000);
+            c.restore();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int count = getChildCount();
+
+        // pass 1: count the number of non-GONE views
+        int numRows = 0;
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+            if (mDisappearingViews.contains(child)) {
+                continue;
+            }
+            numRows++;
+        }
+        if (numRows != mNumRows) {
+            // uh oh, now you made us go and do work
+            
+            final int computedHeight = numRows * mRowHeight;
+            if (DEBUG) {
+                Slog.d(TAG, String.format("rows went from %d to %d, resizing to %dpx",
+                            mNumRows, numRows, computedHeight));
+            }
+
+            mNumRows = numRows;
+
+            if (ANIMATE_LAYOUT && isShown()) {
+                ObjectAnimator.ofInt(this, "forcedHeight", computedHeight)
+                    .setDuration(ANIM_LEN)
+                    .start();
+            } else {
+                setForcedHeight(computedHeight);
+            }
+        }
+
+        // pass 2: you know, do the measuring
+        final int childWidthMS = widthMeasureSpec;
+        final int childHeightMS = MeasureSpec.makeMeasureSpec(
+                mRowHeight, MeasureSpec.EXACTLY);
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+
+            child.measure(childWidthMS, childHeightMS);
+        }
+
+        setMeasuredDimension(
+                getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+                resolveSize(getForcedHeight(), heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int width = right - left;
+        final int height = bottom - top;
+
+        if (DEBUG) Slog.d(TAG, "onLayout: height=" + height);
+
+        final int count = getChildCount();
+        int y = 0;
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+//            final int thisRowHeight = (int)(
+//                ((mAppearingViews.contains(child) || mDisappearingViews.contains(child))
+//                        ? child.getScaleY()
+//                        : 1.0f)
+//                * mRowHeight);
+            final int thisRowHeight = (int)(child.getAlpha() * mRowHeight);
+//            child.layout(0, y, width, y + thisRowHeight);
+            child.layout(0, y, width, y + mRowHeight);
+            y += thisRowHeight;
+        }
+    }
+
+    public void setForcedHeight(int h) {
+        if (DEBUG) Slog.d(TAG, "forcedHeight: " + h);
+        if (h != mHeight) {
+            mHeight = h;
+            requestLayout();
+        }
+    }
+
+    public int getForcedHeight() {
+        return mHeight;
+    }
+}
diff --git a/services/camera/libcameraservice/CameraHardwareInterface.h b/services/camera/libcameraservice/CameraHardwareInterface.h
index 7a18831..a3749cf 100644
--- a/services/camera/libcameraservice/CameraHardwareInterface.h
+++ b/services/camera/libcameraservice/CameraHardwareInterface.h
@@ -552,7 +552,7 @@
 #define anw(n) __to_anw(((struct camera_preview_window *)n)->user)
 
     static int __dequeue_buffer(struct preview_stream_ops* w,
-                      buffer_handle_t** buffer)
+                                buffer_handle_t** buffer, int *stride)
     {
         int rc;
         ANativeWindow *a = anw(w);
@@ -560,8 +560,10 @@
         rc = a->dequeueBuffer(a, &anb);
         if (!rc) {
             rc = a->lockBuffer(a, anb);
-            if (!rc)
+            if (!rc) {
                 *buffer = &anb->handle;
+                *stride = anb->stride;
+            }
             else
                 a->cancelBuffer(a, anb);
         }
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 4c6098d..eff65c2 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -1464,6 +1464,23 @@
         injectionPermission = INJECTION_PERMISSION_GRANTED;
     }
 
+    // Check whether windows listening for outside touches are owned by the same UID. If it is
+    // set the policy flag that we will not reveal coordinate information to this window.
+    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+        const InputWindow* foregroundWindow = mTempTouchState.getFirstForegroundWindow();
+        const int32_t foregroundWindowUid = foregroundWindow->ownerUid;
+        for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+            if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+                const InputWindow* inputWindow = touchedWindow.window;
+                if (inputWindow->ownerUid != foregroundWindowUid) {
+                    mTempTouchState.addOrUpdateWindow(inputWindow,
+                            InputTarget::FLAG_ZERO_COORDS, BitSet32(0));
+                }
+            }
+        }
+    }
+
     // Ensure all touched foreground windows are ready for new input.
     for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
         const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
@@ -1987,7 +2004,8 @@
 
         // Set the X and Y offset depending on the input source.
         float xOffset, yOffset, scaleFactor;
-        if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+        if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER
+                && !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
             scaleFactor = dispatchEntry->scaleFactor;
             xOffset = dispatchEntry->xOffset * scaleFactor;
             yOffset = dispatchEntry->yOffset * scaleFactor;
@@ -2002,6 +2020,14 @@
             xOffset = 0.0f;
             yOffset = 0.0f;
             scaleFactor = 1.0f;
+
+            // We don't want the dispatch target to know.
+            if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
+                for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+                    scaledCoords[i].clear();
+                }
+                usingCoords = scaledCoords;
+            }
         }
 
         // Update the connection's input state.
@@ -2030,9 +2056,11 @@
             MotionSample* nextMotionSample = firstMotionSample->next;
             for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
                 if (usingCoords == scaledCoords) {
-                    for (size_t i = 0; i < motionEntry->pointerCount; i++) {
-                        scaledCoords[i] = nextMotionSample->pointerCoords[i];
-                        scaledCoords[i].scale(scaleFactor);
+                    if (!(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
+                        for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+                            scaledCoords[i] = nextMotionSample->pointerCoords[i];
+                            scaledCoords[i].scale(scaleFactor);
+                        }
                     }
                 } else {
                     usingCoords = nextMotionSample->pointerCoords;
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 37cef90..39d4aeb 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -96,6 +96,12 @@
         /* This flag indicates that a motion event is being split across multiple windows. */
         FLAG_SPLIT = 1 << 2,
 
+        /* This flag indicates that the pointer coordinates dispatched to the application
+         * will be zeroed out to avoid revealing information to an application. This is
+         * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
+         * the same UID from watching all touches. */
+        FLAG_ZERO_COORDS = 1 << 3,
+
         /* This flag indicates that the event should be sent as is.
          * Should always be set unless the event is to be transmuted. */
         FLAG_DISPATCH_AS_IS = 1 << 8,
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 8f179f5..2190b30 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -882,8 +882,7 @@
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
 
         final String[] ifaces = listInterfaces();
-        final NetworkStats.Builder stats = new NetworkStats.Builder(
-                SystemClock.elapsedRealtime(), ifaces.length);
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), ifaces.length);
 
         for (String iface : ifaces) {
             final long rx = getInterfaceCounter(iface, true);
@@ -891,7 +890,7 @@
             stats.addEntry(iface, NetworkStats.UID_ALL, rx, tx);
         }
 
-        return stats.build();
+        return stats;
     }
 
     @Override
@@ -900,7 +899,7 @@
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
 
         final String[] knownUids = PATH_PROC_UID_STAT.list();
-        final NetworkStats.Builder stats = new NetworkStats.Builder(
+        final NetworkStats stats = new NetworkStats(
                 SystemClock.elapsedRealtime(), knownUids.length);
 
         for (String uid : knownUids) {
@@ -908,7 +907,7 @@
             collectNetworkStatsDetail(stats, uidInt);
         }
 
-        return stats.build();
+        return stats;
     }
 
     @Override
@@ -918,13 +917,12 @@
                     android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
         }
 
-        final NetworkStats.Builder stats = new NetworkStats.Builder(
-                SystemClock.elapsedRealtime(), 1);
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
         collectNetworkStatsDetail(stats, uid);
-        return stats.build();
+        return stats;
     }
 
-    private void collectNetworkStatsDetail(NetworkStats.Builder stats, int uid) {
+    private void collectNetworkStatsDetail(NetworkStats stats, int uid) {
         // TODO: kernel module will provide interface-level stats in future
         // TODO: migrate these stats to come across netd in bulk, instead of all
         // these individual file reads.
diff --git a/services/java/com/android/server/net/NetworkIdentity.java b/services/java/com/android/server/net/NetworkIdentity.java
index f7a7c49..4a207f7 100644
--- a/services/java/com/android/server/net/NetworkIdentity.java
+++ b/services/java/com/android/server/net/NetworkIdentity.java
@@ -26,6 +26,7 @@
 import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
 import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
 import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
+import static android.telephony.TelephonyManager.NETWORK_CLASS_UNKNOWN;
 import static android.telephony.TelephonyManager.getNetworkClass;
 
 import android.content.Context;
@@ -148,6 +149,7 @@
         if (isNetworkTypeMobile(type)
                 && Objects.equal(this.subscriberId, subscriberId)) {
             switch (getNetworkClass(subType)) {
+                case NETWORK_CLASS_UNKNOWN:
                 case NETWORK_CLASS_2_G:
                 case NETWORK_CLASS_3_G:
                     return true;
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 2766093..e7d60638 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -20,7 +20,9 @@
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
@@ -28,6 +30,8 @@
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.dumpPolicy;
 import static android.net.NetworkPolicyManager.dumpRules;
+import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
+import static android.net.TrafficStats.isNetworkTemplateMobile;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -53,6 +57,8 @@
 import android.os.IPowerManager;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.telephony.TelephonyManager;
+import android.text.format.Time;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -96,9 +102,14 @@
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     private static final String TAG = "NetworkPolicy";
     private static final boolean LOGD = true;
+    private static final boolean LOGV = false;
 
     private static final int VERSION_CURRENT = 1;
 
+    private static final long KB_IN_BYTES = 1024;
+    private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+    private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+
     private static final String TAG_POLICY_LIST = "policy-list";
     private static final String TAG_NETWORK_POLICY = "network-policy";
     private static final String TAG_UID_POLICY = "uid-policy";
@@ -126,14 +137,14 @@
 
     private boolean mScreenOn;
 
-    /** Current network policy for each UID. */
+    /** Current policy for network templates. */
+    private ArrayList<NetworkPolicy> mNetworkPolicy = Lists.newArrayList();
+
+    /** Current policy for each UID. */
     private SparseIntArray mUidPolicy = new SparseIntArray();
     /** Current derived network rules for each UID. */
     private SparseIntArray mUidRules = new SparseIntArray();
 
-    /** Set of policies for strong network templates. */
-    private HashMap<StrongTemplate, NetworkPolicy> mTemplatePolicy = Maps.newHashMap();
-
     /** Foreground at both UID and PID granularity. */
     private SparseBooleanArray mUidForeground = new SparseBooleanArray();
     private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
@@ -150,8 +161,6 @@
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
 
-    // TODO: dispatch callbacks through handler when locked
-
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
             IPowerManager powerManager, INetworkStatsService networkStats) {
         // TODO: move to using cached NtpTrustedTime
@@ -264,8 +273,7 @@
 
     /**
      * Receiver that watches for {@link IConnectivityManager} to claim network
-     * interfaces. Used to apply {@link NetworkPolicy} when networks match
-     * {@link StrongTemplate}.
+     * interfaces. Used to apply {@link NetworkPolicy} to matching networks.
      */
     private BroadcastReceiver mIfaceReceiver = new BroadcastReceiver() {
         @Override
@@ -273,6 +281,7 @@
             // on background handler thread, and verified CONNECTIVITY_INTERNAL
             // permission above.
             synchronized (mRulesLock) {
+                ensureActiveMobilePolicyLocked();
                 updateIfacesLocked();
             }
         }
@@ -284,7 +293,7 @@
      * remaining quota based on usage cycle and historical stats.
      */
     private void updateIfacesLocked() {
-        if (LOGD) Slog.v(TAG, "updateIfacesLocked()");
+        if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
 
         final NetworkState[] states;
         try {
@@ -307,14 +316,14 @@
         }
 
         // build list of rules and ifaces to enforce them against
-        final HashMap<StrongTemplate, String[]> rules = Maps.newHashMap();
+        final HashMap<NetworkPolicy, String[]> rules = Maps.newHashMap();
         final ArrayList<String> ifaceList = Lists.newArrayList();
-        for (StrongTemplate template : mTemplatePolicy.keySet()) {
+        for (NetworkPolicy policy : mNetworkPolicy) {
 
             // collect all active ifaces that match this template
             ifaceList.clear();
             for (NetworkIdentity ident : networks.keySet()) {
-                if (ident.matchesTemplate(template.networkTemplate, template.subscriberId)) {
+                if (ident.matchesTemplate(policy.networkTemplate, policy.subscriberId)) {
                     final String iface = networks.get(ident);
                     ifaceList.add(iface);
                 }
@@ -322,7 +331,7 @@
 
             if (ifaceList.size() > 0) {
                 final String[] ifaces = ifaceList.toArray(new String[ifaceList.size()]);
-                rules.put(template, ifaces);
+                rules.put(policy, ifaces);
             }
         }
 
@@ -336,41 +345,81 @@
 
         // apply each policy that we found ifaces for; compute remaining data
         // based on current cycle and historical stats, and push to kernel.
-        for (StrongTemplate template : rules.keySet()) {
-            final NetworkPolicy policy = mTemplatePolicy.get(template);
+        for (NetworkPolicy policy : rules.keySet()) {
             final String[] ifaces = rules.get(policy);
 
             final long start = computeLastCycleBoundary(currentTime, policy);
             final long end = currentTime;
 
             final NetworkStats stats;
+            final long total;
             try {
                 stats = mNetworkStats.getSummaryForNetwork(
-                        start, end, template.networkTemplate, template.subscriberId);
+                        start, end, policy.networkTemplate, policy.subscriberId);
+                total = stats.rx[0] + stats.tx[0];
             } catch (RemoteException e) {
-                Slog.w(TAG, "problem reading summary for template " + template.networkTemplate);
+                Slog.w(TAG, "problem reading summary for template " + policy.networkTemplate);
                 continue;
             }
 
-            // remaining "quota" is based on usage in current cycle
-            final long total = stats.rx[0] + stats.tx[0];
-            final long quota = Math.max(0, policy.limitBytes - total);
-
             if (LOGD) {
                 Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces "
-                        + Arrays.toString(ifaces) + " with quota " + quota);
+                        + Arrays.toString(ifaces));
             }
 
-            // TODO: push rule down through NetworkManagementService.setInterfaceQuota()
+            // TODO: register for warning notification trigger through NMS
 
+            if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
+                // remaining "quota" is based on usage in current cycle
+                final long quota = Math.max(0, policy.limitBytes - total);
+
+                // TODO: push quota rule down through NMS
+            }
+        }
+    }
+
+    /**
+     * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we
+     * have at least a default mobile policy defined.
+     */
+    private void ensureActiveMobilePolicyLocked() {
+        if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyLocked()");
+        final String subscriberId = getActiveSubscriberId();
+        if (subscriberId == null) {
+            if (LOGV) Slog.v(TAG, "no active mobile network, ignoring policy check");
+            return;
+        }
+
+        // examine to see if any policy is defined for active mobile
+        boolean mobileDefined = false;
+        for (NetworkPolicy policy : mNetworkPolicy) {
+            if (isNetworkTemplateMobile(policy.networkTemplate)
+                    && Objects.equal(subscriberId, policy.subscriberId)) {
+                mobileDefined = true;
+            }
+        }
+
+        if (!mobileDefined) {
+            Slog.i(TAG, "no policy for active mobile network; generating default policy");
+
+            // default mobile policy has combined 4GB warning, and assume usage
+            // cycle starts today today.
+
+            // TODO: move this policy definition to overlay or secure setting
+            final Time time = new Time(Time.TIMEZONE_UTC);
+            time.setToNow();
+            final int cycleDay = time.monthDay;
+
+            mNetworkPolicy.add(new NetworkPolicy(
+                    TEMPLATE_MOBILE_ALL, subscriberId, cycleDay, 4 * GB_IN_BYTES, LIMIT_DISABLED));
         }
     }
 
     private void readPolicyLocked() {
-        if (LOGD) Slog.v(TAG, "readPolicyLocked()");
+        if (LOGV) Slog.v(TAG, "readPolicyLocked()");
 
         // clear any existing policy and read from disk
-        mTemplatePolicy.clear();
+        mNetworkPolicy.clear();
         mUidPolicy.clear();
 
         FileInputStream fis = null;
@@ -390,13 +439,12 @@
                     } else if (TAG_NETWORK_POLICY.equals(tag)) {
                         final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE);
                         final String subscriberId = in.getAttributeValue(null, ATTR_SUBSCRIBER_ID);
-
                         final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY);
                         final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES);
                         final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES);
 
-                        mTemplatePolicy.put(new StrongTemplate(networkTemplate, subscriberId),
-                                new NetworkPolicy(cycleDay, warningBytes, limitBytes));
+                        mNetworkPolicy.add(new NetworkPolicy(
+                                networkTemplate, subscriberId, cycleDay, warningBytes, limitBytes));
 
                     } else if (TAG_UID_POLICY.equals(tag)) {
                         final int uid = readIntAttribute(in, ATTR_UID);
@@ -419,7 +467,7 @@
     }
 
     private void writePolicyLocked() {
-        if (LOGD) Slog.v(TAG, "writePolicyLocked()");
+        if (LOGV) Slog.v(TAG, "writePolicyLocked()");
 
         FileOutputStream fos = null;
         try {
@@ -433,13 +481,11 @@
             writeIntAttribute(out, ATTR_VERSION, VERSION_CURRENT);
 
             // write all known network policies
-            for (StrongTemplate template : mTemplatePolicy.keySet()) {
-                final NetworkPolicy policy = mTemplatePolicy.get(template);
-
+            for (NetworkPolicy policy : mNetworkPolicy) {
                 out.startTag(null, TAG_NETWORK_POLICY);
-                writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.networkTemplate);
-                if (template.subscriberId != null) {
-                    out.attribute(null, ATTR_SUBSCRIBER_ID, template.subscriberId);
+                writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, policy.networkTemplate);
+                if (policy.subscriberId != null) {
+                    out.attribute(null, ATTR_SUBSCRIBER_ID, policy.subscriberId);
                 }
                 writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
                 writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
@@ -519,25 +565,27 @@
     }
 
     @Override
-    public void setNetworkPolicy(int networkType, String subscriberId, NetworkPolicy policy) {
+    public void setNetworkPolicies(NetworkPolicy[] policies) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
         synchronized (mRulesLock) {
-            mTemplatePolicy.put(new StrongTemplate(networkType, subscriberId), policy);
+            mNetworkPolicy.clear();
+            for (NetworkPolicy policy : policies) {
+                mNetworkPolicy.add(policy);
+            }
 
-            // network policy changed, recompute template rules based on active
-            // interfaces and persist policy.
             updateIfacesLocked();
             writePolicyLocked();
         }
     }
 
     @Override
-    public NetworkPolicy getNetworkPolicy(int networkType, String subscriberId) {
+    public NetworkPolicy[] getNetworkPolicies() {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, TAG);
 
         synchronized (mRulesLock) {
-            return mTemplatePolicy.get(new StrongTemplate(networkType, subscriberId));
+            return mNetworkPolicy.toArray(new NetworkPolicy[mNetworkPolicy.size()]);
         }
     }
 
@@ -547,10 +595,8 @@
 
         synchronized (mRulesLock) {
             fout.println("Network policies:");
-            for (StrongTemplate template : mTemplatePolicy.keySet()) {
-                final NetworkPolicy policy = mTemplatePolicy.get(template);
-                fout.print("  "); fout.println(template.toString());
-                fout.print("    "); fout.println(policy.toString());
+            for (NetworkPolicy policy : mNetworkPolicy) {
+                fout.print("  "); fout.println(policy.toString());
             }
 
             fout.println("Policy status for known UIDs:");
@@ -682,6 +728,12 @@
         mListeners.finishBroadcast();
     }
 
+    private String getActiveSubscriberId() {
+        final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        return telephony.getSubscriberId();
+    }
+
     private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
         final int size = source.size();
         for (int i = 0; i < size; i++) {
@@ -734,40 +786,4 @@
         out.attribute(null, name, Long.toString(value));
     }
 
-    /**
-     * Network template with strong subscriber ID, used as key when defining
-     * {@link NetworkPolicy}.
-     */
-    private static class StrongTemplate {
-        public final int networkTemplate;
-        public final String subscriberId;
-
-        public StrongTemplate(int networkTemplate, String subscriberId) {
-            this.networkTemplate = networkTemplate;
-            this.subscriberId = subscriberId;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(networkTemplate, subscriberId);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof StrongTemplate) {
-                final StrongTemplate template = (StrongTemplate) obj;
-                return template.networkTemplate == networkTemplate
-                        && Objects.equal(template.subscriberId, subscriberId);
-            }
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            return "TemplateIdentity: networkTemplate=" + networkTemplate + ", subscriberId="
-                    + subscriberId;
-        }
-
-    }
-
 }
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 161c393..de69849 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -23,12 +23,12 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.UID_ALL;
-import static android.provider.Settings.Secure.NETSTATS_DETAIL_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_DETAIL_MAX_HISTORY;
+import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
+import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
 import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
-import static android.provider.Settings.Secure.NETSTATS_SUMMARY_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_SUMMARY_MAX_HISTORY;
+import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
+import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -38,6 +38,7 @@
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -88,6 +89,7 @@
 public class NetworkStatsService extends INetworkStatsService.Stub {
     private static final String TAG = "NetworkStats";
     private static final boolean LOGD = true;
+    private static final boolean LOGV = false;
 
     /** File header magic number: "ANET" */
     private static final int FILE_MAGIC = 0x414E4554;
@@ -97,6 +99,7 @@
     private final INetworkManagementService mNetworkManager;
     private final IAlarmManager mAlarmManager;
     private final TrustedTime mTime;
+    private final NetworkStatsSettings mSettings;
 
     private IConnectivityManager mConnManager;
 
@@ -112,22 +115,18 @@
     private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
     private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
 
-    private LongSecureSetting mPollInterval = new LongSecureSetting(
-            NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS);
-    private LongSecureSetting mPersistThreshold = new LongSecureSetting(
-            NETSTATS_PERSIST_THRESHOLD, 16 * KB_IN_BYTES);
-
-    // TODO: adjust these timings for production builds
-    private LongSecureSetting mSummaryBucketDuration = new LongSecureSetting(
-            NETSTATS_SUMMARY_BUCKET_DURATION, 1 * HOUR_IN_MILLIS);
-    private LongSecureSetting mSummaryMaxHistory = new LongSecureSetting(
-            NETSTATS_SUMMARY_MAX_HISTORY, 90 * DAY_IN_MILLIS);
-    private LongSecureSetting mDetailBucketDuration = new LongSecureSetting(
-            NETSTATS_DETAIL_BUCKET_DURATION, 2 * HOUR_IN_MILLIS);
-    private LongSecureSetting mDetailMaxHistory = new LongSecureSetting(
-            NETSTATS_DETAIL_MAX_HISTORY, 90 * DAY_IN_MILLIS);
-
-    private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
+    /**
+     * Settings that can be changed externally.
+     */
+    public interface NetworkStatsSettings {
+        public long getPollInterval();
+        public long getPersistThreshold();
+        public long getNetworkBucketDuration();
+        public long getNetworkMaxHistory();
+        public long getUidBucketDuration();
+        public long getUidMaxHistory();
+        public long getTimeCacheMaxAge();
+    }
 
     private final Object mStatsLock = new Object();
 
@@ -135,19 +134,23 @@
     private HashMap<String, InterfaceIdentity> mActiveIface = Maps.newHashMap();
 
     /** Set of historical stats for known ifaces. */
-    private HashMap<InterfaceIdentity, NetworkStatsHistory> mSummaryStats = Maps.newHashMap();
+    private HashMap<InterfaceIdentity, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
     /** Set of historical stats for known UIDs. */
-    private SparseArray<NetworkStatsHistory> mDetailStats = new SparseArray<NetworkStatsHistory>();
+    private SparseArray<NetworkStatsHistory> mUidStats = new SparseArray<NetworkStatsHistory>();
 
-    private NetworkStats mLastSummaryPoll;
-    private NetworkStats mLastSummaryPersist;
+    /** Flag if {@link #mUidStats} have been loaded from disk. */
+    private boolean mUidStatsLoaded = false;
 
-    private NetworkStats mLastDetailPoll;
+    private NetworkStats mLastNetworkPoll;
+    private NetworkStats mLastNetworkPersist;
+
+    private NetworkStats mLastUidPoll;
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
 
-    private final AtomicFile mSummaryFile;
+    private final AtomicFile mNetworkFile;
+    private final AtomicFile mUidFile;
 
     // TODO: collect detailed uid stats, storing tag-granularity data until next
     // dropbox, and uid summary for a specific bucket count.
@@ -157,7 +160,8 @@
     public NetworkStatsService(
             Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
         // TODO: move to using cached NtpTrustedTime
-        this(context, networkManager, alarmManager, new NtpTrustedTime(), getSystemDir());
+        this(context, networkManager, alarmManager, new NtpTrustedTime(), getSystemDir(),
+                new DefaultNetworkStatsSettings(context));
     }
 
     private static File getSystemDir() {
@@ -165,17 +169,20 @@
     }
 
     public NetworkStatsService(Context context, INetworkManagementService networkManager,
-            IAlarmManager alarmManager, TrustedTime time, File systemDir) {
+            IAlarmManager alarmManager, TrustedTime time, File systemDir,
+            NetworkStatsSettings settings) {
         mContext = checkNotNull(context, "missing Context");
         mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
         mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
         mTime = checkNotNull(time, "missing TrustedTime");
+        mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
 
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
 
-        mSummaryFile = new AtomicFile(new File(systemDir, "netstats.bin"));
+        mNetworkFile = new AtomicFile(new File(systemDir, "netstats.bin"));
+        mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin"));
     }
 
     public void bindConnectivityManager(IConnectivityManager connManager) {
@@ -184,8 +191,10 @@
 
     public void systemReady() {
         synchronized (mStatsLock) {
-            // read historical stats from disk
-            readStatsLocked();
+            // read historical network stats from disk, since policy service
+            // might need them right away. we delay loading detailed UID stats
+            // until actually needed.
+            readNetworkStatsLocked();
         }
 
         // watch for network interfaces to be claimed
@@ -214,14 +223,16 @@
         mContext.unregisterReceiver(mPollReceiver);
         mContext.unregisterReceiver(mShutdownReceiver);
 
-        writeStatsLocked();
-        mSummaryStats.clear();
-        mDetailStats.clear();
+        writeNetworkStatsLocked();
+        writeUidStatsLocked();
+        mNetworkStats.clear();
+        mUidStats.clear();
+        mUidStatsLoaded = false;
     }
 
     /**
      * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
-     * reschedule based on current {@link #mPollInterval} value.
+     * reschedule based on current {@link NetworkStatsSettings#getPollInterval()}.
      */
     private void registerPollAlarmLocked() throws RemoteException {
         if (mPollIntent != null) {
@@ -232,8 +243,8 @@
                 mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
 
         final long currentRealtime = SystemClock.elapsedRealtime();
-        mAlarmManager.setInexactRepeating(
-                AlarmManager.ELAPSED_REALTIME, currentRealtime, mPollInterval.get(), mPollIntent);
+        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
+                mSettings.getPollInterval(), mPollIntent);
     }
 
     @Override
@@ -244,9 +255,9 @@
             // combine all interfaces that match template
             final String subscriberId = getActiveSubscriberId();
             final NetworkStatsHistory combined = new NetworkStatsHistory(
-                    mSummaryBucketDuration.get());
-            for (InterfaceIdentity ident : mSummaryStats.keySet()) {
-                final NetworkStatsHistory history = mSummaryStats.get(ident);
+                    mSettings.getNetworkBucketDuration(), estimateNetworkBuckets());
+            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
+                final NetworkStatsHistory history = mNetworkStats.get(ident);
                 if (ident.matchesTemplate(networkTemplate, subscriberId)) {
                     combined.recordEntireHistory(history);
                 }
@@ -259,8 +270,11 @@
     public NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
-        // TODO: return history for requested uid
-        return null;
+        synchronized (mStatsLock) {
+            // TODO: combine based on template, if we store that granularity
+            ensureUidStatsLoadedLocked();
+            return mUidStats.get(uid);
+        }
     }
 
     @Override
@@ -274,8 +288,8 @@
             long[] networkTotal = new long[2];
 
             // combine total from all interfaces that match template
-            for (InterfaceIdentity ident : mSummaryStats.keySet()) {
-                final NetworkStatsHistory history = mSummaryStats.get(ident);
+            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
+                final NetworkStatsHistory history = mNetworkStats.get(ident);
                 if (ident.matchesTemplate(networkTemplate, subscriberId)) {
                     networkTotal = history.getTotalData(start, end, networkTotal);
                     rx += networkTotal[0];
@@ -283,9 +297,9 @@
                 }
             }
 
-            final NetworkStats.Builder stats = new NetworkStats.Builder(end - start, 1);
+            final NetworkStats stats = new NetworkStats(end - start, 1);
             stats.addEntry(IFACE_ALL, UID_ALL, tx, tx);
-            return stats.build();
+            return stats;
         }
     }
 
@@ -296,17 +310,19 @@
         // TODO: apply networktemplate once granular uid stats are stored.
 
         synchronized (mStatsLock) {
-            final int size = mDetailStats.size();
-            final NetworkStats.Builder stats = new NetworkStats.Builder(end - start, size);
+            ensureUidStatsLoadedLocked();
+
+            final int size = mUidStats.size();
+            final NetworkStats stats = new NetworkStats(end - start, size);
 
             long[] total = new long[2];
             for (int i = 0; i < size; i++) {
-                final int uid = mDetailStats.keyAt(i);
-                final NetworkStatsHistory history = mDetailStats.valueAt(i);
+                final int uid = mUidStats.keyAt(i);
+                final NetworkStatsHistory history = mUidStats.valueAt(i);
                 total = history.getTotalData(start, end, total);
                 stats.addEntry(IFACE_ALL, uid, total[0], total[1]);
             }
-            return stats.build();
+            return stats;
         }
     }
 
@@ -333,7 +349,7 @@
             // permission above.
             synchronized (mStatsLock) {
                 // TODO: acquire wakelock while performing poll
-                performPollLocked();
+                performPollLocked(true);
             }
         }
     };
@@ -355,13 +371,12 @@
      * {@link InterfaceIdentity}.
      */
     private void updateIfacesLocked() {
-        if (LOGD) Slog.v(TAG, "updateIfacesLocked()");
+        if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
 
         // take one last stats snapshot before updating iface mapping. this
         // isn't perfect, since the kernel may already be counting traffic from
         // the updated network.
-        // TODO: verify that we only poll summary stats, not uid details
-        performPollLocked();
+        performPollLocked(false);
 
         final NetworkState[] states;
         try {
@@ -384,11 +399,18 @@
         }
     }
 
-    private void performPollLocked() {
-        if (LOGD) Slog.v(TAG, "performPollLocked()");
+    /**
+     * Periodic poll operation, reading current statistics and recording into
+     * {@link NetworkStatsHistory}.
+     *
+     * @param detailedPoll Indicate if detailed UID stats should be collected
+     *            during this poll operation.
+     */
+    private void performPollLocked(boolean detailedPoll) {
+        if (LOGV) Slog.v(TAG, "performPollLocked()");
 
         // try refreshing time source when stale
-        if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+        if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
             mTime.forceRefresh();
         }
 
@@ -396,42 +418,45 @@
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
                 : System.currentTimeMillis();
 
-        final NetworkStats summary;
-        final NetworkStats detail;
+        final NetworkStats networkStats;
+        final NetworkStats uidStats;
         try {
-            summary = mNetworkManager.getNetworkStatsSummary();
-            detail = mNetworkManager.getNetworkStatsDetail();
+            networkStats = mNetworkManager.getNetworkStatsSummary();
+            uidStats = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
         } catch (RemoteException e) {
             Slog.w(TAG, "problem reading network stats");
             return;
         }
 
-        performSummaryPollLocked(summary, currentTime);
-        performDetailPollLocked(detail, currentTime);
+        performNetworkPollLocked(networkStats, currentTime);
+        if (detailedPoll) {
+            performUidPollLocked(uidStats, currentTime);
+        }
 
         // decide if enough has changed to trigger persist
-        final NetworkStats persistDelta = computeStatsDelta(mLastSummaryPersist, summary);
-        final long persistThreshold = mPersistThreshold.get();
+        final NetworkStats persistDelta = computeStatsDelta(mLastNetworkPersist, networkStats);
+        final long persistThreshold = mSettings.getPersistThreshold();
         for (String iface : persistDelta.getUniqueIfaces()) {
             final int index = persistDelta.findIndex(iface, UID_ALL);
             if (persistDelta.rx[index] > persistThreshold
                     || persistDelta.tx[index] > persistThreshold) {
-                writeStatsLocked();
-                mLastSummaryPersist = summary;
+                writeNetworkStatsLocked();
+                writeUidStatsLocked();
+                mLastNetworkPersist = networkStats;
                 break;
             }
         }
     }
 
     /**
-     * Update {@link #mSummaryStats} historical usage.
+     * Update {@link #mNetworkStats} historical usage.
      */
-    private void performSummaryPollLocked(NetworkStats summary, long currentTime) {
+    private void performNetworkPollLocked(NetworkStats networkStats, long currentTime) {
         final ArrayList<String> unknownIface = Lists.newArrayList();
 
-        final NetworkStats delta = computeStatsDelta(mLastSummaryPoll, summary);
+        final NetworkStats delta = computeStatsDelta(mLastNetworkPoll, networkStats);
         final long timeStart = currentTime - delta.elapsedRealtime;
-        final long maxHistory = mSummaryMaxHistory.get();
+        final long maxHistory = mSettings.getNetworkMaxHistory();
         for (String iface : delta.getUniqueIfaces()) {
             final InterfaceIdentity ident = mActiveIface.get(iface);
             if (ident == null) {
@@ -443,11 +468,11 @@
             final long rx = delta.rx[index];
             final long tx = delta.tx[index];
 
-            final NetworkStatsHistory history = findOrCreateSummaryLocked(ident);
+            final NetworkStatsHistory history = findOrCreateNetworkLocked(ident);
             history.recordData(timeStart, currentTime, rx, tx);
             history.removeBucketsBefore(currentTime - maxHistory);
         }
-        mLastSummaryPoll = summary;
+        mLastNetworkPoll = networkStats;
 
         if (LOGD && unknownIface.size() > 0) {
             Slog.w(TAG, "unknown interfaces " + unknownIface.toString() + ", ignoring those stats");
@@ -455,40 +480,71 @@
     }
 
     /**
-     * Update {@link #mDetailStats} historical usage.
+     * Update {@link #mUidStats} historical usage.
      */
-    private void performDetailPollLocked(NetworkStats detail, long currentTime) {
-        final NetworkStats delta = computeStatsDelta(mLastDetailPoll, detail);
+    private void performUidPollLocked(NetworkStats uidStats, long currentTime) {
+        ensureUidStatsLoadedLocked();
+
+        final NetworkStats delta = computeStatsDelta(mLastUidPoll, uidStats);
         final long timeStart = currentTime - delta.elapsedRealtime;
-        final long maxHistory = mDetailMaxHistory.get();
+        final long maxHistory = mSettings.getUidMaxHistory();
         for (int uid : delta.getUniqueUids()) {
+            // TODO: traverse all ifaces once surfaced in stats
             final int index = delta.findIndex(IFACE_ALL, uid);
-            final long rx = delta.rx[index];
-            final long tx = delta.tx[index];
+            if (index != -1) {
+                final long rx = delta.rx[index];
+                final long tx = delta.tx[index];
 
-            final NetworkStatsHistory history = findOrCreateDetailLocked(uid);
-            history.recordData(timeStart, currentTime, rx, tx);
-            history.removeBucketsBefore(currentTime - maxHistory);
+                final NetworkStatsHistory history = findOrCreateUidLocked(uid);
+                history.recordData(timeStart, currentTime, rx, tx);
+                history.removeBucketsBefore(currentTime - maxHistory);
+            }
         }
-        mLastDetailPoll = detail;
+        mLastUidPoll = uidStats;
     }
 
-    private NetworkStatsHistory findOrCreateSummaryLocked(InterfaceIdentity ident) {
-        NetworkStatsHistory stats = mSummaryStats.get(ident);
-        if (stats == null) {
-            stats = new NetworkStatsHistory(mSummaryBucketDuration.get());
-            mSummaryStats.put(ident, stats);
+    private NetworkStatsHistory findOrCreateNetworkLocked(InterfaceIdentity ident) {
+        final long bucketDuration = mSettings.getNetworkBucketDuration();
+        final NetworkStatsHistory existing = mNetworkStats.get(ident);
+
+        // update when no existing, or when bucket duration changed
+        NetworkStatsHistory updated = null;
+        if (existing == null) {
+            updated = new NetworkStatsHistory(bucketDuration, 10);
+        } else if (existing.bucketDuration != bucketDuration) {
+            updated = new NetworkStatsHistory(
+                    bucketDuration, estimateResizeBuckets(existing, bucketDuration));
+            updated.recordEntireHistory(existing);
         }
-        return stats;
+
+        if (updated != null) {
+            mNetworkStats.put(ident, updated);
+            return updated;
+        } else {
+            return existing;
+        }
     }
 
-    private NetworkStatsHistory findOrCreateDetailLocked(int uid) {
-        NetworkStatsHistory stats = mDetailStats.get(uid);
-        if (stats == null) {
-            stats = new NetworkStatsHistory(mDetailBucketDuration.get());
-            mDetailStats.put(uid, stats);
+    private NetworkStatsHistory findOrCreateUidLocked(int uid) {
+        final long bucketDuration = mSettings.getUidBucketDuration();
+        final NetworkStatsHistory existing = mUidStats.get(uid);
+
+        // update when no existing, or when bucket duration changed
+        NetworkStatsHistory updated = null;
+        if (existing == null) {
+            updated = new NetworkStatsHistory(bucketDuration, 10);
+        } else if (existing.bucketDuration != bucketDuration) {
+            updated = new NetworkStatsHistory(
+                    bucketDuration, estimateResizeBuckets(existing, bucketDuration));
+            updated.recordEntireHistory(existing);
         }
-        return stats;
+
+        if (updated != null) {
+            mUidStats.put(uid, updated);
+            return updated;
+        } else {
+            return existing;
+        }
     }
 
     private InterfaceIdentity findOrCreateInterfaceLocked(String iface) {
@@ -500,15 +556,15 @@
         return ident;
     }
 
-    private void readStatsLocked() {
-        if (LOGD) Slog.v(TAG, "readStatsLocked()");
+    private void readNetworkStatsLocked() {
+        if (LOGV) Slog.v(TAG, "readNetworkStatsLocked()");
 
         // clear any existing stats and read from disk
-        mSummaryStats.clear();
+        mNetworkStats.clear();
 
         FileInputStream fis = null;
         try {
-            fis = mSummaryFile.openRead();
+            fis = mNetworkFile.openRead();
             final DataInputStream in = new DataInputStream(fis);
 
             // verify file magic header intact
@@ -521,13 +577,14 @@
             switch (version) {
                 case VERSION_CURRENT: {
                     // file format is pairs of interfaces and stats:
-                    // summary := size *(InterfaceIdentity NetworkStatsHistory)
+                    // network := size *(InterfaceIdentity NetworkStatsHistory)
 
                     final int size = in.readInt();
                     for (int i = 0; i < size; i++) {
                         final InterfaceIdentity ident = new InterfaceIdentity(in);
                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
-                        mSummaryStats.put(ident, history);
+
+                        mNetworkStats.put(ident, history);
                     }
                     break;
                 }
@@ -544,30 +601,113 @@
         }
     }
 
-    private void writeStatsLocked() {
-        if (LOGD) Slog.v(TAG, "writeStatsLocked()");
+    private void ensureUidStatsLoadedLocked() {
+        if (!mUidStatsLoaded) {
+            readUidStatsLocked();
+            mUidStatsLoaded = true;
+        }
+    }
+
+    private void readUidStatsLocked() {
+        if (LOGV) Slog.v(TAG, "readUidStatsLocked()");
+
+        // clear any existing stats and read from disk
+        mUidStats.clear();
+
+        FileInputStream fis = null;
+        try {
+            fis = mUidFile.openRead();
+            final DataInputStream in = new DataInputStream(fis);
+
+            // verify file magic header intact
+            final int magic = in.readInt();
+            if (magic != FILE_MAGIC) {
+                throw new ProtocolException("unexpected magic: " + magic);
+            }
+
+            final int version = in.readInt();
+            switch (version) {
+                case VERSION_CURRENT: {
+                    // file format is pairs of UIDs and stats:
+                    // uid := size *(UID NetworkStatsHistory)
+
+                    final int size = in.readInt();
+                    for (int i = 0; i < size; i++) {
+                        final int uid = in.readInt();
+                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+                        mUidStats.put(uid, history);
+                    }
+                    break;
+                }
+                default: {
+                    throw new ProtocolException("unexpected version: " + version);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // missing stats is okay, probably first boot
+        } catch (IOException e) {
+            Slog.e(TAG, "problem reading uid stats", e);
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+    }
+
+    private void writeNetworkStatsLocked() {
+        if (LOGV) Slog.v(TAG, "writeNetworkStatsLocked()");
 
         // TODO: consider duplicating stats and releasing lock while writing
 
         FileOutputStream fos = null;
         try {
-            fos = mSummaryFile.startWrite();
+            fos = mNetworkFile.startWrite();
             final DataOutputStream out = new DataOutputStream(fos);
 
             out.writeInt(FILE_MAGIC);
             out.writeInt(VERSION_CURRENT);
 
-            out.writeInt(mSummaryStats.size());
-            for (InterfaceIdentity ident : mSummaryStats.keySet()) {
-                final NetworkStatsHistory history = mSummaryStats.get(ident);
+            out.writeInt(mNetworkStats.size());
+            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
+                final NetworkStatsHistory history = mNetworkStats.get(ident);
                 ident.writeToStream(out);
                 history.writeToStream(out);
             }
 
-            mSummaryFile.finishWrite(fos);
+            mNetworkFile.finishWrite(fos);
         } catch (IOException e) {
             if (fos != null) {
-                mSummaryFile.failWrite(fos);
+                mNetworkFile.failWrite(fos);
+            }
+        }
+    }
+
+    private void writeUidStatsLocked() {
+        if (LOGV) Slog.v(TAG, "writeUidStatsLocked()");
+
+        // TODO: consider duplicating stats and releasing lock while writing
+
+        FileOutputStream fos = null;
+        try {
+            fos = mUidFile.startWrite();
+            final DataOutputStream out = new DataOutputStream(fos);
+
+            out.writeInt(FILE_MAGIC);
+            out.writeInt(VERSION_CURRENT);
+
+            final int size = mUidStats.size();
+
+            out.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                final int uid = mUidStats.keyAt(i);
+                final NetworkStatsHistory history = mUidStats.valueAt(i);
+                out.writeInt(uid);
+                history.writeToStream(out);
+            }
+
+            mUidFile.finishWrite(fos);
+        } catch (IOException e) {
+            if (fos != null) {
+                mUidFile.failWrite(fos);
             }
         }
     }
@@ -590,7 +730,7 @@
             }
 
             if (argSet.contains("poll")) {
-                performPollLocked();
+                performPollLocked(true);
                 pw.println("Forced poll");
                 return;
             }
@@ -603,17 +743,20 @@
             }
 
             pw.println("Known historical stats:");
-            for (InterfaceIdentity ident : mSummaryStats.keySet()) {
-                final NetworkStatsHistory stats = mSummaryStats.get(ident);
+            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
+                final NetworkStatsHistory stats = mNetworkStats.get(ident);
                 pw.print("  ident="); pw.println(ident.toString());
                 stats.dump("    ", pw);
             }
 
             if (argSet.contains("detail")) {
-                pw.println("Known detail stats:");
-                for (int i = 0; i < mDetailStats.size(); i++) {
-                    final int uid = mDetailStats.keyAt(i);
-                    final NetworkStatsHistory stats = mDetailStats.valueAt(i);
+                // since explicitly requested with argument, we're okay to load
+                // from disk if not already in memory.
+                ensureUidStatsLoadedLocked();
+                pw.println("Known UID stats:");
+                for (int i = 0; i < mUidStats.size(); i++) {
+                    final int uid = mUidStats.keyAt(i);
+                    final NetworkStatsHistory stats = mUidStats.valueAt(i);
                     pw.print("  UID="); pw.println(uid);
                     stats.dump("    ", pw);
                 }
@@ -627,47 +770,29 @@
     @Deprecated
     private void generateRandomLocked() {
         long end = System.currentTimeMillis();
-        long start = end - mSummaryMaxHistory.get();
+        long start = end - mSettings.getNetworkMaxHistory();
         long rx = 3 * GB_IN_BYTES;
         long tx = 2 * GB_IN_BYTES;
 
-        mSummaryStats.clear();
+        mNetworkStats.clear();
         for (InterfaceIdentity ident : mActiveIface.values()) {
-            final NetworkStatsHistory stats = findOrCreateSummaryLocked(ident);
+            final NetworkStatsHistory stats = findOrCreateNetworkLocked(ident);
             stats.generateRandom(start, end, rx, tx);
         }
 
         end = System.currentTimeMillis();
-        start = end - mDetailMaxHistory.get();
+        start = end - mSettings.getUidMaxHistory();
         rx = 500 * MB_IN_BYTES;
         tx = 100 * MB_IN_BYTES;
 
-        mDetailStats.clear();
+        mUidStats.clear();
         for (ApplicationInfo info : mContext.getPackageManager().getInstalledApplications(0)) {
             final int uid = info.uid;
-            final NetworkStatsHistory stats = findOrCreateDetailLocked(uid);
+            final NetworkStatsHistory stats = findOrCreateUidLocked(uid);
             stats.generateRandom(start, end, rx, tx);
         }
     }
 
-    private class LongSecureSetting {
-        private String mKey;
-        private long mDefaultValue;
-
-        public LongSecureSetting(String key, long defaultValue) {
-            mKey = key;
-            mDefaultValue = defaultValue;
-        }
-
-        public long get() {
-            if (mContext != null) {
-                return Settings.Secure.getLong(mContext.getContentResolver(), mKey, mDefaultValue);
-            } else {
-                return mDefaultValue;
-            }
-        }
-    }
-
     /**
      * Return the delta between two {@link NetworkStats} snapshots, where {@code
      * before} can be {@code null}.
@@ -686,4 +811,54 @@
         return telephony.getSubscriberId();
     }
 
+    private int estimateNetworkBuckets() {
+        return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration());
+    }
+
+    private int estimateUidBuckets() {
+        return (int) (mSettings.getUidMaxHistory() / mSettings.getUidBucketDuration());
+    }
+
+    private static int estimateResizeBuckets(NetworkStatsHistory existing, long newBucketDuration) {
+        return (int) (existing.bucketCount * existing.bucketDuration / newBucketDuration);
+    }
+
+    /**
+     * Default external settings that read from {@link Settings.Secure}.
+     */
+    private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
+        private final ContentResolver mResolver;
+
+        public DefaultNetworkStatsSettings(Context context) {
+            mResolver = checkNotNull(context.getContentResolver());
+            // TODO: adjust these timings for production builds
+        }
+
+        private long getSecureLong(String name, long def) {
+            return Settings.Secure.getLong(mResolver, name, def);
+        }
+
+        public long getPollInterval() {
+            return getSecureLong(NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS);
+        }
+        public long getPersistThreshold() {
+            return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 16 * KB_IN_BYTES);
+        }
+        public long getNetworkBucketDuration() {
+            return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS);
+        }
+        public long getNetworkMaxHistory() {
+            return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS);
+        }
+        public long getUidBucketDuration() {
+            return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS);
+        }
+        public long getUidMaxHistory() {
+            return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS);
+        }
+        public long getTimeCacheMaxAge() {
+            return DAY_IN_MILLIS;
+        }
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 1eeb56b..476aded 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -297,7 +297,7 @@
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-11-05T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(5, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 5, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -307,7 +307,7 @@
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-10-20T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(20, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 20, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -317,7 +317,7 @@
         final long currentTime = parseTime("2007-02-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-01-30T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(30, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 30, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -327,7 +327,7 @@
         final long currentTime = parseTime("2007-03-14T00:00:00.000Z");
         final long expectedCycle = parseTime("2007-03-01T00:00:00.000Z");
 
-        final NetworkPolicy policy = new NetworkPolicy(30, 1024L, 1024L);
+        final NetworkPolicy policy = new NetworkPolicy(TEMPLATE_WIFI, null, 30, 1024L, 1024L);
         final long actualCycle = computeLastCycleBoundary(currentTime, policy);
         assertEquals(expectedCycle, actualCycle);
     }
@@ -357,8 +357,8 @@
         expectTime(TIME_MAR_10 + elapsedRealtime);
 
         // pretend that 512 bytes total have happened
-        stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry(
-                TEST_IFACE, UID_ALL, 256L, 256L).build();
+        stats = new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, 256L, 256L);
         expect(mStatsService.getSummaryForNetwork(TIME_FEB_15, TIME_MAR_10, TEMPLATE_WIFI, null))
                 .andReturn(stats).atLeastOnce();
 
@@ -366,7 +366,7 @@
         // TODO: write up NetworkManagementService mock
 
         replay();
-        mService.setNetworkPolicy(TEMPLATE_WIFI, null, new NetworkPolicy(CYCLE_DAY, 1024L, 2048L));
+        setNetworkPolicies(new NetworkPolicy(TEMPLATE_WIFI, null, CYCLE_DAY, 1024L, 2048L));
         verifyAndReset();
     }
 
@@ -376,6 +376,10 @@
         return result.toMillis(true);
     }
 
+    private void setNetworkPolicies(NetworkPolicy... policies) {
+        mService.setNetworkPolicies(policies);
+    }
+
     private static NetworkState buildWifi() {
         final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 9846372..2457ff3 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -18,10 +18,13 @@
 
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.TEMPLATE_WIFI;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
 import static org.easymock.EasyMock.anyLong;
 import static org.easymock.EasyMock.createMock;
@@ -47,6 +50,7 @@
 import android.util.TrustedTime;
 
 import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 
 import org.easymock.EasyMock;
 
@@ -62,12 +66,16 @@
     private static final String TEST_IFACE = "test0";
     private static final long TEST_START = 1194220800000L;
 
+    private static final int TEST_UID_1 = 1001;
+    private static final int TEST_UID_2 = 1002;
+
     private BroadcastInterceptingContext mServiceContext;
     private File mStatsDir;
 
     private INetworkManagementService mNetManager;
     private IAlarmManager mAlarmManager;
     private TrustedTime mTime;
+    private NetworkStatsSettings mSettings;
     private IConnectivityManager mConnManager;
 
     private NetworkStatsService mService;
@@ -82,12 +90,14 @@
         mNetManager = createMock(INetworkManagementService.class);
         mAlarmManager = createMock(IAlarmManager.class);
         mTime = createMock(TrustedTime.class);
+        mSettings = createMock(NetworkStatsSettings.class);
         mConnManager = createMock(IConnectivityManager.class);
 
         mService = new NetworkStatsService(
-                mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir);
+                mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir, mSettings);
         mService.bindConnectivityManager(mConnManager);
 
+        expectDefaultSettings();
         expectSystemReady();
 
         replay();
@@ -114,115 +124,93 @@
         super.tearDown();
     }
 
-    private static NetworkState buildWifi() {
-        final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
-        info.setDetailedState(DetailedState.CONNECTED, null, null);
-        final LinkProperties prop = new LinkProperties();
-        prop.setInterfaceName(TEST_IFACE);
-        return new NetworkState(info, prop, null);
-    }
-
-    public void testHistoryForWifi() throws Exception {
+    public void testSummaryStatsWifi() throws Exception {
         long elapsedRealtime = 0;
-        NetworkState[] state = null;
-        NetworkStats stats = null;
-        NetworkStats detail = null;
 
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
-        state = new NetworkState[] { buildWifi() };
-        stats = new NetworkStats.Builder(elapsedRealtime, 0).build();
-        detail = new NetworkStats.Builder(elapsedRealtime, 0).build();
-
-        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
-        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
         expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-        verifyAndReset();
 
         // verify service has empty history for wifi
         assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+        verifyAndReset();
 
         // modify some number on wifi, and trigger poll event
         elapsedRealtime += HOUR_IN_MILLIS;
-        stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry(
-                TEST_IFACE, UID_ALL, 1024L, 2048L).build();
-
-        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
-        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
         expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, 1024L, 2048L));
+        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
-        verifyAndReset();
 
         // verify service recorded history
         assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
+        verifyAndReset();
 
         // and bump forward again, with counters going higher. this is
         // important, since polling should correctly subtract last snapshot.
         elapsedRealtime += DAY_IN_MILLIS;
-        stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry(
-                TEST_IFACE, UID_ALL, 4096L, 8192L).build();
-
-        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
-        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
         expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, 4096L, 8192L));
+        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
-        verifyAndReset();
 
         // verify service recorded history
         assertNetworkTotal(TEMPLATE_WIFI, 4096L, 8192L);
+        verifyAndReset();
+
     }
 
-    public void testHistoryForRebootPersist() throws Exception {
+    public void testStatsRebootPersist() throws Exception {
         long elapsedRealtime = 0;
-        NetworkState[] state = null;
-        NetworkStats stats = null;
-        NetworkStats detail = null;
-
-        // assert that no stats file exists
-        final File statsFile = new File(mStatsDir, "netstats.bin");
-        assertFalse(statsFile.exists());
+        assertStatsFilesExist(false);
 
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
-        state = new NetworkState[] { buildWifi() };
-        stats = new NetworkStats.Builder(elapsedRealtime, 0).build();
-        detail = new NetworkStats.Builder(elapsedRealtime, 0).build();
-
-        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
-        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
         expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-        verifyAndReset();
 
         // verify service has empty history for wifi
         assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+        verifyAndReset();
 
         // modify some number on wifi, and trigger poll event
         elapsedRealtime += HOUR_IN_MILLIS;
-        stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry(
-                TEST_IFACE, UID_ALL, 1024L, 2048L).build();
-
-        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
-        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
         expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, 1024L, 2048L));
+        // TODO: switch these stats to specific iface
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 2)
+                .addEntry(IFACE_ALL, TEST_UID_1, 512L, 256L)
+                .addEntry(IFACE_ALL, TEST_UID_2, 128L, 128L));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
-        verifyAndReset();
 
         // verify service recorded history
         assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
+        assertUidTotal(TEST_UID_1, TEMPLATE_WIFI, 512L, 256L);
+        assertUidTotal(TEST_UID_2, TEMPLATE_WIFI, 128L, 128L);
+        verifyAndReset();
 
         // graceful shutdown system, which should trigger persist of stats, and
         // clear any values in memory.
@@ -230,18 +218,84 @@
 
         // talk with zombie service to assert stats have gone; and assert that
         // we persisted them to file.
+        expectDefaultSettings();
+        replay();
         assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
-        assertTrue(statsFile.exists());
+        verifyAndReset();
+
+        assertStatsFilesExist(true);
 
         // boot through serviceReady() again
+        expectDefaultSettings();
         expectSystemReady();
 
         replay();
         mService.systemReady();
-        verifyAndReset();
 
         // after systemReady(), we should have historical stats loaded again
         assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
+        assertUidTotal(TEST_UID_1, TEMPLATE_WIFI, 512L, 256L);
+        assertUidTotal(TEST_UID_2, TEMPLATE_WIFI, 128L, 128L);
+        verifyAndReset();
+
+    }
+
+    public void testStatsBucketResize() throws Exception {
+        long elapsedRealtime = 0;
+        NetworkStatsHistory history = null;
+        long[] total = null;
+
+        assertStatsFilesExist(false);
+
+        // pretend that wifi network comes online; service should ask about full
+        // network state, and poll any existing interfaces before updating.
+        expectTime(TEST_START + elapsedRealtime);
+        expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        verifyAndReset();
+
+        // modify some number on wifi, and trigger poll event
+        elapsedRealtime += 2 * HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+        expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_ALL, 512L, 512L));
+        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        history = mService.getHistoryForNetwork(TEMPLATE_WIFI);
+        total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
+        assertEquals(512L, total[0]);
+        assertEquals(512L, total[1]);
+        assertEquals(HOUR_IN_MILLIS, history.bucketDuration);
+        assertEquals(2, history.bucketCount);
+        verifyAndReset();
+
+        // now change bucket duration setting and trigger another poll with
+        // exact same values, which should resize existing buckets.
+        expectTime(TEST_START + elapsedRealtime);
+        expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify identical stats, but spread across 4 buckets now
+        history = mService.getHistoryForNetwork(TEMPLATE_WIFI);
+        total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
+        assertEquals(512L, total[0]);
+        assertEquals(512L, total[1]);
+        assertEquals(30 * MINUTE_IN_MILLIS, history.bucketDuration);
+        assertEquals(4, history.bucketCount);
+        verifyAndReset();
 
     }
 
@@ -252,6 +306,13 @@
         assertEquals(tx, total[1]);
     }
 
+    private void assertUidTotal(int uid, int template, long rx, long tx) {
+        final NetworkStatsHistory history = mService.getHistoryForUid(uid, template);
+        final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
+        assertEquals(rx, total[0]);
+        assertEquals(tx, total[1]);
+    }
+
     private void expectSystemReady() throws Exception {
         mAlarmManager.remove(isA(PendingIntent.class));
         expectLastCall().anyTimes();
@@ -261,7 +322,34 @@
         expectLastCall().atLeastOnce();
     }
 
-    public void expectTime(long currentTime) throws Exception {
+    private void expectNetworkState(NetworkState... state) throws Exception {
+        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+    }
+
+    private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+        expect(mNetManager.getNetworkStatsSummary()).andReturn(summary).atLeastOnce();
+    }
+
+    private void expectNetworkStatsDetail(NetworkStats detail) throws Exception {
+        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
+    }
+
+    private void expectDefaultSettings() throws Exception {
+        expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+    }
+
+    private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
+            throws Exception {
+        expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
+        expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
+        expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
+        expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
+        expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
+        expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
+        expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
+    }
+
+    private void expectTime(long currentTime) throws Exception {
         expect(mTime.forceRefresh()).andReturn(false).anyTimes();
         expect(mTime.hasCache()).andReturn(true).anyTimes();
         expect(mTime.currentTimeMillis()).andReturn(currentTime).anyTimes();
@@ -269,12 +357,36 @@
         expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
     }
 
+    private void assertStatsFilesExist(boolean exist) {
+        final File summaryFile = new File(mStatsDir, "netstats.bin");
+        final File detailFile = new File(mStatsDir, "netstats_uid.bin");
+        if (exist) {
+            assertTrue(summaryFile.exists());
+            assertTrue(detailFile.exists());
+        } else {
+            assertFalse(summaryFile.exists());
+            assertFalse(detailFile.exists());
+        }
+    }
+
+    private static NetworkState buildWifiState() {
+        final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
+        info.setDetailedState(DetailedState.CONNECTED, null, null);
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(TEST_IFACE);
+        return new NetworkState(info, prop, null);
+    }
+
+    private static NetworkStats buildEmptyStats(long elapsedRealtime) {
+        return new NetworkStats(elapsedRealtime, 0);
+    }
+
     private void replay() {
-        EasyMock.replay(mNetManager, mAlarmManager, mTime, mConnManager);
+        EasyMock.replay(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
     }
 
     private void verifyAndReset() {
-        EasyMock.verify(mNetManager, mAlarmManager, mTime, mConnManager);
-        EasyMock.reset(mNetManager, mAlarmManager, mTime, mConnManager);
+        EasyMock.verify(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+        EasyMock.reset(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
index d1ee4f6..30afdd8 100644
--- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
@@ -288,11 +288,10 @@
      */
     public void expectGetInterfaceCounter(long rx, long tx) throws Exception {
         // TODO: provide elapsedRealtime mock to match TimeAuthority
-        final NetworkStats.Builder stats = new NetworkStats.Builder(
-                SystemClock.elapsedRealtime(), 1);
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
         stats.addEntry(TEST_IFACE, NetworkStats.UID_ALL, rx, tx);
 
-        expect(mMockNMService.getNetworkStatsSummary()).andReturn(stats.build()).atLeastOnce();
+        expect(mMockNMService.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
     }
 
     /**
diff --git a/tests/GridLayoutTest/res/layout/grid3.xml b/tests/GridLayoutTest/res/layout/grid3.xml
index 31dc75a..5cdacf7 100644
--- a/tests/GridLayoutTest/res/layout/grid3.xml
+++ b/tests/GridLayoutTest/res/layout/grid3.xml
@@ -19,14 +19,17 @@
 
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+
         android:useDefaultMargins="true"
         android:marginsIncludedInAlignment="false"
+
         android:columnCount="4"
         >
 
     <TextView
             android:text="Email account"
             android:textSize="48dip"
+
             android:layout_columnSpan="4"
             android:layout_gravity="center_horizontal"
             />
@@ -34,12 +37,14 @@
     <TextView
             android:text="You can configure email in just a few steps:"
             android:textSize="20dip"
+
             android:layout_columnSpan="4"
             android:layout_gravity="left"
             />
 
     <TextView
             android:text="Email address:"
+
             android:layout_gravity="right"
             />
 
@@ -49,6 +54,7 @@
 
     <TextView
             android:text="Password:"
+
             android:layout_column="0"
             android:layout_gravity="right"
             />
@@ -58,14 +64,15 @@
             />
 
     <Space
-            android:layout_rowWeight="1"
-            android:layout_columnWeight="1"
             android:layout_row="4"
             android:layout_column="2"
+            android:layout_rowWeight="1"
+            android:layout_columnWeight="1"
             />
 
     <Button
             android:text="Manual setup"
+
             android:layout_row="5"
             android:layout_column="3"
             android:layout_gravity="fill_horizontal"
@@ -73,6 +80,7 @@
 
     <Button
             android:text="Next"
+
             android:layout_column="3"
             android:layout_gravity="fill_horizontal"
             />
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java
index 5e29cf1..32365d7 100644
--- a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java
@@ -20,11 +20,15 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.view.View;
-
-import android.widget.*;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.GridLayout;
+import android.widget.Space;
+import android.widget.TextView;
 
 import static android.text.InputType.TYPE_CLASS_TEXT;
-import static android.view.inputmethod.EditorInfo.*;
+import static android.view.inputmethod.EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+import static android.view.inputmethod.EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
 import static android.widget.GridLayout.*;
 
 public class Activity2 extends Activity {
@@ -42,12 +46,12 @@
         Group row6 = new Group(6, CENTER);
         Group row7 = new Group(7, CENTER);
 
-        Group col1a = new Group(1, 5, CENTER);
-        Group col1b = new Group(1, 5, LEFT);
+        Group col1a = new Group(1, 4, CENTER);
+        Group col1b = new Group(1, 4, LEFT);
         Group col1c = new Group(1, RIGHT);
-        Group col2 =  new Group(2, LEFT);
-        Group col3 =  new Group(3, FILL);
-        Group col4 =  new Group(4, FILL);
+        Group col2 = new Group(2, LEFT);
+        Group col3 = new Group(3, FILL);
+        Group col4 = new Group(4, FILL);
 
         {
             TextView v = new TextView(context);
@@ -55,20 +59,17 @@
             v.setText("Email setup");
             vg.addView(v, new LayoutParams(row1, col1a));
         }
-
         {
             TextView v = new TextView(context);
             v.setTextSize(20);
             v.setText("You can configure email in just a few steps:");
             vg.addView(v, new LayoutParams(row2, col1b));
         }
-
         {
             TextView v = new TextView(context);
             v.setText("Email address:");
             vg.addView(v, new LayoutParams(row3, col1c));
         }
-
         {
             EditText v = new EditText(context);
             v.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
@@ -78,13 +79,11 @@
                 vg.addView(v, lp);
             }
         }
-
         {
             TextView v = new TextView(context);
             v.setText("Password:");
             vg.addView(v, new LayoutParams(row4, col1c));
         }
-
         {
             TextView v = new EditText(context);
             v.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD);
@@ -94,7 +93,6 @@
                 vg.addView(v, lp);
             }
         }
-
         {
             Space v = new Space(context);
             {
@@ -104,13 +102,11 @@
                 vg.addView(v, lp);
             }
         }
-
         {
             Button v = new Button(context);
             v.setText("Manual setup");
             vg.addView(v, new LayoutParams(row6, col4));
         }
-
         {
             Button v = new Button(context);
             v.setText("Next");
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index b5f1a17..4837eb9 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -528,12 +528,8 @@
         public void answerCall(String sessionDescription, int timeout) {
             synchronized (SipSessionGroup.this) {
                 if (mPeerProfile == null) return;
-                try {
-                    processCommand(new MakeCallCommand(mPeerProfile,
-                            sessionDescription, timeout));
-                } catch (SipException e) {
-                    onError(e);
-                }
+                doCommandAsync(new MakeCallCommand(mPeerProfile,
+                        sessionDescription, timeout));
             }
         }
 
diff --git a/wifi/java/android/net/wifi/SupplicantState.java b/wifi/java/android/net/wifi/SupplicantState.java
index 91e685f..509b02c 100644
--- a/wifi/java/android/net/wifi/SupplicantState.java
+++ b/wifi/java/android/net/wifi/SupplicantState.java
@@ -216,6 +216,28 @@
         }
     }
 
+    static boolean isDriverActive(SupplicantState state) {
+        switch(state) {
+            case DISCONNECTED:
+            case DORMANT:
+            case INACTIVE:
+            case AUTHENTICATING:
+            case ASSOCIATING:
+            case ASSOCIATED:
+            case SCANNING:
+            case FOUR_WAY_HANDSHAKE:
+            case GROUP_HANDSHAKE:
+            case COMPLETED:
+                return true;
+            case INTERFACE_DISABLED:
+            case UNINITIALIZED:
+            case INVALID:
+                return false;
+            default:
+                throw new IllegalArgumentException("Unknown supplicant state");
+        }
+    }
+
     /** Implement the Parcelable interface {@hide} */
     public int describeContents() {
         return 0;
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 4a45825..4ec4cfc 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -107,7 +107,7 @@
      * <pre>
      * CTRL-EVENT-DRIVER-STATE state
      * </pre>
-     * <code>state</code> is either STARTED or STOPPED
+     * <code>state</code> can be HANGED
      */
     private static final String driverStateEvent = "DRIVER-STATE";
     /**
@@ -304,11 +304,7 @@
             if (state == null) {
                 return;
             }
-            if (state.equals("STOPPED")) {
-                mWifiStateMachine.notifyDriverStopped();
-            } else if (state.equals("STARTED")) {
-                mWifiStateMachine.notifyDriverStarted();
-            } else if (state.equals("HANGED")) {
+            if (state.equals("HANGED")) {
                 mWifiStateMachine.notifyDriverHung();
             }
         }
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 6df37bb..3df3736 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -212,22 +212,18 @@
     static final int SUP_CONNECTION_EVENT                 = BASE + 31;
     /* Connection to supplicant lost */
     static final int SUP_DISCONNECTION_EVENT              = BASE + 32;
-    /* Driver start completed */
-    static final int DRIVER_START_EVENT                   = BASE + 33;
-    /* Driver stop completed */
-    static final int DRIVER_STOP_EVENT                    = BASE + 34;
-    /* Network connection completed */
-    static final int NETWORK_CONNECTION_EVENT             = BASE + 36;
+   /* Network connection completed */
+    static final int NETWORK_CONNECTION_EVENT             = BASE + 33;
     /* Network disconnection completed */
-    static final int NETWORK_DISCONNECTION_EVENT          = BASE + 37;
+    static final int NETWORK_DISCONNECTION_EVENT          = BASE + 34;
     /* Scan results are available */
-    static final int SCAN_RESULTS_EVENT                   = BASE + 38;
+    static final int SCAN_RESULTS_EVENT                   = BASE + 35;
     /* Supplicate state changed */
-    static final int SUPPLICANT_STATE_CHANGE_EVENT        = BASE + 39;
+    static final int SUPPLICANT_STATE_CHANGE_EVENT        = BASE + 36;
     /* Password failure and EAP authentication failure */
-    static final int AUTHENTICATION_FAILURE_EVENT         = BASE + 40;
+    static final int AUTHENTICATION_FAILURE_EVENT         = BASE + 37;
     /* WPS overlap detected */
-    static final int WPS_OVERLAP_EVENT                    = BASE + 41;
+    static final int WPS_OVERLAP_EVENT                    = BASE + 38;
 
 
     /* Supplicant commands */
@@ -1421,6 +1417,35 @@
         return mNetworkInfo.getDetailedState();
     }
 
+
+    private SupplicantState handleSupplicantStateChange(Message message) {
+        StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+        SupplicantState state = stateChangeResult.state;
+        // Supplicant state change
+        // [31-13] Reserved for future use
+        // [8 - 0] Supplicant state (as defined in SupplicantState.java)
+        // 50023 supplicant_state_changed (custom|1|5)
+        EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, state.ordinal());
+        mWifiInfo.setSupplicantState(state);
+        // Network id is only valid when we start connecting
+        if (SupplicantState.isConnecting(state)) {
+            mWifiInfo.setNetworkId(stateChangeResult.networkId);
+        } else {
+            mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+
+        if (state == SupplicantState.ASSOCIATING) {
+            /* BSSID is valid only in ASSOCIATING state */
+            mWifiInfo.setBSSID(stateChangeResult.BSSID);
+        }
+        setNetworkDetailedState(WifiInfo.getDetailedStateOf(state));
+
+        mSupplicantStateTracker.sendMessage(Message.obtain(message));
+        mWpsStateMachine.sendMessage(Message.obtain(message));
+
+        return state;
+    }
+
     /**
      * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
      * using the interface, stopping DHCP & disabling interface
@@ -1674,14 +1699,6 @@
         sendMessage(SCAN_RESULTS_EVENT);
     }
 
-    void notifyDriverStarted() {
-        sendMessage(DRIVER_START_EVENT);
-    }
-
-    void notifyDriverStopped() {
-        sendMessage(DRIVER_STOP_EVENT);
-    }
-
     void notifyDriverHung() {
         setWifiEnabled(false);
         setWifiEnabled(true);
@@ -1737,8 +1754,6 @@
                 case CMD_REASSOCIATE:
                 case SUP_CONNECTION_EVENT:
                 case SUP_DISCONNECTION_EVENT:
-                case DRIVER_START_EVENT:
-                case DRIVER_STOP_EVENT:
                 case NETWORK_CONNECTION_EVENT:
                 case NETWORK_DISCONNECTION_EVENT:
                 case SCAN_RESULTS_EVENT:
@@ -2284,13 +2299,19 @@
         public boolean processMessage(Message message) {
             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
             switch(message.what) {
-                case DRIVER_START_EVENT:
-                    transitionTo(mDriverStartedState);
+               case SUPPLICANT_STATE_CHANGE_EVENT:
+                    SupplicantState state = handleSupplicantStateChange(message);
+                    /* If suplicant is exiting out of INTERFACE_DISABLED state into
+                     * a state that indicates driver has started, it is ready to
+                     * receive driver commands
+                     */
+                    if (SupplicantState.isDriverActive(state)) {
+                        transitionTo(mDriverStartedState);
+                    }
                     break;
                     /* Queue driver commands & connection events */
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case SUPPLICANT_STATE_CHANGE_EVENT:
                 case NETWORK_CONNECTION_EVENT:
                 case NETWORK_DISCONNECTION_EVENT:
                 case AUTHENTICATION_FAILURE_EVENT:
@@ -2429,8 +2450,11 @@
         public boolean processMessage(Message message) {
             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
             switch(message.what) {
-                case DRIVER_STOP_EVENT:
-                    transitionTo(mDriverStoppedState);
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                    SupplicantState state = handleSupplicantStateChange(message);
+                    if (state == SupplicantState.INTERFACE_DISABLED) {
+                        transitionTo(mDriverStoppedState);
+                    }
                     break;
                     /* Queue driver commands */
                 case CMD_START_DRIVER:
@@ -2465,11 +2489,23 @@
         public boolean processMessage(Message message) {
             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
             switch (message.what) {
-                case CMD_START_DRIVER:
-                    mWakeLock.acquire();
-                    WifiNative.startDriverCommand();
-                    transitionTo(mDriverStartingState);
-                    mWakeLock.release();
+               case CMD_START_DRIVER:
+                   mWakeLock.acquire();
+                   WifiNative.startDriverCommand();
+                   mWakeLock.release();
+                   break;
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                    SupplicantState state = handleSupplicantStateChange(message);
+                    /* A driver start causes supplicant to first report an INTERFACE_DISABLED
+                     * state before transitioning out of it for connection. Stay in
+                     * DriverStoppedState until we get an INTERFACE_DISABLED state and transition
+                     * to DriverStarting upon getting that
+                     * TODO: Fix this when the supplicant can be made to just transition out of
+                     * INTERFACE_DISABLED state when driver gets started
+                     */
+                    if (state == SupplicantState.INTERFACE_DISABLED) {
+                        transitionTo(mDriverStartingState);
+                    }
                     break;
                 default:
                     return NOT_HANDLED;
@@ -2535,29 +2571,8 @@
                     sendErrorBroadcast(WifiManager.WPS_OVERLAP_ERROR);
                     break;
                 case SUPPLICANT_STATE_CHANGE_EVENT:
-                    stateChangeResult = (StateChangeResult) message.obj;
-                    SupplicantState state = stateChangeResult.state;
-                    // Supplicant state change
-                    // [31-13] Reserved for future use
-                    // [8 - 0] Supplicant state (as defined in SupplicantState.java)
-                    // 50023 supplicant_state_changed (custom|1|5)
-                    EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, state.ordinal());
-                    mWifiInfo.setSupplicantState(state);
-                    // Network id is only valid when we start connecting
-                    if (SupplicantState.isConnecting(state)) {
-                        mWifiInfo.setNetworkId(stateChangeResult.networkId);
-                    } else {
-                        mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
-                    }
-
-                    if (state == SupplicantState.ASSOCIATING) {
-                        /* BSSID is valid only in ASSOCIATING state */
-                        mWifiInfo.setBSSID(stateChangeResult.BSSID);
-                    }
-
-                    mSupplicantStateTracker.sendMessage(Message.obtain(message));
-                    mWpsStateMachine.sendMessage(Message.obtain(message));
-                    break;
+                    handleSupplicantStateChange(message);
+                   break;
                     /* Do a redundant disconnect without transition */
                 case CMD_DISCONNECT:
                     WifiNative.disconnectCommand();
@@ -2964,12 +2979,7 @@
                     /* Ignore network disconnect */
                 case NETWORK_DISCONNECTION_EVENT:
                     break;
-                case SUPPLICANT_STATE_CHANGE_EVENT:
-                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
-                    setNetworkDetailedState(WifiInfo.getDetailedStateOf(stateChangeResult.state));
-                    /* DriverStartedState does the rest of the handling */
-                    return NOT_HANDLED;
-                case CMD_START_SCAN:
+               case CMD_START_SCAN:
                     /* Disable background scan temporarily during a regular scan */
                     if (mEnableBackgroundScan) {
                         WifiNative.enableBackgroundScanCommand(false);