Merge "Add some extra debug information because this error is only happening on the build server." into ics-aah
diff --git a/api/current.txt b/api/current.txt
index b180f7d..808cb6f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18637,6 +18637,7 @@
     method public android.os.Bundle getBundle();
     method public java.lang.String getLocale();
     method public void onCancel();
+    method public void onClose();
     method public abstract void onCreate();
     method public abstract android.view.textservice.SuggestionsInfo onGetSuggestions(android.view.textservice.TextInfo, int);
     method public android.view.textservice.SuggestionsInfo[] onGetSuggestionsMultiple(android.view.textservice.TextInfo[], int, boolean);
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 02e81b6..cff5df2 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -49,6 +49,7 @@
     private LinkCapabilities mLinkCapabilities;
     private NetworkInfo mNetworkInfo;
     private InterfaceObserver mInterfaceObserver;
+    private String mHwAddr;
 
     /* For sending events to connectivity service handler */
     private Handler mCsHandler;
@@ -102,6 +103,7 @@
         mLinkProperties = new LinkProperties();
         mLinkCapabilities = new LinkCapabilities();
         mLinkUp = false;
+        mHwAddr = null;
 
         mNetworkInfo.setIsAvailable(false);
         setTeardownRequested(false);
@@ -132,7 +134,7 @@
 
         mLinkProperties.clear();
         mNetworkInfo.setIsAvailable(false);
-        mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+        mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
 
         Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
         msg.sendToTarget();
@@ -169,7 +171,7 @@
                 mLinkProperties = dhcpInfoInternal.makeLinkProperties();
                 mLinkProperties.setInterfaceName(mIface);
 
-                mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+                mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
                 Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
                 msg.sendToTarget();
             }
@@ -218,6 +220,12 @@
                     mIface = iface;
                     InterfaceConfiguration config = service.getInterfaceConfig(iface);
                     mLinkUp = config.isActive();
+                    if (config != null && mHwAddr == null) {
+                        mHwAddr = config.hwAddr;
+                        if (mHwAddr != null) {
+                            mNetworkInfo.setExtraInfo(mHwAddr);
+                        }
+                    }
                     reconnect();
                     break;
                 }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 537750a..1e645a1 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -346,6 +346,18 @@
     }
 
     /**
+     * Set the extraInfo field.
+     * @param extraInfo an optional {@code String} providing addditional network state
+     * information passed up from the lower networking layers.
+     * @hide
+     */
+    public void setExtraInfo(String extraInfo) {
+        synchronized (this) {
+            this.mExtraInfo = extraInfo;
+        }
+    }
+
+    /**
      * Report the reason an attempt to establish connectivity failed,
      * if one is available.
      * @return the reason for failure, or null if not available
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 69ac1e7..5c6ef1a 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -16,10 +16,11 @@
 
 package android.net;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import android.util.Log;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.util.Objects;
@@ -54,6 +55,8 @@
     /** {@link #tag} value for total data across all tags. */
     public static final int TAG_NONE = 0;
 
+    // TODO: move fields to "mVariable" notation
+
     /**
      * {@link SystemClock#elapsedRealtime()} timestamp when this data was
      * generated.
@@ -295,8 +298,33 @@
      */
     public int findIndex(String iface, int uid, int set, int tag) {
         for (int i = 0; i < size; i++) {
-            if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
-                    && tag == this.tag[i]) {
+            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+                    && Objects.equal(iface, this.iface[i])) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Find first stats index that matches the requested parameters, starting
+     * search around the hinted index as an optimization.
+     */
+    // @VisibleForTesting
+    public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) {
+        for (int offset = 0; offset < size; offset++) {
+            final int halfOffset = offset / 2;
+
+            // search outwards from hint index, alternating forward and backward
+            final int i;
+            if (offset % 2 == 0) {
+                i = (hintIndex + halfOffset) % size;
+            } else {
+                i = (size + hintIndex - halfOffset - 1) % size;
+            }
+
+            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+                    && Objects.equal(iface, this.iface[i])) {
                 return i;
             }
         }
@@ -423,40 +451,10 @@
      * Subtract the given {@link NetworkStats}, effectively leaving the delta
      * between two snapshots in time. Assumes that statistics rows collect over
      * time, and that none of them have disappeared.
-     *
-     * @throws IllegalArgumentException when given {@link NetworkStats} is
-     *             non-monotonic.
      */
-    public NetworkStats subtract(NetworkStats value) {
-        return subtract(value, true, false);
-    }
-
-    /**
-     * Subtract the given {@link NetworkStats}, effectively leaving the delta
-     * between two snapshots in time. Assumes that statistics rows collect over
-     * time, and that none of them have disappeared.
-     * <p>
-     * Instead of throwing when counters are non-monotonic, this variant clamps
-     * results to never be negative.
-     */
-    public NetworkStats subtractClamped(NetworkStats value) {
-        return subtract(value, false, true);
-    }
-
-    /**
-     * Subtract the given {@link NetworkStats}, effectively leaving the delta
-     * between two snapshots in time. Assumes that statistics rows collect over
-     * time, and that none of them have disappeared.
-     *
-     * @param enforceMonotonic Validate that incoming value is strictly
-     *            monotonic compared to this object.
-     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
-     *            clamp resulting counters at 0 to prevent negative values.
-     */
-    private NetworkStats subtract(
-            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
+    public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
         final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
-        if (enforceMonotonic && deltaRealtime < 0) {
+        if (deltaRealtime < 0) {
             throw new IllegalArgumentException("found non-monotonic realtime");
         }
 
@@ -470,7 +468,7 @@
             entry.tag = tag[i];
 
             // find remote row that matches, and subtract
-            final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
+            final int j = value.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
             if (j == -1) {
                 // newly appearing row, return entire value
                 entry.rxBytes = rxBytes[i];
@@ -485,20 +483,10 @@
                 entry.txBytes = txBytes[i] - value.txBytes[j];
                 entry.txPackets = txPackets[i] - value.txPackets[j];
                 entry.operations = operations[i] - value.operations[j];
-                if (enforceMonotonic
-                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
-                                || entry.txPackets < 0 || entry.operations < 0)) {
-                    Log.v(TAG, "lhs=" + this);
-                    Log.v(TAG, "rhs=" + value);
-                    throw new IllegalArgumentException(
-                            "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
-                }
-                if (clampNegative) {
-                    entry.rxBytes = Math.max(0, entry.rxBytes);
-                    entry.rxPackets = Math.max(0, entry.rxPackets);
-                    entry.txBytes = Math.max(0, entry.txBytes);
-                    entry.txPackets = Math.max(0, entry.txPackets);
-                    entry.operations = Math.max(0, entry.operations);
+
+                if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
+                        || entry.txPackets < 0 || entry.operations < 0) {
+                    throw new NonMonotonicException(this, i, value, j);
                 }
             }
 
@@ -564,6 +552,24 @@
         return stats;
     }
 
+    /**
+     * Return all rows except those attributed to the requested UID; doesn't
+     * mutate the original structure.
+     */
+    public NetworkStats withoutUid(int uid) {
+        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+        Entry entry = new Entry();
+        for (int i = 0; i < size; i++) {
+            entry = getValues(i, entry);
+            if (entry.uid != uid) {
+                stats.addValues(entry);
+            }
+        }
+
+        return stats;
+    }
+
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -625,4 +631,19 @@
             return new NetworkStats[size];
         }
     };
+
+    public static class NonMonotonicException extends Exception {
+        public final NetworkStats left;
+        public final NetworkStats right;
+        public final int leftIndex;
+        public final int rightIndex;
+
+        public NonMonotonicException(
+                NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
+            this.left = checkNotNull(left, "missing left");
+            this.right = checkNotNull(right, "missing right");
+            this.leftIndex = leftIndex;
+            this.rightIndex = rightIndex;
+        }
+    }
 }
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 18eb9f6..cd585b2 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -20,6 +20,7 @@
 import android.app.backup.BackupManager;
 import android.content.Context;
 import android.media.MediaPlayer;
+import android.net.NetworkStats.NonMonotonicException;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
@@ -192,12 +193,15 @@
                 throw new IllegalStateException("not profiling data");
             }
 
-            // subtract starting values and return delta
-            final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
-            final NetworkStats profilingDelta = profilingStop.subtractClamped(
-                    sActiveProfilingStart);
-            sActiveProfilingStart = null;
-            return profilingDelta;
+            try {
+                // subtract starting values and return delta
+                final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
+                final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
+                sActiveProfilingStart = null;
+                return profilingDelta;
+            } catch (NonMonotonicException e) {
+                throw new RuntimeException(e);
+            }
         }
     }
 
diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java
index 2ecf307..28251a6 100644
--- a/core/java/android/service/textservice/SpellCheckerService.java
+++ b/core/java/android/service/textservice/SpellCheckerService.java
@@ -146,6 +146,14 @@
         public void onCancel() {}
 
         /**
+         * Request to close this session.
+         * This function will run on the incoming IPC thread.
+         * So, this is not called on the main thread,
+         * but will be called in series on another thread.
+         */
+        public void onClose() {}
+
+        /**
          * @return Locale for this session
          */
         public String getLocale() {
@@ -162,7 +170,7 @@
 
     // Preventing from exposing ISpellCheckerSession.aidl, create an internal class.
     private static class InternalISpellCheckerSession extends ISpellCheckerSession.Stub {
-        private final ISpellCheckerSessionListener mListener;
+        private ISpellCheckerSessionListener mListener;
         private final Session mSession;
         private final String mLocale;
         private final Bundle mBundle;
@@ -192,6 +200,12 @@
             mSession.onCancel();
         }
 
+        @Override
+        public void onClose() {
+            mSession.onClose();
+            mListener = null;
+        }
+
         public String getLocale() {
             return mLocale;
         }
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 5c3f089..01b114c 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -152,6 +152,7 @@
     public void close() {
         mIsUsed = false;
         try {
+            mSpellCheckerSessionListenerImpl.close();
             mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
         } catch (RemoteException e) {
             // do nothing
@@ -190,6 +191,7 @@
     private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
         private static final int TASK_CANCEL = 1;
         private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
+        private static final int TASK_CLOSE = 3;
         private final Queue<SpellCheckerParams> mPendingTasks =
                 new LinkedList<SpellCheckerParams>();
         private final Handler mHandler;
@@ -224,6 +226,9 @@
                 case TASK_GET_SUGGESTIONS_MULTIPLE:
                     processGetSuggestionsMultiple(scp);
                     break;
+                case TASK_CLOSE:
+                    processClose();
+                    break;
             }
         }
 
@@ -247,6 +252,13 @@
                             suggestionsLimit, sequentialWords));
         }
 
+        public void close() {
+            if (DBG) {
+                Log.w(TAG, "close");
+            }
+            processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false));
+        }
+
         public boolean isDisconnected() {
             return mOpened && mISpellCheckerSession == null;
         }
@@ -284,6 +296,21 @@
             }
         }
 
