Merge "Bring back Android Dreams." into ics-mr1
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/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/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/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 310ace0..0c42926 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -293,7 +293,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];
 
@@ -13898,7 +13898,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;
@@ -13962,7 +13962,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;
         }
     }