+        private void processClose() {
+            if (!checkOpenConnection()) {
+                return;
+            }
+            if (DBG) {
+                Log.w(TAG, "Close spell checker tasks.");
+            }
+            try {
+                mISpellCheckerSession.onClose();
+                mISpellCheckerSession = null;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to close " + e);
+            }
+        }
+
         private void processGetSuggestionsMultiple(SpellCheckerParams scp) {
             if (!checkOpenConnection()) {
                 return;
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index 42581c2..fe5908e 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -74,11 +74,13 @@
     public SurfaceTexture getSurfaceTexture(int videoLayerId) {
         // Create the surface texture.
         if (videoLayerId != mVideoLayerUsingSurfaceTexture
-            || mSurfaceTexture == null) {
-            if (mTextureNames == null) {
-                mTextureNames = new int[1];
-                GLES20.glGenTextures(1, mTextureNames, 0);
+            || mSurfaceTexture == null
+            || mTextureNames == null) {
+            if (mTextureNames != null) {
+                GLES20.glDeleteTextures(1, mTextureNames, 0);
             }
+            mTextureNames = new int[1];
+            GLES20.glGenTextures(1, mTextureNames, 0);
             mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
         }
         mVideoLayerUsingSurfaceTexture = videoLayerId;
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index ee3f23b..41993c4 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -25,12 +25,14 @@
 import android.os.SystemClock;
 import android.util.Slog;
 
+import com.android.internal.util.ProcFileReader;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -107,6 +109,7 @@
         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
         final NetworkStats.Entry entry = new NetworkStats.Entry();
 
+        // TODO: transition to ProcFileReader
         // TODO: read directly from proc once headers are added
         final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
                 KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
@@ -257,71 +260,58 @@
         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
         final NetworkStats.Entry entry = new NetworkStats.Entry();
 
-        // TODO: remove knownLines check once 5087722 verified
-        final HashSet<String> knownLines = Sets.newHashSet();
-        // TODO: remove lastIdx check once 5270106 verified
-        int lastIdx;
+        int idx = 1;
+        int lastIdx = 1;
 
-        final ArrayList<String> keys = Lists.newArrayList();
-        final ArrayList<String> values = Lists.newArrayList();
-        final HashMap<String, String> parsed = Maps.newHashMap();
-
-        BufferedReader reader = null;
-        String line = null;
+        ProcFileReader reader = null;
         try {
-            reader = new BufferedReader(new FileReader(mStatsXtUid));
+            // open and consume header line
+            reader = new ProcFileReader(new FileInputStream(mStatsXtUid));
+            reader.finishLine();
 
-            // parse first line as header
-            line = reader.readLine();
-            splitLine(line, keys);
-            lastIdx = 1;
-
-            // parse remaining lines
-            while ((line = reader.readLine()) != null) {
-                splitLine(line, values);
-                parseLine(keys, values, parsed);
-
-                if (!knownLines.add(line)) {
-                    throw new IllegalStateException("duplicate proc entry: " + line);
-                }
-
-                final int idx = getParsedInt(parsed, KEY_IDX);
+            while (reader.hasMoreData()) {
+                idx = reader.nextInt();
                 if (idx != lastIdx + 1) {
                     throw new IllegalStateException(
                             "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
                 }
                 lastIdx = idx;
 
-                entry.iface = parsed.get(KEY_IFACE);
-                entry.uid = getParsedInt(parsed, KEY_UID);
-                entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
-                entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
-                entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
-                entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
-                entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
-                entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+                entry.iface = reader.nextString();
+                entry.tag = kernelToTag(reader.nextString());
+                entry.uid = reader.nextInt();
+                entry.set = reader.nextInt();
+                entry.rxBytes = reader.nextLong();
+                entry.rxPackets = reader.nextLong();
+                entry.txBytes = reader.nextLong();
+                entry.txPackets = reader.nextLong();
 
                 if (limitUid == UID_ALL || limitUid == entry.uid) {
                     stats.addValues(entry);
                 }
+
+                reader.finishLine();
             }
         } catch (NullPointerException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
+            throw new IllegalStateException("problem parsing idx " + idx, e);
         } catch (NumberFormatException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
+            throw new IllegalStateException("problem parsing idx " + idx, e);
         } catch (IOException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
+            throw new IllegalStateException("problem parsing idx " + idx, e);
         } finally {
             IoUtils.closeQuietly(reader);
         }
+
         return stats;
     }
 
+    @Deprecated
     private static int getParsedInt(HashMap<String, String> parsed, String key) {
         final String value = parsed.get(key);
         return value != null ? Integer.parseInt(value) : 0;
     }
 
+    @Deprecated
     private static long getParsedLong(HashMap<String, String> parsed, String key) {
         final String value = parsed.get(key);
         return value != null ? Long.parseLong(value) : 0;
@@ -330,6 +320,7 @@
     /**
      * Split given line into {@link ArrayList}.
      */
+    @Deprecated
     private static void splitLine(String line, ArrayList<String> outSplit) {
         outSplit.clear();
 
@@ -343,6 +334,7 @@
      * Zip the two given {@link ArrayList} as key and value pairs into
      * {@link HashMap}.
      */
+    @Deprecated
     private static void parseLine(
             ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
         outParsed.clear();
diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
index 5a00603..3c61968 100644
--- a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
+++ b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
@@ -25,4 +25,5 @@
     void onGetSuggestionsMultiple(
             in TextInfo[] textInfos, int suggestionsLimit, boolean multipleWords);
     void onCancel();
+    void onClose();
 }
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
new file mode 100644
index 0000000..72e1f0f
--- /dev/null
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -0,0 +1,199 @@
+/*
+ * 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.internal.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charsets;
+
+/**
+ * Reader that specializes in parsing {@code /proc/} files quickly. Walks
+ * through the stream using a single space {@code ' '} as token separator, and
+ * requires each line boundary to be explicitly acknowledged using
+ * {@link #finishLine()}. Assumes {@link Charsets#US_ASCII} encoding.
+ * <p>
+ * Currently doesn't support formats based on {@code \0}, tabs, or repeated
+ * delimiters.
+ */
+public class ProcFileReader implements Closeable {
+    private final InputStream mStream;
+    private final byte[] mBuffer;
+
+    /** Write pointer in {@link #mBuffer}. */
+    private int mTail;
+    /** Flag when last read token finished current line. */
+    private boolean mLineFinished;
+
+    public ProcFileReader(InputStream stream) throws IOException {
+        this(stream, 4096);
+    }
+
+    public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
+        mStream = stream;
+        mBuffer = new byte[bufferSize];
+
+        // read enough to answer hasMoreData
+        fillBuf();
+    }
+
+    /**
+     * Read more data from {@link #mStream} into internal buffer.
+     */
+    private int fillBuf() throws IOException {
+        final int length = mBuffer.length - mTail;
+        if (length == 0) {
+            throw new IOException("attempting to fill already-full buffer");
+        }
+
+        final int read = mStream.read(mBuffer, mTail, length);
+        if (read != -1) {
+            mTail += read;
+        }
+        return read;
+    }
+
+    /**
+     * Consume number of bytes from beginning of internal buffer. If consuming
+     * all remaining bytes, will attempt to {@link #fillBuf()}.
+     */
+    private void consumeBuf(int count) throws IOException {
+        // TODO: consider moving to read pointer, but for now traceview says
+        // these copies aren't a bottleneck.
+        System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
+        mTail -= count;
+        if (mTail == 0) {
+            fillBuf();
+        }
+    }
+
+    /**
+     * Find buffer index of next token delimiter, usually space or newline. Will
+     * fill buffer as needed.
+     */
+    private int nextTokenIndex() throws IOException {
+        if (mLineFinished) {
+            throw new IOException("no tokens remaining on current line");
+        }
+
+        int i = 0;
+        do {
+            // scan forward for token boundary
+            for (; i < mTail; i++) {
+                final byte b = mBuffer[i];
+                if (b == '\n') {
+                    mLineFinished = true;
+                    return i;
+                }
+                if (b == ' ') {
+                    return i;
+                }
+            }
+        } while (fillBuf() > 0);
+
+        throw new IOException("end of stream while looking for token boundary");
+    }
+
+    /**
+     * Check if stream has more data to be parsed.
+     */
+    public boolean hasMoreData() {
+        return mTail > 0;
+    }
+
+    /**
+     * Finish current line, skipping any remaining data.
+     */
+    public void finishLine() throws IOException {
+        // last token already finished line; reset silently
+        if (mLineFinished) {
+            mLineFinished = false;
+            return;
+        }
+
+        int i = 0;
+        do {
+            // scan forward for line boundary and consume
+            for (; i < mTail; i++) {
+                if (mBuffer[i] == '\n') {
+                    consumeBuf(i + 1);
+                    return;
+                }
+            }
+        } while (fillBuf() > 0);
+
+        throw new IOException("end of stream while looking for line boundary");
+    }
+
+    /**
+     * Parse and return next token as {@link String}.
+     */
+    public String nextString() throws IOException {
+        final int tokenIndex = nextTokenIndex();
+        final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
+        consumeBuf(tokenIndex + 1);
+        return s;
+    }
+
+    /**
+     * Parse and return next token as base-10 encoded {@code long}.
+     */
+    public long nextLong() throws IOException {
+        final int tokenIndex = nextTokenIndex();
+        final boolean negative = mBuffer[0] == '-';
+
+        // TODO: refactor into something like IntegralToString
+        long result = 0;
+        for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
+            final int digit = mBuffer[i] - '0';
+            if (digit < 0 || digit > 9) {
+                throw invalidLong(tokenIndex);
+            }
+
+            // always parse as negative number and apply sign later; this
+            // correctly handles MIN_VALUE which is "larger" than MAX_VALUE.
+            final long next = result * 10 - digit;
+            if (next > result) {
+                throw invalidLong(tokenIndex);
+            }
+            result = next;
+        }
+
+        consumeBuf(tokenIndex + 1);
+        return negative ? result : -result;
+    }
+
+    private NumberFormatException invalidLong(int tokenIndex) {
+        return new NumberFormatException(
+                "invalid long: " + new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII));
+    }
+
+    /**
+     * Parse and return next token as base-10 encoded {@code int}.
+     */
+    public int nextInt() throws IOException {
+        final long value = nextLong();
+        if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) {
+            throw new NumberFormatException("parsed value larger than integer");
+        }
+        return (int) value;
+    }
+
+    public void close() throws IOException {
+        mStream.close();
+    }
+}
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 9eee2f0..0cc883f 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -89,7 +89,7 @@
      * Ensure that downloading on wifi reports reasonable stats.
      */
     @LargeTest
-    public void testWifiDownload() {
+    public void testWifiDownload() throws Exception {
         assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
         NetworkStats pre_test_stats = fetchDataFromProc(mUid);
         String ts = Long.toString(System.currentTimeMillis());
@@ -123,7 +123,7 @@
      * Ensure that downloading on wifi reports reasonable stats.
      */
     @LargeTest
-    public void testWifiUpload() {
+    public void testWifiUpload() throws Exception {
         assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
         // Download a file from the server.
         String ts = Long.toString(System.currentTimeMillis());
@@ -160,7 +160,7 @@
      * accounting still goes to the app making the call and that the numbers still make sense.
      */
     @LargeTest
-    public void testWifiDownloadWithDownloadManager() {
+    public void testWifiDownloadWithDownloadManager() throws Exception {
         assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
         // If we are using the download manager, then the data that is written to /proc/uid_stat/
         // is accounted against download manager's uid, since it uses pre-ICS API.
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_extended b/core/tests/coretests/res/raw/xt_qtaguid_extended
deleted file mode 100644
index 2f3b4ec..0000000
--- a/core/tests/coretests/res/raw/xt_qtaguid_extended
+++ /dev/null
@@ -1,3 +0,0 @@
-acct_tag_hex uid_tag_int iface rx_bytes rx_packets tx_bytes tx_packets teleported_goats idx
-0x0 1000 test0 1024 10 2048 20 2716057 2
-0x0000F00D00000000 1000 test0 512 5 512 5 3370318 3
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical b/core/tests/coretests/res/raw/xt_qtaguid_typical
index 8df4b1b..c1b0d25 100644
--- a/core/tests/coretests/res/raw/xt_qtaguid_typical
+++ b/core/tests/coretests/res/raw/xt_qtaguid_typical
@@ -1,32 +1,71 @@
-idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
-2 wlan0 0x0 0 14615 4270
-3 wlan0 0x0 1000 5175 915
-4 wlan0 0x0 1021 3381 903
-5 wlan0 0x0 10004 333821 53558
-6 wlan0 0x0 10010 4888 37363
-7 wlan0 0x0 10013 52 104
-8 wlan0 0x74182ada00000000 10004 18725 1066
-9 rmnet0 0x0 0 301274 30244
-10 rmnet0 0x0 1000 304 441
-11 rmnet0 0x0 1013 2880 2272
-12 rmnet0 0x0 1021 31407 8430
-13 rmnet0 0x0 10003 32665 3814
-14 rmnet0 0x0 10004 2373141 420112
-15 rmnet0 0x0 10010 870370 1111727
-16 rmnet0 0x0 10013 240 240
-17 rmnet0 0x0 10016 16703 13512
-18 rmnet0 0x0 10017 3990 3269
-19 rmnet0 0x0 10018 474504 14516062
-20 rmnet0 0x0 10019 782804 71077
-21 rmnet0 0x0 10022 70671 49684
-22 rmnet0 0x0 10029 5785354 397159
-23 rmnet0 0x0 10033 2102 1686
-24 rmnet0 0x0 10034 15495464 227694
-25 rmnet0 0x0 10037 31184994 684122
-26 rmnet0 0x0 10051 298687 113485
-27 rmnet0 0x0 10056 29504 20669
-28 rmnet0 0x0 10069 683 596
-29 rmnet0 0x0 10072 34051 12453
-30 rmnet0 0x0 10077 7025393 213866
-31 rmnet0 0x0 10081 354 1178
-32 rmnet0 0x74182ada00000000 10037 28507378 437004
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 wlan0 0x0 0 0 18621 96 2898 44 312 6 15897 58 2412 32 312 6 1010 16 1576 22
+3 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 wlan0 0x0 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+5 wlan0 0x0 1000 1 1949 13 1078 14 0 0 1600 10 349 3 0 0 600 10 478 4
+6 wlan0 0x0 10005 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+7 wlan0 0x0 10005 1 32081 38 5315 50 32081 38 0 0 0 0 5315 50 0 0 0 0
+8 wlan0 0x0 10011 0 35777 53 5718 57 0 0 0 0 35777 53 0 0 0 0 5718 57
+9 wlan0 0x0 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+10 wlan0 0x0 10014 0 0 0 1098 13 0 0 0 0 0 0 0 0 0 0 1098 13
+11 wlan0 0x0 10014 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+12 wlan0 0x0 10021 0 562386 573 49228 549 0 0 0 0 562386 573 0 0 0 0 49228 549
+13 wlan0 0x0 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 10031 0 3425 5 586 6 0 0 0 0 3425 5 0 0 0 0 586 6
+15 wlan0 0x0 10031 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+16 wlan0 0x7fffff0100000000 10021 0 562386 573 49228 549 0 0 0 0 562386 573 0 0 0 0 49228 549
+17 wlan0 0x7fffff0100000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+18 wlan0 0x7fffff0100000000 10031 0 3425 5 586 6 0 0 0 0 3425 5 0 0 0 0 586 6
+19 wlan0 0x7fffff0100000000 10031 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+20 rmnet2 0x0 0 0 547 5 118 2 40 1 243 1 264 3 0 0 62 1 56 1
+21 rmnet2 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 rmnet2 0x0 10001 0 1125899906842624 5 984 11 632 5 0 0 0 0 984 11 0 0 0 0
+23 rmnet2 0x0 10001 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+24 rmnet1 0x0 0 0 26736 174 7098 130 7210 97 18382 64 1144 13 2932 64 4054 64 112 2
+25 rmnet1 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 rmnet1 0x0 1000 0 75774 77 18038 78 75335 72 439 5 0 0 17668 73 370 5 0 0
+27 rmnet1 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+28 rmnet1 0x0 10007 0 269945 578 111632 586 269945 578 0 0 0 0 111632 586 0 0 0 0
+29 rmnet1 0x0 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+30 rmnet1 0x0 10011 0 1741256 6918 769778 7019 1741256 6918 0 0 0 0 769778 7019 0 0 0 0
+31 rmnet1 0x0 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+32 rmnet1 0x0 10014 0 0 0 786 12 0 0 0 0 0 0 786 12 0 0 0 0
+33 rmnet1 0x0 10014 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+34 rmnet1 0x0 10021 0 433533 1454 393420 1604 433533 1454 0 0 0 0 393420 1604 0 0 0 0
+35 rmnet1 0x0 10021 1 21215 33 10278 33 21215 33 0 0 0 0 10278 33 0 0 0 0
+36 rmnet1 0x0 10036 0 6310 25 3284 29 6310 25 0 0 0 0 3284 29 0 0 0 0
+37 rmnet1 0x0 10036 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+38 rmnet1 0x0 10047 0 34264 47 3936 34 34264 47 0 0 0 0 3936 34 0 0 0 0
+39 rmnet1 0x0 10047 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+40 rmnet1 0x4e7700000000 10011 0 9187 27 4248 33 9187 27 0 0 0 0 4248 33 0 0 0 0
+41 rmnet1 0x4e7700000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 rmnet1 0x1000000000000000 10007 0 2109 4 791 4 2109 4 0 0 0 0 791 4 0 0 0 0
+43 rmnet1 0x1000000000000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 rmnet1 0x1000000400000000 10007 0 9811 22 6286 22 9811 22 0 0 0 0 6286 22 0 0 0 0
+45 rmnet1 0x1000000400000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+46 rmnet1 0x1010000000000000 10021 0 164833 426 135392 527 164833 426 0 0 0 0 135392 527 0 0 0 0
+47 rmnet1 0x1010000000000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+48 rmnet1 0x1144000400000000 10011 0 10112 18 3334 17 10112 18 0 0 0 0 3334 17 0 0 0 0
+49 rmnet1 0x1144000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+50 rmnet1 0x1244000400000000 10011 0 1300 3 848 2 1300 3 0 0 0 0 848 2 0 0 0 0
+51 rmnet1 0x1244000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+52 rmnet1 0x3000000000000000 10007 0 10389 14 1521 12 10389 14 0 0 0 0 1521 12 0 0 0 0
+53 rmnet1 0x3000000000000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+54 rmnet1 0x3000000400000000 10007 0 238070 380 93938 404 238070 380 0 0 0 0 93938 404 0 0 0 0
+55 rmnet1 0x3000000400000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+56 rmnet1 0x3010000000000000 10021 0 219110 578 227423 676 219110 578 0 0 0 0 227423 676 0 0 0 0
+57 rmnet1 0x3010000000000000 10021 1 742 3 1265 3 742 3 0 0 0 0 1265 3 0 0 0 0
+58 rmnet1 0x3020000000000000 10021 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+59 rmnet1 0x3020000000000000 10021 1 20473 30 9013 30 20473 30 0 0 0 0 9013 30 0 0 0 0
+60 rmnet1 0x3144000400000000 10011 0 43963 92 34414 116 43963 92 0 0 0 0 34414 116 0 0 0 0
+61 rmnet1 0x3144000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+62 rmnet1 0x3244000400000000 10011 0 3486 8 1520 9 3486 8 0 0 0 0 1520 9 0 0 0 0
+63 rmnet1 0x3244000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+64 rmnet1 0x7fffff0100000000 10021 0 29102 56 8865 60 29102 56 0 0 0 0 8865 60 0 0 0 0
+65 rmnet1 0x7fffff0100000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+66 rmnet1 0x7fffff0300000000 1000 0 995 13 14145 14 995 13 0 0 0 0 14145 14 0 0 0 0
+67 rmnet1 0x7fffff0300000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+68 rmnet0 0x0 0 0 4312 49 1288 23 0 0 0 0 4312 49 0 0 0 0 1288 23
+69 rmnet0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+70 rmnet0 0x0 10080 0 22266 30 20976 30 0 0 0 0 22266 30 0 0 0 0 20976 30
+71 rmnet0 0x0 10080 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set b/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set
deleted file mode 100644
index b302bb7..0000000
--- a/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set
+++ /dev/null
@@ -1,13 +0,0 @@
-idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_packets rx_tcp_bytes rx_udp_packets rx_udp_bytes rx_other_packets rx_other_bytes tx_tcp_packets tx_tcp_bytes tx_udp_packets tx_udp_bytes tx_other_packets tx_other_bytes
-2 rmnet0 0x0 0 0 14855 82 2804 47 2000 45 12799 35 56 2 676 13 2128 34 0 0
-3 rmnet0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-4 rmnet0 0x0 1000 0 278102 253 10487 182 277342 243 760 10 0 0 9727 172 760 10 0 0
-5 rmnet0 0x0 1000 1 26033 30 1401 26 25881 28 152 2 0 0 1249 24 152 2 0 0
-6 rmnet0 0x0 10012 0 40524 272 134138 293 40524 272 0 0 0 0 134138 293 0 0 0 0
-7 rmnet0 0x0 10012 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-8 rmnet0 0x0 10034 0 15791 59 9905 69 15791 59 0 0 0 0 9905 69 0 0 0 0
-9 rmnet0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-10 rmnet0 0x0 10055 0 3602 29 7739 59 3602 29 0 0 0 0 7739 59 0 0 0 0
-11 rmnet0 0x0 10055 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-12 rmnet0 0x7fff000300000000 1000 0 483 4 1931 6 483 4 0 0 0 0 1931 6 0 0 0 0
-13 rmnet0 0x7fff000300000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 7082deb..b37eb46 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -51,6 +51,27 @@
         assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE));
     }
 
+    public void testFindIndexHinted() {
+        final NetworkStats stats = new NetworkStats(TEST_START, 3)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 11)
+                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12)
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 1024L, 8L, 11)
+                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12);
+
+        // verify that we correctly find across regardless of hinting
+        for (int hint = 0; hint < stats.size(); hint++) {
+            assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, hint));
+            assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, hint));
+            assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, hint));
+            assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, hint));
+            assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, hint));
+            assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, hint));
+            assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, hint));
+        }
+    }
+
     public void testAddEntryGrow() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 2);
 
@@ -257,6 +278,22 @@
         assertValues(stats.getTotal(null, ifaces), 1024L, 64L, 0L, 0L, 0L);
     }
 
+    public void testWithoutUid() throws Exception {
+        final NetworkStats before = new NetworkStats(TEST_START, 3)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+
+        final NetworkStats after = before.withoutUid(100);
+        assertEquals(6, before.size());
+        assertEquals(2, after.size());
+        assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+        assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+    }
+
     private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
             int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
         final NetworkStats.Entry entry = stats.getValues(index, null);
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
index 8a64f2b..ea94fa9 100644
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -71,21 +71,12 @@
         stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
 
         final NetworkStats stats = mFactory.readNetworkStatsDetail();
-        assertEquals(31, stats.size());
-        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0, 14615L, 4270L);
-        assertStatsEntry(stats, "wlan0", 10004, SET_DEFAULT, 0, 333821L, 53558L);
-        assertStatsEntry(stats, "wlan0", 10004, SET_DEFAULT, 1947740890, 18725L, 1066L);
-        assertStatsEntry(stats, "rmnet0", 10037, SET_DEFAULT, 0, 31184994L, 684122L);
-        assertStatsEntry(stats, "rmnet0", 10037, SET_DEFAULT, 1947740890, 28507378L, 437004L);
-    }
-
-    public void testNetworkStatsDetailExtended() throws Exception {
-        stageFile(R.raw.xt_qtaguid_extended, new File(mTestProc, "net/xt_qtaguid/stats"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsDetail();
-        assertEquals(2, stats.size());
-        assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0, 1024L, 2048L);
-        assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0xF00D, 512L, 512L);
+        assertEquals(70, stats.size());
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
+        assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
+        assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
+        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
+        assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
     }
 
     public void testNetworkStatsSummary() throws Exception {
@@ -149,12 +140,12 @@
     }
 
     public void testNetworkStatsWithSet() throws Exception {
-        stageFile(R.raw.xt_qtaguid_typical_with_set, new File(mTestProc, "net/xt_qtaguid/stats"));
+        stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
 
         final NetworkStats stats = mFactory.readNetworkStatsDetail();
-        assertEquals(12, stats.size());
-        assertStatsEntry(stats, "rmnet0", 1000, SET_DEFAULT, 0, 278102L, 253L, 10487L, 182L);
-        assertStatsEntry(stats, "rmnet0", 1000, SET_FOREGROUND, 0, 26033L, 30L, 1401L, 26L);
+        assertEquals(70, stats.size());
+        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L, 676L);
+        assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
     }
 
     public void testNetworkStatsSingle() throws Exception {
diff --git a/core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java
new file mode 100644
index 0000000..386a78d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.internal.util;
+
+import android.test.AndroidTestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.Charsets;
+
+/**
+ * Tests for {@link ProcFileReader}.
+ */
+public class ProcFileReaderTest extends AndroidTestCase {
+
+    public void testEmpty() throws Exception {
+        final ProcFileReader reader = buildReader("");
+
+        assertFalse(reader.hasMoreData());
+        try {
+            reader.finishLine();
+            fail("somehow finished line beyond end of stream?");
+        } catch (IOException e) {
+            // expected
+        }
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testSingleString() throws Exception {
+        final ProcFileReader reader = buildReader("a\nb\nc\n");
+
+        assertEquals("a", reader.nextString());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals("b", reader.nextString());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals("c", reader.nextString());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testMixedNumbersSkip() throws Exception {
+        final ProcFileReader reader = buildReader("1 2 3\n4 abc_def 5 6 7 8 9\n10\n");
+
+        assertEquals(1, reader.nextInt());
+        assertEquals(2, reader.nextInt());
+        assertEquals(3, reader.nextInt());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals(4, reader.nextInt());
+        assertEquals("abc_def", reader.nextString());
+        assertEquals(5, reader.nextInt());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals(10, reader.nextInt());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testBufferSize() throws Exception {
+        // read numbers using very small buffer size, exercising fillBuf()
+        final ProcFileReader reader = buildReader("1 21 3 41 5 61 7 81 9 10\n", 3);
+
+        assertEquals(1, reader.nextInt());
+        assertEquals(21, reader.nextInt());
+        assertEquals(3, reader.nextInt());
+        assertEquals(41, reader.nextInt());
+        assertEquals(5, reader.nextInt());
+        assertEquals(61, reader.nextInt());
+        assertEquals(7, reader.nextInt());
+        assertEquals(81, reader.nextInt());
+        assertEquals(9, reader.nextInt());
+        assertEquals(10, reader.nextInt());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testBlankLines() throws Exception {
+        final ProcFileReader reader = buildReader("1\n\n2\n\n3\n");
+
+        assertEquals(1, reader.nextInt());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals(2, reader.nextInt());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals(3, reader.nextInt());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testMinMax() throws Exception {
+        final ProcFileReader reader = buildReader(
+                "1 -1024 9223372036854775807 -9223372036854775808\n");
+
+        assertEquals(1, reader.nextLong());
+        assertEquals(-1024, reader.nextLong());
+        assertEquals(Long.MAX_VALUE, reader.nextLong());
+        assertEquals(Long.MIN_VALUE, reader.nextLong());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testDelimiterNeverFound() throws Exception {
+        final ProcFileReader reader = buildReader("teststringwithoutdelimiters");
+
+        try {
+            reader.nextString();
+            fail("somehow read a string value?");
+        } catch (IOException e) {
+            // expected
+            assertTrue(e.getMessage().contains("end of stream"));
+        }
+    }
+
+    public void testLargerThanBuffer() throws Exception {
+        // try finishing line larger than buffer
+        final ProcFileReader reader = buildReader("1 teststringlongerthanbuffer\n", 4);
+
+        assertEquals(1, reader.nextLong());
+        try {
+            reader.finishLine();
+            fail("somehow finished line?");
+        } catch (IOException e) {
+            // expected
+            assertTrue(e.getMessage().contains("already-full buffer"));
+        }
+    }
+
+    private static ProcFileReader buildReader(String string) throws IOException {
+        return buildReader(string, 2048);
+    }
+
+    private static ProcFileReader buildReader(String string, int bufferSize) throws IOException {
+        return new ProcFileReader(
+                new ByteArrayInputStream(string.getBytes(Charsets.US_ASCII)), bufferSize);
+    }
+}
diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd
index 9ce3649..deb7a2d 100644
--- a/docs/html/guide/developing/device.jd
+++ b/docs/html/guide/developing/device.jd
@@ -58,14 +58,17 @@
 element.</p>
   </li>
   <li>Set up your device to allow installation of non-Market applications. <p>On
-the device, go to <strong>Settings > Applications</strong> and enable
+the device, go to <strong>Settings > Applications</strong> and enable 
 
-<strong>Unknown sources</strong>.</p>
+<strong>Unknown sources</strong> (on an Android 4.0 device, the setting is
+located in <strong>Settings > Security</strong>).</p>
   
   </li>
   <li>Turn on "USB Debugging" on your device.
-    <p>On the device, go to <strong>Settings > Applications > Development</strong>
-    and enable <strong>USB debugging</strong>.</p>
+    <p>On the device, go to <strong>Settings > Applications > Development</strong> 
+    and enable <strong>USB debugging</strong> 
+    (on an Android 4.0 device, the setting is 
+located in <strong>Settings > Developer options</strong>).</p>
   </li>
   <li>Set up your system to detect your device.
     <ul>
diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd
index 9089937..07a0e43 100644
--- a/docs/html/resources/dashboard/opengl.jd
+++ b/docs/html/resources/dashboard/opengl.jd
@@ -57,7 +57,7 @@
 <div class="dashboard-panel">
 
 <img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.2,90.8" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.8,90.2" />
 
 <table>
 <tr>
@@ -66,14 +66,14 @@
 </tr>
 <tr>
 <td>1.1</th>
-<td>9.2%</td>
+<td>9.8%</td>
 </tr>
 <tr>
 <td>2.0</th>
-<td>90.8%</td>
+<td>90.2%</td>
 </tr>
 </table>
 
-<p><em>Data collected during a 7-day period ending on October 3, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p>
 </div>
 
diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd
index 135c6f2..8041096 100644
--- a/docs/html/resources/dashboard/platform-versions.jd
+++ b/docs/html/resources/dashboard/platform-versions.jd
@@ -52,7 +52,7 @@
 <div class="dashboard-panel">
 
 <img alt="" height="250" width="470"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:1.1,1.4,11.7,45.3,0.5,38.2,0.2,0.9,0.7&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" />
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.9,1.4,10.7,40.7,0.5,43.9,0.1,0.9,0.9&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" />
 
 <table>
 <tr>
@@ -61,21 +61,21 @@
   <th>API Level</th>
   <th>Distribution</th>
 </tr>
-<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td>  <td>3</td><td>1.1%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td>  <td>3</td><td>0.9%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td>    <td>4</td><td>1.4%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td>   <td>7</td><td>11.7%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td>    <td>8</td><td>45.3%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td>   <td>7</td><td>10.7%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td>    <td>8</td><td>40.7%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/>
                              Android 2.3.2</a></td><td rowspan="2">Gingerbread</td>    <td>9</td><td>0.5%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/>
-      Android 2.3.7</a></td><!-- Gingerbread -->                                       <td>10</td><td>38.2%</td></tr>
+      Android 2.3.7</a></td><!-- Gingerbread -->                                       <td>10</td><td>43.9%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td>
-                                                   <td rowspan="3">Honeycomb</td>      <td>11</td><td>0.2%</td></tr>
+                                                   <td rowspan="3">Honeycomb</td>      <td>11</td><td>0.1%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>0.9%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>0.7%</td></tr> 
+<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>0.9%</td></tr> 
 </table>
 
-<p><em>Data collected during a 14-day period ending on October 3, 2011</em></p>
+<p><em>Data collected during a 14-day period ending on November 3, 2011</em></p>
 <!--
 <p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p>
 -->
@@ -104,9 +104,9 @@
 <div class="dashboard-panel">
 
 <img alt="" height="250" width="660" style="padding:5px;background:#fff"
-src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.7,99.6,99.6,99.5,99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2|97.0,97.1,97.3,97.5,97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1|93.5,93.9,94.3,94.8,95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6|66.4,68.0,69.8,71.5,73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8|2.5,3.1,4.0,6.1,9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3|1.7,2.2,3.0,5.1,8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid 2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid 2.3.3,131d02,5,5,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" />
+src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.6,99.5,99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0|97.3,97.5,97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.1|94.3,94.8,95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7|69.8,71.5,73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.0|4.0,6.1,9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0|3.0,5.1,8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid 2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid 2.3.3,131d02,5,3,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" />
 
-<p><em>Last historical dataset collected during a 14-day period ending on October 3, 2011</em></p>
+<p><em>Last historical dataset collected during a 14-day period ending on November 3, 2011</em></p>
 
 
 </div><!-- end dashboard-panel -->
diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd
index 67b47d0..ec3034d 100644
--- a/docs/html/resources/dashboard/screens.jd
+++ b/docs/html/resources/dashboard/screens.jd
@@ -60,7 +60,7 @@
 <div class="dashboard-panel">
 
 <img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A2.6,0.1,3.0,71.9,0.9,17.6,2.7,1.2" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A2.9,0.1,3.1,70.8,1.0,17.7,3.0,1.3" />
 
 <table>
 <tr>
@@ -71,31 +71,31 @@
 <th scope="col">xhdpi</th>
 </tr>
 <tr><th scope="row">small</th> 
-<td>1.2%</td>     <!-- small/ldpi -->
+<td>1.3%</td>     <!-- small/ldpi -->
 <td></td>     <!-- small/mdpi -->
-<td>2.7%</td> <!-- small/hdpi -->
+<td>3.0%</td> <!-- small/hdpi -->
 <td></td>     <!-- small/xhdpi -->
 </tr> 
 <tr><th scope="row">normal</th> 
-<td>0.9%</td>  <!-- normal/ldpi -->
-<td>17.6%</td> <!-- normal/mdpi -->
-<td>71.9%</td> <!-- normal/hdpi -->
+<td>1.0%</td>  <!-- normal/ldpi -->
+<td>17.7%</td> <!-- normal/mdpi -->
+<td>70.8%</td> <!-- normal/hdpi -->
 <td></td>      <!-- normal/xhdpi -->
 </tr> 
 <tr><th scope="row">large</th> 
 <td>0.1%</td>     <!-- large/ldpi -->
-<td>3.0%</td> <!-- large/mdpi -->
+<td>3.1%</td> <!-- large/mdpi -->
 <td></td>     <!-- large/hdpi -->
 <td></td>     <!-- large/xhdpi -->
 </tr> 
 <tr><th scope="row">xlarge</th> 
 <td></td>     <!-- xlarge/ldpi -->
-<td>2.6%</td> <!-- xlarge/mdpi -->
+<td>2.9%</td> <!-- xlarge/mdpi -->
 <td></td>     <!-- xlarge/hdpi -->
 <td></td>     <!-- xlarge/xhdpi -->
 </tr> 
 </table>
 
-<p><em>Data collected during a 7-day period ending on October 3, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p>
 </div>
 
diff --git a/include/utils/BlobCache.h b/include/utils/BlobCache.h
index dc45ff0..4f342a2 100644
--- a/include/utils/BlobCache.h
+++ b/include/utils/BlobCache.h
@@ -19,19 +19,21 @@
 
 #include <stddef.h>
 
+#include <utils/Flattenable.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.
+// A BlobCache is an in-memory cache for binary key/value pairs.  A BlobCache
+// does NOT provide any thread-safety guarantees.
 //
-// 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 {
+// The cache contents can be serialized to an in-memory buffer or mmap'd file
+// and then reloaded in a subsequent execution of the program.  This
+// serialization is non-portable and the data should only be used by the device
+// that generated it.
+class BlobCache : public RefBase, public Flattenable {
 public:
 
     // Create an empty blob cache. The blob cache will cache key/value pairs
@@ -58,14 +60,13 @@
     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.
+    // get 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
@@ -77,6 +78,37 @@
     //   0 <= valueSize
     size_t get(const void* key, size_t keySize, void* value, size_t valueSize);
 
+    // getFlattenedSize returns the number of bytes needed to store the entire
+    // serialized cache.
+    virtual size_t getFlattenedSize() const;
+
+    // getFdCount returns the number of file descriptors that will result from
+    // flattening the cache.  This will always return 0 so as to allow the
+    // flattened cache to be saved to disk and then later restored.
+    virtual size_t getFdCount() const;
+
+    // flatten serializes the current contents of the cache into the memory
+    // pointed to by 'buffer'.  The serialized cache contents can later be
+    // loaded into a BlobCache object using the unflatten method.  The contents
+    // of the BlobCache object will not be modified.
+    //
+    // Preconditions:
+    //   size >= this.getFlattenedSize()
+    //   count == 0
+    virtual status_t flatten(void* buffer, size_t size, int fds[],
+            size_t count) const;
+
+    // unflatten replaces the contents of the cache with the serialized cache
+    // contents in the memory pointed to by 'buffer'.  The previous contents of
+    // the BlobCache will be evicted from the cache.  If an error occurs while
+    // unflattening the serialized cache contents then the BlobCache will be
+    // left in an empty state.
+    //
+    // Preconditions:
+    //   count == 0
+    virtual status_t unflatten(void const* buffer, size_t size, int fds[],
+            size_t count);
+
 private:
     // Copying is disallowed.
     BlobCache(const BlobCache&);
@@ -144,6 +176,46 @@
         sp<Blob> mValue;
     };
 
+    // A Header is the header for the entire BlobCache serialization format. No
+    // need to make this portable, so we simply write the struct out.
+    struct Header {
+        // mMagicNumber is the magic number that identifies the data as
+        // serialized BlobCache contents.  It must always contain 'Blb$'.
+        uint32_t mMagicNumber;
+
+        // mBlobCacheVersion is the serialization format version.
+        uint32_t mBlobCacheVersion;
+
+        // mDeviceVersion is the device-specific version of the cache.  This can
+        // be used to invalidate the cache.
+        uint32_t mDeviceVersion;
+
+        // mNumEntries is number of cache entries following the header in the
+        // data.
+        size_t mNumEntries;
+    };
+
+    // An EntryHeader is the header for a serialized cache entry.  No need to
+    // make this portable, so we simply write the struct out.  Each EntryHeader
+    // is followed imediately by the key data and then the value data.
+    //
+    // The beginning of each serialized EntryHeader is 4-byte aligned, so the
+    // number of bytes that a serialized cache entry will occupy is:
+    //
+    //   ((sizeof(EntryHeader) + keySize + valueSize) + 3) & ~3
+    //
+    struct EntryHeader {
+        // mKeySize is the size of the entry key in bytes.
+        size_t mKeySize;
+
+        // mValueSize is the size of the entry value in bytes.
+        size_t mValueSize;
+
+        // mData contains both the key and value data for the cache entry.  The
+        // key comes first followed immediately by the value.
+        uint8_t mData[];
+    };
+
     // 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.
@@ -166,17 +238,12 @@
     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.
+    // nrand48 to generate random numbers when needed.
     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;
 };
 
 }
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index c72a45b..6f84206 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -116,7 +116,7 @@
     // Choose a name using the PID and a process-unique ID.
     mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
 
-    ST_LOGV("SurfaceTexture::SurfaceTexture");
+    ST_LOGV("SurfaceTexture");
     sp<ISurfaceComposer> composer(ComposerService::getComposerService());
     mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
     mNextCrop.makeInvalid();
@@ -125,7 +125,7 @@
 }
 
 SurfaceTexture::~SurfaceTexture() {
-    ST_LOGV("SurfaceTexture::~SurfaceTexture");
+    ST_LOGV("~SurfaceTexture");
     freeAllBuffersLocked();
 }
 
@@ -169,7 +169,7 @@
 }
 
 status_t SurfaceTexture::setBufferCount(int bufferCount) {
-    ST_LOGV("SurfaceTexture::setBufferCount");
+    ST_LOGV("setBufferCount: count=%d", bufferCount);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -217,6 +217,7 @@
 
 status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h)
 {
+    ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
     if (!w || !h) {
         ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
                 w, h);
@@ -230,7 +231,7 @@
 }
 
 status_t SurfaceTexture::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
-    ST_LOGV("SurfaceTexture::requestBuffer");
+    ST_LOGV("requestBuffer: slot=%d", slot);
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
         ST_LOGE("requestBuffer: SurfaceTexture has been abandoned!");
@@ -248,7 +249,7 @@
 
 status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
         uint32_t format, uint32_t usage) {
-    ST_LOGV("SurfaceTexture::dequeueBuffer");
+    ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
 
     if ((w && !h) || (!w && h)) {
         ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
@@ -342,6 +343,8 @@
         // clients are not allowed to dequeue more than one buffer
         // if they didn't set a buffer count.
         if (!mClientBufferCount && dequeuedCount) {
+            ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
+                    "setting the buffer count");
             return -EINVAL;
         }
 
@@ -375,6 +378,8 @@
     }
 
     if (found == INVALID_BUFFER_SLOT) {
+        // This should not happen.
+        ST_LOGE("dequeueBuffer: no available buffer slots");
         return -EBUSY;
     }
 
@@ -427,10 +432,13 @@
         }
         returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
     }
+    ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", buf,
+            mSlots[buf].mGraphicBuffer->handle, returnFlags);
     return returnFlags;
 }
 
 status_t SurfaceTexture::setSynchronousMode(bool enabled) {
+    ST_LOGV("setSynchronousMode: enabled=%d", enabled);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -462,7 +470,7 @@
 
 status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp,
         uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) {
-    ST_LOGV("SurfaceTexture::queueBuffer");
+    ST_LOGV("queueBuffer: slot=%d time=%lld", buf, timestamp);
 
     sp<FrameAvailableListener> listener;
 
@@ -534,7 +542,7 @@
 }
 
 void SurfaceTexture::cancelBuffer(int buf) {
-    ST_LOGV("SurfaceTexture::cancelBuffer");
+    ST_LOGV("cancelBuffer: slot=%d", buf);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -556,7 +564,9 @@
 }
 
 status_t SurfaceTexture::setCrop(const Rect& crop) {
-    ST_LOGV("SurfaceTexture::setCrop");
+    ST_LOGV("setCrop: crop=[%d,%d,%d,%d]", crop.left, crop.top, crop.right,
+            crop.bottom);
+
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
         ST_LOGE("setCrop: SurfaceTexture has been abandoned!");
@@ -567,7 +577,7 @@
 }
 
 status_t SurfaceTexture::setTransform(uint32_t transform) {
-    ST_LOGV("SurfaceTexture::setTransform");
+    ST_LOGV("setTransform: xform=%#x", transform);
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
         ST_LOGE("setTransform: SurfaceTexture has been abandoned!");
@@ -579,7 +589,7 @@
 
 status_t SurfaceTexture::connect(int api,
         uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) {
-    ST_LOGV("SurfaceTexture::connect(this=%p, %d)", this, api);
+    ST_LOGV("connect: api=%d", api);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -612,7 +622,7 @@
 }
 
 status_t SurfaceTexture::disconnect(int api) {
-    ST_LOGV("SurfaceTexture::disconnect(this=%p, %d)", this, api);
+    ST_LOGV("disconnect: api=%d", api);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -640,6 +650,7 @@
             }
             break;
         default:
+            ST_LOGE("disconnect: unknown API %d", api);
             err = -EINVAL;
             break;
     }
@@ -647,13 +658,14 @@
 }
 
 status_t SurfaceTexture::setScalingMode(int mode) {
-    ST_LOGV("SurfaceTexture::setScalingMode(%d)", mode);
+    ST_LOGV("setScalingMode: mode=%d", mode);
 
     switch (mode) {
         case NATIVE_WINDOW_SCALING_MODE_FREEZE:
         case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
             break;
         default:
+            ST_LOGE("unknown scaling mode: %d", mode);
             return BAD_VALUE;
     }
 
@@ -663,7 +675,7 @@
 }
 
 status_t SurfaceTexture::updateTexImage() {
-    ST_LOGV("SurfaceTexture::updateTexImage");
+    ST_LOGV("updateTexImage");
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -713,6 +725,10 @@
             return -EINVAL;
         }
 
+        ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture,
+                mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, buf,
+                mSlots[buf].mGraphicBuffer->handle);
+
         if (mCurrentTexture != INVALID_BUFFER_SLOT) {
             // The current buffer becomes FREE if it was still in the queued
             // state. If it has already been given to the client
@@ -771,7 +787,7 @@
 }
 
 void SurfaceTexture::computeCurrentTransformMatrix() {
-    ST_LOGV("SurfaceTexture::computeCurrentTransformMatrix");
+    ST_LOGV("computeCurrentTransformMatrix");
 
     float xform[16];
     for (int i = 0; i < 16; i++) {
@@ -862,14 +878,14 @@
 }
 
 nsecs_t SurfaceTexture::getTimestamp() {
-    ST_LOGV("SurfaceTexture::getTimestamp");
+    ST_LOGV("getTimestamp");
     Mutex::Autolock lock(mMutex);
     return mCurrentTimestamp;
 }
 
 void SurfaceTexture::setFrameAvailableListener(
         const sp<FrameAvailableListener>& listener) {
-    ST_LOGV("SurfaceTexture::setFrameAvailableListener");
+    ST_LOGV("setFrameAvailableListener");
     Mutex::Autolock lock(mMutex);
     mFrameAvailableListener = listener;
 }
diff --git a/libs/utils/BlobCache.cpp b/libs/utils/BlobCache.cpp
index 590576a..d38aae9 100644
--- a/libs/utils/BlobCache.cpp
+++ b/libs/utils/BlobCache.cpp
@@ -21,10 +21,20 @@
 #include <string.h>
 
 #include <utils/BlobCache.h>
+#include <utils/Errors.h>
 #include <utils/Log.h>
 
 namespace android {
 
+// BlobCache::Header::mMagicNumber value
+static const uint32_t blobCacheMagic = '_Bb$';
+
+// BlobCache::Header::mBlobCacheVersion value
+static const uint32_t blobCacheVersion = 1;
+
+// BlobCache::Header::mDeviceVersion value
+static const uint32_t blobCacheDeviceVersion = 1;
+
 BlobCache::BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize):
         mMaxKeySize(maxKeySize),
         mMaxValueSize(maxValueSize),
@@ -67,12 +77,10 @@
         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.
@@ -129,7 +137,6 @@
                 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);
@@ -152,6 +159,133 @@
     return valueBlobSize;
 }
 
+static inline size_t align4(size_t size) {
+    return (size + 3) & ~3;
+}
+
+size_t BlobCache::getFlattenedSize() const {
+    size_t size = sizeof(Header);
+    for (size_t i = 0; i < mCacheEntries.size(); i++) {
+        const CacheEntry& e(mCacheEntries[i]);
+        sp<Blob> keyBlob = e.getKey();
+        sp<Blob> valueBlob = e.getValue();
+        size = align4(size);
+        size += sizeof(EntryHeader) + keyBlob->getSize() +
+                valueBlob->getSize();
+    }
+    return size;
+}
+
+size_t BlobCache::getFdCount() const {
+    return 0;
+}
+
+status_t BlobCache::flatten(void* buffer, size_t size, int fds[], size_t count)
+        const {
+    if (count != 0) {
+        LOGE("flatten: nonzero fd count: %d", count);
+        return BAD_VALUE;
+    }
+
+    // Write the cache header
+    if (size < sizeof(Header)) {
+        LOGE("flatten: not enough room for cache header");
+        return BAD_VALUE;
+    }
+    Header* header = reinterpret_cast<Header*>(buffer);
+    header->mMagicNumber = blobCacheMagic;
+    header->mBlobCacheVersion = blobCacheVersion;
+    header->mDeviceVersion = blobCacheDeviceVersion;
+    header->mNumEntries = mCacheEntries.size();
+
+    // Write cache entries
+    uint8_t* byteBuffer = reinterpret_cast<uint8_t*>(buffer);
+    off_t byteOffset = align4(sizeof(Header));
+    for (size_t i = 0; i < mCacheEntries.size(); i++) {
+        const CacheEntry& e(mCacheEntries[i]);
+        sp<Blob> keyBlob = e.getKey();
+        sp<Blob> valueBlob = e.getValue();
+        size_t keySize = keyBlob->getSize();
+        size_t valueSize = valueBlob->getSize();
+
+        size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
+        if (byteOffset + entrySize > size) {
+            LOGE("flatten: not enough room for cache entries");
+            return BAD_VALUE;
+        }
+
+        EntryHeader* eheader = reinterpret_cast<EntryHeader*>(
+            &byteBuffer[byteOffset]);
+        eheader->mKeySize = keySize;
+        eheader->mValueSize = valueSize;
+
+        memcpy(eheader->mData, keyBlob->getData(), keySize);
+        memcpy(eheader->mData + keySize, valueBlob->getData(), valueSize);
+
+        byteOffset += align4(entrySize);
+    }
+
+    return OK;
+}
+
+status_t BlobCache::unflatten(void const* buffer, size_t size, int fds[],
+        size_t count) {
+    // All errors should result in the BlobCache being in an empty state.
+    mCacheEntries.clear();
+
+    if (count != 0) {
+        LOGE("unflatten: nonzero fd count: %d", count);
+        return BAD_VALUE;
+    }
+
+    // Read the cache header
+    if (size < sizeof(Header)) {
+        LOGE("unflatten: not enough room for cache header");
+        return BAD_VALUE;
+    }
+    const Header* header = reinterpret_cast<const Header*>(buffer);
+    if (header->mMagicNumber != blobCacheMagic) {
+        LOGE("unflatten: bad magic number: %d", header->mMagicNumber);
+        return BAD_VALUE;
+    }
+    if (header->mBlobCacheVersion != blobCacheVersion ||
+            header->mDeviceVersion != blobCacheDeviceVersion) {
+        // We treat version mismatches as an empty cache.
+        return OK;
+    }
+
+    // Read cache entries
+    const uint8_t* byteBuffer = reinterpret_cast<const uint8_t*>(buffer);
+    off_t byteOffset = align4(sizeof(Header));
+    size_t numEntries = header->mNumEntries;
+    for (size_t i = 0; i < numEntries; i++) {
+        if (byteOffset + sizeof(EntryHeader) > size) {
+            mCacheEntries.clear();
+            LOGE("unflatten: not enough room for cache entry headers");
+            return BAD_VALUE;
+        }
+
+        const EntryHeader* eheader = reinterpret_cast<const EntryHeader*>(
+                &byteBuffer[byteOffset]);
+        size_t keySize = eheader->mKeySize;
+        size_t valueSize = eheader->mValueSize;
+        size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
+
+        if (byteOffset + entrySize > size) {
+            mCacheEntries.clear();
+            LOGE("unflatten: not enough room for cache entry headers");
+            return BAD_VALUE;
+        }
+
+        const uint8_t* data = eheader->mData;
+        set(data, keySize, data + keySize, valueSize);
+
+        byteOffset += align4(entrySize);
+    }
+
+    return OK;
+}
+
 long int BlobCache::blob_random() {
 #ifdef _WIN32
     return rand();
@@ -179,7 +313,7 @@
         mData(copyData ? malloc(size) : data),
         mSize(size),
         mOwnsData(copyData) {
-    if (copyData) {
+    if (data != NULL && copyData) {
         memcpy(const_cast<void*>(mData), data, size);
     }
 }
diff --git a/libs/utils/tests/BlobCache_test.cpp b/libs/utils/tests/BlobCache_test.cpp
index 653ea5e..b64cc39 100644
--- a/libs/utils/tests/BlobCache_test.cpp
+++ b/libs/utils/tests/BlobCache_test.cpp
@@ -14,9 +14,13 @@
  ** limitations under the License.
  */
 
+#include <fcntl.h>
+#include <stdio.h>
+
 #include <gtest/gtest.h>
 
 #include <utils/BlobCache.h>
+#include <utils/Errors.h>
 
 namespace android {
 
@@ -254,4 +258,164 @@
     ASSERT_EQ(maxEntries/2 + 1, numCached);
 }
 
+class BlobCacheFlattenTest : public BlobCacheTest {
+protected:
+    virtual void SetUp() {
+        BlobCacheTest::SetUp();
+        mBC2 = new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE);
+    }
+
+    virtual void TearDown() {
+        mBC2.clear();
+        BlobCacheTest::TearDown();
+    }
+
+    void roundTrip() {
+        size_t size = mBC->getFlattenedSize();
+        uint8_t* flat = new uint8_t[size];
+        ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+        ASSERT_EQ(OK, mBC2->unflatten(flat, size, NULL, 0));
+        delete[] flat;
+    }
+
+    sp<BlobCache> mBC2;
+};
+
+TEST_F(BlobCacheFlattenTest, FlattenOneValue) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+    roundTrip();
+    ASSERT_EQ(size_t(4), mBC2->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(BlobCacheFlattenTest, FlattenFullCache) {
+    // 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, &k, 1);
+    }
+
+    roundTrip();
+
+    // Verify the deserialized cache
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        uint8_t v = 0xee;
+        ASSERT_EQ(size_t(1), mBC2->get(&k, 1, &v, 1));
+        ASSERT_EQ(k, v);
+    }
+}
+
+TEST_F(BlobCacheFlattenTest, FlattenDoesntChangeCache) {
+    // 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, &k, 1);
+    }
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // Verify the cache that we just serialized
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        uint8_t v = 0xee;
+        ASSERT_EQ(size_t(1), mBC->get(&k, 1, &v, 1));
+        ASSERT_EQ(k, v);
+    }
+}
+
+TEST_F(BlobCacheFlattenTest, FlattenCatchesBufferTooSmall) {
+    // 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, &k, 1);
+    }
+
+    size_t size = mBC->getFlattenedSize() - 1;
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(BAD_VALUE, mBC->flatten(flat, size, NULL, 0));
+    delete[] flat;
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadMagic) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    flat[1] = ~flat[1];
+
+    // Bad magic should cause an error.
+    ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // The error should cause the unflatten to result in an empty cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheVersion) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    flat[5] = ~flat[5];
+
+    // Version mismatches shouldn't cause errors, but should not use the
+    // serialized entries
+    ASSERT_EQ(OK, mBC2->unflatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // The version mismatch should cause the unflatten to result in an empty
+    // cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheDeviceVersion) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    flat[10] = ~flat[10];
+
+    // Version mismatches shouldn't cause errors, but should not use the
+    // serialized entries
+    ASSERT_EQ(OK, mBC2->unflatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // The version mismatch should cause the unflatten to result in an empty
+    // cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBufferTooSmall) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+
+    // A buffer truncation shouldt cause an error
+    ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size-1, NULL, 0));
+    delete[] flat;
+
+    // The error should cause the unflatten to result in an empty cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
 } // namespace android
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index b88296f..09152f5 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -56,6 +56,53 @@
     return OK;
 }
 
+// stolen from dalvik/vm/checkJni.cpp
+static bool isValidUtf8(const char* bytes) {
+    while (*bytes != '\0') {
+        unsigned char utf8 = *(bytes++);
+        // Switch on the high four bits.
+        switch (utf8 >> 4) {
+        case 0x00:
+        case 0x01:
+        case 0x02:
+        case 0x03:
+        case 0x04:
+        case 0x05:
+        case 0x06:
+        case 0x07:
+            // Bit pattern 0xxx. No need for any extra bytes.
+            break;
+        case 0x08:
+        case 0x09:
+        case 0x0a:
+        case 0x0b:
+        case 0x0f:
+            /*
+             * Bit pattern 10xx or 1111, which are illegal start bytes.
+             * Note: 1111 is valid for normal UTF-8, but not the
+             * modified UTF-8 used here.
+             */
+            return false;
+        case 0x0e:
+            // Bit pattern 1110, so there are two additional bytes.
+            utf8 = *(bytes++);
+            if ((utf8 & 0xc0) != 0x80) {
+                return false;
+            }
+            // Fall through to take care of the final byte.
+        case 0x0c:
+        case 0x0d:
+            // Bit pattern 110x, so there is one additional byte.
+            utf8 = *(bytes++);
+            if ((utf8 & 0xc0) != 0x80) {
+                return false;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
 class MyMediaScannerClient : public MediaScannerClient
 {
 public:
@@ -123,7 +170,22 @@
             mEnv->ExceptionClear();
             return NO_MEMORY;
         }
-        if ((valueStr = mEnv->NewStringUTF(value)) == NULL) {
+        char *cleaned = NULL;
+        if (!isValidUtf8(value)) {
+            cleaned = strdup(value);
+            char *chp = cleaned;
+            char ch;
+            while ((ch = *chp)) {
+                if (ch & 0x80) {
+                    *chp = '?';
+                }
+                chp++;
+            }
+            value = cleaned;
+        }
+        valueStr = mEnv->NewStringUTF(value);
+        free(cleaned);
+        if (valueStr == NULL) {
             mEnv->DeleteLocalRef(nameStr);
             mEnv->ExceptionClear();
             return NO_MEMORY;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 7cdb76c..70208f8 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -282,7 +282,7 @@
 
                 if (err == -EWOULDBLOCK) {
                     if (mSource->feedMoreTSData() == OK) {
-                        msg->post();
+                        msg->post(10000ll);
                     }
                 }
             } else if (what == ACodec::kWhatEOS) {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
index e3aa8cf..1fa5c0d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
@@ -44,13 +44,7 @@
  */
 public class MediaBassBoostTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
     private String TAG = "MediaBassBoostTest";
-    private final static int MIN_ENERGY_RATIO_2 = 3;
     private final static short TEST_STRENGTH = 500;
-    private final static int TEST_VOLUME = 4;
-    // Implementor UUID for volume controller effect defined in
-    // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
-    private final static UUID VOLUME_EFFECT_UUID =
-        UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b");
 
     private BassBoost mBassBoost = null;
     private int mSession = -1;
@@ -184,85 +178,6 @@
     }
 
     //-----------------------------------------------------------------
-    // 2 - Effect action
-    //----------------------------------
-
-    //Test case 2.0: test actual bass boost influence on sound
-    @LargeTest
-    public void test2_0SoundModification() throws Exception {
-        boolean result = false;
-        String msg = "test2_0SoundModification()";
-        EnergyProbe probe = null;
-        AudioEffect vc = null;
-        MediaPlayer mp = null;
-        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
-        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                           TEST_VOLUME,
-                           0);
-
-        try {
-            probe = new EnergyProbe(0);
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                                AudioEffect.EFFECT_TYPE_NULL,
-                                VOLUME_EFFECT_UUID,
-                                0,
-                                0);
-            vc.setEnabled(true);
-
-            mp = new MediaPlayer();
-            mp.setDataSource(MediaNames.SINE_200_1000);
-            mp.setLooping(true);
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            getBassBoost(mp.getAudioSessionId());
-            mp.prepare();
-            mp.start();
-            Thread.sleep(200);
-            // measure reference energy around 1kHz
-            int refEnergy200 = probe.capture(200);
-            int refEnergy1000 = probe.capture(1000);
-            mBassBoost.setStrength((short)1000);
-            mBassBoost.setEnabled(true);
-            Thread.sleep(4000);
-            // measure energy around 1kHz with band level at min
-            int energy200 = probe.capture(200);
-            int energy1000 = probe.capture(1000);
-            // verify that the energy ration between low and high frequencies is at least
-            // MIN_ENERGY_RATIO_2 times higher with bassboost on.
-            assertTrue(msg + ": bass boost has no effect",
-                    ((float)energy200/(float)energy1000) >
-                    (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000)));
-            result = true;
-        } catch (IllegalArgumentException e) {
-            msg = msg.concat(": Bad parameter value");
-            loge(msg, "Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            msg = msg.concat(": get parameter() rejected");
-            loge(msg, "get parameter() rejected");
-        } catch (IllegalStateException e) {
-            msg = msg.concat("get parameter() called in wrong state");
-            loge(msg, "get parameter() called in wrong state");
-        } catch (InterruptedException e) {
-            loge(msg, "sleep() interrupted");
-        }
-        finally {
-            releaseBassBoost();
-            if (mp != null) {
-                mp.release();
-            }
-            if (vc != null) {
-                vc.release();
-            }
-            if (probe != null) {
-                probe.release();
-            }
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
-        }
-        assertTrue(msg, result);
-    }
-    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
index ee91bbb..da9089d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
@@ -49,11 +49,6 @@
     private final static int MAX_BAND_LEVEL = 1500;
     private final static int TEST_FREQUENCY_MILLIHERTZ = 1000000;
     private final static int MIN_NUMBER_OF_PRESETS = 4;
-    private final static int TEST_VOLUME = 4;
-    // Implementor UUID for volume controller effect defined in
-    // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
-    private final static UUID VOLUME_EFFECT_UUID =
-        UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b");
 
     private Equalizer mEqualizer = null;
     private int mSession = -1;
@@ -252,80 +247,6 @@
     }
 
     //-----------------------------------------------------------------
-    // 2 - Effect action
-    //----------------------------------
-
-    //Test case 2.0: test that the equalizer actually alters the sound
-    @LargeTest
-    public void test2_0SoundModification() throws Exception {
-        boolean result = false;
-        String msg = "test2_0SoundModification()";
-        EnergyProbe probe = null;
-        AudioEffect vc = null;
-        MediaPlayer mp = null;
-        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
-        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                           TEST_VOLUME,
-                           0);
-        try {
-            probe = new EnergyProbe(0);
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                                AudioEffect.EFFECT_TYPE_NULL,
-                                VOLUME_EFFECT_UUID,
-                                0,
-                                0);
-            vc.setEnabled(true);
-
-            mp = new MediaPlayer();
-            mp.setDataSource(MediaNames.SINE_200_1000);
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            getEqualizer(mp.getAudioSessionId());
-            mp.prepare();
-            mp.start();
-            Thread.sleep(500);
-            // measure reference energy around 1kHz
-            int refEnergy = probe.capture(1000);
-            short band = mEqualizer.getBand(1000000);
-            short[] levelRange = mEqualizer.getBandLevelRange();
-            mEqualizer.setBandLevel(band, levelRange[0]);
-            mEqualizer.setEnabled(true);
-            Thread.sleep(500);
-            // measure energy around 1kHz with band level at min
-            int energy = probe.capture(1000);
-            assertTrue(msg + ": equalizer has no effect at 1kHz", energy < refEnergy/4);
-            result = true;
-        } catch (IllegalArgumentException e) {
-            msg = msg.concat(": Bad parameter value");
-            loge(msg, "Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            msg = msg.concat(": get parameter() rejected");
-            loge(msg, "get parameter() rejected");
-        } catch (IllegalStateException e) {
-            msg = msg.concat("get parameter() called in wrong state");
-            loge(msg, "get parameter() called in wrong state");
-        } catch (InterruptedException e) {
-            loge(msg, "sleep() interrupted");
-        }
-        finally {
-            releaseEqualizer();
-            if (mp != null) {
-                mp.release();
-            }
-            if (vc != null) {
-                vc.release();
-            }
-            if (probe != null) {
-                probe.release();
-            }
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
-        }
-        assertTrue(msg, result);
-    }
-
-    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
index b74e525..122545f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
@@ -44,13 +44,7 @@
  */
 public class MediaVirtualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
     private String TAG = "MediaVirtualizerTest";
-    private final static int MIN_ENERGY_RATIO_2 = 2;
     private final static short TEST_STRENGTH = 500;
-    private final static int TEST_VOLUME = 4;
-    // Implementor UUID for volume controller effect defined in
-    // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
-    private final static UUID VOLUME_EFFECT_UUID =
-        UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b");
 
     private Virtualizer mVirtualizer = null;
     private int mSession = -1;
@@ -185,89 +179,6 @@
     }
 
     //-----------------------------------------------------------------
-    // 2 - Effect action
-    //----------------------------------
-
-    //Test case 2.0: test actual virtualizer influence on sound
-    @LargeTest
-    public void test2_0SoundModification() throws Exception {
-        boolean result = false;
-        String msg = "test2_0SoundModification()";
-        EnergyProbe probe = null;
-        AudioEffect vc = null;
-        MediaPlayer mp = null;
-        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
-        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                           TEST_VOLUME,
-                           0);
-
-        try {
-            probe = new EnergyProbe(0);
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                                AudioEffect.EFFECT_TYPE_NULL,
-                                VOLUME_EFFECT_UUID,
-                                0,
-                                0);
-            vc.setEnabled(true);
-
-            mp = new MediaPlayer();
-            mp.setDataSource(MediaNames.SINE_200_1000);
-            mp.setLooping(true);
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            getVirtualizer(mp.getAudioSessionId());
-            mp.prepare();
-            mp.start();
-            Thread.sleep(200);
-            // measure reference energy around 1kHz
-            int refEnergy200 = probe.capture(200);
-            int refEnergy1000 = probe.capture(1000);
-            mVirtualizer.setStrength((short)1000);
-            mVirtualizer.setEnabled(true);
-            Thread.sleep(4000);
-            // measure energy around 1kHz with band level at min
-            int energy200 = probe.capture(200);
-            int energy1000 = probe.capture(1000);
-            // verify that the energy ration between low and high frequencies is at least
-            // MIN_ENERGY_RATIO_2 times higher with virtualizer on.
-            // NOTE: this is what is observed with current virtualizer implementation and the test
-            // audio file but is not the primary effect of the virtualizer. A better way would
-            // be to have a stereo PCM capture and check that a strongly paned input is centered
-            // when output. However, we cannot capture stereo with the visualizer.
-            assertTrue(msg + ": virtualizer has no effect",
-                    ((float)energy200/(float)energy1000) >
-                    (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000)));
-            result = true;
-        } catch (IllegalArgumentException e) {
-            msg = msg.concat(": Bad parameter value");
-            loge(msg, "Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            msg = msg.concat(": get parameter() rejected");
-            loge(msg, "get parameter() rejected");
-        } catch (IllegalStateException e) {
-            msg = msg.concat("get parameter() called in wrong state");
-            loge(msg, "get parameter() called in wrong state");
-        } catch (InterruptedException e) {
-            loge(msg, "sleep() interrupted");
-        }
-        finally {
-            releaseVirtualizer();
-            if (mp != null) {
-                mp.release();
-            }
-            if (vc != null) {
-                vc.release();
-            }
-            if (probe != null) {
-                probe.release();
-            }
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
-        }
-        assertTrue(msg, result);
-    }
-    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 7be2349..9ed7c98 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -390,8 +390,8 @@
     boolean mLockScreenTimerActive;
 
     // visual screen saver support
-    int mScreenSaverTimeout;
-    boolean mScreenSaverEnabled = false;
+    int mScreenSaverTimeout = 0;
+    boolean mScreenSaverEnabled = true;
 
     // Behavior of ENDCALL Button.  (See Settings.System.END_BUTTON_BEHAVIOR.)
     int mEndcallBehavior;
@@ -455,7 +455,7 @@
                     Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
             resolver.registerContentObserver(Settings.System.getUriFor(
                     "fancy_rotation_anim"), false, this);
-            resolver.registerContentObserver(Settings.System.getUriFor(
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.DREAM_TIMEOUT), false, this);
             updateSettings();
         }
@@ -914,9 +914,8 @@
                 updateRotation = true;
             }
 
-            mScreenSaverTimeout = Settings.System.getInt(resolver,
+            mScreenSaverTimeout = Settings.Secure.getInt(resolver,
                     Settings.Secure.DREAM_TIMEOUT, 0);
-            mScreenSaverEnabled = true;
             updateScreenSaverTimeoutLocked();
         }
         if (updateRotation) {
@@ -3440,70 +3439,59 @@
             }
         }
 
-        // Turn this off for now, screen savers not currently enabled.
-        if (false) {
-            synchronized (mLock) {
-                updateScreenSaverTimeoutLocked();
-            }
+        synchronized (mLock) {
+            // Only posts messages; holds no additional locks.
+            updateScreenSaverTimeoutLocked();
         }
     }
 
-    Runnable mScreenSaverActivator = null;
-    /*new Runnable() {
+    Runnable mScreenSaverActivator = new Runnable() {
         public void run() {
-            synchronized (this) {
-                if (!(mScreenSaverEnabled && mScreenOn)) {
-                    Log.w(TAG, "mScreenSaverActivator ran, but the screensaver should not be showing. Who's driving this thing?");
-                    return;
-                }
+            if (!(mScreenSaverEnabled && mScreenOnEarly)) {
+                Log.w(TAG, "mScreenSaverActivator ran, but the screensaver should not be showing. Who's driving this thing?");
+                return;
+            }
 
-                if (localLOGV) Log.v(TAG, "mScreenSaverActivator entering dreamland");
-                try {
-                    String component = Settings.System.getString(
-                            mContext.getContentResolver(), Settings.Secure.DREAM_COMPONENT);
-                    if (component != null) {
-                        ComponentName cn = ComponentName.unflattenFromString(component);
-                        Intent intent = new Intent(Intent.ACTION_MAIN)
-                            .setComponent(cn)
-                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                                | Intent.FLAG_ACTIVITY_NO_USER_ACTION
-                                | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-                        mContext.startActivity(intent);
-                    } else {
-                        Log.e(TAG, "Couldn't start screen saver: none selected");
-                    }
-                } catch (android.content.ActivityNotFoundException exc) {
-                    // no screensaver? give up
-                    Log.e(TAG, "Couldn't start screen saver: none installed");
+            if (localLOGV) Log.v(TAG, "mScreenSaverActivator entering dreamland");
+            try {
+                String component = Settings.Secure.getString(
+                        mContext.getContentResolver(), Settings.Secure.DREAM_COMPONENT);
+                if (component != null) {
+                    ComponentName cn = ComponentName.unflattenFromString(component);
+                    Intent intent = new Intent(Intent.ACTION_MAIN)
+                        .setComponent(cn)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                            | Intent.FLAG_ACTIVITY_NO_USER_ACTION
+                            );
+                    mContext.startActivity(intent);
+                } else {
+                    Log.e(TAG, "Couldn't start screen saver: none selected");
                 }
+            } catch (android.content.ActivityNotFoundException exc) {
+                // no screensaver? give up
+                Log.e(TAG, "Couldn't start screen saver: none installed");
             }
         }
     };
-    */
 
     // Must call while holding mLock
     private void updateScreenSaverTimeoutLocked() {
         if (mScreenSaverActivator == null) return;
 
-        // GAH...  acquiring a lock within a lock?  Please let's fix this.
-        // (Also note this is called from userActivity, with the power manager
-        // lock  held.  Not good.)
-        synchronized (mScreenSaverActivator) {
-            mHandler.removeCallbacks(mScreenSaverActivator);
-            if (mScreenSaverEnabled && mScreenOnEarly && mScreenSaverTimeout > 0) {
-                if (localLOGV)
-                    Log.v(TAG, "scheduling screensaver for " + mScreenSaverTimeout + "ms from now");
-                mHandler.postDelayed(mScreenSaverActivator, mScreenSaverTimeout);
-            } else {
-                if (localLOGV) {
-                    if (mScreenSaverTimeout == 0)
-                        Log.v(TAG, "screen saver disabled by user");
-                    else if (!mScreenOnEarly)
-                        Log.v(TAG, "screen saver disabled while screen off");
-                    else
-                        Log.v(TAG, "screen saver disabled by wakelock");
-                }
+        mHandler.removeCallbacks(mScreenSaverActivator);
+        if (mScreenSaverEnabled && mScreenOnEarly && mScreenSaverTimeout > 0) {
+            if (localLOGV)
+                Log.v(TAG, "scheduling screensaver for " + mScreenSaverTimeout + "ms from now");
+            mHandler.postDelayed(mScreenSaverActivator, mScreenSaverTimeout);
+        } else {
+            if (localLOGV) {
+                if (mScreenSaverTimeout == 0)
+                    Log.v(TAG, "screen saver disabled by user");
+                else if (!mScreenOnEarly)
+                    Log.v(TAG, "screen saver disabled while screen off");
+                else
+                    Log.v(TAG, "screen saver disabled by wakelock");
             }
         }
     }
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index eb75ebc..2af5103 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -751,10 +751,13 @@
                 return;
             }
             ArrayList<AppWidgetId> instances = p.instances;
+            final int callingUid = getCallingUid();
             final int N = instances.size();
             for (int i=0; i<N; i++) {
                 AppWidgetId id = instances.get(i);
-                updateAppWidgetInstanceLocked(id, views);
+                if (canAccessAppWidgetId(id, callingUid)) {
+                    updateAppWidgetInstanceLocked(id, views);
+                }
             }
         }
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index e9bc41d..74fdcde 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -300,7 +300,7 @@
     /**
      * Historical data of past broadcasts, for debugging.
      */
-    static final int MAX_BROADCAST_HISTORY = 100;
+    static final int MAX_BROADCAST_HISTORY = 25;
     final BroadcastRecord[] mBroadcastHistory
             = new BroadcastRecord[MAX_BROADCAST_HISTORY];
 
@@ -13941,7 +13941,7 @@
                         if (curLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
                             // For these apps we will also finish their activities
                             // to help them free memory.
-                            mMainStack.destroyActivitiesLocked(app, false);
+                            mMainStack.destroyActivitiesLocked(app, false, "trim");
                         }
                     }
                     app.trimMemoryLevel = curLevel;
@@ -14005,7 +14005,7 @@
         }
 
         if (mAlwaysFinishActivities) {
-            mMainStack.destroyActivitiesLocked(null, false);
+            mMainStack.destroyActivitiesLocked(null, false, "always-finish");
         }
     }
 
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a47502e..8435eaa 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -947,7 +947,7 @@
         r.state = ActivityState.STOPPED;
         if (!r.finishing) {
             if (r.configDestroy) {
-                destroyActivityLocked(r, true, false);
+                destroyActivityLocked(r, true, false, "stop-config");
                 resumeTopActivityLocked(null);
             }
         }
@@ -976,7 +976,7 @@
                     // instance right now, we need to first completely stop
                     // the current instance before starting the new one.
                     if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev);
-                    destroyActivityLocked(prev, true, false);
+                    destroyActivityLocked(prev, true, false, "pause-config");
                 } else {
                     mStoppingActivities.add(prev);
                     if (mStoppingActivities.size() > 3) {
@@ -1364,7 +1364,8 @@
                 }
             }
 
-            if (!prev.finishing && prev.app != null && prev.app != next.app) {
+            if (!prev.finishing && prev.app != null && prev.app != next.app
+                    && prev.app != mService.mHomeProcess) {
                 // We are switching to a new activity that is in a different
                 // process than the previous one.  Note the previous process,
                 // so we can try to keep it around.
@@ -3113,7 +3114,7 @@
                 if (DEBUG_STATES) Slog.v(TAG, "Stop failed; moving to STOPPED: " + r);
                 r.state = ActivityState.STOPPED;
                 if (r.configDestroy) {
-                    destroyActivityLocked(r, true, false);
+                    destroyActivityLocked(r, true, false, "stop-except");
                 }
             }
         }
@@ -3288,7 +3289,7 @@
         for (i=0; i<NF; i++) {
             ActivityRecord r = (ActivityRecord)finishes.get(i);
             synchronized (mService) {
-                destroyActivityLocked(r, true, false);
+                destroyActivityLocked(r, true, false, "finish-idle");
             }
         }
 
@@ -3487,7 +3488,7 @@
                 || prevState == ActivityState.INITIALIZING) {
             // If this activity is already stopped, we can just finish
             // it right now.
-            return destroyActivityLocked(r, true, true) ? null : r;
+            return destroyActivityLocked(r, true, true, "finish-imm") ? null : r;
         } else {
             // Need to go through the full pause cycle to get this
             // activity into the stopped state and then finish it.
@@ -3593,7 +3594,7 @@
         }
     }
     
-    final void destroyActivitiesLocked(ProcessRecord owner, boolean oomAdj) {
+    final void destroyActivitiesLocked(ProcessRecord owner, boolean oomAdj, String reason) {
         for (int i=mHistory.size()-1; i>=0; i--) {
             ActivityRecord r = mHistory.get(i);
             if (owner != null && r.app != owner) {
@@ -3604,7 +3605,7 @@
             if (r.app != null && r.haveState && !r.visible && r.stopped && !r.finishing
                     && r.state != ActivityState.DESTROYING
                     && r.state != ActivityState.DESTROYED) {
-                destroyActivityLocked(r, true, oomAdj);
+                destroyActivityLocked(r, true, oomAdj, "trim");
             }
         }
     }
@@ -3616,13 +3617,13 @@
      * but then create a new client-side object for this same HistoryRecord.
      */
     final boolean destroyActivityLocked(ActivityRecord r,
-            boolean removeFromApp, boolean oomAdj) {
+            boolean removeFromApp, boolean oomAdj, String reason) {
         if (DEBUG_SWITCH) Slog.v(
             TAG, "Removing activity: token=" + r
               + ", app=" + (r.app != null ? r.app.processName : "(null)"));
         EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY,
                 System.identityHashCode(r),
-                r.task.taskId, r.shortComponentName);
+                r.task.taskId, r.shortComponentName, reason);
 
         boolean removedFromHistory = false;
         
@@ -4109,7 +4110,7 @@
             if (r.app == null || r.app.thread == null) {
                 if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
                         "Switch is destroying non-running " + r);
-                destroyActivityLocked(r, true, false);
+                destroyActivityLocked(r, true, false, "config");
             } else if (r.state == ActivityState.PAUSING) {
                 // A little annoying: we are waiting for this activity to
                 // finish pausing.  Let's not do anything now, but just
diff --git a/services/java/com/android/server/am/EventLogTags.logtags b/services/java/com/android/server/am/EventLogTags.logtags
index aadd37d..a579f44 100644
--- a/services/java/com/android/server/am/EventLogTags.logtags
+++ b/services/java/com/android/server/am/EventLogTags.logtags
@@ -48,7 +48,7 @@
 # Reporting to applications that memory is low
 30017 am_low_memory (Num Processes|1|1)
 # An activity is being destroyed:
-30018 am_destroy_activity (Token|1|5),(Task ID|1|5),(Component Name|3)
+30018 am_destroy_activity (Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
 # An activity has been relaunched, resumed, and is now in the foreground:
 30019 am_relaunch_resume_activity (Token|1|5),(Task ID|1|5),(Component Name|3)
 # An activity has been relaunched:
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index bdad82a..289ea1f 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -19,7 +19,6 @@
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 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_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.READ_PHONE_STATE;
@@ -93,6 +92,7 @@
 import android.os.INetworkManagementService;
 import android.os.IPowerManager;
 import android.os.Message;
+import android.os.MessageQueue.IdleHandler;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -1583,6 +1583,11 @@
         return intent;
     }
 
+    // @VisibleForTesting
+    public void addIdleHandler(IdleHandler handler) {
+        mHandler.getLooper().getQueue().addIdleHandler(handler);
+    }
+
     private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
         final int size = source.size();
         for (int i = 0; i < size; i++) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 789681e..494c655 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -32,7 +32,6 @@
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
-import static android.net.NetworkStatsHistory.randomLong;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.UID_REMOVED;
@@ -49,7 +48,6 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
 import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
@@ -73,9 +71,11 @@
 import android.net.NetworkInfo;
 import android.net.NetworkState;
 import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicException;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.os.Binder;
+import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -150,6 +150,12 @@
     /** Sample recent usage after each poll event. */
     private static final boolean ENABLE_SAMPLE_AFTER_POLL = true;
 
+    private static final String TAG_NETSTATS_ERROR = "netstats_error";
+
+    private static final String DEV = "dev";
+    private static final String XT = "xt";
+    private static final String UID = "uid";
+
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
     private final IAlarmManager mAlarmManager;
@@ -160,6 +166,7 @@
     private final PowerManager.WakeLock mWakeLock;
 
     private IConnectivityManager mConnManager;
+    private DropBoxManager mDropBox;
 
     // @VisibleForTesting
     public static final String ACTION_NETWORK_STATS_POLL =
@@ -306,6 +313,8 @@
 
         // bootstrap initial stats to prevent double-counting later
         bootstrapStats();
+
+        mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
     }
 
     private void shutdownLocked() {
@@ -621,7 +630,6 @@
             // broadcast.
             final int uid = intent.getIntExtra(EXTRA_UID, 0);
             synchronized (mStatsLock) {
-                // TODO: perform one last stats poll for UID
                 mWakeLock.acquire();
                 try {
                     removeUidLocked(uid);
@@ -829,9 +837,9 @@
 
         // persist when enough network data has occurred
         final long persistNetworkDevDelta = computeStatsDelta(
-                mLastPersistNetworkDevSnapshot, networkDevSnapshot, true).getTotalBytes();
+                mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, DEV).getTotalBytes();
         final long persistNetworkXtDelta = computeStatsDelta(
-                mLastPersistNetworkXtSnapshot, networkXtSnapshot, true).getTotalBytes();
+                mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, XT).getTotalBytes();
         final boolean networkOverThreshold = persistNetworkDevDelta > threshold
                 || persistNetworkXtDelta > threshold;
         if (persistForce || (persistNetwork && networkOverThreshold)) {
@@ -842,8 +850,8 @@
         }
 
         // persist when enough uid data has occurred
-        final long persistUidDelta = computeStatsDelta(mLastPersistUidSnapshot, uidSnapshot, true)
-                .getTotalBytes();
+        final long persistUidDelta = computeStatsDelta(
+                mLastPersistUidSnapshot, uidSnapshot, true, UID).getTotalBytes();
         if (persistForce || (persistUid && persistUidDelta > threshold)) {
             writeUidStatsLocked();
             mLastPersistUidSnapshot = uidSnapshot;
@@ -872,7 +880,7 @@
         final HashSet<String> unknownIface = Sets.newHashSet();
 
         final NetworkStats delta = computeStatsDelta(
-                mLastPollNetworkDevSnapshot, networkDevSnapshot, false);
+                mLastPollNetworkDevSnapshot, networkDevSnapshot, false, DEV);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -902,7 +910,7 @@
         final HashSet<String> unknownIface = Sets.newHashSet();
 
         final NetworkStats delta = computeStatsDelta(
-                mLastPollNetworkXtSnapshot, networkXtSnapshot, false);
+                mLastPollNetworkXtSnapshot, networkXtSnapshot, false, XT);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -931,9 +939,10 @@
     private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
         ensureUidStatsLoadedLocked();
 
-        final NetworkStats delta = computeStatsDelta(mLastPollUidSnapshot, uidSnapshot, false);
+        final NetworkStats delta = computeStatsDelta(
+                mLastPollUidSnapshot, uidSnapshot, false, UID);
         final NetworkStats operationsDelta = computeStatsDelta(
-                mLastPollOperationsSnapshot, mOperations, false);
+                mLastPollOperationsSnapshot, mOperations, false, UID);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -1014,6 +1023,9 @@
     private void removeUidLocked(int uid) {
         ensureUidStatsLoadedLocked();
 
+        // perform one last poll before removing
+        performPollLocked(FLAG_PERSIST_ALL);
+
         final ArrayList<UidStatsKey> knownKeys = Lists.newArrayList();
         knownKeys.addAll(mUidStats.keySet());
 
@@ -1031,6 +1043,10 @@
             }
         }
 
+        // clear UID from current stats snapshot
+        mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid);
+        mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
+
         // clear kernel stats associated with UID
         resetKernelUidStats(uid);
 
@@ -1490,10 +1506,25 @@
      * Return the delta between two {@link NetworkStats} snapshots, where {@code
      * before} can be {@code null}.
      */
-    private static NetworkStats computeStatsDelta(
-            NetworkStats before, NetworkStats current, boolean collectStale) {
+    private NetworkStats computeStatsDelta(
+            NetworkStats before, NetworkStats current, boolean collectStale, String type) {
         if (before != null) {
-            return current.subtractClamped(before);
+            try {
+                return current.subtract(before);
+            } catch (NonMonotonicException e) {
+                Log.w(TAG, "found non-monotonic values; saving to dropbox");
+
+                // record error for debugging
+                final StringBuilder builder = new StringBuilder();
+                builder.append("found non-monotonic " + type + "values at left[" + e.leftIndex
+                        + "] - right[" + e.rightIndex + "]\n");
+                builder.append("left=").append(e.left).append('\n');
+                builder.append("right=").append(e.right).append('\n');
+                mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
+
+                // return empty delta to avoid recording broken stats
+                return new NetworkStats(0L, 10);
+            }
         } else if (collectStale) {
             // caller is okay collecting stale stats for first call.
             return current;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index e892b5e..368595f 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -66,6 +66,7 @@
 import android.os.Binder;
 import android.os.INetworkManagementService;
 import android.os.IPowerManager;
+import android.os.MessageQueue.IdleHandler;
 import android.test.AndroidTestCase;
 import android.test.mock.MockPackageManager;
 import android.test.suitebuilder.annotation.LargeTest;
@@ -87,6 +88,7 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.logging.Handler;
 
 import libcore.io.IoUtils;
 
@@ -100,6 +102,10 @@
     private static final long TEST_START = 1194220800000L;
     private static final String TEST_IFACE = "test0";
 
+    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 NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi();
 
     private BroadcastInterceptingContext mServiceContext;
@@ -255,31 +261,37 @@
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
         mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
         mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false);
+        waitUntilIdle();
         assertFalse(mService.isUidForeground(UID_A));
         assertFalse(mService.isUidForeground(UID_B));
 
         // push one of the shared pids into foreground
         mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+        waitUntilIdle();
         assertTrue(mService.isUidForeground(UID_A));
         assertFalse(mService.isUidForeground(UID_B));
 
         // and swap another uid into foreground
         mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
         mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true);
+        waitUntilIdle();
         assertFalse(mService.isUidForeground(UID_A));
         assertTrue(mService.isUidForeground(UID_B));
 
         // push both pid into foreground
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
         mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+        waitUntilIdle();
         assertTrue(mService.isUidForeground(UID_A));
 
         // pull one out, should still be foreground
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        waitUntilIdle();
         assertTrue(mService.isUidForeground(UID_A));
 
         // pull final pid out, should now be background
         mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+        waitUntilIdle();
         assertFalse(mService.isUidForeground(UID_A));
     }
 
@@ -528,13 +540,14 @@
 
         // TODO: consider making strongly ordered mock
         expectRemoveInterfaceQuota(TEST_IFACE);
-        expectSetInterfaceQuota(TEST_IFACE, 1536L);
+        expectSetInterfaceQuota(TEST_IFACE, (2 * MB_IN_BYTES) - 512);
 
         expectClearNotifications();
         future = expectMeteredIfacesChanged(TEST_IFACE);
 
         replay();
-        setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L, SNOOZE_NEVER));
+        setNetworkPolicies(new NetworkPolicy(
+                sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, SNOOZE_NEVER));
         future.get();
         verifyAndReset();
     }
@@ -590,8 +603,8 @@
             future = expectMeteredIfacesChanged();
 
             replay();
-            setNetworkPolicies(
-                    new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L, SNOOZE_NEVER));
+            setNetworkPolicies(new NetworkPolicy(
+                    sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, SNOOZE_NEVER));
             future.get();
             verifyAndReset();
         }
@@ -609,7 +622,7 @@
                     .andReturn(stats).atLeastOnce();
 
             expectRemoveInterfaceQuota(TEST_IFACE);
-            expectSetInterfaceQuota(TEST_IFACE, 2048L);
+            expectSetInterfaceQuota(TEST_IFACE, 2 * MB_IN_BYTES);
 
             expectClearNotifications();
             future = expectMeteredIfacesChanged(TEST_IFACE);
@@ -623,7 +636,7 @@
         // go over warning, which should kick notification
         incrementCurrentTime(MINUTE_IN_MILLIS);
         stats = new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 1536L, 15L, 0L, 0L);
+                .addIfaceValues(TEST_IFACE, 1536 * KB_IN_BYTES, 15L, 0L, 0L);
 
         {
             expectCurrentTime();
@@ -643,7 +656,7 @@
         // go over limit, which should kick notification and dialog
         incrementCurrentTime(MINUTE_IN_MILLIS);
         stats = new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 5120L, 512L, 0L, 0L);
+                .addIfaceValues(TEST_IFACE, 5 * MB_IN_BYTES, 512L, 0L, 0L);
 
         {
             expectCurrentTime();
@@ -799,6 +812,32 @@
         }
     }
 
+    private static class IdleFuture extends AbstractFuture<Void> implements IdleHandler {
+        @Override
+        public Void get() throws InterruptedException, ExecutionException {
+            try {
+                return get(5, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public boolean queueIdle() {
+            set(null);
+            return false;
+        }
+    }
+
+    /**
+     * Wait until {@link #mService} internal {@link Handler} is idle.
+     */
+    private void waitUntilIdle() throws Exception {
+        final IdleFuture future = new IdleFuture();
+        mService.addIdleHandler(future);
+        future.get();
+    }
+
     private static void assertTimeEquals(long expected, long actual) {
         if (expected != actual) {
             fail("expected " + formatTime(expected) + " but was actually " + formatTime(actual));
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index f7dff23..fbc171b 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -83,6 +83,7 @@
     private static final String TAG = "NetworkStatsServiceTest";
 
     private static final String TEST_IFACE = "test0";
+    private static final String TEST_IFACE2 = "test1";
     private static final long TEST_START = 1194220800000L;
 
     private static final String IMSI_1 = "310004";
@@ -418,8 +419,12 @@
         expectCurrentTime();
         expectDefaultSettings();
         expectNetworkState(buildMobile3gState(IMSI_2));
-        expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
         expectNetworkStatsPoll();
 
         replay();
@@ -432,9 +437,11 @@
         expectCurrentTime();
         expectDefaultSettings();
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 128L, 1L, 1024L, 8L));
+                .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 1024L, 8L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
         expectNetworkStatsPoll();
 
@@ -499,6 +506,15 @@
         // special "removed" bucket.
         expectCurrentTime();
         expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+                .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+        expectNetworkStatsPoll();
+
         replay();
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_BLUE);
@@ -553,9 +569,11 @@
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkState(buildMobile4gState());
+        expectNetworkState(buildMobile4gState(TEST_IFACE2));
         expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
         expectNetworkStatsPoll();
 
         replay();
@@ -569,8 +587,10 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+                .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
         expectNetworkStatsPoll();
 
         mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
@@ -625,6 +645,8 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
         expectNetworkStatsPoll();
 
@@ -881,11 +903,11 @@
         return new NetworkState(info, prop, null, subscriberId);
     }
 
-    private static NetworkState buildMobile4gState() {
+    private static NetworkState buildMobile4gState(String iface) {
         final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
-        prop.setInterfaceName(TEST_IFACE);
+        prop.setInterfaceName(iface);
         return new NetworkState(info, prop, null);
     }
 
diff --git a/tests/FrameworkPerf/res/layout/button_layout.xml b/tests/FrameworkPerf/res/layout/button_layout.xml
new file mode 100644
index 0000000..7786a25
--- /dev/null
+++ b/tests/FrameworkPerf/res/layout/button_layout.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="FooBarYou" />
+</LinearLayout>
diff --git a/tests/FrameworkPerf/res/layout/image_button_layout.xml b/tests/FrameworkPerf/res/layout/image_button_layout.xml
new file mode 100644
index 0000000..65b12b3
--- /dev/null
+++ b/tests/FrameworkPerf/res/layout/image_button_layout.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+    <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:src="@drawable/stat_happy"/>
+</LinearLayout>
diff --git a/tests/FrameworkPerf/res/layout/large_layout.xml b/tests/FrameworkPerf/res/layout/large_layout.xml
index b6ac88c..39bbe34 100644
--- a/tests/FrameworkPerf/res/layout/large_layout.xml
+++ b/tests/FrameworkPerf/res/layout/large_layout.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- 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.
diff --git a/tests/FrameworkPerf/res/layout/small_layout.xml b/tests/FrameworkPerf/res/layout/small_layout.xml
index 9fcbb26..e78a176 100644
--- a/tests/FrameworkPerf/res/layout/small_layout.xml
+++ b/tests/FrameworkPerf/res/layout/small_layout.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!-- 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.
diff --git a/tests/FrameworkPerf/res/layout/view_layout.xml b/tests/FrameworkPerf/res/layout/view_layout.xml
new file mode 100644
index 0000000..0171eef
--- /dev/null
+++ b/tests/FrameworkPerf/res/layout/view_layout.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+    <View android:layout_width="wrap_content" android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java b/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
index 5e15224..3979902 100644
--- a/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
+++ b/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Bundle;
@@ -29,13 +31,16 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
+import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Xml;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
+import android.widget.Button;
 import android.widget.Spinner;
 import android.widget.TextView;
 
@@ -46,6 +51,9 @@
 import java.io.RandomAccessFile;
 import java.util.ArrayList;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 /**
  * So you thought sync used up your battery life.
  */
@@ -57,8 +65,10 @@
 
     Spinner mFgSpinner;
     Spinner mBgSpinner;
-    TextView mLog;
     TextView mTestTime;
+    Button mStartButton;
+    Button mStopButton;
+    TextView mLog;
     PowerManager.WakeLock mPartialWakeLock;
 
     long mMaxRunTime = 5000;
@@ -100,12 +110,21 @@
             new WriteFileOp(), new ReadFileOp(),
             new ReadFileOp(), new WriteFileOp(),
             new ReadFileOp(), new ReadFileOp(),
+            new OpenXmlResOp(), new NoOp(),
+            new ReadXmlAttrsOp(), new NoOp(),
             new ParseXmlResOp(), new NoOp(),
             new ParseLargeXmlResOp(), new NoOp(),
             new LayoutInflaterOp(), new NoOp(),
             new LayoutInflaterLargeOp(), new NoOp(),
+            new LayoutInflaterViewOp(), new NoOp(),
+            new LayoutInflaterButtonOp(), new NoOp(),
+            new LayoutInflaterImageButtonOp(), new NoOp(),
+            new CreateBitmapOp(), new NoOp(),
+            new CreateRecycleBitmapOp(), new NoOp(),
             new LoadSmallBitmapOp(), new NoOp(),
+            new LoadRecycleSmallBitmapOp(), new NoOp(),
             new LoadLargeBitmapOp(), new NoOp(),
+            new LoadRecycleLargeBitmapOp(), new NoOp(),
             new LoadSmallScaledBitmapOp(), new NoOp(),
             new LoadLargeScaledBitmapOp(), new NoOp(),
     };
@@ -122,12 +141,21 @@
             new CreateWriteSyncFileOp(),
             new WriteFileOp(),
             new ReadFileOp(),
+            new OpenXmlResOp(),
+            new ReadXmlAttrsOp(),
             new ParseXmlResOp(),
             new ParseLargeXmlResOp(),
             new LayoutInflaterOp(),
             new LayoutInflaterLargeOp(),
+            new LayoutInflaterViewOp(),
+            new LayoutInflaterButtonOp(),
+            new LayoutInflaterImageButtonOp(),
+            new CreateBitmapOp(),
+            new CreateRecycleBitmapOp(),
             new LoadSmallBitmapOp(),
+            new LoadRecycleSmallBitmapOp(),
             new LoadLargeBitmapOp(),
+            new LoadRecycleLargeBitmapOp(),
             new LoadSmallScaledBitmapOp(),
             new LoadLargeScaledBitmapOp(),
     };
@@ -208,17 +236,22 @@
         mBgSpinner.setAdapter(adapter);
         mBgSpinner.setOnItemSelectedListener(this);
 
-        findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
+        mTestTime = (TextView)findViewById(R.id.testtime);
+
+        mStartButton = (Button)findViewById(R.id.start);
+        mStartButton.setOnClickListener(new View.OnClickListener() {
             @Override public void onClick(View v) {
                 startRunning();
             }
         });
-        findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() {
+        mStopButton = (Button)findViewById(R.id.stop);
+        mStopButton.setOnClickListener(new View.OnClickListener() {
             @Override public void onClick(View v) {
                 stopRunning();
             }
         });
-        mTestTime = (TextView)findViewById(R.id.testtime);
+        mStopButton.setEnabled(false);
+
         mLog = (TextView)findViewById(R.id.log);
 
         PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
@@ -267,9 +300,17 @@
             fgOp = mFgTest;
             bgOp = mBgTest;
         } else if (mFgTest != null) {
+            // Skip null test.
+            if (mCurOpIndex == 0) {
+                mCurOpIndex = 1;
+            }
             fgOp = mFgTest;
             bgOp = mAvailOps[mCurOpIndex];
         } else {
+            // Skip null test.
+            if (mCurOpIndex == 0) {
+                mCurOpIndex = 1;
+            }
             fgOp = mAvailOps[mCurOpIndex];
             bgOp = mBgTest;
         }
@@ -297,12 +338,13 @@
                         stopRunning();
                         return;
                     }
-                }
-                mCurOpIndex++;
-                if (mCurOpIndex >= mAvailOps.length) {
-                    log("Finished");
-                    stopRunning();
-                    return;
+                } else {
+                    mCurOpIndex++;
+                    if (mCurOpIndex >= mAvailOps.length) {
+                        log("Finished");
+                        stopRunning();
+                        return;
+                    }
                 }
                 startCurOp();
             }
@@ -313,6 +355,11 @@
         if (!mStarted) {
             log("Start");
             mStarted = true;
+            mStartButton.setEnabled(false);
+            mStopButton.setEnabled(true);
+            mTestTime.setEnabled(false);
+            mFgSpinner.setEnabled(false);
+            mBgSpinner.setEnabled(false);
             updateWakeLock();
             startService(new Intent(this, SchedulerService.class));
             mCurOpIndex = 0;
@@ -325,6 +372,11 @@
     void stopRunning() {
         if (mStarted) {
             mStarted = false;
+            mStartButton.setEnabled(true);
+            mStopButton.setEnabled(false);
+            mTestTime.setEnabled(true);
+            mFgSpinner.setEnabled(true);
+            mBgSpinner.setEnabled(true);
             updateWakeLock();
             stopService(new Intent(this, SchedulerService.class));
             for (int i=0; i<mResults.size(); i++) {
@@ -333,7 +385,7 @@
                 float bgMsPerOp = result.getBgMsPerOp();
                 String fgMsPerOpStr = fgMsPerOp != 0 ? Float.toString(fgMsPerOp) : "";
                 String bgMsPerOpStr = bgMsPerOp != 0 ? Float.toString(bgMsPerOp) : "";
-                Log.i(TAG, "\t" + result.name + "\t" + result.fgOps
+                Log.i("PerfRes", "\t" + result.name + "\t" + result.fgOps
                         + "\t" + result.getFgMsPerOp() + "\t" + result.fgTime
                         + "\t" + result.fgLongName + "\t" + result.bgOps
                         + "\t" + result.getBgMsPerOp() + "\t" + result.bgTime
@@ -662,6 +714,71 @@
         }
     }
 
+    static class OpenXmlResOp extends Op {
+        Context mContext;
+
+        OpenXmlResOp() {
+            super("OpenXmlRes", "Open (and close) an XML resource");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            XmlResourceParser parser = mContext.getResources().getLayout(R.xml.simple);
+            parser.close();
+            return true;
+        }
+    }
+
+    static class ReadXmlAttrsOp extends Op {
+        Context mContext;
+        XmlResourceParser mParser;
+        AttributeSet mAttrs;
+
+        ReadXmlAttrsOp() {
+            super("ReadXmlAttrs", "Read attributes from an XML tag");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+            mParser = mContext.getResources().getLayout(R.xml.simple);
+            mAttrs = Xml.asAttributeSet(mParser);
+
+            int eventType;
+            try {
+                // Find the first <item> tag.
+                eventType = mParser.getEventType();
+                String tagName;
+                do {
+                    if (eventType == XmlPullParser.START_TAG) {
+                        tagName = mParser.getName();
+                        if (tagName.equals("item")) {
+                            break;
+                        }
+                    }
+                    eventType = mParser.next();
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+            } catch (XmlPullParserException e) {
+                throw new RuntimeException("I died", e);
+            } catch (IOException e) {
+                throw new RuntimeException("I died", e);
+            }
+        }
+
+        void onTerm(Context context) {
+            mParser.close();
+        }
+
+        boolean onRun() {
+            TypedArray a = mContext.obtainStyledAttributes(mAttrs,
+                    com.android.internal.R.styleable.MenuItem);
+            a.recycle();
+            return true;
+        }
+    }
+
     static class ParseXmlResOp extends Op {
         Context mContext;
 
@@ -702,7 +819,7 @@
         Context mContext;
 
         LayoutInflaterOp() {
-            super("LayoutInflaterOp", "Inflate layout resource");
+            super("LayoutInflater", "Inflate layout resource");
         }
 
         void onInit(Context context, boolean foreground) {
@@ -724,7 +841,7 @@
         Context mContext;
 
         LayoutInflaterLargeOp() {
-            super("LayoutInflaterLargeOp", "Inflate large layout resource");
+            super("LayoutInflaterLarge", "Inflate large layout resource");
         }
 
         void onInit(Context context, boolean foreground) {
@@ -742,6 +859,111 @@
         }
     }
 
+    static class LayoutInflaterViewOp extends Op {
+        Context mContext;
+
+        LayoutInflaterViewOp() {
+            super("LayoutInflaterView", "Inflate layout with 50 View objects");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            if (Looper.myLooper() == null) {
+                Looper.prepare();
+            }
+            LayoutInflater inf = (LayoutInflater)mContext.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            inf.inflate(R.layout.view_layout, null);
+            return true;
+        }
+    }
+
+    static class LayoutInflaterButtonOp extends Op {
+        Context mContext;
+
+        LayoutInflaterButtonOp() {
+            super("LayoutInflaterButton", "Inflate layout with 50 Button objects");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            if (Looper.myLooper() == null) {
+                Looper.prepare();
+            }
+            LayoutInflater inf = (LayoutInflater)mContext.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            inf.inflate(R.layout.button_layout, null);
+            return true;
+        }
+    }
+
+    static class LayoutInflaterImageButtonOp extends Op {
+        Context mContext;
+
+        LayoutInflaterImageButtonOp() {
+            super("LayoutInflaterImageButton", "Inflate layout with 50 ImageButton objects");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            if (Looper.myLooper() == null) {
+                Looper.prepare();
+            }
+            LayoutInflater inf = (LayoutInflater)mContext.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            inf.inflate(R.layout.image_button_layout, null);
+            return true;
+        }
+    }
+
+    static class CreateBitmapOp extends Op {
+        Context mContext;
+
+        CreateBitmapOp() {
+            super("CreateBitmap", "Create a Bitmap");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
+            Bitmap bm = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
+            return true;
+        }
+    }
+
+    static class CreateRecycleBitmapOp extends Op {
+        Context mContext;
+
+        CreateRecycleBitmapOp() {
+            super("CreateRecycleBitmap", "Create and recycle a Bitmap");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
+            Bitmap bm = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
+            bm.recycle();
+            return true;
+        }
+    }
+
     static class LoadSmallBitmapOp extends Op {
         Context mContext;
 
@@ -758,6 +980,26 @@
             opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
             Bitmap bm = BitmapFactory.decodeResource(mContext.getResources(),
                     R.drawable.stat_sample, opts);
+            return true;
+        }
+    }
+
+    static class LoadRecycleSmallBitmapOp extends Op {
+        Context mContext;
+
+        LoadRecycleSmallBitmapOp() {
+            super("LoadRecycleSmallBitmap", "Load and recycle small raw bitmap");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
+            Bitmap bm = BitmapFactory.decodeResource(mContext.getResources(),
+                    R.drawable.stat_sample, opts);
             bm.recycle();
             return true;
         }
@@ -779,6 +1021,26 @@
             opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
             Bitmap bm = BitmapFactory.decodeResource(mContext.getResources(),
                     R.drawable.wallpaper_goldengate, opts);
+            return true;
+        }
+    }
+
+    static class LoadRecycleLargeBitmapOp extends Op {
+        Context mContext;
+
+        LoadRecycleLargeBitmapOp() {
+            super("LoadRecycleLargeBitmap", "Load and recycle large raw bitmap");
+        }
+
+        void onInit(Context context, boolean foreground) {
+            mContext = context;
+        }
+
+        boolean onRun() {
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
+            Bitmap bm = BitmapFactory.decodeResource(mContext.getResources(),
+                    R.drawable.wallpaper_goldengate, opts);
             bm.recycle();
             return true;
         }
@@ -800,7 +1062,6 @@
             opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
             Bitmap bm = BitmapFactory.decodeResource(mContext.getResources(),
                     R.drawable.stat_sample_scale, opts);
-            bm.recycle();
             return true;
         }
     }
@@ -821,7 +1082,6 @@
             opts.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
             Bitmap bm = BitmapFactory.decodeResource(mContext.getResources(),
                     R.drawable.wallpaper_goldengate_scale, opts);
-            bm.recycle();
             return true;
         }
     }
diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y
index cc04d15..3c16e15 100644
--- a/tools/aidl/aidl_language_y.y
+++ b/tools/aidl/aidl_language_y.y
@@ -87,7 +87,7 @@
                                                         b->name = $2.buffer;
                                                         b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
                                                         b->semicolon_token = $3.buffer;
-                                                        b->flattening_methods = PARCELABLE_DATA;
+                                                        b->flattening_methods = PARCELABLE_DATA | RPC_DATA;
                                                         $$.user_data = b;
                                                     }
     |   PARCELABLE ';'                              {
diff --git a/tools/aidl/generate_java_rpc.cpp b/tools/aidl/generate_java_rpc.cpp
index e4867e4..852b0c1 100644
--- a/tools/aidl/generate_java_rpc.cpp
+++ b/tools/aidl/generate_java_rpc.cpp
@@ -5,7 +5,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-Type* SERVICE_CONTEXT_TYPE = new Type("android.content",
+Type* ANDROID_CONTEXT_TYPE = new Type("android.content",
         "Context", Type::BUILT_IN, false, false, false);
 Type* PRESENTER_BASE_TYPE = new Type("com.android.athome.connector",
         "EventListener", Type::BUILT_IN, false, false, false);
@@ -13,8 +13,6 @@
         "EventListener.Listener", Type::BUILT_IN, false, false, false);
 Type* RPC_BROKER_TYPE = new Type("com.android.athome.connector", "Broker",
         Type::BUILT_IN, false, false, false);
-Type* RPC_CONTAINER_TYPE = new Type("com.android.athome.connector", "ConnectorContainer",
-        Type::BUILT_IN, false, false, false);
 // TODO: Just use Endpoint, so this works for all endpoints.
 Type* RPC_CONNECTOR_TYPE = new Type("com.android.athome.connector", "Connector",
         Type::BUILT_IN, false, false, false);
@@ -458,7 +456,7 @@
 void
 EndpointBaseClass::generate_ctor()
 {
-    Variable* container = new Variable(RPC_CONTAINER_TYPE, "container");
+    Variable* container = new Variable(ANDROID_CONTEXT_TYPE, "context");
     Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
     Method* ctor = new Method;
         ctor->modifiers = PUBLIC;